FEATURE: new {{mini-tag-chooser}} replaces {{tag-chooser}} in composer

This commit is contained in:
Joffrey JAFFEUX 2018-02-13 17:23:12 +01:00 committed by GitHub
parent 0a95d2a21f
commit 6bfc25d895
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 360 additions and 24 deletions

View File

@ -65,7 +65,7 @@
</div>
{{/if}}
{{#if canEditTags}}
{{tag-chooser tags=model.tags tabIndex="4" categoryId=model.categoryId}}
{{mini-tag-chooser tags=model.tags tabindex="4" categoryId=model.categoryId}}
{{/if}}

View File

@ -0,0 +1,224 @@
import ComboBox from "select-kit/components/combo-box";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from 'discourse/lib/ajax-error';
import { default as computed } from "ember-addons/ember-computed-decorators";
import renderTag from "discourse/lib/render-tag";
const { get, isEmpty, isPresent, run } = Ember;
export default ComboBox.extend({
allowContentReplacement: true,
pluginApiIdentifiers: ["mini-tag-chooser"],
classNames: ["mini-tag-chooser"],
classNameBindings: ["noTags"],
verticalOffset: 3,
filterable: true,
noTags: Ember.computed.empty("computedTags"),
allowAny: true,
init() {
this._super();
this.set("termMatchesForbidden", false);
this.set("templateForRow", (rowComponent) => {
const tag = rowComponent.get("computedContent");
return renderTag(get(tag, "value"), {
count: get(tag, "originalContent.count"),
noHref: true
});
});
},
@computed("tags")
computedTags(tags) {
return Ember.makeArray(tags);
},
validateCreate(term) {
const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g");
term = term.replace(filterRegexp, "").trim().toLowerCase();
if (!term.length || this.get("termMatchesForbidden")) {
return false;
}
if (this.get("siteSettings.max_tag_length") < term.length) {
return false;
}
return true;
},
validateSelect() {
return this.get("computedTags").length < this.get("siteSettings.max_tags_per_topic") &&
this.site.get("can_create_tag");
},
didRender() {
this._super();
this.$().on("click.mini-tag-chooser", ".selected-tag", (event) => {
event.stopImmediatePropagation();
this.send("removeTag", $(event.target).attr("data-value"));
});
},
willDestroyElement() {
this._super();
$(".select-kit-body").off("click.mini-tag-chooser");
const searchDebounce = this.get("searchDebounce");
if (isPresent(searchDebounce)) { run.cancel(searchDebounce); }
},
didPressEscape(event) {
const $lastSelectedTag = $(".selected-tag.selected:last");
if ($lastSelectedTag && this.get("isExpanded")) {
$lastSelectedTag.removeClass("selected");
this._destroyEvent(event);
} else {
this._super(event);
}
},
didPressBackspace() {
if (!this.get("isExpanded")) {
this.expand();
return;
}
const $lastSelectedTag = $(".selected-tag:last");
if (!isEmpty(this.get("filter"))) {
$lastSelectedTag.removeClass("is-highlighted");
return;
}
if (!$lastSelectedTag.length) return;
if (!$lastSelectedTag.hasClass("is-highlighted")) {
$lastSelectedTag.addClass("is-highlighted");
} else {
this.send("removeTag", $lastSelectedTag.attr("data-value"));
}
},
@computed("tags.[]", "filter")
collectionHeader(tags, filter) {
if (!Ember.isEmpty(tags)) {
let output = "";
if (tags.length >= 20) {
tags = tags.filter(t => t.indexOf(filter) >= 0);
}
tags.map((tag) => {
output += `
<button class="selected-tag" data-value="${tag}">
${tag}
</button>
`;
});
return `<div class="selected-tags">${output}</div>`;
}
},
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
if (isEmpty(this.get("computedTags"))) {
content.label = I18n.t("tagging.choose_for_topic");
} else {
content.label = this.get("computedTags").join(",");
}
return content;
},
actions: {
removeTag(tag) {
let tags = this.get("computedTags");
delete tags[tags.indexOf(tag)];
this.set("tags", tags.filter(t => t));
this.set("content", []);
this.set("searchDebounce", run.debounce(this, this._searchTags, 200));
},
onExpand() {
this.set("searchDebounce", run.debounce(this, this._searchTags, 200));
},
onFilter(filter) {
filter = isEmpty(filter) ? null : filter;
this.set("searchDebounce", run.debounce(this, this._searchTags, filter, 200));
},
onSelect(tag) {
if (isEmpty(this.get("computedTags"))) {
this.set("tags", Ember.makeArray(tag));
} else {
this.set("tags", this.get("computedTags").concat(tag));
}
this.set("content", []);
this.set("searchDebounce", run.debounce(this, this._searchTags, 200));
}
},
muateAttributes() {
this.set("value", null);
},
_searchTags(query) {
this.startLoading();
const selectedTags = Ember.makeArray(this.get("computedTags")).filter(t => t);
const self = this;
const sortTags = this.siteSettings.tags_sort_alphabetically;
const data = {
q: query,
limit: this.siteSettings.max_tag_search_results,
categoryId: this.get("categoryId")
};
if (selectedTags) {
data.selected_tags = selectedTags.slice(0, 100);
}
ajax(Discourse.getURL("/tags/filter/search"), {
quietMillis: 200,
cache: true,
dataType: "json",
data,
}).then(json => {
let results = json.results;
self.set("termMatchesForbidden", json.forbidden ? true : false);
if (sortTags) {
results = results.sort((a, b) => a.id > b.id);
}
const content = results.map((result) => {
return {
id: result.text,
name: result.text,
count: result.count
};
}).filter(c => !selectedTags.includes(c.id));
self.set("content", content);
self.stopLoading();
this.autoHighlight();
}).catch(error => {
self.stopLoading();
popupAjaxError(error);
});
}
});

View File

@ -252,10 +252,6 @@ export default SelectKitComponent.extend({
this.autoHighlight();
},
validateComputedContentItem(computedContentItem) {
return !this.get("computedValues").includes(computedContentItem.value);
},
actions: {
clearSelection() {
this.send("deselect", this.get("selectedComputedContents"));
@ -263,7 +259,8 @@ export default SelectKitComponent.extend({
},
create(computedContentItem) {
if (this.validateComputedContentItem(computedContentItem)) {
if (!this.get("computedValues").includes(computedContentItem.value) &&
this.validateCreate(computedContentItem.value)) {
this.get("computedContent").pushObject(computedContentItem);
this._boundaryActionHandler("onCreate");
this.send("select", computedContentItem);
@ -274,9 +271,14 @@ export default SelectKitComponent.extend({
select(computedContentItem) {
this.willSelect(computedContentItem);
if (this.validateSelect(computedContentItem)) {
this.get("computedValues").pushObject(computedContentItem.value);
Ember.run.next(() => this.mutateAttributes());
Ember.run.schedule("afterRender", () => this.didSelect(computedContentItem));
} else {
this._boundaryActionHandler("onSelectFailure");
}
},
deselect(rowComputedContentItems) {

View File

@ -16,6 +16,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
layoutName: "select-kit/templates/components/select-kit",
classNames: ["select-kit"],
classNameBindings: [
"isLoading",
"isFocused",
"isExpanded",
"isDisabled",
@ -30,6 +31,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
isExpanded: false,
isFocused: false,
isHidden: false,
isLoading: false,
renderedBodyOnce: false,
renderedFilterOnce: false,
tabindex: 0,
@ -41,6 +43,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
autoFilterable: false,
filterable: false,
filter: "",
previousFilter: null,
filterPlaceholder: "select_kit.filter_placeholder",
filterIcon: "search",
headerIcon: null,
@ -118,6 +121,10 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
return this.baseComputedContentItem(contentItem, options);
},
validateCreate() { return true; },
validateSelect() { return true; },
baseComputedContentItem(contentItem, options) {
let originalContent;
options = options || {};
@ -163,7 +170,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
@computed("filter", "computedContent")
shouldDisplayCreateRow(filter, computedContent) {
if (computedContent.map(c => c.value).includes(filter)) return false;
if (this.get("allowAny") && filter.length > 0) return true;
if (this.get("allowAny") && filter.length > 0 && this.validateCreate(filter)) return true;
return false;
},
@ -184,7 +191,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
@computed("filter")
templateForCreateRow() {
return (rowComponent) => {
return I18n.t("select_box.create", {
return I18n.t("select_kit.create", {
content: rowComponent.get("computedContent.name")
});
};
@ -238,6 +245,16 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
this.setProperties({ filter: "" });
},
startLoading() {
this.set("isLoading", true);
this._boundaryActionHandler("onStartLoading");
},
stopLoading() {
this.set("isLoading", false);
this._boundaryActionHandler("onStopLoading");
},
_setCollectionHeaderComputedContent() {
const collectionHeaderComputedContent = applyCollectionHeaderCallbacks(
this.get("pluginApiIdentifiers"),
@ -283,9 +300,12 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
},
filterComputedContent(filter) {
if (filter === this.get("previousFilter")) return;
this.setProperties({
highlightedValue: null,
renderedFilterOnce: true,
previousFilter: filter,
filter
});
this.autoHighlight();

View File

@ -145,10 +145,6 @@ export default SelectKitComponent.extend({
});
},
validateComputedContentItem(computedContentItem) {
return this.get("computedValue") !== computedContentItem.value;
},
actions: {
clearSelection() {
this.send("deselect", this.get("selectedComputedContent"));
@ -156,7 +152,8 @@ export default SelectKitComponent.extend({
},
create(computedContentItem) {
if (this.validateComputedContentItem(computedContentItem)) {
if (this.get("computedValue") !== computedContentItem.value &&
this.validateCreate(computedContentItem.value)) {
this.get("computedContent").pushObject(computedContentItem);
this._boundaryActionHandler("onCreate");
this.send("select", computedContentItem);
@ -166,10 +163,14 @@ export default SelectKitComponent.extend({
},
select(rowComputedContentItem) {
if (this.validateSelect(rowComputedContentItem)) {
this.willSelect(rowComputedContentItem);
this.set("computedValue", rowComputedContentItem.value);
this.mutateAttributes();
run.schedule("afterRender", () => this.didSelect(rowComputedContentItem));
} else {
this._boundaryActionHandler("onSelectFailure");
}
},
deselect(rowComputedContentItem) {

View File

@ -108,6 +108,7 @@ export default Ember.Mixin.create({
.on("keydown.select-kit", (event) => {
const keyCode = event.keyCode || event.which;
if (keyCode === this.keys.BACKSPACE) this.backspaceFromFilter(event);
if (keyCode === this.keys.TAB) this.tabFromFilter(event);
if (keyCode === this.keys.ESC) this.escapeFromFilter(event);
if (keyCode === this.keys.ENTER) this.enterFromFilter(event);
@ -207,6 +208,7 @@ export default Ember.Mixin.create({
upAndDownFromFilter(event) { this.didPressUpAndDownArrows(event); },
backspaceFromHeader(event) { this.didPressBackspace(event); },
backspaceFromFilter(event) { this.didPressBackspace(event); },
enterFromHeader(event) { this.didPressEnter(event); },
enterFromFilter(event) { this.didPressEnter(event); },

View File

@ -6,6 +6,7 @@
computedContent=headerComputedContent
deselect=(action "deselect")
toggle=(action "toggle")
isLoading=isLoading
filterComputedContent=(action "filterComputedContent")
clearSelection=(action "clearSelection")
options=headerComponentOptions
@ -14,6 +15,7 @@
<div class="select-kit-body">
{{component filterComponent
filter=filter
isLoading=isLoading
icon=filterIcon
shouldDisplayFilter=shouldDisplayFilter
placeholder=(i18n filterPlaceholder)

View File

@ -10,6 +10,10 @@
value=filter
}}
{{#if icon}}
{{#if isLoading}}
{{loading-spinner size="small"}}
{{else}}
{{#if icon}}
{{d-icon icon class="filter-icon"}}
{{/if}}
{{/if}}

View File

@ -221,9 +221,9 @@
flex-basis: 50%;
}
.tag-chooser {
.mini-tag-chooser {
flex: 1 1 25%;
margin: 0 0 5px 10px;
margin: 0 0 5px 5px;
background: $secondary;
@media all and (max-width: 900px) {
margin: 0;

View File

@ -0,0 +1,81 @@
.select-kit {
&.combo-box {
&.mini-tag-chooser {
margin-bottom: 5px;
margin-left: 5px;
&.is-expanded {
.select-kit-header {
border: 1px solid $tertiary;
-webkit-box-shadow: $tertiary 0 0 6px 0px;
box-shadow: $tertiary 0 0 6px 0px;
}
}
&.no-tags {
.select-kit-header .selected-name {
color: $primary-medium;
}
}
.select-kit-body {
max-width: 500px;
width: 500px;
border: 1px solid $primary-low;
}
.select-kit-filter {
border-top: 0;
}
.select-kit-wrapper {
display: none;
}
.select-kit-row {
&.is-selected {
background: none;
}
&.is-highlighted.is-selected {
background: $tertiary-low;
}
.discourse-tag-count {
margin-left: 5px;
}
}
.select-kit-collection {
.collection-header {
max-height: 125px;
overflow-y: auto;
.selected-tags {
display: flex;
padding: 3px;
flex-wrap: wrap;
border-bottom: 1px solid $primary-low;
}
.selected-tag {
background: $primary-low;
border-radius: 2px;
padding: 2px 4px;
margin: 2px;
border: 0;
&.is-highlighted {
box-shadow: 0 0 2px $danger, 0 1px 0 rgba(0,0,0,0.05);
}
&:before {
content: '\f00d';
font-family: 'FontAwesome';
}
}
}
}
}
}
}