From 3729a91be35f370b63d7e30479b4e801702d6b49 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Thu, 23 Jul 2015 16:40:54 +0930 Subject: [PATCH] Update for new API --- extensions/subscriptions/.editorconfig | 32 ++++ extensions/subscriptions/.eslintignore | 5 + extensions/subscriptions/.eslintrc | 171 ++++++++++++++++++ extensions/subscriptions/bootstrap.php | 6 +- extensions/subscriptions/flarum.json | 13 +- extensions/subscriptions/js/bootstrap.js | 93 ---------- .../subscriptions/js/{ => forum}/Gulpfile.js | 2 +- .../subscriptions/js/{ => forum}/package.json | 0 .../js/forum/src/addSubscriptionBadge.js | 34 ++++ .../js/forum/src/addSubscriptionControls.js | 34 ++++ .../js/forum/src/addSubscriptionFilter.js | 26 +++ .../src/components/NewPostNotification.js | 20 ++ .../forum/src/components/SubscriptionMenu.js | 80 ++++++++ .../src/components/SubscriptionMenuItem.js | 17 ++ extensions/subscriptions/js/forum/src/main.js | 28 +++ .../src/components/new-post-notification.js | 16 -- .../src/components/subscription-menu-item.js | 17 -- .../js/src/components/subscription-menu.js | 54 ------ extensions/subscriptions/less/extension.less | 29 --- .../subscriptions/less/forum/extension.less | 28 +++ extensions/subscriptions/locale/en.yml | 15 +- extensions/subscriptions/src/Extension.php | 18 ++ .../src/Gambits/SubscriptionGambit.php | 24 +++ .../src/Handlers/SubscriptionSaver.php | 31 ---- .../Handlers/SubscriptionSearchModifier.php | 26 --- .../src/Listeners/AddApiAttributes.php | 21 +++ .../src/Listeners/AddClientAssets.php | 51 ++++++ .../src/Listeners/HideIgnoredDiscussions.php | 33 ++++ .../NotifyNewPosts.php} | 41 +++-- .../src/Listeners/PersistSubscriptionData.php | 31 ++++ .../NewPostBlueprint.php} | 20 +- .../subscriptions/src/SubscriptionGambit.php | 37 ---- .../src/SubscriptionsServiceProvider.php | 59 ------ .../views/emails/newPost.blade.php | 6 +- 34 files changed, 712 insertions(+), 406 deletions(-) create mode 100644 extensions/subscriptions/.editorconfig create mode 100644 extensions/subscriptions/.eslintignore create mode 100644 extensions/subscriptions/.eslintrc delete mode 100644 extensions/subscriptions/js/bootstrap.js rename extensions/subscriptions/js/{ => forum}/Gulpfile.js (54%) rename extensions/subscriptions/js/{ => forum}/package.json (100%) create mode 100644 extensions/subscriptions/js/forum/src/addSubscriptionBadge.js create mode 100644 extensions/subscriptions/js/forum/src/addSubscriptionControls.js create mode 100644 extensions/subscriptions/js/forum/src/addSubscriptionFilter.js create mode 100644 extensions/subscriptions/js/forum/src/components/NewPostNotification.js create mode 100644 extensions/subscriptions/js/forum/src/components/SubscriptionMenu.js create mode 100644 extensions/subscriptions/js/forum/src/components/SubscriptionMenuItem.js create mode 100644 extensions/subscriptions/js/forum/src/main.js delete mode 100644 extensions/subscriptions/js/src/components/new-post-notification.js delete mode 100644 extensions/subscriptions/js/src/components/subscription-menu-item.js delete mode 100644 extensions/subscriptions/js/src/components/subscription-menu.js delete mode 100644 extensions/subscriptions/less/extension.less create mode 100644 extensions/subscriptions/less/forum/extension.less create mode 100644 extensions/subscriptions/src/Extension.php create mode 100644 extensions/subscriptions/src/Gambits/SubscriptionGambit.php delete mode 100755 extensions/subscriptions/src/Handlers/SubscriptionSaver.php delete mode 100755 extensions/subscriptions/src/Handlers/SubscriptionSearchModifier.php create mode 100755 extensions/subscriptions/src/Listeners/AddApiAttributes.php create mode 100755 extensions/subscriptions/src/Listeners/AddClientAssets.php create mode 100755 extensions/subscriptions/src/Listeners/HideIgnoredDiscussions.php rename extensions/subscriptions/src/{Handlers/NewPostNotifier.php => Listeners/NotifyNewPosts.php} (60%) create mode 100755 extensions/subscriptions/src/Listeners/PersistSubscriptionData.php rename extensions/subscriptions/src/{NewPostNotification.php => Notifications/NewPostBlueprint.php} (63%) delete mode 100644 extensions/subscriptions/src/SubscriptionGambit.php delete mode 100644 extensions/subscriptions/src/SubscriptionsServiceProvider.php diff --git a/extensions/subscriptions/.editorconfig b/extensions/subscriptions/.editorconfig new file mode 100644 index 000000000..5612a5e74 --- /dev/null +++ b/extensions/subscriptions/.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/subscriptions/.eslintignore b/extensions/subscriptions/.eslintignore new file mode 100644 index 000000000..86b7c8854 --- /dev/null +++ b/extensions/subscriptions/.eslintignore @@ -0,0 +1,5 @@ +**/bower_components/**/* +**/node_modules/**/* +vendor/**/* +**/Gulpfile.js +**/dist/**/* diff --git a/extensions/subscriptions/.eslintrc b/extensions/subscriptions/.eslintrc new file mode 100644 index 000000000..9cebc759d --- /dev/null +++ b/extensions/subscriptions/.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/subscriptions/bootstrap.php b/extensions/subscriptions/bootstrap.php index d4aeb005f..1dece9cb5 100644 --- a/extensions/subscriptions/bootstrap.php +++ b/extensions/subscriptions/bootstrap.php @@ -1,9 +1,5 @@ app->register('Flarum\Subscriptions\SubscriptionsServiceProvider'); +return 'Flarum\Subscriptions\Extension'; diff --git a/extensions/subscriptions/flarum.json b/extensions/subscriptions/flarum.json index 90107ae87..17c81c6a7 100644 --- a/extensions/subscriptions/flarum.json +++ b/extensions/subscriptions/flarum.json @@ -1,16 +1,21 @@ { - "name": "flarum-subscriptions", + "name": "subscriptions", "title": "Subscriptions", "description": "Allow users to follow discussions and receive notifications for new 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/subscriptions", + "issues": "https://github.com/flarum/subscriptions/issues" } -} \ No newline at end of file +} diff --git a/extensions/subscriptions/js/bootstrap.js b/extensions/subscriptions/js/bootstrap.js deleted file mode 100644 index 219caf426..000000000 --- a/extensions/subscriptions/js/bootstrap.js +++ /dev/null @@ -1,93 +0,0 @@ -import { extend, override } from 'flarum/extension-utils'; -import app from 'flarum/app'; -import Model from 'flarum/model'; -import Component from 'flarum/component'; -import Discussion from 'flarum/models/discussion'; -import Badge from 'flarum/components/badge'; -import ActionButton from 'flarum/components/action-button'; -import SettingsPage from 'flarum/components/settings-page'; -import DiscussionPage from 'flarum/components/discussion-page'; -import IndexPage from 'flarum/components/index-page'; -import IndexNavItem from 'flarum/components/index-nav-item'; -import DiscussionList from 'flarum/components/discussion-list'; -import icon from 'flarum/helpers/icon'; - -import SubscriptionMenu from 'flarum-subscriptions/components/subscription-menu'; -import NewPostNotification from 'flarum-subscriptions/components/new-post-notification'; - -app.initializers.add('flarum-subscriptions', function() { - - app.notificationComponentRegistry['newPost'] = NewPostNotification; - - Discussion.prototype.subscription = Model.prop('subscription'); - - // Add subscription badges to discussions. - extend(Discussion.prototype, 'badges', function(badges) { - var badge; - - switch (this.subscription()) { - case 'follow': - badge = Badge.component({ label: 'Following', icon: 'star', className: 'badge-follow' }); - break; - - case 'ignore': - badge = Badge.component({ label: 'Ignoring', icon: 'eye-slash', className: 'badge-ignore' }); - } - - if (badge) { - badges.add('subscription', badge); - } - }); - - extend(Discussion.prototype, 'userControls', function(items, context) { - if (app.session.user() && !(context instanceof DiscussionPage)) { - var states = { - none: {label: 'Follow', icon: 'star', save: 'follow'}, - follow: {label: 'Unfollow', icon: 'star-o', save: false}, - ignore: {label: 'Unignore', icon: 'eye', save: false} - }; - var subscription = this.subscription() || 'none'; - - items.add('subscription', ActionButton.component({ - label: states[subscription].label, - icon: states[subscription].icon, - onclick: this.save.bind(this, {subscription: states[subscription].save}) - })); - } - }); - - extend(DiscussionPage.prototype, 'sidebarItems', function(items) { - if (app.session.user()) { - var discussion = this.discussion(); - items.add('subscription', SubscriptionMenu.component({discussion}), {after: 'controls'}); - } - }); - - extend(IndexPage.prototype, 'navItems', function(items) { - if (app.session.user()) { - var params = this.stickyParams(); - params.filter = 'following'; - - items.add('following', IndexNavItem.component({ - href: app.route('index.filter', params), - label: 'Following', - icon: 'star' - }), {after: 'allDiscussions'}); - } - }); - - extend(DiscussionList.prototype, 'params', function(params) { - if (params.filter === 'following') { - params.q = (params.q || '')+' is:following'; - } - }); - - // Add a notification preference. - extend(SettingsPage.prototype, 'notificationTypes', function(items) { - items.add('newPost', { - name: 'newPost', - label: [icon('star'), " Someone posts in a discussion I'm following"] - }); - }); - -}); diff --git a/extensions/subscriptions/js/Gulpfile.js b/extensions/subscriptions/js/forum/Gulpfile.js similarity index 54% rename from extensions/subscriptions/js/Gulpfile.js rename to extensions/subscriptions/js/forum/Gulpfile.js index 41e50fe34..2c0b6d96c 100644 --- a/extensions/subscriptions/js/Gulpfile.js +++ b/extensions/subscriptions/js/forum/Gulpfile.js @@ -1,5 +1,5 @@ var gulp = require('flarum-gulp'); gulp({ - modulePrefix: 'flarum-subscriptions' + modulePrefix: 'subscriptions' }); diff --git a/extensions/subscriptions/js/package.json b/extensions/subscriptions/js/forum/package.json similarity index 100% rename from extensions/subscriptions/js/package.json rename to extensions/subscriptions/js/forum/package.json diff --git a/extensions/subscriptions/js/forum/src/addSubscriptionBadge.js b/extensions/subscriptions/js/forum/src/addSubscriptionBadge.js new file mode 100644 index 000000000..8a350efda --- /dev/null +++ b/extensions/subscriptions/js/forum/src/addSubscriptionBadge.js @@ -0,0 +1,34 @@ +import { extend } from 'flarum/extend'; +import Discussion from 'flarum/models/Discussion'; +import Badge from 'flarum/components/Badge'; + +export default function addSubscriptionBadge() { + extend(Discussion.prototype, 'badges', function(badges) { + let badge; + + switch (this.subscription()) { + case 'follow': + badge = Badge.component({ + label: app.trans('subscriptions.following'), + icon: 'star', + type: 'following' + }); + break; + + case 'ignore': + badge = Badge.component({ + label: app.trans('subscriptions.ignoring'), + icon: 'eye-slash', + type: 'ignoring' + }); + break; + + default: + // no default + } + + if (badge) { + badges.add('subscription', badge); + } + }); +} diff --git a/extensions/subscriptions/js/forum/src/addSubscriptionControls.js b/extensions/subscriptions/js/forum/src/addSubscriptionControls.js new file mode 100644 index 000000000..cee848d7a --- /dev/null +++ b/extensions/subscriptions/js/forum/src/addSubscriptionControls.js @@ -0,0 +1,34 @@ +import { extend } from 'flarum/extend'; +import Button from 'flarum/components/Button'; +import DiscussionPage from 'flarum/components/DiscussionPage'; +import DiscussionControls from 'flarum/utils/DiscussionControls'; + +import SubscriptionMenu from 'subscriptions/components/SubscriptionMenu'; + +export default function addSubscriptionControls() { + extend(DiscussionControls, 'userControls', function(items, discussion, context) { + if (app.session.user && !(context instanceof DiscussionPage)) { + const states = { + none: {label: app.trans('subscriptions.follow'), icon: 'star', save: 'follow'}, + follow: {label: app.trans('subscriptions.unfollow'), icon: 'star-o', save: false}, + ignore: {label: app.trans('subscriptions.unignore'), icon: 'eye', save: false} + }; + + const subscription = discussion.subscription() || 'none'; + + items.add('subscription', Button.component({ + children: states[subscription].label, + icon: states[subscription].icon, + onclick: discussion.save.bind(discussion, {subscription: states[subscription].save}) + })); + } + }); + + extend(DiscussionPage.prototype, 'sidebarItems', function(items) { + if (app.session.user) { + const discussion = this.discussion; + + items.add('subscription', SubscriptionMenu.component({discussion})); + } + }); +} diff --git a/extensions/subscriptions/js/forum/src/addSubscriptionFilter.js b/extensions/subscriptions/js/forum/src/addSubscriptionFilter.js new file mode 100644 index 000000000..36a37a902 --- /dev/null +++ b/extensions/subscriptions/js/forum/src/addSubscriptionFilter.js @@ -0,0 +1,26 @@ +import { extend } from 'flarum/extend'; +import LinkButton from 'flarum/components/LinkButton'; +import IndexPage from 'flarum/components/IndexPage'; +import DiscussionList from 'flarum/components/DiscussionList'; + +export default function addSubscriptionControls() { + extend(IndexPage.prototype, 'navItems', function(items) { + if (app.session.user) { + const params = this.stickyParams(); + + params.filter = 'following'; + + items.add('following', LinkButton.component({ + href: app.route('index.filter', params), + children: app.trans('subscriptions.following'), + icon: 'star' + }), 50); + } + }); + + extend(DiscussionList.prototype, 'requestParams', function(params) { + if (params.filter === 'following') { + params.q = (params.q || '') + ' is:following'; + } + }); +} diff --git a/extensions/subscriptions/js/forum/src/components/NewPostNotification.js b/extensions/subscriptions/js/forum/src/components/NewPostNotification.js new file mode 100644 index 000000000..6c066659b --- /dev/null +++ b/extensions/subscriptions/js/forum/src/components/NewPostNotification.js @@ -0,0 +1,20 @@ +import Notification from 'flarum/components/Notification'; +import username from 'flarum/helpers/username'; + +export default class NewPostNotification extends Notification { + icon() { + return 'star'; + } + + href() { + const notification = this.props.notification; + const discussion = notification.subject(); + const content = notification.content() || {}; + + return app.route.discussion(discussion, content.postNumber); + } + + content() { + return app.trans('subscriptions.new_post_notification', {user: this.props.notification.sender()}); + } +} diff --git a/extensions/subscriptions/js/forum/src/components/SubscriptionMenu.js b/extensions/subscriptions/js/forum/src/components/SubscriptionMenu.js new file mode 100644 index 000000000..abbe4edbd --- /dev/null +++ b/extensions/subscriptions/js/forum/src/components/SubscriptionMenu.js @@ -0,0 +1,80 @@ +import Component from 'flarum/Component'; +import Button from 'flarum/components/Button'; +import icon from 'flarum/helpers/icon'; + +import SubscriptionMenuItem from 'subscriptions/components/SubscriptionMenuItem'; + +export default class SubscriptionMenu extends Component { + view() { + const discussion = this.props.discussion; + const subscription = discussion.subscription(); + + let buttonLabel = app.trans('subscriptions.follow'); + let buttonIcon = 'star-o'; + const buttonClass = 'SubscriptionMenu-button--' + subscription; + + switch (subscription) { + case 'follow': + buttonLabel = app.trans('subscriptions.following'); + buttonIcon = 'star'; + break; + + case 'ignore': + buttonLabel = app.trans('subscriptions.ignoring'); + buttonIcon = 'eye-slash'; + break; + + default: + // no default + } + + const options = [ + { + subscription: false, + icon: 'star-o', + label: app.trans('subscriptions.not_following'), + description: app.trans('subscriptions.not_following_description') + }, + { + subscription: 'follow', + icon: 'star', + label: app.trans('subscriptions.following'), + description: app.trans('subscriptions.following_description') + }, + { + subscription: 'ignore', + icon: 'eye-slash', + label: app.trans('subscriptions.ignoring'), + description: app.trans('subscriptions.ignoring_description') + } + ]; + + return ( +
+ {Button.component({ + className: 'Button SubscriptionMenu-button ' + buttonClass, + icon: buttonIcon, + children: buttonLabel, + onclick: this.saveSubscription.bind(this, discussion, ['follow', 'ignore'].indexOf(subscription) !== -1 ? false : 'follow') + })} + + + + +
+ ); + } + + saveSubscription(discussion, subscription) { + discussion.save({subscription}); + } +} diff --git a/extensions/subscriptions/js/forum/src/components/SubscriptionMenuItem.js b/extensions/subscriptions/js/forum/src/components/SubscriptionMenuItem.js new file mode 100644 index 000000000..494034009 --- /dev/null +++ b/extensions/subscriptions/js/forum/src/components/SubscriptionMenuItem.js @@ -0,0 +1,17 @@ +import Component from 'flarum/Component'; +import icon from 'flarum/helpers/icon'; + +export default class SubscriptionMenuItem extends Component { + view() { + return ( + + ); + } +} diff --git a/extensions/subscriptions/js/forum/src/main.js b/extensions/subscriptions/js/forum/src/main.js new file mode 100644 index 000000000..9b8467c04 --- /dev/null +++ b/extensions/subscriptions/js/forum/src/main.js @@ -0,0 +1,28 @@ +import { extend } from 'flarum/extend'; +import app from 'flarum/app'; +import Model from 'flarum/Model'; +import Discussion from 'flarum/models/Discussion'; +import NotificationGrid from 'flarum/components/NotificationGrid'; + +import addSubscriptionBadge from 'subscriptions/addSubscriptionBadge'; +import addSubscriptionControls from 'subscriptions/addSubscriptionControls'; +import addSubscriptionFilter from 'subscriptions/addSubscriptionFilter'; +import NewPostNotification from 'subscriptions/components/NewPostNotification'; + +app.initializers.add('subscriptions', function() { + app.notificationComponents.newPost = NewPostNotification; + + Discussion.prototype.subscription = Model.attribute('subscription'); + + addSubscriptionBadge(); + addSubscriptionControls(); + addSubscriptionFilter(); + + extend(NotificationGrid.prototype, 'notificationTypes', function(items) { + items.add('newPost', { + name: 'newPost', + icon: 'star', + label: app.trans('subscriptions.notify_new_post') + }); + }); +}); diff --git a/extensions/subscriptions/js/src/components/new-post-notification.js b/extensions/subscriptions/js/src/components/new-post-notification.js deleted file mode 100644 index a2ccd259d..000000000 --- a/extensions/subscriptions/js/src/components/new-post-notification.js +++ /dev/null @@ -1,16 +0,0 @@ -import Notification from 'flarum/components/notification'; -import username from 'flarum/helpers/username'; - -export default class NewPostNotification extends Notification { - view() { - var notification = this.props.notification; - var discussion = notification.subject(); - var content = notification.content() || {}; - - return super.view({ - href: app.route.discussion(discussion, content.postNumber), - icon: 'star', - content: [username(notification.sender()), ' posted'] - }); - } -} diff --git a/extensions/subscriptions/js/src/components/subscription-menu-item.js b/extensions/subscriptions/js/src/components/subscription-menu-item.js deleted file mode 100644 index d388c221e..000000000 --- a/extensions/subscriptions/js/src/components/subscription-menu-item.js +++ /dev/null @@ -1,17 +0,0 @@ -import Component from 'flarum/component'; -import icon from 'flarum/helpers/icon'; - -export default class SubscriptionMenuItem extends Component { - view() { - return m('a.subscription-menu-item.has-icon[href=javascript:;]', { - onclick: this.props.onclick - }, [ - this.props.active ? icon('check icon') : '', - m('span.label', - icon(this.props.icon+' icon'), - m('strong', this.props.label), - m('span.description', this.props.description) - ) - ]); - } -} diff --git a/extensions/subscriptions/js/src/components/subscription-menu.js b/extensions/subscriptions/js/src/components/subscription-menu.js deleted file mode 100644 index 68cc308f0..000000000 --- a/extensions/subscriptions/js/src/components/subscription-menu.js +++ /dev/null @@ -1,54 +0,0 @@ -import Component from 'flarum/component'; -import ActionButton from 'flarum/components/action-button'; -import icon from 'flarum/helpers/icon'; - -import SubscriptionMenuItem from 'flarum-subscriptions/components/subscription-menu-item'; - -export default class SubscriptionMenu extends Component { - view() { - var discussion = this.props.discussion; - var subscription = discussion.subscription(); - - var buttonLabel = 'Follow'; - var buttonIcon = 'star-o'; - var buttonClass = 'btn-subscription-'+subscription; - - switch (subscription) { - case 'follow': - buttonLabel = 'Following'; - buttonIcon = 'star'; - break; - - case 'ignore': - buttonLabel = 'Ignoring'; - buttonIcon = 'eye-slash'; - } - - var options = [ - {subscription: false, icon: 'star-o', label: 'Not Following', description: 'Be notified when @mentioned.'}, - {subscription: 'follow', icon: 'star', label: 'Following', description: 'Be notified of all replies.'}, - {subscription: 'ignore', icon: 'eye-slash', label: 'Ignoring', description: 'Never be notified. Hide from the discussion list.'} - ]; - - return m('div.dropdown.btn-group.subscription-menu', [ - ActionButton.component({ - className: 'btn btn-default '+buttonClass, - icon: buttonIcon, - label: buttonLabel, - onclick: this.saveSubscription.bind(this, discussion, ['follow', 'ignore'].indexOf(subscription) !== -1 ? false : 'follow') - }), - - m('a.dropdown-toggle.btn.btn-default.btn-icon[href=javascript:;][data-toggle=dropdown]', {className: buttonClass}, icon('caret-down icon-caret')), - - m('ul.dropdown-menu.pull-right', options.map(props => { - props.onclick = this.saveSubscription.bind(this, discussion, props.subscription); - props.active = subscription === props.subscription; - return m('li', SubscriptionMenuItem.component(props)); - })) - ]); - } - - saveSubscription(discussion, subscription) { - discussion.save({subscription}); - } -} diff --git a/extensions/subscriptions/less/extension.less b/extensions/subscriptions/less/extension.less deleted file mode 100644 index 1ebc4c3ae..000000000 --- a/extensions/subscriptions/less/extension.less +++ /dev/null @@ -1,29 +0,0 @@ -.badge-follow { - background: #fc0; -} -.badge-ignore { - background: #aaa; -} -.btn-subscription-follow { - .button-variant(#de8e00, #fff2ae, #fff2ae); -} -.subscription-menu .dropdown-menu { - min-width: 260px; -} -.subscription-menu-item { - & .label { - padding-left: 25px; - display: block; - white-space: normal; - - & strong { - display: block; - } - & .description { - display: block; - color: @fl-body-muted-color; - font-size: 12px; - margin-top: 3px; - } - } -} diff --git a/extensions/subscriptions/less/forum/extension.less b/extensions/subscriptions/less/forum/extension.less new file mode 100644 index 000000000..66c301dd8 --- /dev/null +++ b/extensions/subscriptions/less/forum/extension.less @@ -0,0 +1,28 @@ +.Badge--following { + background: #fc0; +} +.Badge--ignoring { + background: #aaa; +} +.SubscriptionMenu-button--follow { + .Button--color(#de8e00, #fff2ae); + // TODO: dark mode +} +.SubscriptionMenu .Dropdown-menu { + min-width: 260px; +} +.SubscriptionMenuItem-label { + padding-left: 25px; + display: block; + white-space: normal; + + & strong { + display: block; + } +} +.SubscriptionMenuItem-description { + display: block; + color: @muted-color; + font-size: 12px; + margin-top: 3px; +} diff --git a/extensions/subscriptions/locale/en.yml b/extensions/subscriptions/locale/en.yml index 16a9b2eb3..e9921a922 100644 --- a/extensions/subscriptions/locale/en.yml +++ b/extensions/subscriptions/locale/en.yml @@ -1,2 +1,13 @@ -flarum-subscriptions: - # hello_world: Hello, world! +subscriptions: + following: Following + ignoring: Ignoring + follow: Follow + unfollow: Unfollow + ignore: Ignore + notify_new_post: Someone posts in a discussion I'm following + new_post_notification: "{username} posted" + not_following: Not Following + not_following_description: Be notified when @mentioned. + following_description: Be notified of all replies. + ignoring_description: Never be notified. Hide from the discussion list. + unignore: Unignore diff --git a/extensions/subscriptions/src/Extension.php b/extensions/subscriptions/src/Extension.php new file mode 100644 index 000000000..8ecc35000 --- /dev/null +++ b/extensions/subscriptions/src/Extension.php @@ -0,0 +1,18 @@ +loadViewsFrom(__DIR__.'/../views', 'subscriptions'); + + $events->subscribe('Flarum\Subscriptions\Listeners\AddClientAssets'); + $events->subscribe('Flarum\Subscriptions\Listeners\AddApiAttributes'); + $events->subscribe('Flarum\Subscriptions\Listeners\PersistSubscriptionData'); + $events->subscribe('Flarum\Subscriptions\Listeners\NotifyNewPosts'); + $events->subscribe('Flarum\Subscriptions\Listeners\HideIgnoredDiscussions'); + } +} diff --git a/extensions/subscriptions/src/Gambits/SubscriptionGambit.php b/extensions/subscriptions/src/Gambits/SubscriptionGambit.php new file mode 100644 index 000000000..15198cccd --- /dev/null +++ b/extensions/subscriptions/src/Gambits/SubscriptionGambit.php @@ -0,0 +1,24 @@ +getActor(); + + // might be better as `id IN (subquery)`? + $method = $negate ? 'whereNotExists' : 'whereExists'; + $search->getQuery()->$method(function ($query) use ($actor, $matches) { + $query->select(app('db')->raw(1)) + ->from('users_discussions') + ->whereRaw('discussion_id = discussions.id') + ->where('user_id', $actor->id) + ->where('subscription', $matches[1] === 'follow' ? 'follow' : 'ignore'); + }); + } +} diff --git a/extensions/subscriptions/src/Handlers/SubscriptionSaver.php b/extensions/subscriptions/src/Handlers/SubscriptionSaver.php deleted file mode 100755 index e0dfe863c..000000000 --- a/extensions/subscriptions/src/Handlers/SubscriptionSaver.php +++ /dev/null @@ -1,31 +0,0 @@ -listen('Flarum\Core\Events\DiscussionWillBeSaved', __CLASS__.'@whenDiscussionWillBeSaved'); - } - - public function whenDiscussionWillBeSaved(DiscussionWillBeSaved $event) - { - $discussion = $event->discussion; - $data = $event->command->data; - - if (isset($data['subscription'])) { - $user = $event->command->user; - $subscription = $data['subscription']; - - $state = $discussion->stateFor($user); - - if (! in_array($subscription, ['follow', 'ignore'])) { - $subscription = null; - } - - $state->subscription = $subscription; - $state->save(); - } - } -} diff --git a/extensions/subscriptions/src/Handlers/SubscriptionSearchModifier.php b/extensions/subscriptions/src/Handlers/SubscriptionSearchModifier.php deleted file mode 100755 index a40268dee..000000000 --- a/extensions/subscriptions/src/Handlers/SubscriptionSearchModifier.php +++ /dev/null @@ -1,26 +0,0 @@ -listen('Flarum\Core\Events\DiscussionSearchWillBePerformed', __CLASS__.'@filterIgnored'); - } - - public function filterIgnored(DiscussionSearchWillBePerformed $event) - { - if (! $event->criteria->query) { - // might be better as `id IN (subquery)`? - $user = $event->criteria->user; - $event->searcher->getQuery()->whereNotExists(function ($query) use ($user) { - $query->select(app('db')->raw(1)) - ->from('users_discussions') - ->whereRaw('discussion_id = discussions.id') - ->where('user_id', $user->id) - ->where('subscription', 'ignore'); - }); - } - } -} diff --git a/extensions/subscriptions/src/Listeners/AddApiAttributes.php b/extensions/subscriptions/src/Listeners/AddApiAttributes.php new file mode 100755 index 000000000..e1dec1972 --- /dev/null +++ b/extensions/subscriptions/src/Listeners/AddApiAttributes.php @@ -0,0 +1,21 @@ +listen(ApiAttributes::class, __CLASS__.'@addAttributes'); + } + + public function addAttributes(ApiAttributes $event) + { + if ($event->serializer instanceof DiscussionSerializer && + ($state = $event->model->state)) { + $event->attributes['subscription'] = $state->subscription ?: false; + } + } +} diff --git a/extensions/subscriptions/src/Listeners/AddClientAssets.php b/extensions/subscriptions/src/Listeners/AddClientAssets.php new file mode 100755 index 000000000..6be04ed67 --- /dev/null +++ b/extensions/subscriptions/src/Listeners/AddClientAssets.php @@ -0,0 +1,51 @@ +listen(RegisterLocales::class, __CLASS__.'@addLocale'); + $events->listen(BuildClientView::class, __CLASS__.'@addAssets'); + $events->listen(RegisterForumRoutes::class, __CLASS__.'@addRoutes'); + } + + 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('subscriptions/main'); + + $event->forumTranslations([ + 'subscriptions.following', + 'subscriptions.ignoring', + 'subscriptions.follow', + 'subscriptions.unfollow', + 'subscriptions.ignore', + 'subscriptions.notify_new_post', + 'subscriptions.new_post_notification', + 'subscriptions.not_following', + 'subscriptions.not_following_description', + 'subscriptions.following_description', + 'subscriptions.ignoring_description', + 'subscriptions.unignore' + ]); + } + + public function addRoutes(RegisterForumRoutes $event) + { + $event->get('/following', 'flarum.forum.following'); + } +} diff --git a/extensions/subscriptions/src/Listeners/HideIgnoredDiscussions.php b/extensions/subscriptions/src/Listeners/HideIgnoredDiscussions.php new file mode 100755 index 000000000..057af21e1 --- /dev/null +++ b/extensions/subscriptions/src/Listeners/HideIgnoredDiscussions.php @@ -0,0 +1,33 @@ +listen(RegisterDiscussionGambits::class, __CLASS__.'@addGambit'); + $events->listen(DiscussionSearchWillBePerformed::class, __CLASS__.'@filterIgnored'); + } + + public function addGambit(RegisterDiscussionGambits $event) + { + $event->gambits->add('Flarum\Subscriptions\Gambits\SubscriptionGambit'); + } + + public function filterIgnored(DiscussionSearchWillBePerformed $event) + { + if (! $event->criteria->query) { + // might be better as `id IN (subquery)`? + $actor = $event->search->getActor(); + $event->search->getQuery()->whereNotExists(function ($query) use ($actor) { + $query->select(app('flarum.db')->raw(1)) + ->from('users_discussions') + ->whereRaw('discussion_id = discussions.id') + ->where('user_id', $actor->id) + ->where('subscription', 'ignore'); + }); + } + } +} diff --git a/extensions/subscriptions/src/Handlers/NewPostNotifier.php b/extensions/subscriptions/src/Listeners/NotifyNewPosts.php similarity index 60% rename from extensions/subscriptions/src/Handlers/NewPostNotifier.php rename to extensions/subscriptions/src/Listeners/NotifyNewPosts.php index ae9b8a43f..16bd1bfe9 100755 --- a/extensions/subscriptions/src/Handlers/NewPostNotifier.php +++ b/extensions/subscriptions/src/Listeners/NotifyNewPosts.php @@ -1,14 +1,15 @@ -notifications = $notifications; } - /** - * Register the listeners for the subscriber. - * - * @param \Illuminate\Contracts\Events\Dispatcher $events - */ public function subscribe(Dispatcher $events) { + $events->listen(RegisterNotificationTypes::class, __CLASS__.'@addNotificationType'); + // Register with '1' as priority so this runs before discussion metadata // is updated, as we need to compare the user's last read number to that // of the previous post. - $events->listen('Flarum\Core\Events\PostWasPosted', __CLASS__.'@whenPostWasPosted', 1); - $events->listen('Flarum\Core\Events\PostWasHidden', __CLASS__.'@whenPostWasHidden'); - $events->listen('Flarum\Core\Events\PostWasRestored', __CLASS__.'@whenPostWasRestored'); - $events->listen('Flarum\Core\Events\PostWasDeleted', __CLASS__.'@whenPostWasDeleted'); + $events->listen(PostWasPosted::class, __CLASS__.'@whenPostWasPosted', 1); + $events->listen(PostWasHidden::class, __CLASS__.'@whenPostWasHidden'); + $events->listen(PostWasRestored::class, __CLASS__.'@whenPostWasRestored'); + $events->listen(PostWasDeleted::class, __CLASS__.'@whenPostWasDeleted'); + } + + public function addNotificationType(RegisterNotificationTypes $event) + { + $event->register( + NewPostBlueprint::class, + 'Flarum\Api\Serializers\DiscussionBasicSerializer', + ['alert', 'email'] + ); } public function whenPostWasPosted(PostWasPosted $event) @@ -67,6 +74,6 @@ class NewPostNotifier protected function getNotification($post) { - return new NewPostNotification($post); + return new NewPostBlueprint($post); } } diff --git a/extensions/subscriptions/src/Listeners/PersistSubscriptionData.php b/extensions/subscriptions/src/Listeners/PersistSubscriptionData.php new file mode 100755 index 000000000..a4e8f66f8 --- /dev/null +++ b/extensions/subscriptions/src/Listeners/PersistSubscriptionData.php @@ -0,0 +1,31 @@ +listen(DiscussionWillBeSaved::class, __CLASS__.'@whenDiscussionWillBeSaved'); + } + + public function whenDiscussionWillBeSaved(DiscussionWillBeSaved $event) + { + $discussion = $event->discussion; + $data = $event->data; + + if (isset($data['attributes']['subscription'])) { + $actor = $event->actor; + $subscription = $data['attributes']['subscription']; + + $state = $discussion->stateFor($actor); + + if (! in_array($subscription, ['follow', 'ignore'])) { + $subscription = null; + } + + $state->subscription = $subscription; + $state->save(); + } + } +} diff --git a/extensions/subscriptions/src/NewPostNotification.php b/extensions/subscriptions/src/Notifications/NewPostBlueprint.php similarity index 63% rename from extensions/subscriptions/src/NewPostNotification.php rename to extensions/subscriptions/src/Notifications/NewPostBlueprint.php index 38ce0581b..eeaf5f5cc 100644 --- a/extensions/subscriptions/src/NewPostNotification.php +++ b/extensions/subscriptions/src/Notifications/NewPostBlueprint.php @@ -1,10 +1,11 @@ - 'flarum-subscriptions::emails.newPost']; + return ['text' => 'subscriptions::emails.newPost']; } public function getEmailSubject() @@ -45,11 +46,6 @@ class NewPostNotification extends NotificationAbstract public static function getSubjectModel() { - return 'Flarum\Core\Models\Discussion'; - } - - public static function isEmailable() - { - return true; + return 'Flarum\Core\Discussions\Discussion'; } } diff --git a/extensions/subscriptions/src/SubscriptionGambit.php b/extensions/subscriptions/src/SubscriptionGambit.php deleted file mode 100644 index b9c734d4e..000000000 --- a/extensions/subscriptions/src/SubscriptionGambit.php +++ /dev/null @@ -1,37 +0,0 @@ -getUser(); - - // might be better as `id IN (subquery)`? - $method = $negate ? 'whereNotExists' : 'whereExists'; - $searcher->getQuery()->$method(function ($query) use ($user, $matches) { - $query->select(app('db')->raw(1)) - ->from('users_discussions') - ->whereRaw('discussion_id = discussions.id') - ->where('user_id', $user->id) - ->where('subscription', $matches[1] === 'follow' ? 'follow' : 'ignore'); - }); - } -} diff --git a/extensions/subscriptions/src/SubscriptionsServiceProvider.php b/extensions/subscriptions/src/SubscriptionsServiceProvider.php deleted file mode 100644 index e1543173c..000000000 --- a/extensions/subscriptions/src/SubscriptionsServiceProvider.php +++ /dev/null @@ -1,59 +0,0 @@ -loadViewsFrom(__DIR__.'/../views', 'flarum-subscriptions'); - - $this->extend([ - (new Extend\Locale('en'))->translations(__DIR__.'/../locale/en.yml'), - - (new Extend\ForumClient()) - ->assets([ - __DIR__.'/../js/dist/extension.js', - __DIR__.'/../less/extension.less' - ]) - ->translations([ - // Add the keys of translations you would like to be available - // for use by the JS client application. - ]) - ->route('get', '/following', 'flarum.forum.following'), - - (new Extend\ApiSerializer('Flarum\Api\Serializers\DiscussionSerializer')) - ->attributes(function (&$attributes, $discussion, $user) { - if ($state = $discussion->stateFor($user)) { - $attributes['subscription'] = $state->subscription ?: false; - } - }), - - new Extend\EventSubscriber('Flarum\Subscriptions\Handlers\SubscriptionSaver'), - new Extend\EventSubscriber('Flarum\Subscriptions\Handlers\SubscriptionSearchModifier'), - new Extend\EventSubscriber('Flarum\Subscriptions\Handlers\NewPostNotifier'), - - new Extend\DiscussionGambit('Flarum\Subscriptions\SubscriptionGambit'), - - (new Extend\NotificationType('Flarum\Subscriptions\NewPostNotification', 'Flarum\Api\Serializers\DiscussionBasicSerializer')) - ->enableByDefault('alert') - ->enableByDefault('email') - ]); - } - - /** - * Register the service provider. - * - * @return void - */ - public function register() - { - // - } -} diff --git a/extensions/subscriptions/views/emails/newPost.blade.php b/extensions/subscriptions/views/emails/newPost.blade.php index 0cc3a2af8..0592d3006 100644 --- a/extensions/subscriptions/views/emails/newPost.blade.php +++ b/extensions/subscriptions/views/emails/newPost.blade.php @@ -1,13 +1,13 @@ Hey {{ $user->username }}! -{{ $notification->post->user->username }} made a post in a discussion you're following: {{ $notification->post->discussion->title }} +{{ $blueprint->post->user->username }} made a post in a discussion you're following: {{ $blueprint->post->discussion->title }} To view the new activity, check out the following link: -{{ \Flarum\Core::config('base_url') }}/d/{{ $notification->post->discussion_id }}/-/{{ $notification->post->number }} +{{ \Flarum\Core::config('base_url') }}/d/{{ $blueprint->post->discussion_id }}/-/{{ $blueprint->post->number }} --- -{{ strip_tags($notification->post->contentHtml) }} +{{ strip_tags($blueprint->post->contentHtml) }} ---