-
- {tags
- .filter((tag) => filter || !tag.parent() || this.selected.includes(tag.parent() as Tag))
- .map((tag) => (
- - (this.selectedTag = tag)}
- onclick={this.toggleTag.bind(this, tag)}
- >
- {tagIcon(tag)}
- {highlight(tag.name(), filter)}
- {tag.description() ? {tag.description()} : ''}
-
- ))}
-
- {!!app.forum.attribute('canBypassTagCounts') && (
-
- (this.bypassReqs = !this.bypassReqs)} isToggled={this.bypassReqs}>
- {app.translator.trans('flarum-tags.forum.choose_tags.bypass_requirements')}
-
-
- )}
-
,
- ];
- }
-
- meetsRequirements(primaryCount: number, secondaryCount: number) {
- if (this.bypassReqs) {
- return true;
- }
-
- return primaryCount >= this.minPrimary && secondaryCount >= this.minSecondary;
- }
-
- toggleTag(tag: Tag) {
- // Won't happen, needed for type safety.
- if (!this.tags) return;
-
- if (this.selected.includes(tag)) {
- this.removeTag(tag);
- } else {
- this.addTag(tag);
- }
-
- if (this.filter()) {
- this.filter('');
- this.selectedTag = this.tags[0];
- }
-
- this.onready();
- }
-
- select(e: KeyboardEvent) {
- // Ctrl + Enter submits the selection, just Enter completes the current entry
- if (e.metaKey || e.ctrlKey || (this.selectedTag && this.selected.includes(this.selectedTag))) {
- if (this.selected.length) {
- // The DOM submit method doesn't emit a `submit event, so we
- // simulate a manual submission so our `onsubmit` logic is run.
- this.$('button[type="submit"]').click();
- }
- } else if (this.selectedTag) {
- this.getItem(this.selectedTag)[0].dispatchEvent(new Event('click'));
- }
- }
-
- selectableItems() {
- return this.$('.TagDiscussionModal-list > li');
- }
-
- getCurrentNumericIndex() {
- if (!this.selectedTag) return -1;
-
- return this.selectableItems().index(this.getItem(this.selectedTag));
- }
-
- getItem(selectedTag: Tag) {
- return this.selectableItems().filter(`[data-index="${selectedTag.id()}"]`);
- }
-
- setIndex(index: number, scrollToItem: boolean) {
- const $items = this.selectableItems();
- const $dropdown = $items.parent();
-
- if (index < 0) {
- index = $items.length - 1;
- } else if (index >= $items.length) {
- index = 0;
- }
-
- const $item = $items.eq(index);
-
- this.selectedTag = app.store.getById('tags', $item.attr('data-index')!);
-
- m.redraw();
-
- if (scrollToItem && this.selectedTag) {
- const dropdownScroll = $dropdown.scrollTop()!;
- const dropdownTop = $dropdown.offset()!.top;
- const dropdownBottom = dropdownTop + $dropdown.outerHeight()!;
- const itemTop = $item.offset()!.top;
- const itemBottom = itemTop + $item.outerHeight()!;
-
- let scrollTop;
- if (itemTop < dropdownTop) {
- scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt($dropdown.css('padding-top'), 10);
- } else if (itemBottom > dropdownBottom) {
- scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt($dropdown.css('padding-bottom'), 10);
+ m.redraw();
+ });
}
- if (typeof scrollTop !== 'undefined') {
- $dropdown.stop(true).animate({ scrollTop }, 100);
- }
- }
- }
-
- onsubmit(e: SubmitEvent) {
- e.preventDefault();
-
- const discussion = this.attrs.discussion;
- const tags = this.selected;
-
- if (discussion) {
- discussion.save({ relationships: { tags } }).then(() => {
- if (app.current.matches(DiscussionPage)) {
- app.current.get('stream').update();
- }
- m.redraw();
- });
- }
-
- if (this.attrs.onsubmit) this.attrs.onsubmit(tags);
-
- this.hide();
+ if (suppliedOnsubmit) suppliedOnsubmit(tags);
+ };
}
}
diff --git a/extensions/tags/js/src/forum/index.ts b/extensions/tags/js/src/forum/index.ts
index b314a0487..0087ed79d 100644
--- a/extensions/tags/js/src/forum/index.ts
+++ b/extensions/tags/js/src/forum/index.ts
@@ -3,12 +3,11 @@ import Model from 'flarum/common/Model';
import Discussion from 'flarum/common/models/Discussion';
import IndexPage from 'flarum/forum/components/IndexPage';
+import TagListState from '../common/states/TagListState';
import Tag from '../common/models/Tag';
import TagsPage from './components/TagsPage';
import DiscussionTaggedPost from './components/DiscussionTaggedPost';
-import TagListState from './states/TagListState';
-
import addTagList from './addTagList';
import addTagFilter from './addTagFilter';
import addTagLabels from './addTagLabels';
diff --git a/extensions/tags/less/forum/TagDiscussionModal.less b/extensions/tags/less/common/TagSelectionModal.less
similarity index 95%
rename from extensions/tags/less/forum/TagDiscussionModal.less
rename to extensions/tags/less/common/TagSelectionModal.less
index f71acf31a..788b3fbe8 100644
--- a/extensions/tags/less/forum/TagDiscussionModal.less
+++ b/extensions/tags/less/common/TagSelectionModal.less
@@ -1,4 +1,4 @@
-.TagDiscussionModal {
+.TagSelectionModal {
@media @tablet-up {
.Modal-header {
background: @control-bg;
@@ -29,16 +29,16 @@
}
@media @tablet, @desktop, @desktop-hd {
- .TagDiscussionModal-form {
+ .TagSelectionModal-form {
display: table;
width: 100%;
}
- .TagDiscussionModal-form-input {
+ .TagSelectionModal-form-input {
display: table-cell;
width: 100%;
vertical-align: top;
}
- .TagDiscussionModal-form-submit {
+ .TagSelectionModal-form-submit {
display: table-cell;
padding-left: 15px;
}
diff --git a/extensions/tags/less/common/common.less b/extensions/tags/less/common/common.less
index 809308389..e58ceace4 100644
--- a/extensions/tags/less/common/common.less
+++ b/extensions/tags/less/common/common.less
@@ -1,3 +1,4 @@
@import "root";
@import "TagLabel";
@import "TagIcon";
+@import "TagSelectionModal";
diff --git a/extensions/tags/less/forum.less b/extensions/tags/less/forum.less
index f1a34a8e2..15b2ba32d 100644
--- a/extensions/tags/less/forum.less
+++ b/extensions/tags/less/forum.less
@@ -1,7 +1,6 @@
@import "common/common";
@import "forum/TagCloud";
-@import "forum/TagDiscussionModal";
@import "forum/TagHero";
@import "forum/TagTiles";
@import "forum/ToggleButton";
diff --git a/extensions/tags/locale/en.yml b/extensions/tags/locale/en.yml
index b7656c550..2ede1863a 100644
--- a/extensions/tags/locale/en.yml
+++ b/extensions/tags/locale/en.yml
@@ -38,6 +38,10 @@ flarum-tags:
restrict_by_tag_heading: Restrict by Tag
tag_discussions_label: Tag discussions
+ # These translations are used in the Tags custom settings component.
+ settings:
+ button_text: => flarum-tags.ref.choose_tags
+
# These translations are used in the Tag Settings modal dialog.
tag_settings:
range_separator_text: " to "
@@ -66,16 +70,12 @@ flarum-tags:
# These translations are used by the Choose Tags modal dialog.
choose_tags:
- bypass_requirements: Bypass tag requirements
- choose_primary_placeholder: "{count, plural, one {Choose a primary tag} other {Choose # primary tags}}"
- choose_secondary_placeholder: "{count, plural, one {Choose 1 more tag} other {Choose # more tags}}"
edit_title: "Edit Tags for {title}"
- submit_button: => core.ref.okay
title: Choose Tags for Your Discussion
# These translations are used by the composer when starting a discussion.
composer_discussion:
- choose_tags_link: Choose Tags
+ choose_tags_link: => flarum-tags.ref.choose_tags
# These translations are used by the discussion control buttons.
discussion_controls:
@@ -107,11 +107,22 @@ flarum-tags:
# This translation is displayed in place of the name of a tag that's been deleted.
deleted_tag_text: Deleted
+ # These translations are used in the tag selection modal.
+ tag_selection_modal:
+ bypass_requirements: Bypass tag requirements
+ choose_primary_placeholder: "{count, plural, one {Choose a primary tag} other {Choose # primary tags}}"
+ choose_secondary_placeholder: => flarum-tags.ref.choose_tags_placeholder
+ choose_tags_placeholder: => flarum-tags.ref.choose_tags_placeholder
+ submit_button: => core.ref.okay
+ title: => flarum-tags.ref.choose_tags
+
##
# REUSED TRANSLATIONS - These keys should not be used directly in code!
##
# Translations in this namespace are referenced by two or more unique keys.
ref:
+ choose_tags: Choose Tags
+ choose_tags_placeholder: "{count, plural, one {Choose 1 more tag} other {Choose # more tags}}"
name: Name
tags: Tags
diff --git a/framework/core/js/src/common/compat.ts b/framework/core/js/src/common/compat.ts
index b63c028ee..394dc6fd7 100644
--- a/framework/core/js/src/common/compat.ts
+++ b/framework/core/js/src/common/compat.ts
@@ -4,6 +4,7 @@ import Session from './Session';
import Store from './Store';
import BasicEditorDriver from './utils/BasicEditorDriver';
import evented from './utils/evented';
+import KeyboardNavigatable from './utils/KeyboardNavigatable';
import liveHumanTimes from './utils/liveHumanTimes';
import ItemList from './utils/ItemList';
import mixin from './utils/mixin';
@@ -94,6 +95,7 @@ export default {
Store: Store,
'utils/BasicEditorDriver': BasicEditorDriver,
'utils/evented': evented,
+ 'utils/KeyboardNavigatable': KeyboardNavigatable,
'utils/liveHumanTimes': liveHumanTimes,
'utils/ItemList': ItemList,
'utils/mixin': mixin,
diff --git a/framework/core/js/src/common/components/Modal.tsx b/framework/core/js/src/common/components/Modal.tsx
index 4cde799b0..d34f55044 100644
--- a/framework/core/js/src/common/components/Modal.tsx
+++ b/framework/core/js/src/common/components/Modal.tsx
@@ -30,7 +30,10 @@ export interface IDismissibleOptions {
* The `Modal` component displays a modal dialog, wrapped in a form. Subclasses
* should implement the `className`, `title`, and `content` methods.
*/
-export default abstract class Modal