From e3b26b48a9e5d6bf4438b948a2f6448bc2d4800e Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Fri, 12 Jun 2015 16:43:41 +0930 Subject: [PATCH] New tag selection modal when composing a discussion Also numerous bug fixes. Still WIP --- extensions/tags/js/bootstrap.js | 8 +- extensions/tags/js/src/add-tag-composer.js | 54 ++++ .../tags/js/src/add-tag-discussion-control.js | 18 ++ extensions/tags/js/src/add-tag-labels.js | 9 +- ...n.js => discussion-tagged-notification.js} | 0 ...oved-post.js => discussion-tagged-post.js} | 0 .../src/components/move-discussion-modal.js | 58 ----- .../js/src/components/tag-discussion-modal.js | 234 ++++++++++++++++++ extensions/tags/js/src/helpers/tags-label.js | 3 +- extensions/tags/js/src/utils/sort-tags.js | 25 ++ extensions/tags/less/extension.less | 120 +++++++++ .../2015_02_24_000000_create_tags_table.php | 9 +- ...onWasMoved.php => DiscussionWasTagged.php} | 12 +- .../tags/src/Handlers/CategorySaver.php | 35 --- extensions/tags/src/Handlers/TagSaver.php | 56 +++++ extensions/tags/src/TagsServiceProvider.php | 6 +- 16 files changed, 536 insertions(+), 111 deletions(-) create mode 100644 extensions/tags/js/src/add-tag-composer.js create mode 100644 extensions/tags/js/src/add-tag-discussion-control.js rename extensions/tags/js/src/components/{discussion-moved-notification.js => discussion-tagged-notification.js} (100%) rename extensions/tags/js/src/components/{discussion-moved-post.js => discussion-tagged-post.js} (100%) delete mode 100644 extensions/tags/js/src/components/move-discussion-modal.js create mode 100644 extensions/tags/js/src/components/tag-discussion-modal.js create mode 100644 extensions/tags/js/src/utils/sort-tags.js rename extensions/tags/src/Events/{DiscussionWasMoved.php => DiscussionWasTagged.php} (75%) delete mode 100755 extensions/tags/src/Handlers/CategorySaver.php create mode 100755 extensions/tags/src/Handlers/TagSaver.php diff --git a/extensions/tags/js/bootstrap.js b/extensions/tags/js/bootstrap.js index 3570940d4..0a6e0e70f 100644 --- a/extensions/tags/js/bootstrap.js +++ b/extensions/tags/js/bootstrap.js @@ -8,6 +8,8 @@ import TagsPage from 'flarum-tags/components/tags-page'; import addTagList from 'flarum-tags/add-tag-list'; import addTagFilter from 'flarum-tags/add-tag-filter'; import addTagLabels from 'flarum-tags/add-tag-labels'; +import addTagDiscussionControl from 'flarum-tags/add-tag-discussion-control'; +import addTagComposer from 'flarum-tags/add-tag-composer'; app.initializers.add('flarum-tags', function() { // Register routes. @@ -17,7 +19,7 @@ app.initializers.add('flarum-tags', function() { // Register models. app.store.models['tags'] = Tag; Discussion.prototype.tags = Model.many('tags'); - Discussion.prototype.canMove = Model.prop('canMove'); + Discussion.prototype.canTag = Model.prop('canTag'); // Add a list of tags to the index navigation. addTagList(); @@ -28,7 +30,7 @@ app.initializers.add('flarum-tags', function() { // Add tags to the discussion list and discussion hero. addTagLabels(); - // addMoveDiscussionControl(); + addTagDiscussionControl(); - // addDiscussionComposer(); + addTagComposer(); }); diff --git a/extensions/tags/js/src/add-tag-composer.js b/extensions/tags/js/src/add-tag-composer.js new file mode 100644 index 000000000..ab2cd983f --- /dev/null +++ b/extensions/tags/js/src/add-tag-composer.js @@ -0,0 +1,54 @@ +import { extend, override } from 'flarum/extension-utils'; +import IndexPage from 'flarum/components/index-page'; +import DiscussionComposer from 'flarum/components/discussion-composer'; +import icon from 'flarum/helpers/icon'; + +import TagDiscussionModal from 'flarum-tags/components/tag-discussion-modal'; +import tagsLabel from 'flarum-tags/helpers/tags-label'; + +export default function() { + override(IndexPage.prototype, 'composeNewDiscussion', function(original, deferred) { + var tag = app.store.getBy('tags', 'slug', this.params().tags); + + app.modal.show( + new TagDiscussionModal({ + selectedTags: tag ? [tag] : [], + onsubmit: tags => { + original(deferred).then(component => component.tags(tags)); + } + }) + ); + + return deferred.promise; + }); + + // Add tag-selection abilities to the discussion composer. + DiscussionComposer.prototype.tags = m.prop([]); + DiscussionComposer.prototype.chooseTags = function() { + app.modal.show( + new TagDiscussionModal({ + selectedTags: this.tags().slice(0), + onsubmit: tags => { + this.tags(tags); + this.$('textarea').focus(); + } + }) + ); + }; + + // Add a tag-selection menu to the discussion composer's header, after the + // title. + extend(DiscussionComposer.prototype, 'headerItems', function(items) { + var tags = this.tags(); + + items.add('tags', m('a[href=javascript:;][tabindex=-1].control-change-tags', {onclick: this.chooseTags.bind(this)}, [ + tagsLabel(tags) + ])); + }); + + // Add the selected tags as data to submit to the server. + extend(DiscussionComposer.prototype, 'data', function(data) { + data.links = data.links || {}; + data.links.tags = this.tags(); + }); +}; diff --git a/extensions/tags/js/src/add-tag-discussion-control.js b/extensions/tags/js/src/add-tag-discussion-control.js new file mode 100644 index 000000000..449179a7d --- /dev/null +++ b/extensions/tags/js/src/add-tag-discussion-control.js @@ -0,0 +1,18 @@ +import { extend } from 'flarum/extension-utils'; +import Discussion from 'flarum/models/discussion'; +import ActionButton from 'flarum/components/action-button'; + +import TagDiscussionModal from 'flarum-tags/components/tag-discussion-modal'; + +export default function() { + // Add a control allowing the discussion to be moved to another category. + extend(Discussion.prototype, 'controls', function(items) { + if (this.canTag()) { + items.add('tags', ActionButton.component({ + label: 'Edit Tags', + icon: 'tag', + onclick: () => app.modal.show(new TagDiscussionModal({ discussion: this })) + }), {after: 'rename'}); + } + }); +}; diff --git a/extensions/tags/js/src/add-tag-labels.js b/extensions/tags/js/src/add-tag-labels.js index 11edc01bb..61e70dd88 100644 --- a/extensions/tags/js/src/add-tag-labels.js +++ b/extensions/tags/js/src/add-tag-labels.js @@ -4,12 +4,13 @@ import DiscussionPage from 'flarum/components/discussion-page'; import DiscussionHero from 'flarum/components/discussion-hero'; import tagsLabel from 'flarum-tags/helpers/tags-label'; +import sortTags from 'flarum-tags/utils/sort-tags'; export default function() { // Add tag labels to each discussion in the discussion list. extend(DiscussionList.prototype, 'infoItems', function(items, discussion) { var tags = discussion.tags(); - if (tags) { + if (tags && tags.length) { items.add('tags', tagsLabel(tags.filter(tag => tag.slug() !== this.props.params.tags)), {first: true}); } }); @@ -21,8 +22,8 @@ export default function() { // Restyle a discussion's hero to use its first tag's color. extend(DiscussionHero.prototype, 'view', function(view) { - var tags = this.props.discussion.tags(); - if (tags) { + var tags = sortTags(this.props.discussion.tags()); + if (tags && tags.length) { view.attrs.style = 'color: #fff; background-color: '+tags[0].color(); } }); @@ -31,7 +32,7 @@ export default function() { // before the title. Put the title on its own line. extend(DiscussionHero.prototype, 'items', function(items) { var tags = this.props.discussion.tags(); - if (tags) { + if (tags && tags.length) { items.add('tags', tagsLabel(tags, {link: true}), {before: 'title'}); items.title.content.wrapperClass = 'block-item'; diff --git a/extensions/tags/js/src/components/discussion-moved-notification.js b/extensions/tags/js/src/components/discussion-tagged-notification.js similarity index 100% rename from extensions/tags/js/src/components/discussion-moved-notification.js rename to extensions/tags/js/src/components/discussion-tagged-notification.js diff --git a/extensions/tags/js/src/components/discussion-moved-post.js b/extensions/tags/js/src/components/discussion-tagged-post.js similarity index 100% rename from extensions/tags/js/src/components/discussion-moved-post.js rename to extensions/tags/js/src/components/discussion-tagged-post.js diff --git a/extensions/tags/js/src/components/move-discussion-modal.js b/extensions/tags/js/src/components/move-discussion-modal.js deleted file mode 100644 index cf8a8722f..000000000 --- a/extensions/tags/js/src/components/move-discussion-modal.js +++ /dev/null @@ -1,58 +0,0 @@ -import Component from 'flarum/component'; -import DiscussionPage from 'flarum/components/discussion-page'; -import icon from 'flarum/helpers/icon'; -import categoryLabel from 'flarum-categories/helpers/category-label'; - -export default class MoveDiscussionModal extends Component { - constructor(props) { - super(props); - - this.categories = m.prop(app.store.all('categories')); - } - - view() { - var discussion = this.props.discussion; - var discussionCategory = discussion && discussion.category(); - - return m('div.modal-dialog.modal-move-discussion', [ - m('div.modal-content', [ - m('button.btn.btn-icon.btn-link.close.back-control', {onclick: app.modal.close.bind(app.modal)}, icon('times')), - m('div.modal-header', m('h3.title-control', discussion - ? ['Move ', m('em', discussion.title()), ' from ', categoryLabel(discussionCategory), ' to...'] - : ['Start a Discussion In...'])), - m('div', [ - m('ul.category-list', [ - this.categories().map(category => - (discussion && discussionCategory && category.id() === discussionCategory.id()) ? '' : m('li.category-tile', {style: 'background-color: '+category.color()}, [ - m('a[href=javascript:;]', {onclick: this.save.bind(this, category)}, [ - m('h3.title', category.title()), - m('p.description', category.description()), - m('span.count', category.discussionsCount()+' discussions'), - ]) - ]) - ) - ]) - ]) - ]) - ]); - } - - save(category) { - var discussion = this.props.discussion; - - if (discussion) { - discussion.save({links: {category}}).then(discussion => { - if (app.current instanceof DiscussionPage) { - app.current.stream.sync(); - } - m.redraw(); - }); - } - - this.props.onchange && this.props.onchange(category); - - app.modal.close(); - - m.redraw.strategy('none'); - } -} diff --git a/extensions/tags/js/src/components/tag-discussion-modal.js b/extensions/tags/js/src/components/tag-discussion-modal.js new file mode 100644 index 000000000..2b6d00653 --- /dev/null +++ b/extensions/tags/js/src/components/tag-discussion-modal.js @@ -0,0 +1,234 @@ +import FormModal from 'flarum/components/form-modal'; +import DiscussionPage from 'flarum/components/discussion-page'; +import highlight from 'flarum/helpers/highlight'; +import classList from 'flarum/utils/class-list'; + +import tagLabel from 'flarum-tags/helpers/tag-label'; +import tagIcon from 'flarum-tags/helpers/tag-icon'; +import sortTags from 'flarum-tags/utils/sort-tags'; + +export default class TagDiscussionModal extends FormModal { + constructor(props) { + super(props); + + this.tags = sortTags(app.store.all('tags')); + + this.selected = m.prop([]); + if (this.props.selectedTags) { + this.props.selectedTags.map(this.addTag.bind(this)); + } else if (this.props.discussion) { + this.props.discussion.tags().map(this.addTag.bind(this)); + } + + this.filter = m.prop(''); + + this.index = m.prop(this.tags[0].id()); + + this.focused = m.prop(false); + } + + addTag(tag) { + var selected = this.selected(); + var parent = tag.parent(); + if (parent) { + var index = selected.indexOf(parent); + if (index === -1) { + selected.push(parent); + } + } + selected.push(tag); + } + + removeTag(tag) { + var selected = this.selected(); + var index = selected.indexOf(tag); + selected.splice(index, 1); + selected.filter(selected => selected.parent() && selected.parent() === tag).forEach(child => { + var index = selected.indexOf(child); + selected.splice(index, 1); + }); + } + + view() { + var discussion = this.props.discussion; + var selected = this.selected(); + + var tags = this.tags; + var filter = this.filter().toLowerCase(); + + if (filter) { + tags = tags.filter(tag => tag.name().substr(0, filter.length).toLowerCase() === filter); + } + + if (tags.indexOf(this.index()) === -1) { + this.index(tags[0]); + } + + return super.view({ + className: 'tag-discussion-modal', + title: discussion + ? ['Edit Tags for ', m('em', discussion.title())] + : 'Start a Discussion About...', + body: [ + m('div.tags-form', [ + m('div.tags-input.form-control', {className: this.focused() ? 'focus' : ''}, [ + m('span.tags-input-selected', selected.map(tag => + m('span.remove-tag', {onclick: () => { + this.removeTag(tag); + this.ready(); + }}, tagLabel(tag)) + )), + m('input.form-control', { + placeholder: !selected.length ? 'Choose one or more topics' : '', + value: this.filter(), + oninput: m.withAttr('value', this.filter), + onkeydown: this.onkeydown.bind(this), + onfocus: () => this.focused(true), + onblur: () => this.focused(false) + }) + ]), + m('button[type=submit].btn.btn-primary', {disabled: !selected.length}, 'Confirm') + ]) + ], + footer: [ + m('ul.tags-select', tags.map(tag => + filter || !tag.parent() || selected.indexOf(tag.parent()) !== -1 + ? m('li', { + 'data-index': tag.id(), + className: classList({ + category: tag.position() !== null, + selected: selected.indexOf(tag) !== -1, + active: this.index() == tag + }), + style: { + color: tag.color() + }, + onmouseover: () => { + this.index(tag); + }, + onclick: () => { + var selected = this.selected(); + var index = selected.indexOf(tag); + if (index !== -1) { + this.removeTag(tag); + } else { + this.addTag(tag); + } + if (this.filter()) { + this.filter(''); + this.index(this.tags[0]); + } + this.ready(); + } + }, [ + tagIcon(tag), + m('span.name', highlight(tag.name(), filter)), + tag.description() ? m('span.description', tag.description()) : '' + ]) + : '' + )) + ] + }); + } + + onkeydown(e) { + switch (e.which) { + case 40: + case 38: // Down/Up + e.preventDefault(); + this.setIndex(this.getCurrentNumericIndex() + (e.which === 40 ? 1 : -1), true); + break; + + case 13: // Return + e.preventDefault(); + if (e.metaKey || e.ctrlKey || this.selected().indexOf(this.index()) !== -1) { + if (this.selected().length) { + this.$('form').submit(); + } + } else { + this.getItem(this.index())[0].dispatchEvent(new Event('click')); + } + break; + + case 8: // Backspace + if (e.target.selectionStart == 0 && e.target.selectionEnd == 0) { + e.preventDefault(); + var selected = this.selected(); + selected.splice(selected.length - 1, 1); + } + } + } + + selectableItems() { + return this.$('.tags-select > li'); + } + + getCurrentNumericIndex() { + return this.selectableItems().index( + this.getItem(this.index()) + ); + } + + getItem(index) { + var $items = this.selectableItems(); + return $items.filter('[data-index='+index.id()+']'); + } + + setIndex(index, scrollToItem) { + var $items = this.selectableItems(); + var $dropdown = $items.parent(); + + if (index < 0) { + index = $items.length - 1; + } else if (index >= $items.length) { + index = 0; + } + + var $item = $items.eq(index); + + this.index(app.store.getById('tags', $item.attr('data-index'))); + + m.redraw(); + + if (scrollToItem) { + var dropdownScroll = $dropdown.scrollTop(); + var dropdownTop = $dropdown.offset().top; + var dropdownBottom = dropdownTop + $dropdown.outerHeight(); + var itemTop = $item.offset().top; + var itemBottom = itemTop + $item.outerHeight(); + + var scrollTop; + if (itemTop < dropdownTop) { + scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt($dropdown.css('padding-top')); + } else if (itemBottom > dropdownBottom) { + scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt($dropdown.css('padding-bottom')); + } + + if (typeof scrollTop !== 'undefined') { + $dropdown.stop(true).animate({scrollTop}, 100); + } + } + } + + onsubmit(e) { + e.preventDefault(); + + var discussion = this.props.discussion; + var tags = this.selected(); + + if (discussion) { + discussion.save({links: {tags}}).then(discussion => { + if (app.current instanceof DiscussionPage) { + app.current.stream.sync(); + } + m.redraw(); + }); + } + + this.props.onsubmit && this.props.onsubmit(tags); + + app.modal.close(); + + m.redraw.strategy('none'); + } +} diff --git a/extensions/tags/js/src/helpers/tags-label.js b/extensions/tags/js/src/helpers/tags-label.js index b2e288814..d725f9fef 100644 --- a/extensions/tags/js/src/helpers/tags-label.js +++ b/extensions/tags/js/src/helpers/tags-label.js @@ -1,4 +1,5 @@ import tagLabel from 'flarum-tags/helpers/tag-label'; +import sortTags from 'flarum-tags/utils/sort-tags'; export default function tagsLabel(tags, attrs) { attrs = attrs || {}; @@ -8,7 +9,7 @@ export default function tagsLabel(tags, attrs) { delete attrs.link; if (tags) { - tags.forEach(tag => { + sortTags(tags).forEach(tag => { children.push(tagLabel(tag, {link})); }); } else { diff --git a/extensions/tags/js/src/utils/sort-tags.js b/extensions/tags/js/src/utils/sort-tags.js new file mode 100644 index 000000000..9e3a72750 --- /dev/null +++ b/extensions/tags/js/src/utils/sort-tags.js @@ -0,0 +1,25 @@ +export default function sortTags(tags) { + return tags.slice(0).sort((a, b) => { + var aPos = a.position(); + var bPos = b.position(); + + var aParent = a.parent(); + var bParent = b.parent(); + + if (aPos === null && bPos === null) { + return b.discussionsCount() - a.discussionsCount(); + } else if (bPos === null) { + return -1; + } else if (aPos === null) { + return 1; + } else if (aParent === bParent) { + return aPos - bPos; + } else if (aParent) { + return aParent.position() - bPos; + } else if (bParent) { + return aPos - bParent.position(); + } + + return 0; + }); +}; diff --git a/extensions/tags/less/extension.less b/extensions/tags/less/extension.less index 9d6266bc4..bc14a3d5b 100644 --- a/extensions/tags/less/extension.less +++ b/extensions/tags/less/extension.less @@ -5,6 +5,7 @@ padding: 0.2em 0.55em; border-radius: @border-radius-base; background: @fl-body-secondary-color; + color: @fl-body-muted-color; &.untagged { background: transparent; @@ -91,3 +92,122 @@ margin-left: 10px; } } + +.tag-discussion-modal { + & .modal-header { + background: @fl-body-secondary-color; + padding: 20px; + + & h3 { + text-align: left; + color: @fl-body-muted-color; + font-size: 16px; + } + } + & .modal-body { + padding: 0 20px 20px; + } + & .modal-footer { + padding: 1px 0 0; + text-align: left; + } +} +.tags-form { + padding-right: 100px; + overflow: hidden; + + & .tags-input { + float: left; + } + & .btn { + margin-right: -100px; + float: right; + width: 85px; + } +} +.tags-input { + padding-top: 0; + padding-bottom: 0; + overflow: hidden; + white-space: nowrap; + + & input { + display: inline; + outline: none; + margin-top: -2px; + border: 0 !important; + padding: 0; + width: 100%; + margin-right: -100%; + } + & .remove-tag { + cursor: not-allowed; + } +} +.tags-input-selected { + & .tag-label { + margin-right: 5px; + } +} + +.tags-select { + padding: 0; + margin: 0; + list-style: none; + overflow: auto; + max-height: 50vh; + + & > li { + padding: 7px 20px; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + + &.category { + padding-top: 10px; + padding-bottom: 10px; + + & .name { + font-size: 16px; + } + &.selected .tag-icon:before { + color: #fff; + } + } + &.active { + background: @fl-body-secondary-color; + } + & .name { + display: inline-block; + width: 150px; + margin-right: 10px; + margin-left: 10px; + } + & .description { + color: @fl-body-muted-color; + font-size: 12px; + } + &.selected { + & .tag-icon { + position: relative; + + &:before { + .fa(); + content: @fa-var-check; + color: @fl-body-muted-color; + position: absolute; + font-size: 14px; + width: 100%; + text-align: center; + padding-top: 1px; + } + } + } + & mark { + font-weight: bold; + background: none; + box-shadow: none; + color: inherit; + } + } +} diff --git a/extensions/tags/migrations/2015_02_24_000000_create_tags_table.php b/extensions/tags/migrations/2015_02_24_000000_create_tags_table.php index a3b1f64bc..f74187ff7 100644 --- a/extensions/tags/migrations/2015_02_24_000000_create_tags_table.php +++ b/extensions/tags/migrations/2015_02_24_000000_create_tags_table.php @@ -17,13 +17,20 @@ class CreateTagsTable extends Migration $table->string('name', 100); $table->string('slug', 100); $table->text('description')->nullable(); + $table->string('color', 50)->nullable(); $table->string('background_path', 100)->nullable(); + $table->string('background_mode', 100)->nullable(); $table->string('icon_path', 100)->nullable(); - $table->integer('discussions_count')->unsigned()->default(0); + $table->integer('position')->nullable(); $table->integer('parent_id')->unsigned()->nullable(); $table->string('default_sort', 50)->nullable(); + + $table->integer('discussions_count')->unsigned()->default(0); + $table->integer('last_time')->unsigned()->nullable(); + $table->integer('last_discussion_id')->unsigned()->nullable(); + }); } diff --git a/extensions/tags/src/Events/DiscussionWasMoved.php b/extensions/tags/src/Events/DiscussionWasTagged.php similarity index 75% rename from extensions/tags/src/Events/DiscussionWasMoved.php rename to extensions/tags/src/Events/DiscussionWasTagged.php index 6393accbe..33c96bd2f 100644 --- a/extensions/tags/src/Events/DiscussionWasMoved.php +++ b/extensions/tags/src/Events/DiscussionWasTagged.php @@ -1,9 +1,9 @@ -discussion = $discussion; $this->user = $user; - $this->oldCategoryId = $oldCategoryId; + $this->oldTags = $oldTags; } } diff --git a/extensions/tags/src/Handlers/CategorySaver.php b/extensions/tags/src/Handlers/CategorySaver.php deleted file mode 100755 index 8074da9ed..000000000 --- a/extensions/tags/src/Handlers/CategorySaver.php +++ /dev/null @@ -1,35 +0,0 @@ -listen('Flarum\Core\Events\DiscussionWillBeSaved', __CLASS__.'@whenDiscussionWillBeSaved'); - } - - public function whenDiscussionWillBeSaved(DiscussionWillBeSaved $event) - { - if (isset($event->command->data['links']['category']['linkage'])) { - $linkage = $event->command->data['links']['category']['linkage']; - - $categoryId = (int) $linkage['id']; - $discussion = $event->discussion; - $user = $event->command->user; - - $oldCategoryId = (int) $discussion->category_id; - - if ($oldCategoryId === $categoryId) { - return; - } - - $discussion->category_id = $categoryId; - - if ($discussion->exists) { - $discussion->raise(new DiscussionWasMoved($discussion, $user, $oldCategoryId)); - } - } - } -} diff --git a/extensions/tags/src/Handlers/TagSaver.php b/extensions/tags/src/Handlers/TagSaver.php new file mode 100755 index 000000000..d2d107259 --- /dev/null +++ b/extensions/tags/src/Handlers/TagSaver.php @@ -0,0 +1,56 @@ +listen('Flarum\Core\Events\DiscussionWillBeSaved', __CLASS__.'@whenDiscussionWillBeSaved'); + $events->listen('Flarum\Core\Events\DiscussionWasDeleted', __CLASS__.'@whenDiscussionWasDeleted'); + } + + public function whenDiscussionWillBeSaved(DiscussionWillBeSaved $event) + { + if (isset($event->command->data['links']['tags']['linkage'])) { + $discussion = $event->discussion; + $user = $event->command->user; + $linkage = (array) $event->command->data['links']['tags']['linkage']; + + $newTagIds = []; + foreach ($linkage as $link) { + $newTagIds[] = (int) $link['id']; + } + + $oldTags = []; + + if ($discussion->exists) { + $oldTags = $discussion->tags()->get(); + $oldTagIds = $oldTags->lists('id'); + + if ($oldTagIds == $newTagIds) { + return; + } + } + + // @todo is there a better (safer) way to do this? + // maybe store some info on the discussion model and then use the + // DiscussionWasTagged event to actually save the data? + Discussion::saved(function ($discussion) use ($newTagIds) { + $discussion->tags()->sync($newTagIds); + }); + + if ($discussion->exists) { + $discussion->raise(new DiscussionWasTagged($discussion, $user, $oldTags->all())); + } + } + } + + public function whenDiscussionWasDeleted(DiscussionWasDeleted $event) + { + $event->discussion->tags()->sync([]); + } +} diff --git a/extensions/tags/src/TagsServiceProvider.php b/extensions/tags/src/TagsServiceProvider.php index 4d4e1d157..328149a45 100644 --- a/extensions/tags/src/TagsServiceProvider.php +++ b/extensions/tags/src/TagsServiceProvider.php @@ -25,9 +25,9 @@ class TagsServiceProvider extends ServiceProvider ]), new EventSubscribers([ - // 'Flarum\Categories\Handlers\DiscussionMovedNotifier', + // 'Flarum\Tags\Handlers\DiscussionTaggedNotifier', 'Flarum\Tags\Handlers\TagPreloader', - // 'Flarum\Categories\Handlers\CategorySaver' + 'Flarum\Tags\Handlers\TagSaver' ]), new Relationship('Flarum\Core\Models\Discussion', 'tags', function ($model) { @@ -38,7 +38,7 @@ class TagsServiceProvider extends ServiceProvider new ApiInclude(['discussions.index', 'discussions.show'], 'tags', true), - (new Permission('discussion.editTags')) + (new Permission('discussion.tag')) ->serialize() ->grant(function ($grant, $user) { $grant->where('start_user_id', $user->id);