diff --git a/extensions/tags/js/forum/src/components/TagDiscussionModal.js b/extensions/tags/js/forum/src/components/TagDiscussionModal.js
index 090811a1f..ddd6ddadf 100644
--- a/extensions/tags/js/forum/src/components/TagDiscussionModal.js
+++ b/extensions/tags/js/forum/src/components/TagDiscussionModal.js
@@ -3,6 +3,7 @@ import DiscussionPage from 'flarum/components/DiscussionPage';
import Button from 'flarum/components/Button';
import highlight from 'flarum/helpers/highlight';
import classList from 'flarum/utils/classList';
+import extractText from 'flarum/utils/extractText';
import tagLabel from 'tags/helpers/tagLabel';
import tagIcon from 'tags/helpers/tagIcon';
@@ -24,6 +25,19 @@ export default class TagDiscussionModal extends Modal {
} else if (this.props.discussion) {
this.props.discussion.tags().map(this.addTag.bind(this));
}
+
+ this.minPrimary = app.forum.attribute('minPrimaryTags');
+ this.maxPrimary = app.forum.attribute('maxPrimaryTags');
+ this.minSecondary = app.forum.attribute('minSecondaryTags');
+ this.maxSecondary = app.forum.attribute('maxSecondaryTags');
+ }
+
+ primaryCount() {
+ return this.selected.filter(tag => tag.isPrimary()).length;
+ }
+
+ secondaryCount() {
+ return this.selected.filter(tag => !tag.isPrimary()).length;
}
/**
@@ -75,17 +89,46 @@ export default class TagDiscussionModal extends Modal {
: app.trans('tags.tag_new_discussion_title');
}
+ getInstruction(primaryCount, secondaryCount) {
+ if (primaryCount < this.minPrimary) {
+ return app.trans('tags.choose_primary_tags', {count: this.minPrimary - primaryCount});
+ } else if (secondaryCount < this.minSecondary) {
+ return app.trans('tags.choose_secondary_tags', {count: this.minSecondary - secondaryCount});
+ }
+
+ return '';
+ }
+
content() {
let tags = this.tags;
const filter = this.filter().toLowerCase();
+ const primaryCount = this.primaryCount();
+ const secondaryCount = this.secondaryCount();
+ // Filter out all child tags whose parents have not been selected. This
+ // makes it impossible to select a child if its parent hasn't been selected.
+ tags = tags.filter(tag => {
+ const parent = tag.parent();
+ return parent === false || this.selected.indexOf(parent) !== -1;
+ });
+
+ // If the number of selected primary/secondary tags is at the maximum, then
+ // we'll filter out all other tags of that type.
+ if (primaryCount >= app.forum.attribute('maxPrimaryTags')) {
+ tags = tags.filter(tag => !tag.isPrimary() || this.selected.indexOf(tag) !== -1);
+ }
+
+ if (secondaryCount >= app.forum.attribute('maxSecondaryTags')) {
+ tags = tags.filter(tag => tag.isPrimary() || this.selected.indexOf(tag) !== -1);
+ }
+
+ // If the user has entered text in the filter input, then filter by tags
+ // whose name matches what they've entered.
if (filter) {
tags = tags.filter(tag => tag.name().substr(0, filter.length).toLowerCase() === filter);
}
- if (tags.indexOf(this.index) === -1) {
- this.index = tags[0];
- }
+ if (tags.indexOf(this.index) === -1) this.index = tags[0];
return [
];
diff --git a/extensions/tags/js/lib/models/Tag.js b/extensions/tags/js/lib/models/Tag.js
index 0cfcb74a7..391a4a7bc 100644
--- a/extensions/tags/js/lib/models/Tag.js
+++ b/extensions/tags/js/lib/models/Tag.js
@@ -1,5 +1,6 @@
import Model from 'flarum/Model';
import mixin from 'flarum/utils/mixin';
+import computed from 'flarum/utils/computed';
export default class Tag extends mixin(Model, {
name: Model.attribute('name'),
@@ -21,5 +22,7 @@ export default class Tag extends mixin(Model, {
lastDiscussion: Model.hasOne('lastDiscussion'),
isRestricted: Model.attribute('isRestricted'),
- canStartDiscussion: Model.attribute('canStartDiscussion')
+ canStartDiscussion: Model.attribute('canStartDiscussion'),
+
+ isPrimary: computed('position', 'parent', (position, parent) => position !== null && parent === false)
}) {}
diff --git a/extensions/tags/less/forum/TagDiscussionModal.less b/extensions/tags/less/forum/TagDiscussionModal.less
index e5ba8bd0f..2cdb63cbe 100644
--- a/extensions/tags/less/forum/TagDiscussionModal.less
+++ b/extensions/tags/less/forum/TagDiscussionModal.less
@@ -58,8 +58,12 @@
cursor: not-allowed;
}
.TagsInput-selected {
- .TagLabel {
+ .TagsInput-tag {
margin-right: 5px;
+
+ &:last-child {
+ margin-right: 10px;
+ }
}
}
diff --git a/extensions/tags/locale/en.yml b/extensions/tags/locale/en.yml
index d536396d3..66846de51 100644
--- a/extensions/tags/locale/en.yml
+++ b/extensions/tags/locale/en.yml
@@ -7,7 +7,12 @@ tags:
tag_new_discussion_title: Choose Tags for Your Discussion
edit_discussion_tags_title: "Edit Tags for {title}"
edit_discussion_tags_link: Edit Tags
- discussion_tags_placeholder: Choose one or more topics
+ choose_primary_tags:
+ one: Choose a primary tag
+ other: "Choose {count} primary tags"
+ choose_secondary_tags:
+ one: Choose 1 more tag
+ other: "Choose {count} more tags"
confirm: Confirm
more: More...
tag_cloud_title: Tags
diff --git a/extensions/tags/src/Listeners/AddApiAttributes.php b/extensions/tags/src/Listeners/AddApiAttributes.php
index 33ddaf64e..8668b50bd 100755
--- a/extensions/tags/src/Listeners/AddApiAttributes.php
+++ b/extensions/tags/src/Listeners/AddApiAttributes.php
@@ -70,6 +70,15 @@ class AddApiAttributes
if ($event->serializer instanceof DiscussionSerializer) {
$event->attributes['canTag'] = $event->model->can($event->actor, 'tag');
}
+
+ if ($event->serializer instanceof ForumSerializer) {
+ $settings = app('Flarum\Core\Settings\SettingsRepository');
+
+ $event->attributes['minPrimaryTags'] = $settings->get('tags.min_primary_tags');
+ $event->attributes['maxPrimaryTags'] = $settings->get('tags.max_primary_tags');
+ $event->attributes['minSecondaryTags'] = $settings->get('tags.min_secondary_tags');
+ $event->attributes['maxSecondaryTags'] = $settings->get('tags.max_secondary_tags');
+ }
}
public function addRoutes(RegisterApiRoutes $event)
diff --git a/extensions/tags/src/Listeners/AddClientAssets.php b/extensions/tags/src/Listeners/AddClientAssets.php
index 242c28fb4..9d311ad82 100755
--- a/extensions/tags/src/Listeners/AddClientAssets.php
+++ b/extensions/tags/src/Listeners/AddClientAssets.php
@@ -35,7 +35,8 @@ class AddClientAssets
'tags.tag_new_discussion_title',
'tags.edit_discussion_tags_title',
'tags.edit_discussion_tags_link',
- 'tags.discussion_tags_placeholder',
+ 'tags.choose_primary_tags',
+ 'tags.choose_secondary_tags',
'tags.confirm',
'tags.more',
'tags.tag_cloud_title',
diff --git a/extensions/tags/src/Listeners/PersistData.php b/extensions/tags/src/Listeners/PersistData.php
index 6099b1f19..cacf96b9c 100755
--- a/extensions/tags/src/Listeners/PersistData.php
+++ b/extensions/tags/src/Listeners/PersistData.php
@@ -5,9 +5,18 @@ use Flarum\Tags\Events\DiscussionWasTagged;
use Flarum\Events\DiscussionWillBeSaved;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Exceptions\PermissionDeniedException;
+use Flarum\Core\Settings\SettingsRepository;
+use Flarum\Tags\TagCountException;
class PersistData
{
+ protected $settings;
+
+ public function __construct(SettingsRepository $settings)
+ {
+ $this->settings = $settings;
+ }
+
public function subscribe($events)
{
$events->listen(DiscussionWillBeSaved::class, [$this, 'whenDiscussionWillBeSaved']);
@@ -25,13 +34,25 @@ class PersistData
$newTagIds[] = (int) $link['id'];
}
- $newTags = Tag::whereIn('id', $newTagIds);
+ $newTags = Tag::whereIn('id', $newTagIds)->get();
+ $primaryCount = 0;
+ $secondaryCount = 0;
+
foreach ($newTags as $tag) {
if (! $tag->can($actor, 'startDiscussion')) {
throw new PermissionDeniedException;
}
+
+ if ($tag->position !== null && $tag->parent_id === null) {
+ $primaryCount++;
+ } else {
+ $secondaryCount++;
+ }
}
+ $this->validatePrimaryTagCount($primaryCount);
+ $this->validateSecondaryTagCount($secondaryCount);
+
$oldTags = [];
if ($discussion->exists) {
@@ -50,4 +71,24 @@ class PersistData
});
}
}
+
+ protected function validatePrimaryTagCount($count)
+ {
+ $min = $this->settings->get('tags.min_primary_tags');
+ $max = $this->settings->get('tags.max_primary_tags');
+
+ if ($count < $min || $count > $max) {
+ throw new TagCountException(['tags' => sprintf('Discussion must have between %d and %d primary tags.', $min, $max)]);
+ }
+ }
+
+ protected function validateSecondaryTagCount($count)
+ {
+ $min = $this->settings->get('tags.min_secondary_tags');
+ $max = $this->settings->get('tags.max_secondary_tags');
+
+ if ($count < $min || $count > $max) {
+ throw new TagCountException(['tags' => sprintf('Discussion must have between %d and %d secondary tags.', $min, $max)]);
+ }
+ }
}
diff --git a/extensions/tags/src/TagCountException.php b/extensions/tags/src/TagCountException.php
new file mode 100644
index 000000000..b59614d2b
--- /dev/null
+++ b/extensions/tags/src/TagCountException.php
@@ -0,0 +1,7 @@
+