From e57eb5a14f901f7e19440d4335ae6dd915b8f70d Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Tue, 23 Jun 2015 10:25:24 +0930 Subject: [PATCH] Initial commit --- extensions/likes/.gitignore | 4 + extensions/likes/LICENSE.txt | 21 ++++ extensions/likes/bootstrap.php | 9 ++ extensions/likes/composer.json | 7 ++ extensions/likes/flarum.json | 16 ++++ extensions/likes/js/.gitignore | 3 + extensions/likes/js/Gulpfile.js | 5 + extensions/likes/js/bootstrap.js | 96 +++++++++++++++++++ extensions/likes/js/package.json | 7 ++ .../src/components/post-liked-notification.js | 16 ++++ .../js/src/components/post-likes-modal.js | 24 +++++ extensions/likes/less/extension.less | 0 extensions/likes/locale/en.yml | 2 + ..._05_11_000000_create_posts_likes_table.php | 31 ++++++ extensions/likes/src/Events/PostWasLiked.php | 27 ++++++ .../likes/src/Events/PostWasUnliked.php | 27 ++++++ extensions/likes/src/Handlers/LikedSaver.php | 47 +++++++++ .../likes/src/Handlers/PostLikedNotifier.php | 50 ++++++++++ extensions/likes/src/LikesServiceProvider.php | 64 +++++++++++++ .../likes/src/PostLikedNotification.php | 38 ++++++++ 20 files changed, 494 insertions(+) create mode 100644 extensions/likes/.gitignore create mode 100644 extensions/likes/LICENSE.txt create mode 100644 extensions/likes/bootstrap.php create mode 100644 extensions/likes/composer.json create mode 100644 extensions/likes/flarum.json create mode 100644 extensions/likes/js/.gitignore create mode 100644 extensions/likes/js/Gulpfile.js create mode 100644 extensions/likes/js/bootstrap.js create mode 100644 extensions/likes/js/package.json create mode 100644 extensions/likes/js/src/components/post-liked-notification.js create mode 100644 extensions/likes/js/src/components/post-likes-modal.js create mode 100644 extensions/likes/less/extension.less create mode 100644 extensions/likes/locale/en.yml create mode 100644 extensions/likes/migrations/2015_05_11_000000_create_posts_likes_table.php create mode 100644 extensions/likes/src/Events/PostWasLiked.php create mode 100644 extensions/likes/src/Events/PostWasUnliked.php create mode 100755 extensions/likes/src/Handlers/LikedSaver.php create mode 100755 extensions/likes/src/Handlers/PostLikedNotifier.php create mode 100644 extensions/likes/src/LikesServiceProvider.php create mode 100644 extensions/likes/src/PostLikedNotification.php diff --git a/extensions/likes/.gitignore b/extensions/likes/.gitignore new file mode 100644 index 000000000..a4f3b125e --- /dev/null +++ b/extensions/likes/.gitignore @@ -0,0 +1,4 @@ +/vendor +composer.phar +.DS_Store +Thumbs.db diff --git a/extensions/likes/LICENSE.txt b/extensions/likes/LICENSE.txt new file mode 100644 index 000000000..aa1e5fb86 --- /dev/null +++ b/extensions/likes/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2015 Toby Zerner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/likes/bootstrap.php b/extensions/likes/bootstrap.php new file mode 100644 index 000000000..b205492d2 --- /dev/null +++ b/extensions/likes/bootstrap.php @@ -0,0 +1,9 @@ +app->register('Flarum\Likes\LikesServiceProvider'); diff --git a/extensions/likes/composer.json b/extensions/likes/composer.json new file mode 100644 index 000000000..cf8a3fc29 --- /dev/null +++ b/extensions/likes/composer.json @@ -0,0 +1,7 @@ +{ + "autoload": { + "psr-4": { + "Flarum\\Likes\\": "src/" + } + } +} diff --git a/extensions/likes/flarum.json b/extensions/likes/flarum.json new file mode 100644 index 000000000..7d4a6a284 --- /dev/null +++ b/extensions/likes/flarum.json @@ -0,0 +1,16 @@ +{ + "name": "flarum-likes", + "title": "Likes", + "description": "Allows users to like posts.", + "tags": [], + "version": "0.1.0", + "author": { + "name": "Toby Zerner", + "email": "toby@flarum.org'" + }, + "license": "MIT", + "require": { + "php": ">=5.4.0", + "flarum": ">0.1.0" + } +} \ No newline at end of file diff --git a/extensions/likes/js/.gitignore b/extensions/likes/js/.gitignore new file mode 100644 index 000000000..372e20a51 --- /dev/null +++ b/extensions/likes/js/.gitignore @@ -0,0 +1,3 @@ +bower_components +node_modules +dist diff --git a/extensions/likes/js/Gulpfile.js b/extensions/likes/js/Gulpfile.js new file mode 100644 index 000000000..7f1b72f96 --- /dev/null +++ b/extensions/likes/js/Gulpfile.js @@ -0,0 +1,5 @@ +var gulp = require('flarum-gulp'); + +gulp({ + modulePrefix: 'flarum-likes' +}); diff --git a/extensions/likes/js/bootstrap.js b/extensions/likes/js/bootstrap.js new file mode 100644 index 000000000..7dd42942e --- /dev/null +++ b/extensions/likes/js/bootstrap.js @@ -0,0 +1,96 @@ +import { extend, override } from 'flarum/extension-utils'; +import app from 'flarum/app'; +import Post from 'flarum/models/post'; +import Model from 'flarum/model'; +import DiscussionPage from 'flarum/components/discussion-page'; +import ActionButton from 'flarum/components/action-button'; +import CommentPost from 'flarum/components/comment-post'; +import punctuate from 'flarum/helpers/punctuate'; +import username from 'flarum/helpers/username'; + +import PostLikedNotification from 'flarum-likes/components/post-liked-notification'; +import PostLikesModal from 'flarum-likes/components/post-likes-modal'; + +app.initializers.add('flarum-likes', function() { + + app.notificationComponentRegistry['postLiked'] = PostLikedNotification; + + Post.prototype.canLike = Model.prop('canLike'); + Post.prototype.likes = Model.many('likes'); + + extend(DiscussionPage.prototype, 'params', function(params) { + params.include.push('posts.likes'); + }); + + extend(CommentPost.prototype, 'footerItems', function(items) { + var post = this.props.post; + var likes = post.likes(); + + if (likes && likes.length) { + + var limit = 3; + + var names = likes.slice(0, limit).map(user => { + return m('a', { + href: app.route.user(user), + config: m.route + }, [ + app.session.user() && user === app.session.user() ? 'You' : username(user) + ]) + }); + + if (likes.length > limit + 1) { + names.push( + m('a', { + href: '#', + onclick: function(e) { + e.preventDefault(); + app.modal.show(new PostLikesModal({ post })); + } + }, (likes.length - limit)+' others') + ); + } + + items.add('liked', + m('div.liked-by', [ + punctuate(names), + ' like this.' + ]) + ); + } + }); + + extend(CommentPost.prototype, 'actionItems', function(items) { + var post = this.props.post; + if (post.isHidden() || !post.canLike()) return; + + var isLiked = app.session.user() && post.likes().some(user => user === app.session.user()); + + items.add('like', + ActionButton.component({ + icon: 'thumbs-o-up', + label: isLiked ? 'Unlike' : 'Like', + onclick: () => { + isLiked = !isLiked; + + post.save({ isLiked }); + + var linkage = post.data().links.likes.linkage; + linkage.some((like, i) => { + if (like.id == app.session.user().id()) { + linkage.splice(i, 1); + return true; + } + }); + + if (isLiked) { + linkage.unshift({ type: 'users', id: app.session.user().id() }); + } + + m.redraw(); + } + }) + ); + }); + +}); diff --git a/extensions/likes/js/package.json b/extensions/likes/js/package.json new file mode 100644 index 000000000..3e0ef919d --- /dev/null +++ b/extensions/likes/js/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "devDependencies": { + "gulp": "^3.8.11", + "flarum-gulp": "git+https://github.com/flarum/gulp.git" + } +} diff --git a/extensions/likes/js/src/components/post-liked-notification.js b/extensions/likes/js/src/components/post-liked-notification.js new file mode 100644 index 000000000..d50601ee8 --- /dev/null +++ b/extensions/likes/js/src/components/post-liked-notification.js @@ -0,0 +1,16 @@ +import Notification from 'flarum/components/notification'; +import username from 'flarum/helpers/username'; + +export default class PostLikedNotification extends Notification { + view() { + var notification = this.props.notification; + var post = notification.subject(); + var auc = notification.additionalUnreadCount(); + + return super.view({ + href: app.route.post(post), + icon: 'thumbs-o-up', + content: [username(notification.sender()), auc ? ' and '+auc+' others' : '', ' liked your post #', post.number()] + }); + } +} diff --git a/extensions/likes/js/src/components/post-likes-modal.js b/extensions/likes/js/src/components/post-likes-modal.js new file mode 100644 index 000000000..1345d5601 --- /dev/null +++ b/extensions/likes/js/src/components/post-likes-modal.js @@ -0,0 +1,24 @@ +import FormModal from 'flarum/components/form-modal'; +import avatar from 'flarum/helpers/avatar'; +import username from 'flarum/helpers/username'; + +export default class PostLikesModal extends FormModal { + view() { + var post = this.props.post; + + return super.view({ + className: 'post-likes-modal', + title: 'Users Who Like This', + body: [ + m('ul.post-likes-list', [ + post.likes().map(user => + m('li', m('a', {href: app.route.user(user), config: m.route}, [ + avatar(user), + username(user) + ])) + ) + ]) + ] + }); + } +} diff --git a/extensions/likes/less/extension.less b/extensions/likes/less/extension.less new file mode 100644 index 000000000..e69de29bb diff --git a/extensions/likes/locale/en.yml b/extensions/likes/locale/en.yml new file mode 100644 index 000000000..b2e5edd51 --- /dev/null +++ b/extensions/likes/locale/en.yml @@ -0,0 +1,2 @@ +flarum-likes: + # hello_world: Hello, world! diff --git a/extensions/likes/migrations/2015_05_11_000000_create_posts_likes_table.php b/extensions/likes/migrations/2015_05_11_000000_create_posts_likes_table.php new file mode 100644 index 000000000..aca524853 --- /dev/null +++ b/extensions/likes/migrations/2015_05_11_000000_create_posts_likes_table.php @@ -0,0 +1,31 @@ +getSchemaBuilder()->create('posts_likes', function (Blueprint $table) { + $table->integer('post_id')->unsigned(); + $table->integer('user_id')->unsigned(); + $table->primary(['post_id', 'user_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + app('db')->getSchemaBuilder()->drop('posts_likes'); + } +} diff --git a/extensions/likes/src/Events/PostWasLiked.php b/extensions/likes/src/Events/PostWasLiked.php new file mode 100644 index 000000000..f578a51e8 --- /dev/null +++ b/extensions/likes/src/Events/PostWasLiked.php @@ -0,0 +1,27 @@ +post = $post; + $this->user = $user; + } +} diff --git a/extensions/likes/src/Events/PostWasUnliked.php b/extensions/likes/src/Events/PostWasUnliked.php new file mode 100644 index 000000000..db5936057 --- /dev/null +++ b/extensions/likes/src/Events/PostWasUnliked.php @@ -0,0 +1,27 @@ +post = $post; + $this->user = $user; + } +} diff --git a/extensions/likes/src/Handlers/LikedSaver.php b/extensions/likes/src/Handlers/LikedSaver.php new file mode 100755 index 000000000..f8ad58b99 --- /dev/null +++ b/extensions/likes/src/Handlers/LikedSaver.php @@ -0,0 +1,47 @@ +listen('Flarum\Core\Events\PostWillBeSaved', __CLASS__.'@whenPostWillBeSaved'); + $events->listen('Flarum\Core\Events\PostWasDeleted', __CLASS__.'@whenPostWasDeleted'); + } + + public function whenPostWillBeSaved(PostWillBeSaved $event) + { + $post = $event->post; + $data = $event->command->data; + + if ($post->exists && isset($data['isLiked'])) { + $user = $event->command->user; + $liked = (bool) $data['isLiked']; + + if (! $post->can($user, 'like')) { + throw new PermissionDeniedException; + } + + if ($liked) { + $post->likes()->attach($user->id); + + $post->raise(new PostWasLiked($post, $user)); + } else { + $post->likes()->detach($user->id); + + $post->raise(new PostWasUnliked($post, $user)); + } + } + } + + public function whenPostWasDeleted(PostWasDeleted $event) + { + $event->post->likes()->detach(); + } +} diff --git a/extensions/likes/src/Handlers/PostLikedNotifier.php b/extensions/likes/src/Handlers/PostLikedNotifier.php new file mode 100755 index 000000000..48e884705 --- /dev/null +++ b/extensions/likes/src/Handlers/PostLikedNotifier.php @@ -0,0 +1,50 @@ +notifications = $notifications; + } + + /** + * Register the listeners for the subscriber. + * + * @param \Illuminate\Contracts\Events\Dispatcher $events + */ + public function subscribe(Dispatcher $events) + { + $events->listen('Flarum\Likes\Events\PostWasLiked', __CLASS__.'@whenPostWasLiked'); + $events->listen('Flarum\Likes\Events\PostWasUnliked', __CLASS__.'@whenPostWasUnliked'); + } + + public function whenPostWasLiked(PostWasLiked $event) + { + if ($event->post->user->id != $event->user->id) { + $this->sync($event->post, $event->user, [$event->post->user]); + } + } + + public function whenPostWasUnliked(PostWasUnliked $event) + { + if ($event->post->user->id != $event->user->id) { + $this->sync($event->post, $event->user, []); + } + } + + public function sync($post, $user, array $recipients) + { + $this->notifications->sync( + new PostLikedNotification($post, $user), + $recipients + ); + } +} diff --git a/extensions/likes/src/LikesServiceProvider.php b/extensions/likes/src/LikesServiceProvider.php new file mode 100644 index 000000000..5bb36dadd --- /dev/null +++ b/extensions/likes/src/LikesServiceProvider.php @@ -0,0 +1,64 @@ +extend([ + (new Extend\Locale('en'))->translations(__DIR__.'/../locale/en.yml'), + + (new Extend\ForumClient) + ->assets([ + __DIR__.'/../js/dist/extension.js', + __DIR__.'/../less/extension.less' + ]), + + (new Extend\Model('Flarum\Core\Models\Post')) + ->belongsToMany('likes', 'Flarum\Core\Models\User', 'posts_likes', 'post_id', 'user_id'), + + (new Extend\ApiSerializer('Flarum\Api\Serializers\PostSerializer')) + ->hasMany('likes', 'Flarum\Api\Serializers\UserBasicSerializer') + ->attributes(function (&$attributes, $post, $user) { + $attributes['canLike'] = $post->can($user, 'like'); + }), + + (new Extend\ApiAction('Flarum\Api\Actions\Discussions\ShowAction')) + ->addInclude('posts.likes'), + + (new Extend\ApiAction([ + 'Flarum\Api\Actions\Posts\IndexAction', + 'Flarum\Api\Actions\Posts\ShowAction', + 'Flarum\Api\Actions\Posts\CreateAction', + 'Flarum\Api\Actions\Posts\UpdateAction' + ])) + ->addInclude('likes'), + + new Extend\EventSubscriber('Flarum\Likes\Handlers\LikedSaver'), + new Extend\EventSubscriber('Flarum\Likes\Handlers\PostLikedNotifier'), + + (new Extend\NotificationType( + 'Flarum\Likes\PostLikedNotification', + 'Flarum\Api\Serializers\PostBasicSerializer' + )) + ->enableByDefault('alert') + ]); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + // + } +} diff --git a/extensions/likes/src/PostLikedNotification.php b/extensions/likes/src/PostLikedNotification.php new file mode 100644 index 000000000..bcd79e150 --- /dev/null +++ b/extensions/likes/src/PostLikedNotification.php @@ -0,0 +1,38 @@ +post = $post; + $this->user = $user; + } + + public function getSubject() + { + return $this->post; + } + + public function getSender() + { + return $this->user; + } + + public static function getType() + { + return 'postLiked'; + } + + public static function getSubjectModel() + { + return 'Flarum\Core\Models\Post'; + } +}