diff --git a/extensions/likes/.editorconfig b/extensions/likes/.editorconfig new file mode 100644 index 000000000..5612a5e74 --- /dev/null +++ b/extensions/likes/.editorconfig @@ -0,0 +1,32 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.js] +indent_style = space +indent_size = 2 + +[*.{css,less}] +indent_style = space +indent_size = 2 + +[*.html] +indent_style = space +indent_size = 2 + +[*.{diff,md}] +trim_trailing_whitespace = false + +[*.php] +indent_style = space +indent_size = 4 diff --git a/extensions/likes/.eslintignore b/extensions/likes/.eslintignore new file mode 100644 index 000000000..86b7c8854 --- /dev/null +++ b/extensions/likes/.eslintignore @@ -0,0 +1,5 @@ +**/bower_components/**/* +**/node_modules/**/* +vendor/**/* +**/Gulpfile.js +**/dist/**/* diff --git a/extensions/likes/.eslintrc b/extensions/likes/.eslintrc new file mode 100644 index 000000000..9cebc759d --- /dev/null +++ b/extensions/likes/.eslintrc @@ -0,0 +1,171 @@ +{ + "parser": "babel-eslint", // https://github.com/babel/babel-eslint + "env": { // http://eslint.org/docs/user-guide/configuring.html#specifying-environments + "browser": true // browser global variables + }, + "ecmaFeatures": { + "arrowFunctions": true, + "blockBindings": true, + "classes": true, + "defaultParams": true, + "destructuring": true, + "forOf": true, + "generators": false, + "modules": true, + "objectLiteralComputedProperties": true, + "objectLiteralDuplicateProperties": false, + "objectLiteralShorthandMethods": true, + "objectLiteralShorthandProperties": true, + "spread": true, + "superInFunctions": true, + "templateStrings": true, + "jsx": true + }, + "globals": { + "m": true, + "app": true, + "$": true, + "moment": true + }, + "rules": { +/** + * Strict mode + */ + // babel inserts "use strict"; for us + "strict": [2, "never"], // http://eslint.org/docs/rules/strict + +/** + * ES6 + */ + "no-var": 2, // http://eslint.org/docs/rules/no-var + "prefer-const": 2, // http://eslint.org/docs/rules/prefer-const + +/** + * Variables + */ + "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow + "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names + "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars + "vars": "local", + "args": "after-used" + }], + "no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define + +/** + * Possible errors + */ + "comma-dangle": [2, "never"], // http://eslint.org/docs/rules/comma-dangle + "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign + "no-console": 1, // http://eslint.org/docs/rules/no-console + "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger + "no-alert": 1, // http://eslint.org/docs/rules/no-alert + "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition + "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys + "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case + "no-empty": 2, // http://eslint.org/docs/rules/no-empty + "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign + "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast + "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi + "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign + "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations + "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp + "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace + "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls + "no-reserved-keys": 2, // http://eslint.org/docs/rules/no-reserved-keys + "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays + "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable + "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan + "block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var + +/** + * Best practices + */ + "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return + "curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly + "default-case": 2, // http://eslint.org/docs/rules/default-case + "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation + "allowKeywords": true + }], + "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq + "no-caller": 2, // http://eslint.org/docs/rules/no-caller + "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return + "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null + "no-eval": 2, // http://eslint.org/docs/rules/no-eval + "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native + "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind + "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough + "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal + "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval + "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks + "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func + "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str + "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign + "no-new": 2, // http://eslint.org/docs/rules/no-new + "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func + "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers + "no-octal": 2, // http://eslint.org/docs/rules/no-octal + "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape + "no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign + "no-proto": 2, // http://eslint.org/docs/rules/no-proto + "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare + "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign + "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare + "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences + "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal + "no-with": 2, // http://eslint.org/docs/rules/no-with + "radix": 2, // http://eslint.org/docs/rules/radix + "vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top + "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife + "yoda": 2, // http://eslint.org/docs/rules/yoda + +/** + * Style + */ + "indent": [2, 2], // http://eslint.org/docs/rules/indent + "brace-style": [2, // http://eslint.org/docs/rules/brace-style + "1tbs", { + "allowSingleLine": true + }], + "quotes": [ + 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes + ], + "camelcase": [2, { // http://eslint.org/docs/rules/camelcase + "properties": "never" + }], + "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing + "before": false, + "after": true + }], + "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style + "eol-last": 2, // http://eslint.org/docs/rules/eol-last + "func-names": 1, // http://eslint.org/docs/rules/func-names + "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing + "beforeColon": false, + "afterColon": true + }], + "new-cap": [2, { // http://eslint.org/docs/rules/new-cap + "newIsCap": true + }], + "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines + "max": 2 + }], + "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object + "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func + "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces + "no-wrap-func": 2, // http://eslint.org/docs/rules/no-wrap-func + "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle + "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var + "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks + "semi": [2, "always"], // http://eslint.org/docs/rules/semi + "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing + "before": false, + "after": true + }], + "space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords + "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks + "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren + "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops + "space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case + "spaced-line-comment": 2, // http://eslint.org/docs/rules/spaced-line-comment + } +} diff --git a/extensions/likes/bootstrap.php b/extensions/likes/bootstrap.php index b205492d2..15cfc3883 100644 --- a/extensions/likes/bootstrap.php +++ b/extensions/likes/bootstrap.php @@ -1,9 +1,5 @@ app->register('Flarum\Likes\LikesServiceProvider'); +return 'Flarum\Likes\Extension'; diff --git a/extensions/likes/flarum.json b/extensions/likes/flarum.json index 7d4a6a284..5cc2f97de 100644 --- a/extensions/likes/flarum.json +++ b/extensions/likes/flarum.json @@ -1,16 +1,21 @@ { - "name": "flarum-likes", + "name": "likes", "title": "Likes", "description": "Allows users to like posts.", - "tags": [], + "keywords": ["discussions"], "version": "0.1.0", "author": { "name": "Toby Zerner", - "email": "toby@flarum.org'" + "email": "toby@flarum.org", + "homepage": "http://tobyzerner.com" }, "license": "MIT", "require": { "php": ">=5.4.0", "flarum": ">0.1.0" + }, + "support": { + "source": "https://github.com/flarum/likes", + "issues": "https://github.com/flarum/likes/issues" } -} \ No newline at end of file +} diff --git a/extensions/likes/js/bootstrap.js b/extensions/likes/js/bootstrap.js deleted file mode 100644 index 440149814..000000000 --- a/extensions/likes/js/bootstrap.js +++ /dev/null @@ -1,109 +0,0 @@ -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 SettingsPage from 'flarum/components/settings-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 icon from 'flarum/helpers/icon'; - -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', [ - icon('thumbs-o-up icon'), - punctuate(names), - names.length === 1 && (!app.session.user() || likes[0] !== app.session.user()) ? ' likes this.' : ' like this.' - ]), - {before: 'replies'} - ); - } - }); - - 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(); - } - }), - {before: 'reply'} - ); - }); - - // Add a notification preference. - extend(SettingsPage.prototype, 'notificationTypes', function(items) { - items.add('postLiked', { - name: 'postLiked', - label: [icon('thumbs-o-up'), ' Someone likes my post'] - }); - }); - -}); diff --git a/extensions/likes/js/Gulpfile.js b/extensions/likes/js/forum/Gulpfile.js similarity index 60% rename from extensions/likes/js/Gulpfile.js rename to extensions/likes/js/forum/Gulpfile.js index 7f1b72f96..5687a2f40 100644 --- a/extensions/likes/js/Gulpfile.js +++ b/extensions/likes/js/forum/Gulpfile.js @@ -1,5 +1,5 @@ var gulp = require('flarum-gulp'); gulp({ - modulePrefix: 'flarum-likes' + modulePrefix: 'likes' }); diff --git a/extensions/likes/js/package.json b/extensions/likes/js/forum/package.json similarity index 100% rename from extensions/likes/js/package.json rename to extensions/likes/js/forum/package.json diff --git a/extensions/likes/js/forum/src/addLikeAction.js b/extensions/likes/js/forum/src/addLikeAction.js new file mode 100644 index 000000000..5ae0a5bfc --- /dev/null +++ b/extensions/likes/js/forum/src/addLikeAction.js @@ -0,0 +1,41 @@ +import { extend } from 'flarum/extend'; +import app from 'flarum/app'; +import Button from 'flarum/components/Button'; +import CommentPost from 'flarum/components/CommentPost'; + +export default function() { + extend(CommentPost.prototype, 'actionItems', function(items) { + const post = this.props.post; + + if (post.isHidden() || !post.canLike()) return; + + let isLiked = app.session.user && post.likes().some(user => user === app.session.user); + + items.add('like', + Button.component({ + children: app.trans(isLiked ? 'likes.unlike_action' : 'likes.like_action'), + className: 'Button Button--text', + onclick: () => { + isLiked = !isLiked; + + post.save({isLiked}); + + // We've saved the fact that we do or don't like the post, but in order + // to provide instantaneous feedback to the user, we'll need to add or + // remove the like from the relationship data manually. + const data = post.data.relationships.likes.data; + data.some((like, i) => { + if (like.id === app.session.user.id()) { + data.splice(i, 1); + return true; + } + }); + + if (isLiked) { + data.unshift({type: 'users', id: app.session.user.id()}); + } + } + }) + ); + }); +} diff --git a/extensions/likes/js/forum/src/addLikesList.js b/extensions/likes/js/forum/src/addLikesList.js new file mode 100644 index 000000000..e4581af69 --- /dev/null +++ b/extensions/likes/js/forum/src/addLikesList.js @@ -0,0 +1,60 @@ +import { extend } from 'flarum/extend'; +import app from 'flarum/app'; +import DiscussionPage from 'flarum/components/DiscussionPage'; +import CommentPost from 'flarum/components/CommentPost'; +import punctuate from 'flarum/helpers/punctuate'; +import username from 'flarum/helpers/username'; +import icon from 'flarum/helpers/icon'; + +import PostLikesModal from 'likes/components/PostLikesModal'; + +export default function() { + extend(DiscussionPage.prototype, 'params', function(params) { + params.include.push('posts.likes'); + }); + + extend(CommentPost.prototype, 'footerItems', function(items) { + const post = this.props.post; + const likes = post.likes(); + + if (likes && likes.length) { + const limit = 3; + + // Construct a list of names of users who have like this post. Make sure the + // current user is first in the list, and cap a maximum of 3 names. + const names = likes.sort(a => a === app.session.user ? -1 : 1) + .slice(0, limit) + .map(user => { + return ( + + {user === app.session.user ? 'You' : username(user)} + + ); + }); + + // If there are more users that we've run out of room to display, add a "x + // others" name to the end of the list. Clicking on it will display a modal + // with a full list of names. + if (likes.length > limit + 1) { + names.push( + { + e.preventDefault(); + app.modal.show(new PostLikesModal({post})); + }}> + {app.trans('likes.others', {count: likes.length - limit})} + + ); + } + + items.add('liked', ( +
+ {icon('thumbs-o-up')} + {app.trans('likes.post_liked_by' + (likes[0] === app.session.user ? '_self' : ''), { + count: names.length, + users: punctuate(names) + })} +
+ )); + } + }); +} diff --git a/extensions/likes/js/forum/src/components/PostLikedNotification.js b/extensions/likes/js/forum/src/components/PostLikedNotification.js new file mode 100644 index 000000000..8b42df07d --- /dev/null +++ b/extensions/likes/js/forum/src/components/PostLikedNotification.js @@ -0,0 +1,27 @@ +import Notification from 'flarum/components/Notification'; + +export default class PostLikedNotification extends Notification { + icon() { + return 'thumbs-o-up'; + } + + href() { + return app.route.post(this.props.notification.subject()); + } + + content() { + const notification = this.props.notification; + const post = notification.subject(); + const user = notification.sender(); + const auc = notification.additionalUnreadCount(); + + return app.trans('likes.post_liked_notification', { + user, + username: auc ? punctuate([ + username(user), + app.trans('core.others', {count: auc}) + ]) : undefined, + number: post.number() + }); + } +} diff --git a/extensions/likes/js/forum/src/components/PostLikesModal.js b/extensions/likes/js/forum/src/components/PostLikesModal.js new file mode 100644 index 000000000..0d1fb69ca --- /dev/null +++ b/extensions/likes/js/forum/src/components/PostLikesModal.js @@ -0,0 +1,30 @@ +import Modal from 'flarum/components/Modal'; +import avatar from 'flarum/helpers/avatar'; +import username from 'flarum/helpers/username'; + +export default class PostLikesModal extends Modal { + className() { + return 'PostLikesModal Modal--small'; + } + + title() { + return app.trans('likes.post_likes_modal_title'); + } + + content() { + return ( +
+ +
+ ); + } +} diff --git a/extensions/likes/js/forum/src/main.js b/extensions/likes/js/forum/src/main.js new file mode 100644 index 000000000..269dd7780 --- /dev/null +++ b/extensions/likes/js/forum/src/main.js @@ -0,0 +1,25 @@ +import { extend } from 'flarum/extend'; +import app from 'flarum/app'; +import Post from 'flarum/models/Post'; +import Model from 'flarum/Model'; +import NotificationGrid from 'flarum/components/NotificationGrid'; + +import addLikeAction from 'likes/addLikeAction'; +import addLikesList from 'likes/addLikesList'; +import PostLikedNotification from 'likes/components/PostLikedNotification'; + +app.notificationComponents.postLiked = PostLikedNotification; + +Post.prototype.canLike = Model.attribute('canLike'); +Post.prototype.likes = Model.hasMany('likes'); + +addLikeAction(); +addLikesList(); + +extend(NotificationGrid.prototype, 'notificationTypes', function(items) { + items.add('postLiked', { + name: 'postLiked', + icon: 'thumbs-o-up', + label: app.trans('likes.notify_post_liked') + }); +}); diff --git a/extensions/likes/js/src/components/post-liked-notification.js b/extensions/likes/js/src/components/post-liked-notification.js deleted file mode 100644 index d50601ee8..000000000 --- a/extensions/likes/js/src/components/post-liked-notification.js +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 1345d5601..000000000 --- a/extensions/likes/js/src/components/post-likes-modal.js +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index e69de29bb..000000000 diff --git a/extensions/likes/less/forum/extension.less b/extensions/likes/less/forum/extension.less new file mode 100644 index 000000000..58a8ae3bf --- /dev/null +++ b/extensions/likes/less/forum/extension.less @@ -0,0 +1,23 @@ +.PostLikesModal-list { + list-style: none; + padding: 0; + margin: 0; + + a { + color: @text-color; + font-size: 15px; + font-weight: bold; + display: block; + margin-bottom: 10px; + text-decoration: none; + + &:hover .username { + text-decoration: underline; + } + } + .Avatar { + .Avatar--size(32px); + vertical-align: middle; + margin-right: 5px; + } +} diff --git a/extensions/likes/locale/en.yml b/extensions/likes/locale/en.yml index b2e5edd51..f54c2b865 100644 --- a/extensions/likes/locale/en.yml +++ b/extensions/likes/locale/en.yml @@ -1,2 +1,11 @@ -flarum-likes: - # hello_world: Hello, world! +likes: + post_liked_notification: "{username} liked your post #{number}" + post_likes_modal_title: Users Who Like This + post_liked_by_self: "{users} like this." + post_liked_by: + one: "{users} likes this." + other: "{users} like this." + unlike_action: Unlike + like_action: Like + notify_post_liked: Someone likes my post + others: "{count} others" diff --git a/extensions/likes/src/Events/PostWasLiked.php b/extensions/likes/src/Events/PostWasLiked.php index f578a51e8..948eb0d45 100644 --- a/extensions/likes/src/Events/PostWasLiked.php +++ b/extensions/likes/src/Events/PostWasLiked.php @@ -1,23 +1,23 @@ subscribe('Flarum\Likes\Listeners\AddClientAssets'); + $events->subscribe('Flarum\Likes\Listeners\AddModelRelationship'); + $events->subscribe('Flarum\Likes\Listeners\AddApiAttributes'); + $events->subscribe('Flarum\Likes\Listeners\PersistData'); + $events->subscribe('Flarum\Likes\Listeners\NotifyPostLiked'); + } +} diff --git a/extensions/likes/src/Handlers/LikedSaver.php b/extensions/likes/src/Handlers/LikedSaver.php deleted file mode 100755 index f8ad58b99..000000000 --- a/extensions/likes/src/Handlers/LikedSaver.php +++ /dev/null @@ -1,47 +0,0 @@ -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 deleted file mode 100755 index 48e884705..000000000 --- a/extensions/likes/src/Handlers/PostLikedNotifier.php +++ /dev/null @@ -1,50 +0,0 @@ -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 deleted file mode 100644 index 5bb36dadd..000000000 --- a/extensions/likes/src/LikesServiceProvider.php +++ /dev/null @@ -1,64 +0,0 @@ -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/Listeners/AddApiAttributes.php b/extensions/likes/src/Listeners/AddApiAttributes.php new file mode 100755 index 000000000..6c45bc549 --- /dev/null +++ b/extensions/likes/src/Listeners/AddApiAttributes.php @@ -0,0 +1,50 @@ +listen(ApiAttributes::class, __CLASS__.'@addAttributes'); + $events->listen(ApiRelationship::class, __CLASS__.'@addRelationship'); + $events->listen(BuildApiAction::class, __CLASS__.'@includeLikes'); + } + + public function addAttributes(ApiAttributes $event) + { + if ($event->serializer instanceof PostSerializer) { + $event->attributes['canLike'] = (bool) $event->model->can($event->actor, 'like'); + } + } + + public function addRelationship(ApiRelationship $event) + { + if ($event->serializer instanceof PostSerializer && + $event->relationship === 'likes') { + return $event->serializer->hasMany('Flarum\Api\Serializers\UserBasicSerializer', 'likes'); + } + } + + public function includeLikes(BuildApiAction $event) + { + $action = $event->action; + + if ($action instanceof Discussions\ShowAction) { + $event->addInclude('posts.likes'); + } + + if ($action instanceof Posts\IndexAction || + $action instanceof Posts\ShowAction || + $action instanceof Posts\CreateAction || + $action instanceof Posts\UpdateAction) { + $event->addInclude('likes'); + } + } +} diff --git a/extensions/likes/src/Listeners/AddClientAssets.php b/extensions/likes/src/Listeners/AddClientAssets.php new file mode 100755 index 000000000..8b013e03c --- /dev/null +++ b/extensions/likes/src/Listeners/AddClientAssets.php @@ -0,0 +1,40 @@ +listen(RegisterLocales::class, __CLASS__.'@addLocale'); + $events->listen(BuildClientView::class, __CLASS__.'@addAssets'); + } + + public function addLocale(RegisterLocales $event) + { + $event->addTranslations('en', __DIR__.'/../../locale/en.yml'); + } + + public function addAssets(BuildClientView $event) + { + $event->forumAssets([ + __DIR__.'/../../js/forum/dist/extension.js', + __DIR__.'/../../less/forum/extension.less' + ]); + + $event->forumBootstrapper('likes/main'); + + $event->forumTranslations([ + 'likes.post_liked_notification', + 'likes.post_likes_modal_title', + 'likes.post_liked_by_self', + 'likes.post_liked_by', + 'likes.unlike_action', + 'likes.like_action', + 'likes.notify_post_liked', + 'likes.others' + ]); + } +} diff --git a/extensions/likes/src/Listeners/AddModelRelationship.php b/extensions/likes/src/Listeners/AddModelRelationship.php new file mode 100755 index 000000000..a3ce71005 --- /dev/null +++ b/extensions/likes/src/Listeners/AddModelRelationship.php @@ -0,0 +1,21 @@ +listen(ModelRelationship::class, __CLASS__.'@addRelationship'); + } + + public function addRelationship(ModelRelationship $event) + { + if ($event->model instanceof Post && + $event->relationship === 'likes') { + return $event->model->belongsToMany('Flarum\Core\Users\User', 'posts_likes', 'post_id', 'user_id', 'likes'); + } + } +} diff --git a/extensions/likes/src/Listeners/NotifyPostLiked.php b/extensions/likes/src/Listeners/NotifyPostLiked.php new file mode 100755 index 000000000..606579aa8 --- /dev/null +++ b/extensions/likes/src/Listeners/NotifyPostLiked.php @@ -0,0 +1,56 @@ +notifications = $notifications; + } + + public function subscribe(Dispatcher $events) + { + $events->listen(RegisterNotificationTypes::class, __CLASS__.'@registerNotificationType'); + $events->listen(PostWasLiked::class, __CLASS__.'@whenPostWasLiked'); + $events->listen(PostWasUnliked::class, __CLASS__.'@whenPostWasUnliked'); + } + + public function registerNotificationType(RegisterNotificationTypes $event) + { + $event->register( + 'Flarum\Likes\Notifications\PostLikedBlueprint', + 'Flarum\Api\Serializers\PostBasicSerializer', + ['alert'] + ); + } + + public function whenPostWasLiked(PostWasLiked $event) + { + $this->sync($event->post, $event->user, [$event->post->user]); + } + + public function whenPostWasUnliked(PostWasUnliked $event) + { + $this->sync($event->post, $event->user, []); + } + + public function sync(Post $post, User $user, array $recipients) + { + if ($post->user->id != $user->id) { + $this->notifications->sync( + new PostLikedBlueprint($post, $user), + $recipients + ); + } + } +} diff --git a/extensions/likes/src/Listeners/PersistData.php b/extensions/likes/src/Listeners/PersistData.php new file mode 100755 index 000000000..f68ad158b --- /dev/null +++ b/extensions/likes/src/Listeners/PersistData.php @@ -0,0 +1,48 @@ +listen(PostWillBeSaved::class, __CLASS__.'@whenPostWillBeSaved'); + $events->listen(PostWasDeleted::class, __CLASS__.'@whenPostWasDeleted'); + } + + public function whenPostWillBeSaved(PostWillBeSaved $event) + { + $post = $event->post; + $data = $event->data; + + if ($post->exists && isset($data['attributes']['isLiked'])) { + $actor = $event->actor; + $liked = (bool) $data['attributes']['isLiked']; + + if (! $post->can($actor, 'like')) { + throw new PermissionDeniedException; + } + + if ($liked) { + $post->likes()->attach($actor->id); + + $post->raise(new PostWasLiked($post, $actor)); + } else { + $post->likes()->detach($actor->id); + + $post->raise(new PostWasUnliked($post, $actor)); + } + } + } + + public function whenPostWasDeleted(PostWasDeleted $event) + { + $event->post->likes()->detach(); + } +} diff --git a/extensions/likes/src/PostLikedNotification.php b/extensions/likes/src/Notifications/PostLikedBlueprint.php similarity index 60% rename from extensions/likes/src/PostLikedNotification.php rename to extensions/likes/src/Notifications/PostLikedBlueprint.php index bcd79e150..be6cef1bf 100644 --- a/extensions/likes/src/PostLikedNotification.php +++ b/extensions/likes/src/Notifications/PostLikedBlueprint.php @@ -1,10 +1,10 @@ -user; } + public function getData() + { + return null; + } + public static function getType() { return 'postLiked'; @@ -33,6 +38,6 @@ class PostLikedNotification extends NotificationAbstract public static function getSubjectModel() { - return 'Flarum\Core\Models\Post'; + return 'Flarum\Core\Posts\Post'; } }