diff --git a/app/assets/javascripts/admin/components/penalty-post-action.js.es6 b/app/assets/javascripts/admin/components/penalty-post-action.js.es6 index 6a703105fbf..09f267390ca 100644 --- a/app/assets/javascripts/admin/components/penalty-post-action.js.es6 +++ b/app/assets/javascripts/admin/components/penalty-post-action.js.es6 @@ -21,13 +21,11 @@ export default Component.extend({ actions: { penaltyChanged() { - let postAction = this.postAction; - // If we switch to edit mode, jump to the edit textarea - if (postAction === "edit") { + if (this.postAction === "edit") { scheduleOnce("afterRender", () => { - let elem = this.element; - let body = elem.closest(".modal-body"); + const elem = this.element; + const body = elem.closest(".modal-body"); body.scrollTop(body.height()); elem.querySelector(".post-editor").focus(); }); diff --git a/app/assets/javascripts/admin/components/permalink-form.js.es6 b/app/assets/javascripts/admin/components/permalink-form.js.es6 index 34d222d30f6..e1b62a11df6 100644 --- a/app/assets/javascripts/admin/components/permalink-form.js.es6 +++ b/app/assets/javascripts/admin/components/permalink-form.js.es6 @@ -20,6 +20,19 @@ export default Component.extend({ ]; }, + didInsertElement() { + this._super(...arguments); + + schedule("afterRender", () => { + $(this.element.querySelector(".external-url")).keydown(e => { + // enter key + if (e.keyCode === 13) { + this.send("submit"); + } + }); + }); + }, + focusPermalink() { schedule("afterRender", () => this.element.querySelector(".permalink-url").focus() @@ -64,19 +77,10 @@ export default Component.extend({ } ); } + }, + + onChangePermalinkType(type) { + this.set("permalinkType", type); } - }, - - didInsertElement() { - this._super(...arguments); - - schedule("afterRender", () => { - $(this.element.querySelector(".external-url")).keydown(e => { - // enter key - if (e.keyCode === 13) { - this.send("submit"); - } - }); - }); } }); diff --git a/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 b/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 index ff83e13e520..24e9bb23b96 100644 --- a/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 +++ b/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 @@ -1,16 +1,15 @@ -import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; import Category from "discourse/models/category"; +import { computed } from "@ember/object"; export default Component.extend({ - @discourseComputed("value") - selectedCategories: { - get(value) { - return Category.findByIds(value.split("|")); - }, - set(value) { - this.set("value", value.mapBy("id").join("|")); - return value; + selectedCategories: computed("value", function() { + return Category.findByIds(this.value.split("|").filter(Boolean)); + }), + + actions: { + onChangeSelectedCategories(value) { + this.set("value", (value || []).mapBy("id").join("|")); } } }); diff --git a/app/assets/javascripts/admin/components/site-settings/compact-list.js.es6 b/app/assets/javascripts/admin/components/site-settings/compact-list.js.es6 new file mode 100644 index 00000000000..f68e78b5722 --- /dev/null +++ b/app/assets/javascripts/admin/components/site-settings/compact-list.js.es6 @@ -0,0 +1,43 @@ +import Component from "@ember/component"; +import { computed } from "@ember/object"; +import { makeArray } from "discourse-common/lib/helpers"; + +export default Component.extend({ + tokenSeparator: "|", + + createdChoices: null, + + settingValue: computed("value", function() { + return this.value + .toString() + .split(this.tokenSeparator) + .filter(Boolean); + }), + + settingChoices: computed( + "settingValue", + "setting.choices.[]", + "createdChoices.[]", + function() { + return [ + ...new Set([ + ...makeArray(this.settingValue), + ...makeArray(this.setting.choices), + ...makeArray(this.createdChoices) + ]) + ]; + } + ), + + actions: { + onChangeListSetting(value) { + this.set("value", value.join(this.tokenSeparator)); + }, + + onChangeChoices(choices) { + this.set("createdChoices", [ + ...new Set([...makeArray(this.createdChoices), ...makeArray(choices)]) + ]); + } + } +}); diff --git a/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 b/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 index 97736c36ca1..41dcdbcb4d5 100644 --- a/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 +++ b/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 @@ -1,11 +1,25 @@ -import discourseComputed from "discourse-common/utils/decorators"; +import { computed } from "@ember/object"; import Component from "@ember/component"; export default Component.extend({ - @discourseComputed() - groupChoices() { - return this.site.get("groups").map(g => { + tokenSeparator: "|", + + nameProperty: "name", + valueProperty: "id", + + groupChoices: computed("site.groups", function() { + return (this.site.groups || []).map(g => { return { name: g.name, id: g.id.toString() }; }); + }), + + settingValue: computed("value", function() { + return (this.value || "").split(this.tokenSeparator).filter(Boolean); + }), + + actions: { + onChangeGroupListSetting(value) { + this.set("value", value.join(this.tokenSeparator)); + } } }); diff --git a/app/assets/javascripts/admin/components/value-list.js.es6 b/app/assets/javascripts/admin/components/value-list.js.es6 index 6582f20e7f3..99f1cc00d44 100644 --- a/app/assets/javascripts/admin/components/value-list.js.es6 +++ b/app/assets/javascripts/admin/components/value-list.js.es6 @@ -1,20 +1,18 @@ import discourseComputed from "discourse-common/utils/decorators"; import { makeArray } from "discourse-common/lib/helpers"; -import { empty, alias } from "@ember/object/computed"; +import { empty, reads } from "@ember/object/computed"; import Component from "@ember/component"; import { on } from "discourse-common/utils/decorators"; export default Component.extend({ classNameBindings: [":value-list"], - inputInvalid: empty("newValue"), - inputDelimiter: null, inputType: null, newValue: "", collection: null, values: null, - noneKey: alias("addKey"), + noneKey: reads("addKey"), @on("didReceiveAttrs") _setupCollection() { @@ -47,7 +45,7 @@ export default Component.extend({ addValue(newValue) { if (this.inputInvalid) return; - this.set("newValue", ""); + this.set("newValue", null); this._addValue(newValue); }, @@ -62,12 +60,26 @@ export default Component.extend({ _addValue(value) { this.collection.addObject(value); + + if (this.choices) { + this.set("choices", this.choices.rejectBy("id", value)); + } else { + this.set("choices", []); + } + this._saveValues(); }, _removeValue(value) { - const collection = this.collection; - collection.removeObject(value); + this.collection.removeObject(value); + + const item = { id: value, name: value }; + if (this.choices) { + this.choices.addObject(item); + } else { + this.set("choices", makeArray(item)); + } + this._saveValues(); }, diff --git a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 index 140353e1435..411f50534aa 100644 --- a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 @@ -1,5 +1,5 @@ import discourseComputed, { observes } from "discourse-common/utils/decorators"; -import { alias } from "@ember/object/computed"; +import { reads } from "@ember/object/computed"; import { inject } from "@ember/controller"; import Controller from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; @@ -10,15 +10,37 @@ export default Controller.extend(bufferedProperty("model"), { adminBadges: inject(), saving: false, savingStatus: "", - - badgeTypes: alias("adminBadges.badgeTypes"), - badgeGroupings: alias("adminBadges.badgeGroupings"), - badgeTriggers: alias("adminBadges.badgeTriggers"), - protectedSystemFields: alias("adminBadges.protectedSystemFields"), - - readOnly: alias("buffered.system"), + badgeTypes: reads("adminBadges.badgeTypes"), + badgeGroupings: reads("adminBadges.badgeGroupings"), + badgeTriggers: reads("adminBadges.badgeTriggers"), + protectedSystemFields: reads("adminBadges.protectedSystemFields"), + readOnly: reads("buffered.system"), showDisplayName: propertyNotEqual("name", "displayName"), + init() { + this._super(...arguments); + + // this is needed because the model doesnt have default values + // and as we are using a bufferedProperty it's not accessible + // in any other way + Ember.run.next(() => { + if (!this.model.badge_type_id) { + this.model.set("badge_type_id", this.get("badgeTypes.firstObject.id")); + } + + if (!this.model.badge_grouping_id) { + this.model.set( + "badge_grouping_id", + this.get("badgeGroupings.firstObject.id") + ); + } + + if (!this.model.trigger) { + this.model.set("trigger", this.get("badgeTriggers.firstObject.id")); + } + }); + }, + @discourseComputed("model.query", "buffered.query") hasQuery(modelQuery, bufferedQuery) { if (bufferedQuery) { diff --git a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 index 51d53509adb..0460c314c75 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 @@ -278,7 +278,7 @@ export default Controller.extend(CanCheckEmails, { }, resetCustomGroups() { - this.set("customGroupIdsBuffer", null); + this.set("customGroupIdsBuffer", this.model.customGroups.mapBy("id")); }, savePrimaryGroup() { diff --git a/app/assets/javascripts/admin/controllers/modals/admin-color-scheme-select-base.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-color-scheme-select-base.js.es6 index ea64338a83f..e0b7bfe7e6e 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-color-scheme-select-base.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-color-scheme-select-base.js.es6 @@ -5,6 +5,17 @@ import ModalFunctionality from "discourse/mixins/modal-functionality"; export default Controller.extend(ModalFunctionality, { adminCustomizeColors: inject(), + selectedBaseThemeId: null, + + init() { + this._super(...arguments); + + const defaultScheme = this.get( + "adminCustomizeColors.baseColorSchemes.0.base_scheme_id" + ); + this.set("selectedBaseThemeId", defaultScheme); + }, + actions: { selectBase() { this.adminCustomizeColors.send( diff --git a/app/assets/javascripts/admin/routes/admin-user-index.js.es6 b/app/assets/javascripts/admin/routes/admin-user-index.js.es6 index 51af9f2d2e8..a0aeadd590d 100644 --- a/app/assets/javascripts/admin/routes/admin-user-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-user-index.js.es6 @@ -19,7 +19,7 @@ export default DiscourseRoute.extend({ controller.setProperties({ originalPrimaryGroupId: model.primary_group_id, availableGroups: this._availableGroups, - customGroupIdsBuffer: null, + customGroupIdsBuffer: model.customGroups.mapBy("id"), model }); } diff --git a/app/assets/javascripts/admin/templates/api-keys-new.hbs b/app/assets/javascripts/admin/templates/api-keys-new.hbs index 4ddc37befd4..aea83704d38 100644 --- a/app/assets/javascripts/admin/templates/api-keys-new.hbs +++ b/app/assets/javascripts/admin/templates/api-keys-new.hbs @@ -20,7 +20,7 @@ {{/admin-form-row}} {{#admin-form-row label="admin.api.user_mode"}} - {{combo-box content=userModes value=userMode onSelect=(action "changeUserMode")}} + {{combo-box content=userModes value=userMode onChange=(action "changeUserMode")}} {{/admin-form-row}} {{#if showUserSelector}} @@ -28,11 +28,11 @@ {{user-selector single="true" usernames=model.username placeholderKey="admin.api.user_placeholder" - }} + }} {{/admin-form-row}} {{/if}} {{#admin-form-row}} {{d-button icon="check" label="admin.api.save" action=(action "save") class="btn-primary"}} {{/admin-form-row}} {{/if}} - \ No newline at end of file + diff --git a/app/assets/javascripts/admin/templates/badges-show.hbs b/app/assets/javascripts/admin/templates/badges-show.hbs index 7a50b1084bf..72147c85408 100644 --- a/app/assets/javascripts/admin/templates/badges-show.hbs +++ b/app/assets/javascripts/admin/templates/badges-show.hbs @@ -23,30 +23,35 @@
- {{combo-box name="badge_type_id" - value=buffered.badge_type_id - content=badgeTypes - allowInitialValueMutation=true - isDisabled=readOnly}} + {{combo-box + name="badge_type_id" + value=buffered.badge_type_id + content=badgeTypes + onChange=(action (mut buffered.badge_type_id)) + isDisabled=readOnly + }}
- {{combo-box name="badge_grouping_id" + {{combo-box + name="badge_grouping_id" value=buffered.badge_grouping_id content=badgeGroupings class="badge-selector" - nameProperty="name"}} + nameProperty="name" + onChange=(action (mut buffered.badge_grouping_id)) + }} {{d-button class="btn-default" action=(route-action "editGroupings") icon="pencil-alt"}} + }}
-
{{#if buffered.system}} @@ -95,12 +100,13 @@
- {{combo-box name="trigger" - value=buffered.trigger - content=badgeTriggers - optionValuePath="content.id" - optionLabelPath="content.name" - disabled=readOnly}} + {{combo-box + name="trigger" + value=buffered.trigger + content=badgeTriggers + onChange=(action (mut buffered.trigger)) + disabled=readOnly + }}
{{/if}} {{/if}} diff --git a/app/assets/javascripts/admin/templates/components/admin-report.hbs b/app/assets/javascripts/admin/templates/components/admin-report.hbs index bdc3b5b12a7..f8a36fa7d3c 100644 --- a/app/assets/javascripts/admin/templates/components/admin-report.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-report.hbs @@ -130,7 +130,10 @@
- {{date-input date=startDate onChange=(action "onChangeStartDate")}} + {{date-input + date=startDate + onChange=(action "onChangeStartDate") + }}
@@ -140,7 +143,10 @@
- {{date-input date=endDate onChange=(action "onChangeEndDate")}} + {{date-input + date=endDate + onChange=(action "onChangeEndDate") + }}
{{/if}} diff --git a/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs b/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs index a23d21f6e97..68b1810a8ce 100644 --- a/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs @@ -1,6 +1,10 @@ {{#if editing}} {{#admin-form-row label="admin.user_fields.type"}} - {{combo-box content=fieldTypes value=buffered.field_type}} + {{combo-box + content=fieldTypes + value=buffered.field_type + onChange=(action (mut buffered.field_type)) + }} {{/admin-form-row}} {{#admin-form-row label="admin.user_fields.name"}} diff --git a/app/assets/javascripts/admin/templates/components/embeddable-host.hbs b/app/assets/javascripts/admin/templates/components/embeddable-host.hbs index 559544abcef..b7833d621d3 100644 --- a/app/assets/javascripts/admin/templates/components/embeddable-host.hbs +++ b/app/assets/javascripts/admin/templates/components/embeddable-host.hbs @@ -13,7 +13,11 @@
{{i18n "admin.embedding.category"}}
- {{category-chooser value=categoryId class="small"}} + {{category-chooser + value=categoryId + class="small" + onChange=(action (mut categoryId)) + }} {{d-button icon="check" action=(action "save") class="btn-primary" disabled=cantSave}} diff --git a/app/assets/javascripts/admin/templates/components/penalty-post-action.hbs b/app/assets/javascripts/admin/templates/components/penalty-post-action.hbs index 1c8ffe56d8e..1a672329cd2 100644 --- a/app/assets/javascripts/admin/templates/components/penalty-post-action.hbs +++ b/app/assets/javascripts/admin/templates/components/penalty-post-action.hbs @@ -4,7 +4,11 @@ {{{i18n 'admin.user.penalty_post_actions'}}} - {{combo-box value=postAction content=penaltyActions onSelect=(action "penaltyChanged")}} + {{combo-box + value=postAction + content=penaltyActions + onChange=(action "penaltyChanged") + }} {{#if editing}} diff --git a/app/assets/javascripts/admin/templates/components/permalink-form.hbs b/app/assets/javascripts/admin/templates/components/permalink-form.hbs index 8efdbbf581a..1f5d1c06f21 100644 --- a/app/assets/javascripts/admin/templates/components/permalink-form.hbs +++ b/app/assets/javascripts/admin/templates/components/permalink-form.hbs @@ -8,7 +8,11 @@ autocorrect="off" autocapitalize="off"}} -{{combo-box content=permalinkTypes value=permalinkType}} +{{combo-box + content=permalinkTypes + value=permalinkType + onChange=(action (mut permalinkType)) +}} {{text-field value=permalink_type_value diff --git a/app/assets/javascripts/admin/templates/components/report-filters/category.hbs b/app/assets/javascripts/admin/templates/components/report-filters/category.hbs index 94aa60f7d05..62de5e0bed0 100644 --- a/app/assets/javascripts/admin/templates/components/report-filters/category.hbs +++ b/app/assets/javascripts/admin/templates/components/report-filters/category.hbs @@ -1,7 +1,5 @@ {{search-advanced-category-chooser filterable=true value=category - castInteger=true - onSelectNone=(action "onChange") - onDeselect=(action "onDeselect") - onSelect=(action "onChange")}} + onChange=(action (mut category)) +}} diff --git a/app/assets/javascripts/admin/templates/components/report-filters/file-extension.hbs b/app/assets/javascripts/admin/templates/components/report-filters/file-extension.hbs index 871179d63d6..88d71d0747e 100644 --- a/app/assets/javascripts/admin/templates/components/report-filters/file-extension.hbs +++ b/app/assets/javascripts/admin/templates/components/report-filters/file-extension.hbs @@ -4,5 +4,5 @@ allowAny=filter.allow_any value=filter.default none="admin.dashboard.report_filter_any" - onSelectNone=(action "onChange") - onSelect=(action "onChange")}} + onChange=(action "onChange") +}} diff --git a/app/assets/javascripts/admin/templates/components/report-filters/group.hbs b/app/assets/javascripts/admin/templates/components/report-filters/group.hbs index 0209c1cb6e8..9e17bda2ac3 100644 --- a/app/assets/javascripts/admin/templates/components/report-filters/group.hbs +++ b/app/assets/javascripts/admin/templates/components/report-filters/group.hbs @@ -1,10 +1,9 @@ {{combo-box - castInteger=true filterable=true - valueAttribute="value" + valueProperty="value" content=groupOptions value=groupId allowAny=filter.allow_any none="admin.dashboard.reports.groups" - onSelectNone=(action "onChange") - onSelect=(action "onChange")}} + onChange=(action "onChange") +}} diff --git a/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs b/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs index 6bc99fdbd11..2be732079ea 100644 --- a/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs +++ b/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs @@ -1,4 +1,10 @@ {{i18n 'admin.logs.screened_ips.form.label'}} {{text-field value=ip_address disabled=formSubmitted class="ip-address-input" placeholderKey="admin.logs.screened_ips.form.ip_address" autocorrect="off" autocapitalize="off"}} -{{combo-box content=actionNames value=actionName}} + +{{combo-box + content=actionNames + value=actionName + onChange=(action (mut actionName)) +}} + {{d-button class="btn-default" action=(action "submit") disabled=formSubmitted label="admin.logs.screened_ips.form.add"}} diff --git a/app/assets/javascripts/admin/templates/components/site-settings/category-list.hbs b/app/assets/javascripts/admin/templates/components/site-settings/category-list.hbs index 0aadec18d81..14d7210604c 100644 --- a/app/assets/javascripts/admin/templates/components/site-settings/category-list.hbs +++ b/app/assets/javascripts/admin/templates/components/site-settings/category-list.hbs @@ -1,3 +1,7 @@ -{{category-selector categories=selectedCategories}} +{{category-selector + categories=selectedCategories + onChange=(action "onChangeSelectedCategories") +}} +
{{{unbound setting.description}}}
{{setting-validation-message message=validationMessage}} diff --git a/app/assets/javascripts/admin/templates/components/site-settings/category.hbs b/app/assets/javascripts/admin/templates/components/site-settings/category.hbs index e3ab41137c1..d6b6bee5658 100644 --- a/app/assets/javascripts/admin/templates/components/site-settings/category.hbs +++ b/app/assets/javascripts/admin/templates/components/site-settings/category.hbs @@ -1,3 +1,7 @@ -{{category-chooser value=value allowUncategorized="true"}} +{{category-chooser + value=value + allowUncategorized=true + onChange=(action (mut value)) +}} {{setting-validation-message message=validationMessage}}
{{{unbound setting.description}}}
diff --git a/app/assets/javascripts/admin/templates/components/site-settings/compact-list.hbs b/app/assets/javascripts/admin/templates/components/site-settings/compact-list.hbs index 551ca3d45d3..86fce97f15e 100644 --- a/app/assets/javascripts/admin/templates/components/site-settings/compact-list.hbs +++ b/app/assets/javascripts/admin/templates/components/site-settings/compact-list.hbs @@ -1,3 +1,11 @@ -{{list-setting settingValue=value choices=setting.choices settingName=setting.setting allowAny=allowAny}} +{{list-setting + value=settingValue + settingName=setting.setting + allowAny=allowAny + choices=settingChoices + onChange=(action "onChangeListSetting") + onChangeChoices=(action "onChangeChoices") +}} + {{setting-validation-message message=validationMessage}}
{{{unbound setting.description}}}
diff --git a/app/assets/javascripts/admin/templates/components/site-settings/enum.hbs b/app/assets/javascripts/admin/templates/components/site-settings/enum.hbs index 9b3da3178e3..2612679d723 100644 --- a/app/assets/javascripts/admin/templates/components/site-settings/enum.hbs +++ b/app/assets/javascripts/admin/templates/components/site-settings/enum.hbs @@ -1,4 +1,15 @@ -{{combo-box castInteger=true valueAttribute="value" content=setting.validValues value=value none=setting.allowsNone}} +{{combo-box + valueProperty="value" + content=setting.validValues + value=value + onChange=(action (mut value)) + allowAny=setting.allowsNone +}} + {{preview}} + {{setting-validation-message message=validationMessage}} -
{{{unbound setting.description}}}
+ +
+ {{{unbound setting.description}}} +
diff --git a/app/assets/javascripts/admin/templates/components/site-settings/group-list.hbs b/app/assets/javascripts/admin/templates/components/site-settings/group-list.hbs index 005f14398ee..768311c3f96 100644 --- a/app/assets/javascripts/admin/templates/components/site-settings/group-list.hbs +++ b/app/assets/javascripts/admin/templates/components/site-settings/group-list.hbs @@ -1,3 +1,10 @@ -{{list-setting settingValue=value choices=groupChoices settingName='name'}} +{{list-setting + value=settingValue + choices=groupChoices + settingName="name" + nameProperty=nameProperty + valueProperty=valueProperty + onChange=(action "onChangeGroupListSetting") +}} {{setting-validation-message message=validationMessage}}
{{{unbound setting.description}}}
diff --git a/app/assets/javascripts/admin/templates/components/value-list.hbs b/app/assets/javascripts/admin/templates/components/value-list.hbs index 372de2ac1d1..a5ade53ee08 100644 --- a/app/assets/javascripts/admin/templates/components/value-list.hbs +++ b/app/assets/javascripts/admin/templates/components/value-list.hbs @@ -2,12 +2,19 @@
{{#each collection as |value index|}}
- {{d-button action=(action "removeValue") - actionParam=value - icon="times" - class="btn-default remove-value-btn btn-small"}} + {{d-button + action=(action "removeValue") + actionParam=value + icon="times" + class="remove-value-btn btn-small" + }} - {{input title=value value=value class="value-input" focus-out=(action "changeValue" index)}} + {{input + title=value + value=value + class="value-input" + focus-out=(action "changeValue" index) + }}
{{/each}}
@@ -15,7 +22,10 @@ {{combo-box allowAny=true - allowContentReplacement=true none=noneKey + valueProperty=null + nameProperty=null + value=newValue content=filteredChoices - onSelect=(action "selectChoice")}} + onChange=(action "selectChoice") +}} diff --git a/app/assets/javascripts/admin/templates/customize-email-templates.hbs b/app/assets/javascripts/admin/templates/customize-email-templates.hbs index d89624e477f..55434c9e463 100644 --- a/app/assets/javascripts/admin/templates/customize-email-templates.hbs +++ b/app/assets/javascripts/admin/templates/customize-email-templates.hbs @@ -1,8 +1,8 @@ {{combo-box content=sortedTemplates - valueAttribute="id" + valueProperty="id" nameProperty="title" - onSelect=(action "selectTemplate") + onChange=(action "selectTemplate") }} {{outlet}} diff --git a/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs b/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs index bba9a724ca0..d48127916ac 100644 --- a/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs +++ b/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs @@ -30,7 +30,13 @@ {{/if}} {{else}} - {{i18n "admin.logs.staff_actions.filter"}} {{combo-box content=userHistoryActions value=filterActionId none="admin.logs.staff_actions.all" onSelect=(action "filterActionIdChanged")}} + {{i18n "admin.logs.staff_actions.filter"}} + {{combo-box + content=userHistoryActions + value=filterActionId + none="admin.logs.staff_actions.all" + onChange=(action "filterActionIdChanged") + }} {{/if}} {{d-button class="btn-default" action=(action "exportStaffActionLogs") label="admin.export_csv.button_text" icon="download"}} diff --git a/app/assets/javascripts/admin/templates/modal/admin-color-scheme-select-base.hbs b/app/assets/javascripts/admin/templates/modal/admin-color-scheme-select-base.hbs index e1978b918ab..15a39314cd3 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-color-scheme-select-base.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-color-scheme-select-base.hbs @@ -1,16 +1,20 @@
{{#d-modal-body title="admin.customize.colors.select_base.title"}} {{i18n "admin.customize.colors.select_base.description"}} - {{combo-box content=model - value=selectedBaseThemeId - allowInitialValueMutation=true - valueAttribute="base_scheme_id"}} + {{combo-box + content=model + value=selectedBaseThemeId + onChange=(action (mut selectedBaseThemeId)) + valueProperty="base_scheme_id" + }} {{/d-modal-body}} +
diff --git a/app/assets/javascripts/admin/templates/modal/admin-install-theme.hbs b/app/assets/javascripts/admin/templates/modal/admin-install-theme.hbs index a41bfa93a94..7307abb411a 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-install-theme.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-install-theme.hbs @@ -89,7 +89,12 @@ {{input value=name placeholder=placeholder}}
{{I18n "admin.customize.theme.create_type"}}
- {{combo-box valueAttribute="value" content=createTypes value=selectedType}} + {{combo-box + valueProperty="value" + content=createTypes + value=selectedType + onChange=(action (mut selectedType)) + }} {{/if}} diff --git a/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs b/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs index 7f830916cd4..0b123cdc6a3 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs @@ -8,7 +8,9 @@ label="admin.user.silence_duration" includeFarFuture=true clearable=false - input=silenceUntil}} + input=silenceUntil + onChangeInput=(action (mut silenceUntil)) + }} diff --git a/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs b/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs index cc4a075fe8f..a10ecd9c952 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs @@ -9,7 +9,9 @@ label="admin.user.suspend_duration" includeFarFuture=true clearable=false - input=suspendUntil}} + input=suspendUntil + onChangeInput=(action (mut suspendUntil)) + }} {{suspension-details reason=reason message=message}} diff --git a/app/assets/javascripts/admin/templates/reports-show.hbs b/app/assets/javascripts/admin/templates/reports-show.hbs index dac1aa0bbe0..6c6a8fb279e 100644 --- a/app/assets/javascripts/admin/templates/reports-show.hbs +++ b/app/assets/javascripts/admin/templates/reports-show.hbs @@ -4,4 +4,5 @@ filters=model reportOptions=reportOptions showFilteringUI=true - onRefresh=(route-action "onParamsChange")}} + onRefresh=(route-action "onParamsChange") +}} diff --git a/app/assets/javascripts/admin/templates/search-logs-index.hbs b/app/assets/javascripts/admin/templates/search-logs-index.hbs index 805c4fe4cfc..b56a1400fbb 100644 --- a/app/assets/javascripts/admin/templates/search-logs-index.hbs +++ b/app/assets/javascripts/admin/templates/search-logs-index.hbs @@ -1,6 +1,11 @@
- {{period-chooser period=period}} - {{combo-box content=searchTypeOptions value=searchType class='search-logs-filter'}} + {{period-chooser period=period onChange=(action (mut period))}} + {{combo-box + content=searchTypeOptions + value=searchType + class="search-logs-filter" + onChange=(action (mut searchType)) + }}
{{#conditional-loading-spinner condition=loading}} diff --git a/app/assets/javascripts/admin/templates/search-logs-term.hbs b/app/assets/javascripts/admin/templates/search-logs-term.hbs index e31a80d590e..91d5d31361b 100644 --- a/app/assets/javascripts/admin/templates/search-logs-term.hbs +++ b/app/assets/javascripts/admin/templates/search-logs-term.hbs @@ -1,6 +1,11 @@
- {{period-chooser period=period}} - {{combo-box content=searchTypeOptions value=searchType class='search-logs-filter'}} + {{period-chooser period=period onChange=(action (mut period))}} + {{combo-box + content=searchTypeOptions + value=searchType + class="search-logs-filter" + onChange=(action (mut searchType)) + }}

diff --git a/app/assets/javascripts/admin/templates/user-badges.hbs b/app/assets/javascripts/admin/templates/user-badges.hbs index 9f81cd2f741..6b6716b4847 100644 --- a/app/assets/javascripts/admin/templates/user-badges.hbs +++ b/app/assets/javascripts/admin/templates/user-badges.hbs @@ -15,8 +15,13 @@ {{else}}
- - {{combo-box forceEscape=true filterable=true value=selectedBadgeId content=grantableBadges}} + + {{combo-box + filterable=true + value=selectedBadgeId + content=grantableBadges + onChange=(action (mut selectedBadgeId)) + }}
diff --git a/app/assets/javascripts/admin/templates/user-index.hbs b/app/assets/javascripts/admin/templates/user-index.hbs index 7eb9504fb5c..d69f3b97062 100644 --- a/app/assets/javascripts/admin/templates/user-index.hbs +++ b/app/assets/javascripts/admin/templates/user-index.hbs @@ -298,7 +298,7 @@
{{d-button href="/admin/api/keys" label="admin.api.manage_keys"}} - +
{{/if}} @@ -350,8 +350,9 @@
{{combo-box content=site.trustLevels - value=model.trust_level nameProperty="detailedName" + value=model.trustLevel.id + onChange=(action (mut model.trust_level)) }} {{#if model.dirty}} @@ -514,9 +515,10 @@
{{i18n "admin.groups.custom"}}
{{admin-group-selector - selected=model.customGroups - available=availableGroups - buffer=customGroupIdsBuffer}} + content=availableGroups + value=customGroupIdsBuffer + onChange=(action (mut customGroupIdsBuffer)) + }}
{{#if customGroupsDirty}}
@@ -532,7 +534,9 @@ {{combo-box content=model.customGroups value=model.primary_group_id - none="admin.groups.no_primary"}} + none="admin.groups.no_primary" + onChange=(action (mut model.primary_group_id)) + }}
{{#if primaryGroupDirty}}
diff --git a/app/assets/javascripts/admin/templates/web-hooks-show.hbs b/app/assets/javascripts/admin/templates/web-hooks-show.hbs index cc49f3ac58c..ee7cb36eca1 100644 --- a/app/assets/javascripts/admin/templates/web-hooks-show.hbs +++ b/app/assets/javascripts/admin/templates/web-hooks-show.hbs @@ -14,9 +14,12 @@
- {{combo-box content=contentTypes - name="content-type" - value=model.content_type}} + {{combo-box + content=contentTypes + name="content-type" + value=model.content_type + onChange=(action (mut model.content_type)) + }}
@@ -48,7 +51,10 @@
- {{category-selector categories=model.categories}} + {{category-selector + categories=model.categories + onChange=(action (mut model.categories)) + }}
{{i18n 'admin.web_hooks.categories_filter_instructions'}}
{{#if showTagsFilter}} diff --git a/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 b/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 index aa32589a3a2..2c2fd9043ff 100644 --- a/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 +++ b/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 @@ -637,6 +637,12 @@ registerIconRenderer({ params.title ).replace(/'/g, "'")}'>${html}`; } + if (params.translatedtitle) { + html = `${html}`; + } return html; }, diff --git a/app/assets/javascripts/discourse/components/auth-token-dropdown.es6 b/app/assets/javascripts/discourse/components/auth-token-dropdown.es6 index 8187c6fde1c..ef4d819ead7 100644 --- a/app/assets/javascripts/discourse/components/auth-token-dropdown.es6 +++ b/app/assets/javascripts/discourse/components/auth-token-dropdown.es6 @@ -1,13 +1,16 @@ import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; +import { computed } from "@ember/object"; export default DropdownSelectBoxComponent.extend({ classNames: ["auth-token-dropdown"], - headerIcon: "wrench", - allowInitialValueMutation: false, - showFullTitle: false, - computeContent() { - const content = [ + selectKitOptions: { + icon: "wrench", + showFullTitle: false + }, + + content: computed(function() { + return [ { id: "notYou", icon: "user-times", @@ -21,12 +24,10 @@ export default DropdownSelectBoxComponent.extend({ description: "" } ]; - - return content; - }, + }), actions: { - onSelect(id) { + onChange(id) { switch (id) { case "notYou": this.showToken(this.token); diff --git a/app/assets/javascripts/discourse/components/d-navigation.js.es6 b/app/assets/javascripts/discourse/components/d-navigation.js.es6 index 041e21e4a8d..df6dc3c0a92 100644 --- a/app/assets/javascripts/discourse/components/d-navigation.js.es6 +++ b/app/assets/javascripts/discourse/components/d-navigation.js.es6 @@ -49,5 +49,22 @@ export default Component.extend(FilterModeMixin, { noSubcategories, persistedQueryParams: params }); + }, + + actions: { + changeCategoryNotificationLevel(notificationLevel) { + this.category.setNotification(notificationLevel); + }, + + selectCategoryAdminDropdownAction(actionId) { + switch (actionId) { + case "create": + this.createCategory(); + break; + case "reorder": + this.reorderCategories(); + break; + } + } } }); diff --git a/app/assets/javascripts/discourse/components/date-input.js.es6 b/app/assets/javascripts/discourse/components/date-input.js.es6 index 9da5a5e16c9..7bb967455ff 100644 --- a/app/assets/javascripts/discourse/components/date-input.js.es6 +++ b/app/assets/javascripts/discourse/components/date-input.js.es6 @@ -32,8 +32,9 @@ export default Component.extend({ didUpdateAttrs() { this._super(...arguments); - if (this._picker) { - this._picker.setDate(this.date, true); + if (this._picker && typeof date === "string") { + const [year, month, day] = this.date.split("-").map(x => parseInt(x, 10)); + this._picker.setDate(new Date(year, month - 1, day), true); } }, @@ -84,7 +85,7 @@ export default Component.extend({ this._picker && this._picker.hide(); if (this.onChange) { - this.onChange(moment(value).toDate()); + this.onChange(value); } }, @@ -103,5 +104,11 @@ export default Component.extend({ _opts() { return null; + }, + + actions: { + onInput(event) { + this._picker && this._picker.setDate(event.target.value, true); + } } }); diff --git a/app/assets/javascripts/discourse/components/date-picker-future.js.es6 b/app/assets/javascripts/discourse/components/date-picker-future.js.es6 index 74ef9afd7c2..e285c68dbc9 100644 --- a/app/assets/javascripts/discourse/components/date-picker-future.js.es6 +++ b/app/assets/javascripts/discourse/components/date-picker-future.js.es6 @@ -10,7 +10,8 @@ export default DatePicker.extend({ moment() .add(1, "day") .toDate(), - setDefaultDate: !!this.defaultDate + setDefaultDate: !!this.defaultDate, + minDate: this.minDate || moment().toDate() }; } }); diff --git a/app/assets/javascripts/discourse/components/date-picker.js.es6 b/app/assets/javascripts/discourse/components/date-picker.js.es6 index 4a32928ac02..c10d7ecdd8f 100644 --- a/app/assets/javascripts/discourse/components/date-picker.js.es6 +++ b/app/assets/javascripts/discourse/components/date-picker.js.es6 @@ -1,4 +1,4 @@ -import { next } from "@ember/runloop"; +import { schedule } from "@ember/runloop"; import Component from "@ember/component"; /* global Pikaday:true */ import loadScript from "discourse/lib/load-script"; @@ -28,7 +28,7 @@ export default Component.extend({ _loadPikadayPicker(container) { loadScript("/javascripts/pikaday.js").then(() => { - next(() => { + schedule("afterRender", () => { const options = { field: this.element.querySelector(".date-picker"), container: container || null, diff --git a/app/assets/javascripts/discourse/components/edit-category-security.js.es6 b/app/assets/javascripts/discourse/components/edit-category-security.js.es6 index 00ad068467c..ddabd805889 100644 --- a/app/assets/javascripts/discourse/components/edit-category-security.js.es6 +++ b/app/assets/javascripts/discourse/components/edit-category-security.js.es6 @@ -9,6 +9,16 @@ export default buildCategoryPanel("security", { showPendingGroupChangesAlert: false, interactedWithDropdowns: false, + @on("init") + _setup() { + this.setProperties({ + selectedGroup: this.get("category.availableGroups.firstObject"), + selectedPermission: this.get( + "category.availablePermissions.firstObject.id" + ) + }); + }, + @on("init") _registerValidator() { this.registerValidator(() => { @@ -24,8 +34,18 @@ export default buildCategoryPanel("security", { }, actions: { - onDropdownChange() { - this.set("interactedWithDropdowns", true); + onSelectGroup(selectedGroup) { + this.setProperties({ + interactedWithDropdowns: true, + selectedGroup + }); + }, + + onSelectPermission(selectedPermission) { + this.setProperties({ + interactedWithDropdowns: true, + selectedPermission + }); }, editPermissions() { @@ -42,11 +62,8 @@ export default buildCategoryPanel("security", { }); } - this.set( - "selectedGroup", - this.get("category.availableGroups.firstObject") - ); this.setProperties({ + selectedGroup: this.get("category.availableGroups.firstObject"), showPendingGroupChangesAlert: false, interactedWithDropdowns: false }); diff --git a/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 b/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 index 18f2ac04f0c..4d2be762d3d 100644 --- a/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 +++ b/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 @@ -1,5 +1,5 @@ import { isEmpty } from "@ember/utils"; -import { alias, equal, or } from "@ember/object/computed"; +import { equal, or, readOnly } from "@ember/object/computed"; import { schedule } from "@ember/runloop"; import Component from "@ember/component"; import discourseComputed, { @@ -16,7 +16,7 @@ import { } from "discourse/controllers/edit-topic-timer"; export default Component.extend({ - selection: alias("topicTimer.status_type"), + selection: readOnly("topicTimer.status_type"), autoOpen: equal("selection", OPEN_STATUS_TYPE), autoClose: equal("selection", CLOSE_STATUS_TYPE), autoDelete: equal("selection", DELETE_STATUS_TYPE), @@ -27,16 +27,11 @@ export default Component.extend({ @discourseComputed( "topicTimer.updateTime", - "loading", "publishToCategory", "topicTimer.category_id" ) - saveDisabled(updateTime, loading, publishToCategory, topicTimerCategoryId) { - return ( - isEmpty(updateTime) || - loading || - (publishToCategory && !topicTimerCategoryId) - ); + saveDisabled(updateTime, publishToCategory, topicTimerCategoryId) { + return isEmpty(updateTime) || (publishToCategory && !topicTimerCategoryId); }, @discourseComputed("topic.visible") @@ -70,5 +65,25 @@ export default Component.extend({ this.set("topicTimer.based_on_last_post", false); }); } + }, + + didReceiveAttrs() { + this._super(...arguments); + + // TODO: get rid of this hack + schedule("afterRender", () => { + if (!this.get("topicTimer.status_type")) { + this.set( + "topicTimer.status_type", + this.get("timerTypes.firstObject.id") + ); + } + }); + }, + + actions: { + onChangeTimerType(value) { + this.set("topicTimer.status_type", value); + } } }); diff --git a/app/assets/javascripts/discourse/components/future-date-input.js.es6 b/app/assets/javascripts/discourse/components/future-date-input.js.es6 index 2c1bb70b54c..d84ab3c3340 100644 --- a/app/assets/javascripts/discourse/components/future-date-input.js.es6 +++ b/app/assets/javascripts/discourse/components/future-date-input.js.es6 @@ -45,9 +45,10 @@ export default Component.extend({ const dateTime = moment(`${this.date}${time}`); if (dateTime.isValid()) { - this.set("input", dateTime.format(FORMAT)); + this.attrs.onChangeInput && + this.attrs.onChangeInput(dateTime.format(FORMAT)); } else { - this.set("input", null); + this.attrs.onChangeInput && this.attrs.onChangeInput(null); } }, @@ -109,7 +110,10 @@ export default Component.extend({ } if (isCustom) { - return date || time; + if (date) { + return moment(`${date}${time ? " " + time : ""}`).isAfter(moment()); + } + return time; } else { return input; } diff --git a/app/assets/javascripts/discourse/components/group-member-dropdown.js.es6 b/app/assets/javascripts/discourse/components/group-member-dropdown.js.es6 index 7a860c29de3..1ee959ecb2d 100644 --- a/app/assets/javascripts/discourse/components/group-member-dropdown.js.es6 +++ b/app/assets/javascripts/discourse/components/group-member-dropdown.js.es6 @@ -1,23 +1,16 @@ -import discourseComputed from "discourse-common/utils/decorators"; import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; +import { computed } from "@ember/object"; export default DropdownSelectBoxComponent.extend({ pluginApiIdentifiers: ["group-member-dropdown"], - classNames: "group-member-dropdown", - showFullTitle: false, - allowInitialValueMutation: false, - allowAutoSelectFirst: false, + classNames: ["group-member-dropdown"], - init() { - this._super(...arguments); - - this.headerIcon = ["wrench"]; + selectKitOptions: { + icon: "wrench", + showFullTitle: false }, - autoHighlight() {}, - - @discourseComputed("member.owner") - content(isOwner) { + content: computed("member.owner", function() { const items = [ { id: "removeMember", @@ -29,8 +22,8 @@ export default DropdownSelectBoxComponent.extend({ } ]; - if (this.currentUser && this.currentUser.admin) { - if (isOwner) { + if (this.get("currentUser.admin")) { + if (this.member.owner) { items.push({ id: "removeOwner", name: I18n.t("groups.members.remove_owner"), @@ -52,19 +45,5 @@ export default DropdownSelectBoxComponent.extend({ } return items; - }, - - mutateValue(id) { - switch (id) { - case "removeMember": - this.removeMember(this.member); - break; - case "makeOwner": - this.makeOwner(this.get("member.username")); - break; - case "removeOwner": - this.removeOwner(this.member); - break; - } - } + }) }); diff --git a/app/assets/javascripts/discourse/components/groups-form-membership-fields.js.es6 b/app/assets/javascripts/discourse/components/groups-form-membership-fields.js.es6 index b85eef4fa5a..304f3d808d5 100644 --- a/app/assets/javascripts/discourse/components/groups-form-membership-fields.js.es6 +++ b/app/assets/javascripts/discourse/components/groups-form-membership-fields.js.es6 @@ -1,7 +1,10 @@ import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; +import { computed } from "@ember/object"; export default Component.extend({ + tokenSeparator: "|", + init() { this._super(...arguments); @@ -17,6 +20,17 @@ export default Component.extend({ ]; }, + groupTrustLevel: computed( + "model.grant_trust_level", + "trustLevelOptions", + function() { + return ( + this.model.get("grant_trust_level") || + this.trustLevelOptions.firstObject.value + ); + } + ), + @discourseComputed("model.visibility_level", "model.public_admission") disableMembershipRequestSetting(visibility_level, publicAdmission) { visibility_level = parseInt(visibility_level, 10); @@ -30,5 +44,11 @@ export default Component.extend({ disablePublicSetting(visibility_level, allowMembershipRequests) { visibility_level = parseInt(visibility_level, 10); return allowMembershipRequests || visibility_level > 1; + }, + + actions: { + onChangeEmailDomainsSetting(value) { + this.set("model.emailDomains", value.join(this.tokenSeparator)); + } } }); diff --git a/app/assets/javascripts/discourse/components/reviewable-field-tags.js.es6 b/app/assets/javascripts/discourse/components/reviewable-field-tags.js.es6 new file mode 100644 index 00000000000..3e23d8b884f --- /dev/null +++ b/app/assets/javascripts/discourse/components/reviewable-field-tags.js.es6 @@ -0,0 +1,12 @@ +export default Ember.Component.extend({ + actions: { + onChange(tags) { + this.valueChanged && + this.valueChanged({ + target: { + value: tags + } + }); + } + } +}); diff --git a/app/assets/javascripts/discourse/components/reviewable-item.js.es6 b/app/assets/javascripts/discourse/components/reviewable-item.js.es6 index ef6626ebecb..f655a92eb00 100644 --- a/app/assets/javascripts/discourse/components/reviewable-item.js.es6 +++ b/app/assets/javascripts/discourse/components/reviewable-item.js.es6 @@ -182,10 +182,13 @@ export default Component.extend({ .finally(() => this.set("updating", false)); }, - categoryChanged(category) { + categoryChanged(categoryId) { + let category = Category.findById(categoryId); + if (!category) { category = Category.findUncategorized(); } + this._updates.category_id = category.id; }, diff --git a/app/assets/javascripts/discourse/components/search-advanced-options.js.es6 b/app/assets/javascripts/discourse/components/search-advanced-options.js.es6 index db8c11ea4b6..855fed4e287 100644 --- a/app/assets/javascripts/discourse/components/search-advanced-options.js.es6 +++ b/app/assets/javascripts/discourse/components/search-advanced-options.js.es6 @@ -553,7 +553,6 @@ export default Component.extend({ } }, - @observes("searchedTerms.time.when", "searchedTerms.time.days") updateSearchTermForPostTime() { const match = this.filterBlocks(REGEXP_POST_TIME_PREFIX); const timeDaysFilter = this.get("searchedTerms.time.days"); @@ -603,5 +602,28 @@ export default Component.extend({ badgeFinder(term) { return Badge.findAll({ search: term }); + }, + + actions: { + onChangeWhenTime(time) { + if (time) { + this.set("searchedTerms.time.when", time); + this.updateSearchTermForPostTime(); + } + }, + onChangeWhenDate(date) { + if (date) { + this.set("searchedTerms.time.days", moment(date).format("YYYY-MM-DD")); + this.updateSearchTermForPostTime(); + } + }, + + onChangeCategory(categoryId) { + if (categoryId) { + this.set("searchedTerms.category", Category.findById(categoryId)); + } else { + this.set("searchedTerms.category", null); + } + } } }); diff --git a/app/assets/javascripts/discourse/components/shared-draft-controls.js.es6 b/app/assets/javascripts/discourse/components/shared-draft-controls.js.es6 index 0b988e75cf3..4374380e03f 100644 --- a/app/assets/javascripts/discourse/components/shared-draft-controls.js.es6 +++ b/app/assets/javascripts/discourse/components/shared-draft-controls.js.es6 @@ -11,8 +11,8 @@ export default Component.extend({ }, actions: { - updateDestinationCategory(category) { - return this.topic.updateDestinationCategory(category.get("id")); + updateDestinationCategory(categoryId) { + return this.topic.updateDestinationCategory(categoryId); }, publish() { diff --git a/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 b/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 index 608d2e588ca..d7dc2cb19ab 100644 --- a/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 +++ b/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 @@ -1,47 +1,38 @@ import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; +import { computed } from "@ember/object"; export default DropdownSelectBoxComponent.extend({ pluginApiIdentifiers: ["tags-admin-dropdown"], classNames: "tags-admin-dropdown", - showFullTitle: false, - allowInitialValueMutation: false, actionsMapping: null, - init() { - this._super(...arguments); - - this.headerIcon = ["bars", "caret-down"]; + selectKitOptions: { + icons: ["bars", "caret-down"], + showFullTitle: false }, - autoHighlight() {}, - - computeContent() { - const items = [ + content: computed(function() { + return [ { id: "manageGroups", name: I18n.t("tagging.manage_groups"), description: I18n.t("tagging.manage_groups_description"), - icon: "wrench", - __sk_row_type: "noopRow" + icon: "wrench" }, { id: "uploadTags", name: I18n.t("tagging.upload"), description: I18n.t("tagging.upload_description"), - icon: "upload", - __sk_row_type: "noopRow" + icon: "upload" }, { id: "deleteUnusedTags", name: I18n.t("tagging.delete_unused"), description: I18n.t("tagging.delete_unused_description"), - icon: "trash-alt", - __sk_row_type: "noopRow" + icon: "trash-alt" } ]; - - return items; - }, + }), actions: { onSelect(id) { diff --git a/app/assets/javascripts/discourse/components/topic-timer-info.js.es6 b/app/assets/javascripts/discourse/components/topic-timer-info.js.es6 index 5396fbda08e..4b758ed6a24 100644 --- a/app/assets/javascripts/discourse/components/topic-timer-info.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-timer-info.js.es6 @@ -17,15 +17,6 @@ export default Component.extend({ notice: null, showTopicTimer: null, - rerenderTriggers: [ - "topicClosed", - "statusType", - "executeAt", - "basedOnLastPost", - "duration", - "categoryId" - ], - @discourseComputed("statusType") canRemoveTimer(type) { if (type === REMINDER_TYPE) return true; @@ -38,7 +29,7 @@ export default Component.extend({ }, renderTopicTimer() { - if (!this.executeAt) { + if (!this.executeAt || this.executeAt < moment()) { this.set("showTopicTimer", null); return; } @@ -50,7 +41,6 @@ export default Component.extend({ const statusUpdateAt = moment(this.executeAt); const duration = moment.duration(statusUpdateAt - moment()); const minutesLeft = duration.asMinutes(); - if (minutesLeft > 0) { let rerenderDelay = 1000; if (minutesLeft > 2160) { @@ -82,9 +72,11 @@ export default Component.extend({ ); } - this.set("title", `${moment(this.executeAt).format("LLLL")}`.htmlSafe()); - this.set("notice", `${I18n.t(this._noticeKey(), options)}`.htmlSafe()); - this.set("showTopicTimer", true); + this.setProperties({ + title: `${moment(this.executeAt).format("LLLL")}`.htmlSafe(), + notice: `${I18n.t(this._noticeKey(), options)}`.htmlSafe(), + showTopicTimer: true + }); // TODO Sam: concerned this can cause a heavy rerender loop if (ENV.environment !== "test") { diff --git a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 index 2c04b1ca1c3..a410f1fc901 100644 --- a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 @@ -100,13 +100,19 @@ export default Controller.extend(ModalFunctionality, { }); } }) - .catch(error => { - popupAjaxError(error); - }) + .catch(popupAjaxError) .finally(() => this.set("loading", false)); }, actions: { + onChangeStatusType(value) { + this.set("topicTimer.status_type", value); + }, + + onChangeUpdateTime(value) { + this.set("topicTimer.updateTime", value); + }, + saveTimer() { if (!this.get("topicTimer.updateTime")) { this.flash( diff --git a/app/assets/javascripts/discourse/controllers/group-index.js.es6 b/app/assets/javascripts/discourse/controllers/group-index.js.es6 index dcf5e07f3df..6916164dd65 100644 --- a/app/assets/javascripts/discourse/controllers/group-index.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group-index.js.es6 @@ -86,6 +86,20 @@ export default Controller.extend({ this.toggleProperty("showActions"); }, + actOnGroup(member, actionId) { + switch (actionId) { + case "removeMember": + this.send("removeMember", member); + break; + case "makeOwner": + this.send("makeOwner", member.username); + break; + case "removeOwner": + this.send("removeOwner", member); + break; + } + }, + removeMember(user) { this.model.removeMember(user, this.memberParams); }, diff --git a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 index 086e5cc0b61..1fbed8353b7 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 @@ -14,6 +14,7 @@ import { isiPad, iOSWithVisualViewport } from "discourse/lib/utilities"; +import { computed } from "@ember/object"; const USER_HOMES = { 1: "latest", @@ -76,6 +77,17 @@ export default Controller.extend(PreferencesTabController, { }); }, + homepageId: computed( + "model.user_option.homepage_id", + "userSelectableHome.[]", + function() { + return ( + this.model.user_option.homepage_id || + this.userSelectableHome.firstObject.value + ); + } + ), + @discourseComputed titleCountModes() { return TITLE_COUNT_MODES.map(value => { @@ -195,6 +207,8 @@ export default Controller.extend(PreferencesTabController, { // Force refresh when leaving this screen Discourse.set("assetVersion", "forceRefresh"); + + this.set("textSize", newSize); } } }); diff --git a/app/assets/javascripts/discourse/controllers/tags-show.js.es6 b/app/assets/javascripts/discourse/controllers/tags-show.js.es6 index 0c3d6e64dd9..c4d79e8643a 100644 --- a/app/assets/javascripts/discourse/controllers/tags-show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/tags-show.js.es6 @@ -155,9 +155,8 @@ export default Controller.extend(BulkTopicSelection, FilterModeMixin, { }); }, - changeTagNotification(id) { - const tagNotification = this.tagNotification; - tagNotification.update({ notification_level: id }); + changeTagNotificationLevel(notificationLevel) { + this.tagNotification.update({ notification_level: notificationLevel }); } } }); diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index c474fa50fa7..25f515420b5 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -242,12 +242,12 @@ export default Controller.extend(bufferedProperty("model"), { }, actions: { - topicCategoryChanged(selection) { - this.set("buffered.category_id", selection.value); + topicCategoryChanged(categoryId) { + this.set("buffered.category_id", categoryId); }, - topicTagsChanged({ target }) { - this.set("buffered.tags", target.value); + topicTagsChanged(value) { + this.set("buffered.tags", value); }, deletePending(pending) { diff --git a/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 b/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 index 724d8b9909f..8a216985164 100644 --- a/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 @@ -59,6 +59,9 @@ export default Controller.extend({ }, actions: { + changeGroupNotificationLevel(notificationLevel) { + this.group.setNotification(notificationLevel, this.get("user.id")); + }, archive() { this.bulkOperation("archive_messages"); }, diff --git a/app/assets/javascripts/discourse/controllers/user.js.es6 b/app/assets/javascripts/discourse/controllers/user.js.es6 index 36bd0c9213f..90eeb3a837a 100644 --- a/app/assets/javascripts/discourse/controllers/user.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user.js.es6 @@ -9,7 +9,7 @@ import CanCheckEmails from "discourse/mixins/can-check-emails"; import User from "discourse/models/user"; import optionalService from "discourse/lib/optional-service"; import { prioritizeNameInUx } from "discourse/lib/settings"; -import { set } from "@ember/object"; +import { set, computed } from "@ember/object"; export default Controller.extend(CanCheckEmails, { indexStream: false, @@ -136,6 +136,21 @@ export default Controller.extend(CanCheckEmails, { } }, + userNotificationLevel: computed( + "currentUser.ignored_ids", + "model.ignored", + "model.muted", + function() { + if (this.get("model.ignored")) { + return "changeToIgnored"; + } else if (this.get("model.muted")) { + return "changeToMuted"; + } else { + return "changeToNormal"; + } + } + ), + actions: { collapseProfile() { this.set("forceExpand", false); diff --git a/app/assets/javascripts/discourse/helpers/component-for-collection.js.es6 b/app/assets/javascripts/discourse/helpers/component-for-collection.js.es6 new file mode 100644 index 00000000000..384012a0e6c --- /dev/null +++ b/app/assets/javascripts/discourse/helpers/component-for-collection.js.es6 @@ -0,0 +1,8 @@ +import { registerUnbound } from "discourse-common/lib/helpers"; + +registerUnbound( + "component-for-collection", + (collectionIdentifier, selectKit) => { + return selectKit.modifyComponentForCollection(collectionIdentifier); + } +); diff --git a/app/assets/javascripts/discourse/helpers/component-for-row.js.es6 b/app/assets/javascripts/discourse/helpers/component-for-row.js.es6 new file mode 100644 index 00000000000..4f8ba12822e --- /dev/null +++ b/app/assets/javascripts/discourse/helpers/component-for-row.js.es6 @@ -0,0 +1,8 @@ +import { registerUnbound } from "discourse-common/lib/helpers"; + +registerUnbound( + "component-for-row", + (collectionForIdentifier, item, selectKit) => { + return selectKit.modifyComponentForRow(collectionForIdentifier, item); + } +); diff --git a/app/assets/javascripts/discourse/lib/discourse-location.js.es6 b/app/assets/javascripts/discourse/lib/discourse-location.js.es6 index b1238d8d390..d9d504171b9 100644 --- a/app/assets/javascripts/discourse/lib/discourse-location.js.es6 +++ b/app/assets/javascripts/discourse/lib/discourse-location.js.es6 @@ -17,7 +17,7 @@ const DiscourseLocation = EmberObject.extend({ this._super(...arguments); this.set("location", this.location || window.location); - this.initState(); + this.initOptions(); }, /** @@ -25,9 +25,9 @@ const DiscourseLocation = EmberObject.extend({ Used to set state on first call to setURL - @method initState + @method initOptions */ - initState() { + initOptions() { const history = this.history || window.history; if (history && history.scrollRestoration) { history.scrollRestoration = "manual"; diff --git a/app/assets/javascripts/discourse/lib/url.js.es6 b/app/assets/javascripts/discourse/lib/url.js.es6 index 6d7fa31966e..3dc13d0d799 100644 --- a/app/assets/javascripts/discourse/lib/url.js.es6 +++ b/app/assets/javascripts/discourse/lib/url.js.es6 @@ -292,6 +292,10 @@ const DiscourseURL = EmberObject.extend({ return this.handleURL(path, opts); }, + routeToUrl(url, opts = {}) { + this.routeTo(Discourse.getURL(url), opts); + }, + rewrite(regexp, replacement, opts) { rewrites.push({ regexp, replacement, opts: opts || {} }); }, diff --git a/app/assets/javascripts/discourse/models/category.js.es6 b/app/assets/javascripts/discourse/models/category.js.es6 index 97a745bd5bd..0e576a4b8f2 100644 --- a/app/assets/javascripts/discourse/models/category.js.es6 +++ b/app/assets/javascripts/discourse/models/category.js.es6 @@ -120,15 +120,14 @@ const Category = RestModel.extend({ return topicCount > (this.num_featured_topics || 2); }, - @discourseComputed("topic_count", "subcategories") - totalTopicCount(topicCount, subcats) { - let count = topicCount; - if (subcats) { - subcats.forEach(s => { - count += s.get("topic_count"); + @discourseComputed("topic_count", "subcategories.[]") + totalTopicCount(topicCount, subcategories) { + if (subcategories) { + subcategories.forEach(subcategory => { + topicCount += subcategory.topic_count; }); } - return count; + return topicCount; }, save() { diff --git a/app/assets/javascripts/discourse/templates/components/badge-title.hbs b/app/assets/javascripts/discourse/templates/components/badge-title.hbs index 53615951994..abb66e44aa2 100644 --- a/app/assets/javascripts/discourse/templates/components/badge-title.hbs +++ b/app/assets/javascripts/discourse/templates/components/badge-title.hbs @@ -12,7 +12,9 @@ {{combo-box value=selectedUserBadgeId nameProperty="badge.name" - content=selectableUserBadges}} + content=selectableUserBadges + onChange=(action (mut selectedUserBadgeId)) + }}
diff --git a/app/assets/javascripts/discourse/templates/components/bread-crumbs.hbs b/app/assets/javascripts/discourse/templates/components/bread-crumbs.hbs index c7bfe57aaf4..b42063fb9bb 100644 --- a/app/assets/javascripts/discourse/templates/components/bread-crumbs.hbs +++ b/app/assets/javascripts/discourse/templates/components/bread-crumbs.hbs @@ -1,18 +1,20 @@ {{#each categoryBreadcrumbs as |breadcrumb|}} {{#if breadcrumb.hasOptions}} {{category-drop - category=breadcrumb.category + category=breadcrumb.category + categories=breadcrumb.options + options=(hash parentCategory=breadcrumb.parentCategory - categories=breadcrumb.options subCategory=breadcrumb.isSubcategory - noSubcategories=breadcrumb.noSubcategories}} + noSubcategories=breadcrumb.noSubcategories + autoFilterable=true + ) + }} {{/if}} {{/each}} {{#if siteSettings.tagging_enabled}} - {{tag-drop - currentCategory=category - tagId=tagId}} + {{tag-drop currentCategory=category tagId=tagId}} {{/if}} {{plugin-outlet name="bread-crumbs-right" connectorTagName="li" tagName=""}} diff --git a/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs b/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs index 8f010be7cec..3f26b6e3c5d 100644 --- a/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs +++ b/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs @@ -3,12 +3,13 @@ {{else}} {{composer-actions composerModel=model - options=model.replyOptions + replyOptions=model.replyOptions canWhisper=canWhisper openComposer=openComposer closeComposer=closeComposer action=model.action - tabindex=tabindex}} + tabindex=tabindex + }} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/templates/components/d-editor.hbs index 9435d744f80..5acae90928b 100644 --- a/app/assets/javascripts/discourse/templates/components/d-editor.hbs +++ b/app/assets/javascripts/discourse/templates/components/d-editor.hbs @@ -5,12 +5,15 @@ {{#each group.buttons as |b|}} {{#if b.popupMenu}} {{toolbar-popup-menu-options - onSelect=onPopupMenuAction + content=popupMenuOptions + onChange=onPopupMenuAction onExpand=(action b.action b) - title=b.title - headerIcon=b.icon class=b.className - content=popupMenuOptions}} + options=(hash + popupTitle=b.title + icon=b.icon + ) + }} {{else}} {{d-button action=b.action diff --git a/app/assets/javascripts/discourse/templates/components/d-navigation.hbs b/app/assets/javascripts/discourse/templates/components/d-navigation.hbs index a97af68cbc8..6a0d8e330c8 100644 --- a/app/assets/javascripts/discourse/templates/components/d-navigation.hbs +++ b/app/assets/javascripts/discourse/templates/components/d-navigation.hbs @@ -2,14 +2,18 @@ {{#if showCategoryAdmin}} {{categories-admin-dropdown - create=createCategory - reorder=reorderCategories}} + onChange=(action "selectCategoryAdminDropdownAction") + }} {{/if}} {{navigation-bar navItems=navItems filterMode=filterMode category=category}} {{#if showCategoryNotifications}} - {{category-notifications-button value=category.notification_level category=category}} + {{category-notifications-button + value=category.notification_level + category=category + onChange=(action "changeCategoryNotificationLevel") + }} {{/if}} {{plugin-outlet name="before-create-topic-button" diff --git a/app/assets/javascripts/discourse/templates/components/date-input.hbs b/app/assets/javascripts/discourse/templates/components/date-input.hbs index e29eb0e73af..3ed544b507c 100644 --- a/app/assets/javascripts/discourse/templates/components/date-input.hbs +++ b/app/assets/javascripts/discourse/templates/components/date-input.hbs @@ -2,4 +2,6 @@ type=inputType class="date-picker" placeholder=placeholder - value=value}} + value=value + input=(action "onInput") +}} diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs index 95e681f3966..19d6e724dcc 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs @@ -17,7 +17,9 @@ excludeCategoryId=category.id categories=parentCategories allowSubCategories=true - allowUncategorized=false}} + allowUncategorized=false + onChange=(action (mut category.parent_category_id)) + }} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-security.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-security.hbs index 9b238a1a1c6..e6c7dd5634b 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-security.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-security.hbs @@ -22,18 +22,21 @@ {{/unless}} {{#if editingPermissions}} {{#if category.availableGroups}} - {{combo-box class="available-groups" - allowInitialValueMutation=true - allowContentReplacement=true - content=category.availableGroups - onSelect=(action "onDropdownChange") - value=selectedGroup}} - {{combo-box allowInitialValueMutation=true - class="permission-selector" - nameProperty="description" - content=category.availablePermissions - onSelect=(action "onDropdownChange") - value=selectedPermission}} + {{combo-box + class="available-groups" + content=category.availableGroups + onChange=(action "onSelectGroup") + value=selectedGroup + valueProperty=null + nameProperty=null + }} + {{combo-box + class="permission-selector" + nameProperty="description" + content=category.availablePermissions + onChange=(action "onSelectPermission") + value=selectedPermission + }} {{d-button action=(action "addPermission" selectedGroup selectedPermission) class="btn-primary add-permission" diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs index 1f65507a768..fab2c089fde 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs @@ -33,10 +33,13 @@ {{i18n "category.search_priority.label"}}
- {{combo-box valueAttribute="value" - id="category-search-priority" - content=searchPrioritiesOptions - value=category.search_priority}} + {{combo-box + valueProperty="value" + id="category-search-priority" + content=searchPrioritiesOptions + value=category.search_priority + onChange=(action (mut category.search_priority)) + }}
@@ -145,7 +148,7 @@ {{i18n "category.default_view"}}
- {{combo-box valueAttribute="value" id="category-default-view" content=availableViews value=category.default_view}} + {{combo-box valueProperty="value" id="category-default-view" content=availableViews value=category.default_view}}
@@ -154,7 +157,7 @@ {{i18n "category.default_top_period"}}
- {{combo-box valueAttribute="value" id="category-default-period" content=availableTopPeriods value=category.default_top_period}} + {{combo-box valueProperty="value" id="category-default-period" content=availableTopPeriods value=category.default_top_period}}
@@ -163,9 +166,9 @@ {{i18n "category.sort_order"}}
- {{combo-box valueAttribute="value" content=availableSorts value=category.sort_order none="category.sort_options.default"}} + {{combo-box valueProperty="value" content=availableSorts value=category.sort_order none="category.sort_options.default"}} {{#unless isDefaultSortOrder}} - {{combo-box castBoolean=true valueAttribute="value" content=sortAscendingOptions value=category.sort_ascending none="category.sort_options.default"}} + {{combo-box castBoolean=true valueProperty="value" content=sortAscendingOptions value=category.sort_ascending none="category.sort_options.default"}} {{/unless}}
@@ -184,7 +187,7 @@ - {{combo-box valueAttribute="value" id="subcategory-list-style" content=availableSubcategoryListStyles value=category.subcategory_list_style}} + {{combo-box valueProperty="value" id="subcategory-list-style" content=availableSubcategoryListStyles value=category.subcategory_list_style}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs index 681e5e24dca..36151b12afc 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs @@ -6,12 +6,17 @@ tags=category.allowed_tags everyTag=true excludeSynonyms=true - unlimitedTagCount=true}} + unlimitedTagCount=true + onChange=(action (mut category.allowed_tags)) + }}
- {{tag-group-chooser id="category-allowed-tag-groups" tagGroups=category.allowed_tag_groups}} + {{tag-group-chooser + id="category-allowed-tag-groups" + tagGroups=category.allowed_tag_groups + }} {{#link-to 'tagGroups'}}{{i18n 'category.manage_tag_groups_link'}}{{/link-to}}
diff --git a/app/assets/javascripts/discourse/templates/components/edit-topic-timer-form.hbs b/app/assets/javascripts/discourse/templates/components/edit-topic-timer-form.hbs index 6fe586b226a..1fbfa3f9797 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-topic-timer-form.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-topic-timer-form.hbs @@ -1,39 +1,52 @@
- {{combo-box class="timer-type" allowInitialValueMutation=true content=timerTypes value=selection}} + {{combo-box + class="timer-type" + onChange=onChangeStatusType + content=timerTypes + value=selection + }}
{{#if showTimeOnly}} {{future-date-input - input=topicTimer.updateTime - label="topic.topic_status_update.when" - statusType=selection - includeWeekend=true - basedOnLastPost=topicTimer.based_on_last_post}} + input=(readonly topicTimer.updateTime) + label="topic.topic_status_update.when" + statusType=selection + includeWeekend=true + basedOnLastPost=topicTimer.based_on_last_post + onChangeInput=onChangeUpdateTime + }} {{else if publishToCategory}}
{{category-chooser value=topicTimer.category_id - excludeCategoryId=excludeCategoryId}} + excludeCategoryId=excludeCategoryId + onChange=(action (mut topicTimer.category_id)) + }}
{{future-date-input - input=topicTimer.updateTime - label="topic.topic_status_update.when" - statusType=selection - includeWeekend=true - categoryId=topicTimer.category_id - basedOnLastPost=topicTimer.based_on_last_post}} + input=(readonly topicTimer.updateTime) + label="topic.topic_status_update.when" + statusType=selection + includeWeekend=true + categoryId=topicTimer.category_id + basedOnLastPost=topicTimer.based_on_last_post + onChangeInput=onChangeUpdateTime + }} {{else if autoClose}} {{future-date-input - input=topicTimer.updateTime - label="topic.topic_status_update.when" - statusType=selection - includeWeekend=true - basedOnLastPost=topicTimer.based_on_last_post - lastPostedAt=model.last_posted_at}} + input=topicTimer.updateTime + label="topic.topic_status_update.when" + statusType=selection + includeWeekend=true + basedOnLastPost=topicTimer.based_on_last_post + lastPostedAt=model.last_posted_at + onChangeInput=onChangeUpdateTime + }} {{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/future-date-input.hbs b/app/assets/javascripts/discourse/templates/components/future-date-input.hbs index fc27997d5dc..c1571ad9c58 100644 --- a/app/assets/javascripts/discourse/templates/components/future-date-input.hbs +++ b/app/assets/javascripts/discourse/templates/components/future-date-input.hbs @@ -2,21 +2,29 @@
{{future-date-input-selector - minimumResultsForSearch=-1 - statusType=statusType - value=selection - input=input - includeDateTime=includeDateTime - includeWeekend=includeWeekend - includeFarFuture=includeFarFuture - includeMidFuture=includeMidFuture - clearable=clearable - none="topic.auto_update_input.none"}} + minimumResultsForSearch=-1 + statusType=statusType + value=(readonly selection) + input=(readonly input) + includeDateTime=includeDateTime + includeWeekend=includeWeekend + includeFarFuture=includeFarFuture + includeMidFuture=includeMidFuture + clearable=clearable + none="topic.auto_update_input.none" + onChangeInput=onChangeInput + onChange=(action (mut selection)) + }}
{{#if displayDateAndTimePicker}}
- {{d-icon "calendar-alt"}} {{date-picker-future value=date defaultDate=date}} + {{d-icon "calendar-alt"}} + {{date-picker-future + value=date + defaultDate=date + onSelect=(action (mut date)) + }}
@@ -44,11 +52,12 @@ {{#if showTopicStatusInfo}}
{{topic-timer-info - statusType=statusType - executeAt=executeAt - basedOnLastPost=basedOnLastPost - duration=duration - categoryId=categoryId}} + statusType=statusType + executeAt=executeAt + basedOnLastPost=basedOnLastPost + duration=duration + categoryId=categoryId + }}
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/group-navigation.hbs b/app/assets/javascripts/discourse/templates/components/group-navigation.hbs index 60fcbfda937..eb9c9140cb7 100644 --- a/app/assets/javascripts/discourse/templates/components/group-navigation.hbs +++ b/app/assets/javascripts/discourse/templates/components/group-navigation.hbs @@ -6,7 +6,10 @@ {{/link-to}} {{else}} - {{group-dropdown content=group.extras.visible_group_names value=group.name}} + {{group-dropdown + groups=group.extras.visible_group_names + value=group.name + }} {{/if}} {{#each tabs as |tab|}} diff --git a/app/assets/javascripts/discourse/templates/components/groups-form-interaction-fields.hbs b/app/assets/javascripts/discourse/templates/components/groups-form-interaction-fields.hbs index 959e2e12d13..9185adfdead 100644 --- a/app/assets/javascripts/discourse/templates/components/groups-form-interaction-fields.hbs +++ b/app/assets/javascripts/discourse/templates/components/groups-form-interaction-fields.hbs @@ -3,12 +3,15 @@ - {{combo-box name="alias" - valueAttribute="value" - value=model.visibility_level - content=visibilityLevelOptions - castInteger=true - class="groups-form-visibility-level"}} + {{combo-box + name="alias" + valueProperty="value" + value=model.visibility_level + content=visibilityLevelOptions + castInteger=true + class="groups-form-visibility-level" + onChange=(action (mut model.visibility_level)) + }}
{{i18n 'admin.groups.manage.interaction.visibility_levels.description'}} @@ -19,7 +22,7 @@ {{combo-box name="alias" - valueAttribute="value" + valueProperty="value" value=model.members_visibility_level content=visibilityLevelOptions castInteger=true @@ -35,21 +38,27 @@ - {{combo-box name="alias" - valueAttribute="value" - value=model.mentionable_level - content=aliasLevelOptions - class="groups-form-mentionable-level"}} + {{combo-box + name="alias" + valueProperty="value" + value=model.mentionable_level + content=aliasLevelOptions + class="groups-form-mentionable-level" + onChange=(action (mut model.mentionable_level)) + }}
- {{combo-box name="alias" - valueAttribute="value" - value=model.messageable_level - content=aliasLevelOptions - class="groups-form-messageable-level"}} + {{combo-box + name="alias" + valueProperty="value" + value=model.messageable_level + content=aliasLevelOptions + class="groups-form-messageable-level" + onChange=(action (mut model.messageable_level)) + }}
diff --git a/app/assets/javascripts/discourse/templates/components/groups-form-membership-fields.hbs b/app/assets/javascripts/discourse/templates/components/groups-form-membership-fields.hbs index 757c14c40a2..7d1e05cb58d 100644 --- a/app/assets/javascripts/discourse/templates/components/groups-form-membership-fields.hbs +++ b/app/assets/javascripts/discourse/templates/components/groups-form-membership-fields.hbs @@ -6,9 +6,12 @@ {{i18n 'admin.groups.manage.membership.automatic_membership_email_domains'}} - {{list-setting name="automatic_membership" - settingValue=model.emailDomains - class="group-form-automatic-membership-automatic"}} + {{list-setting + name="automatic_membership" + settingValue=model.emailDomains + class="group-form-automatic-membership-automatic" + onChange=(action "onChangeEmailDomainsSetting") + }}
diff --git a/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs b/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs index 05cc82a2c3e..1250e0a0e40 100644 --- a/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs +++ b/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs @@ -15,7 +15,10 @@
- {{search-advanced-category-chooser value=searchedTerms.category}} + {{search-advanced-category-chooser + value=searchedTerms.category.id + onChange=(action "onChangeCategory") + }}
@@ -47,7 +50,9 @@ allowCreate=false filterPlaceholder=null everyTag=true - unlimitedTagCount=true}} + unlimitedTagCount=true + onChange=(action (mut searchedTerms.tags)) + }}
@@ -68,13 +73,27 @@ {{/if}} - {{combo-box id="in" valueAttribute="value" content=inOptions value=searchedTerms.in none="user.locale.any"}} + {{combo-box + id="in" + valueProperty="value" + content=inOptions + value=searchedTerms.in + none="user.locale.any" + onChange=(action (mut searchedTerms.in)) + }}
- {{combo-box id="status" valueAttribute="value" content=statusOptions value=searchedTerms.status none="user.locale.any"}} + {{combo-box + id="status" + valueProperty="value" + content=statusOptions + value=searchedTerms.status + none="user.locale.any" + onChange=(action (mut searchedTerms.status)) + }}
@@ -83,8 +102,18 @@
- {{combo-box id="postTime" valueAttribute="value" content=postTimeOptions value=searchedTerms.time.when}} - {{date-picker value=searchedTerms.time.days id="search-post-date"}} + {{combo-box + id="postTime" + valueProperty="value" + content=postTimeOptions + value=searchedTerms.time.when + onChange=(action "onChangeWhenTime") + }} + {{date-input + date=searchedTerms.time.days + onChange=(action "onChangeWhenDate") + id="search-post-date" + }}
diff --git a/app/assets/javascripts/discourse/templates/components/shared-draft-controls.hbs b/app/assets/javascripts/discourse/templates/components/shared-draft-controls.hbs index 0f458144831..23b2d45ae28 100644 --- a/app/assets/javascripts/discourse/templates/components/shared-draft-controls.hbs +++ b/app/assets/javascripts/discourse/templates/components/shared-draft-controls.hbs @@ -8,7 +8,8 @@ {{category-chooser value=topic.destination_category_id - onChooseCategory=(action "updateDestinationCategory")}} + onChange=(action "updateDestinationCategory") + }}
diff --git a/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs b/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs index 32483ceb611..67f890abd2a 100644 --- a/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs +++ b/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs @@ -54,7 +54,10 @@
{{#if showNotificationsButton}} - {{topic-notifications-button notificationLevel=topic.details.notification_level topic=topic}} + {{topic-notifications-button + notificationLevel=topic.details.notification_level + topic=topic + }} {{/if}} {{plugin-outlet name="after-topic-footer-buttons" diff --git a/app/assets/javascripts/discourse/templates/components/user-fields/dropdown.hbs b/app/assets/javascripts/discourse/templates/components/user-fields/dropdown.hbs index 7544439e479..81edab075f8 100644 --- a/app/assets/javascripts/discourse/templates/components/user-fields/dropdown.hbs +++ b/app/assets/javascripts/discourse/templates/components/user-fields/dropdown.hbs @@ -1,6 +1,12 @@
- {{combo-box id=(concat 'user-' elementId) content=field.options value=value none=noneLabel}} + {{combo-box + id=(concat 'user-' elementId) + content=field.options + value=value + none=noneLabel + onChange=(action (mut value)) + }}
{{{field.description}}}
diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs index 4a5a1789462..0f11a312cd3 100644 --- a/app/assets/javascripts/discourse/templates/composer.hbs +++ b/app/assets/javascripts/discourse/templates/composer.hbs @@ -73,16 +73,28 @@ {{#if model.showCategoryChooser}}
{{category-chooser - fullWidthOnMobile=true value=model.categoryId - scopedCategoryId=scopedCategoryId + tabindex="3" + onChange=(action (mut model.categoryId)) isDisabled=disableCategoryChooser - tabindex="3"}} + options=(hash + scopedCategoryId=scopedCategoryId + ) + }} {{popup-input-tip validation=categoryValidation}}
{{/if}} {{#if canEditTags}} - {{mini-tag-chooser tags=model.tags tabindex="4" categoryId=model.categoryId minimum=model.minimumRequiredTags isDisabled=disableTagsChooser}} + {{mini-tag-chooser + value=model.tags + tabindex=4 + isDisabled=disableTagsChooser + onChange=(action (mut model.tags)) + options=(hash + categoryId=model.categoryId + minimum=model.minimumRequiredTags + ) + }} {{popup-input-tip validation=tagValidation}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/full-page-search.hbs b/app/assets/javascripts/discourse/templates/full-page-search.hbs index 9d5e739cab3..56082d42ce9 100644 --- a/app/assets/javascripts/discourse/templates/full-page-search.hbs +++ b/app/assets/javascripts/discourse/templates/full-page-search.hbs @@ -55,7 +55,12 @@ {{i18n "search.sort_by"}} - {{combo-box value=sortOrder content=sortOrders castInteger=true}} + {{combo-box + value=sortOrder + content=sortOrders + castInteger=true + onChange=(action (mut sortOrder)) + }} {{/if}} @@ -194,12 +199,18 @@ {{#if site.mobileView}} {{#if expanded}}
- {{search-advanced-options searchTerm=searchTerm isExpanded=expanded}} + {{search-advanced-options + searchTerm=searchTerm + isExpanded=expanded + }}
{{/if}} {{else}}
- {{search-advanced-options searchTerm=searchTerm isExpanded=true}} + {{search-advanced-options + searchTerm=searchTerm + isExpanded=true + }} {{d-button label="submit" diff --git a/app/assets/javascripts/discourse/templates/group-index.hbs b/app/assets/javascripts/discourse/templates/group-index.hbs index a29fc281b6f..01393c0dfa1 100644 --- a/app/assets/javascripts/discourse/templates/group-index.hbs +++ b/app/assets/javascripts/discourse/templates/group-index.hbs @@ -63,11 +63,13 @@ {{#if canManageGroup}} {{group-member-dropdown - removeMember=(action "removeMember") - makeOwner=(action "makeOwner") - removeOwner=(action "removeOwner") - member=m - group=model}} + removeMember=(action "removeMember") + makeOwner=(action "makeOwner") + removeOwner=(action "removeOwner") + member=m + group=model + onChange=(action "actOnGroup" m) + }} {{/if}} {{!-- group parameter is used by plugins --}} diff --git a/app/assets/javascripts/discourse/templates/groups/index.hbs b/app/assets/javascripts/discourse/templates/groups/index.hbs index 64893e5c326..21cbe557a91 100644 --- a/app/assets/javascripts/discourse/templates/groups/index.hbs +++ b/app/assets/javascripts/discourse/templates/groups/index.hbs @@ -12,12 +12,16 @@ placeholderKey="groups.index.all" class="groups-header-filters-name no-blur"}} - {{combo-box value=type - content=types - clearable=true - allowAutoSelectFirst=false - noneLabel="groups.index.filter" - class="groups-header-filters-type"}} + {{combo-box + value=type + content=types + class="groups-header-filters-type" + onChange=(action (mut type)) + options=(hash + clearable=true + none="groups.index.filter" + ) + }}
diff --git a/app/assets/javascripts/discourse/templates/mobile/group-index.hbs b/app/assets/javascripts/discourse/templates/mobile/group-index.hbs index 8bf3d5207c7..3101a84429f 100644 --- a/app/assets/javascripts/discourse/templates/mobile/group-index.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/group-index.hbs @@ -7,9 +7,7 @@
{{#if canManageGroup}} {{#if currentUser.admin}} - {{group-members-dropdown - showAddMembersModal=(route-action "showAddMembersModal") - showBulkAddModal=(route-action "showBulkAddModal")}} + {{group-members-dropdown onChange=(action "groupMembersDropdown")}} {{else}} {{d-button icon="plus" diff --git a/app/assets/javascripts/discourse/templates/mobile/users.hbs b/app/assets/javascripts/discourse/templates/mobile/users.hbs index 8cdd3fc7a1c..776409d6b52 100644 --- a/app/assets/javascripts/discourse/templates/mobile/users.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/users.hbs @@ -4,7 +4,7 @@ {{plugin-outlet name="users-top" connectorTagName='div' args=(hash model=model)}}
- {{period-chooser period=period}} + {{period-chooser period=period onChange=(action (mut period))}} {{text-field value=nameInput placeholderKey="directory.filter_name" class="filter-name no-blur"}}
diff --git a/app/assets/javascripts/discourse/templates/modal/bookmark.hbs b/app/assets/javascripts/discourse/templates/modal/bookmark.hbs index ff1800cdcd3..c975f699c84 100644 --- a/app/assets/javascripts/discourse/templates/modal/bookmark.hbs +++ b/app/assets/javascripts/discourse/templates/modal/bookmark.hbs @@ -24,17 +24,17 @@ {{#if userHasTimezoneSet}} {{#tap-tile-grid activeTile=selectedReminderType as |grid|}} {{#if usingMobileDevice}} - + {{/if}} {{#if showLaterToday}} - {{tap-tile icon="far-moon" text=laterTodayFormatted tileId=reminderTypes.LATER_TODAY activeTile=grid.activeTile onSelect=(action "selectReminderType")}} + {{tap-tile icon="far-moon" text=laterTodayFormatted tileId=reminderTypes.LATER_TODAY activeTile=grid.activeTile onChange=(action "selectReminderType")}} {{/if}} - {{tap-tile icon="briefcase" text=nextBusinessDayFormatted tileId=reminderTypes.NEXT_BUSINESS_DAY activeTile=grid.activeTile onSelect=(action "selectReminderType")}} - {{tap-tile icon="far-sun" text=tomorrowFormatted tileId=reminderTypes.TOMORROW activeTile=grid.activeTile onSelect=(action "selectReminderType")}} - {{tap-tile icon="far-clock" text=nextWeekFormatted tileId=reminderTypes.NEXT_WEEK activeTile=grid.activeTile onSelect=(action "selectReminderType")}} - {{tap-tile icon="far-calendar-plus" text=nextMonthFormatted tileId=reminderTypes.NEXT_MONTH activeTile=grid.activeTile onSelect=(action "selectReminderType")}} - + {{tap-tile icon="briefcase" text=nextBusinessDayFormatted tileId=reminderTypes.NEXT_BUSINESS_DAY activeTile=grid.activeTile onChange=(action "selectReminderType")}} + {{tap-tile icon="far-sun" text=tomorrowFormatted tileId=reminderTypes.TOMORROW activeTile=grid.activeTile onChange=(action "selectReminderType")}} + {{tap-tile icon="far-clock" text=nextWeekFormatted tileId=reminderTypes.NEXT_WEEK activeTile=grid.activeTile onChange=(action "selectReminderType")}} + {{tap-tile icon="far-calendar-plus" text=nextMonthFormatted tileId=reminderTypes.NEXT_MONTH activeTile=grid.activeTile onChange=(action "selectReminderType")}} + {{/tap-tile-grid}} {{else}}
{{{i18n "bookmarks.no_timezone" basePath=basePath }}}
diff --git a/app/assets/javascripts/discourse/templates/modal/bulk-change-category.hbs b/app/assets/javascripts/discourse/templates/modal/bulk-change-category.hbs index 8726b3ff001..005b266389a 100644 --- a/app/assets/javascripts/discourse/templates/modal/bulk-change-category.hbs +++ b/app/assets/javascripts/discourse/templates/modal/bulk-change-category.hbs @@ -1,6 +1,11 @@

{{i18n "topics.bulk.choose_new_category"}}

-

{{category-chooser value=newCategoryId}}

+

+ {{category-chooser + value=newCategoryId + onChange=(action (mut newCategoryId)) + }} +

{{#conditional-loading-spinner condition=loading}} {{d-button action=(action "changeCategory") label="topics.bulk.change_category"}} diff --git a/app/assets/javascripts/discourse/templates/modal/change-timestamp.hbs b/app/assets/javascripts/discourse/templates/modal/change-timestamp.hbs index f9e542574e2..d49178a6b49 100644 --- a/app/assets/javascripts/discourse/templates/modal/change-timestamp.hbs +++ b/app/assets/javascripts/discourse/templates/modal/change-timestamp.hbs @@ -11,7 +11,7 @@ {{date-picker-past value=(unbound date) containerId="date-container" - onSelect=(action (mut date))}} + onChange=(action (mut date))}} {{input type="time" value=time}} diff --git a/app/assets/javascripts/discourse/templates/modal/convert-to-public-topic.hbs b/app/assets/javascripts/discourse/templates/modal/convert-to-public-topic.hbs index b13804bb0b4..66d01ec2b42 100644 --- a/app/assets/javascripts/discourse/templates/modal/convert-to-public-topic.hbs +++ b/app/assets/javascripts/discourse/templates/modal/convert-to-public-topic.hbs @@ -3,7 +3,10 @@
{{i18n "topic.make_public.choose_category"}}
- {{category-chooser value=publicCategoryId}} + {{category-chooser + value=publicCategoryId + onChange=(action (mut publicCategoryId)) + }} {{/d-modal-body}} diff --git a/app/assets/javascripts/discourse/templates/modal/edit-topic-timer.hbs b/app/assets/javascripts/discourse/templates/modal/edit-topic-timer.hbs index 95fee49f5b3..71d74321139 100644 --- a/app/assets/javascripts/discourse/templates/modal/edit-topic-timer.hbs +++ b/app/assets/javascripts/discourse/templates/modal/edit-topic-timer.hbs @@ -15,7 +15,10 @@ topic=model topicTimer=topicTimer timerTypes=selections - updateTime=updateTime}} + updateTime=updateTime + onChangeStatusType=(action "onChangeStatusType") + onChangeUpdateTime=(action "onChangeUpdateTime") + }} {{/d-modal-body}} {{future-date-input label="user.user_notifications.ignore_duration_when" - input=ignoredUntil + input=(readonly ignoredUntil) includeWeekend=true includeDateTime=false includeMidFuture=true - includeFarFuture=false}} + includeFarFuture=false + onChangeInput=(action (mut ignoredUntil)) + }}

{{i18n "user.user_notifications.ignore_duration_note"}}

{{/d-modal-body}} diff --git a/app/assets/javascripts/discourse/templates/modal/ignore-duration.hbs b/app/assets/javascripts/discourse/templates/modal/ignore-duration.hbs index 79cd89aca7b..33db2479db7 100644 --- a/app/assets/javascripts/discourse/templates/modal/ignore-duration.hbs +++ b/app/assets/javascripts/discourse/templates/modal/ignore-duration.hbs @@ -5,7 +5,9 @@ includeWeekend=true includeDateTime=false includeMidFuture=true - includeFarFuture=false}} + includeFarFuture=false + onChangeInput=(action (mut ignoredUntil)) + }}

{{i18n "user.user_notifications.ignore_duration_note"}}

{{/d-modal-body}} diff --git a/app/assets/javascripts/discourse/templates/modal/move-to-topic.hbs b/app/assets/javascripts/discourse/templates/modal/move-to-topic.hbs index 0574ff843f1..893d9342460 100644 --- a/app/assets/javascripts/discourse/templates/modal/move-to-topic.hbs +++ b/app/assets/javascripts/discourse/templates/modal/move-to-topic.hbs @@ -78,7 +78,11 @@ {{text-field value=topicName placeholderKey="composer.title_placeholder" elementId='split-topic-name'}} - {{category-chooser value=categoryId class="small"}} + {{category-chooser + value=categoryId + class="small" + onChange=(action (mut categoryId)) + }} {{#if canAddTags}} {{tag-chooser tags=tags filterable=true categoryId=categoryId}} diff --git a/app/assets/javascripts/discourse/templates/preferences/account.hbs b/app/assets/javascripts/discourse/templates/preferences/account.hbs index 292af19e4d4..1e7ea80d959 100644 --- a/app/assets/javascripts/discourse/templates/preferences/account.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/account.hbs @@ -144,7 +144,9 @@ {{combo-box value=newTitleInput content=model.availableTitles - none="user.title.none"}} + none="user.title.none" + onChange=(action (mut newTitleInput)) + }}
{{/if}} diff --git a/app/assets/javascripts/discourse/templates/preferences/categories.hbs b/app/assets/javascripts/discourse/templates/preferences/categories.hbs index 671242a16a9..d9a2dbf913a 100644 --- a/app/assets/javascripts/discourse/templates/preferences/categories.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/categories.hbs @@ -6,7 +6,11 @@ {{#if canSee}} {{i18n 'user.tracked_topics_link'}} {{/if}} - {{category-selector categories=model.watchedCategories blacklist=selectedCategories}} + {{category-selector + categories=model.watchedCategories + blacklist=selectedCategories + onChange=(action (mut model.watchedCategories)) + }}
{{i18n 'user.watched_categories_instructions'}}
@@ -16,14 +20,22 @@ {{#if canSee}} {{i18n 'user.tracked_topics_link'}} {{/if}} - {{category-selector categories=model.trackedCategories blacklist=selectedCategories}} + {{category-selector + categories=model.trackedCategories + blacklist=selectedCategories + onChange=(action (mut model.trackedCategories)) + }}
{{i18n 'user.tracked_categories_instructions'}}
- {{category-selector categories=model.watchedFirstPostCategories blacklist=selectedCategories}} + {{category-selector + categories=model.watchedFirstPostCategories + blacklist=selectedCategories + onChange=(action (mut model.watchedFirstPostCategories)) + }}
{{i18n 'user.watched_first_post_categories_instructions'}}
@@ -33,7 +45,11 @@ {{#if canSee}} {{i18n 'user.tracked_topics_link'}} {{/if}} - {{category-selector categories=model.mutedCategories blacklist=selectedCategories}} + {{category-selector + categories=model.mutedCategories + blacklist=selectedCategories + onChange=(action (mut model.mutedCategories)) + }}
{{i18n (if hideMutedTags 'user.muted_categories_instructions' 'user.muted_categories_instructions_dont_hide')}}
{{/unless}} diff --git a/app/assets/javascripts/discourse/templates/preferences/emails.hbs b/app/assets/javascripts/discourse/templates/preferences/emails.hbs index 780a64dd2ce..568aa093a6b 100644 --- a/app/assets/javascripts/discourse/templates/preferences/emails.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/emails.hbs @@ -10,10 +10,13 @@
- {{combo-box valueAttribute="value" - content=emailLevelOptions - value=model.user_option.email_messages_level - id="user-email-messages-level"}} + {{combo-box + valueProperty="value" + content=emailLevelOptions + value=model.user_option.email_messages_level + id="user-email-messages-level" + onChange=(action (mut model.user_option.email_messages_level)) + }} {{#if emailMessagesLevelAway}}
{{emailFrequencyInstructions}}
{{/if}} @@ -21,10 +24,13 @@
- {{combo-box valueAttribute="value" - content=emailLevelOptions - value=model.user_option.email_level - id="user-email-level"}} + {{combo-box + valueProperty="value" + content=emailLevelOptions + value=model.user_option.email_level + id="user-email-level" + onChange=(action (mut model.user_option.email_level)) + }} {{#if emailLevelAway}}
{{emailFrequencyInstructions}}
{{/if}} @@ -32,7 +38,12 @@
- {{combo-box valueAttribute="value" content=previousRepliesOptions value=model.user_option.email_previous_replies}} + {{combo-box + valueProperty="value" + content=previousRepliesOptions + value=model.user_option.email_previous_replies + onChange=(action (mut model.user_option.email_previous_replies)) + }}
{{preference-checkbox labelKey="user.email_in_reply_to" checked=model.user_option.email_in_reply_to}} @@ -45,7 +56,13 @@ {{preference-checkbox labelKey="user.email_digests.title" disabled=model.user_option.mailing_list_mode checked=model.user_option.email_digests}} {{#if model.user_option.email_digests}}
- {{combo-box valueAttribute="value" filterable=true content=digestFrequencies value=model.user_option.digest_after_minutes}} + {{combo-box + valueProperty="value" + filterable=true + content=digestFrequencies + value=model.user_option.digest_after_minutes + onChange=(action (mut model.user_option.digest_after_minutes)) + }}
{{preference-checkbox labelKey="user.include_tl0_in_digests" disabled=model.user_option.mailing_list_mode checked=model.user_option.include_tl0_in_digests}} {{/if}} @@ -59,7 +76,12 @@
{{{i18n 'user.mailing_list_mode.instructions'}}}
{{#if model.user_option.mailing_list_mode}}
- {{combo-box valueAttribute="value" content=mailingListModeOptions value=model.user_option.mailing_list_mode_frequency}} + {{combo-box + valueProperty="value" + content=mailingListModeOptions + value=model.user_option.mailing_list_mode_frequency + onChange=(action (mut model.user_option.mailing_list_mode_frequency)) + }}
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/preferences/interface.hbs b/app/assets/javascripts/discourse/templates/preferences/interface.hbs index 24d245b8c0b..7549831fb40 100644 --- a/app/assets/javascripts/discourse/templates/preferences/interface.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/interface.hbs @@ -2,7 +2,11 @@
- {{combo-box content=userSelectableThemes value=themeId}} + {{combo-box + content=userSelectableThemes + value=themeId + onChange=(action (mut themeId)) + }}
{{#if showThemeSetDefault}}
@@ -15,7 +19,12 @@
- {{combo-box valueAttribute="value" content=textSizes value=textSize onSelect=(action "selectTextSize")}} + {{combo-box + valueProperty="value" + content=textSizes + value=textSize + onChange=(action "selectTextSize") + }}
{{#if showTextSetDefault}}
@@ -28,7 +37,16 @@
- {{combo-box filterable=true valueAttribute="value" content=availableLocales value=model.locale none="user.locale.default"}} + {{combo-box + valueProperty="value" + content=availableLocales + value=model.locale + onChange=(action (mut model.locale)) + options=(hash + filterable=true + none="user.locale.default" + ) + }}
{{i18n 'user.locale.instructions'}} @@ -40,7 +58,12 @@
- {{combo-box content=userSelectableHome valueAttribute="value" value=model.user_option.homepage_id}} + {{combo-box + content=userSelectableHome + valueProperty="value" + value=homepageId + onChange=(action (mut model.user_option.homepage_id)) + }}
@@ -60,10 +83,13 @@ {{preference-checkbox labelKey="user.dynamic_favicon" checked=model.user_option.dynamic_favicon class="pref-dynamic-favicon"}}
- {{combo-box valueAttribute="value" - content=titleCountModes - value=model.user_option.title_count_mode - id="user-title-count-mode"}} + {{combo-box + valueProperty="value" + content=titleCountModes + value=model.user_option.title_count_mode + id="user-title-count-mode" + onChange=(action (mut model.user_option.title_count_mode)) + }}
diff --git a/app/assets/javascripts/discourse/templates/preferences/notifications.hbs b/app/assets/javascripts/discourse/templates/preferences/notifications.hbs index e97b28d901b..089c5a01621 100644 --- a/app/assets/javascripts/discourse/templates/preferences/notifications.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/notifications.hbs @@ -3,22 +3,43 @@
- {{combo-box class="duration" valueAttribute="value" content=considerNewTopicOptions value=model.user_option.new_topic_duration_minutes}} + {{combo-box + class="duration" + valueProperty="value" + content=considerNewTopicOptions + value=model.user_option.new_topic_duration_minutes + onChange=(action (mut model.user_option.new_topic_duration_minutes)) + }}
- {{combo-box valueAttribute="value" content=autoTrackDurations value=model.user_option.auto_track_topics_after_msecs}} + {{combo-box + valueProperty="value" + content=autoTrackDurations + value=model.user_option.auto_track_topics_after_msecs + onChange=(action (mut model.user_option.auto_track_topics_after_msecs)) + }}
- {{combo-box valueAttribute="value" content=notificationLevelsForReplying value=model.user_option.notification_level_when_replying}} + {{combo-box + valueProperty="value" + content=notificationLevelsForReplying + value=model.user_option.notification_level_when_replying + onChange=(action (mut model.user_option.notification_level_when_replying)) + }}
- {{combo-box valueAttribute="value" content=likeNotificationFrequencies value=model.user_option.like_notification_frequency}} + {{combo-box + valueProperty="value" + content=likeNotificationFrequencies + value=model.user_option.like_notification_frequency + onChange=(action (mut model.user_option.like_notification_frequency)) + }}
diff --git a/app/assets/javascripts/discourse/templates/preferences/profile.hbs b/app/assets/javascripts/discourse/templates/preferences/profile.hbs index 47dbc7a143b..8db3932ba3d 100644 --- a/app/assets/javascripts/discourse/templates/preferences/profile.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/profile.hbs @@ -9,7 +9,11 @@
- {{timezone-input value=model.user_option.timezone onSelect=(action (mut model.user_option.timezone)) class="input-xxlarge"}} + {{timezone-input + value=model.user_option.timezone + onChange=(action (mut model.user_option.timezone)) + class="input-xxlarge" + }}
diff --git a/app/assets/javascripts/discourse/templates/review-index.hbs b/app/assets/javascripts/discourse/templates/review-index.hbs index 277c7723f89..54f138f217f 100644 --- a/app/assets/javascripts/discourse/templates/review-index.hbs +++ b/app/assets/javascripts/discourse/templates/review-index.hbs @@ -19,7 +19,11 @@
- {{combo-box value=filterStatus content=statuses}} + {{combo-box + value=filterStatus + content=statuses + onChange=(action (mut filterStatus)) + }}
{{#if filtersExpanded}} @@ -28,17 +32,30 @@
- {{combo-box value=filterType content=allTypes none="review.filters.type.all"}} + {{combo-box + value=filterType + content=allTypes + none="review.filters.type.all" + onChange=(action (mut filterType)) + }}
- {{combo-box value=filterPriority content=priorities}} + {{combo-box + value=filterPriority + content=priorities + onChange=(action (mut filterPriority)) + }}
- {{category-chooser none="review.filters.all_categories" value=filterCategoryId}} + {{category-chooser + none="review.filters.all_categories" + value=filterCategoryId + onChange=(action (mut filterCategoryId)) + }}
@@ -65,7 +82,11 @@
{{i18n "review.order_by"}} - {{combo-box value=filterSortOrder content=sortOrders}} + {{combo-box + value=filterSortOrder + content=sortOrders + onChange=(action (mut filterSortOrder)) + }}
{{/if}} @@ -73,8 +94,8 @@ {{d-button icon="sync" label="review.filters.refresh" - - class="btn-primary refresh" action=(action "refresh")}} + class="btn-primary refresh" + action=(action "refresh")}} {{#if site.mobileView}} {{d-button diff --git a/app/assets/javascripts/discourse/templates/review-settings.hbs b/app/assets/javascripts/discourse/templates/review-settings.hbs index 2669a442d89..f798ca59c64 100644 --- a/app/assets/javascripts/discourse/templates/review-settings.hbs +++ b/app/assets/javascripts/discourse/templates/review-settings.hbs @@ -5,7 +5,11 @@
{{rst.title}}
- {{combo-box value=rst.reviewable_priority content=settings.reviewable_priorities}} + {{combo-box + value=rst.reviewable_priority + content=settings.reviewable_priorities + onChange=(action (mut rst.reviewable_priority)) + }}
{{/each}} diff --git a/app/assets/javascripts/discourse/templates/tags/show.hbs b/app/assets/javascripts/discourse/templates/tags/show.hbs index 09a6722ce83..923dfb991f5 100644 --- a/app/assets/javascripts/discourse/templates/tags/show.hbs +++ b/app/assets/javascripts/discourse/templates/tags/show.hbs @@ -25,11 +25,12 @@

{{/if}} - {{#if tagNotification}} {{#unless additionalTags}} - {{tag-notifications-button action=(action "changeTagNotification") - notificationLevel=tagNotification.notification_level}} + {{tag-notifications-button + onChange=(action "changeTagNotificationLevel") + value=tagNotification.notification_level + }} {{/unless}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index bcc3184d82f..1bd7d80f974 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -24,16 +24,21 @@ {{#if showCategoryChooser}} {{category-chooser class="small" - value=(unbound buffered.category_id) - onSelectAny=(action "topicCategoryChanged")}} + value=buffered.category_id + onChange=(action "topicCategoryChanged") + }} {{/if}} {{#if canEditTags}} {{mini-tag-chooser - filterable=true - tags=(unbound buffered.tags) - categoryId=(unbound buffered.category_id) - onChangeTags=(action "topicTagsChanged")}} + value=buffered.tags + onChange=(action "topicTagsChanged") + options=(hash + filterable=true + categoryId=buffered.category_id + filterable=true + ) + }} {{/if}} {{plugin-outlet name="edit-topic" args=(hash model=model buffered=buffered)}} diff --git a/app/assets/javascripts/discourse/templates/user.hbs b/app/assets/javascripts/discourse/templates/user.hbs index 27409f093b3..4261b2ccef5 100644 --- a/app/assets/javascripts/discourse/templates/user.hbs +++ b/app/assets/javascripts/discourse/templates/user.hbs @@ -52,7 +52,11 @@ {{#if canMuteOrIgnoreUser}}
  • - {{user-notifications-dropdown user=model updateNotificationLevel=(action "updateNotificationLevel")}} + {{user-notifications-dropdown + user=model + value=userNotificationLevel + updateNotificationLevel=(action "updateNotificationLevel") + }}
  • {{/if}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/user/messages.hbs b/app/assets/javascripts/discourse/templates/user/messages.hbs index 158e1fac299..3dc34f67a15 100644 --- a/app/assets/javascripts/discourse/templates/user/messages.hbs +++ b/app/assets/javascripts/discourse/templates/user/messages.hbs @@ -101,7 +101,10 @@ {{/if}} {{#if isGroup}} - {{group-notifications-button value=group.group_user.notification_level group=group user=model}} + {{group-notifications-button + value=group.group_user.notification_level + onChange=(action "changeGroupNotificationLevel") + }} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/users.hbs b/app/assets/javascripts/discourse/templates/users.hbs index f384e8a580c..6a44c7ce1b0 100644 --- a/app/assets/javascripts/discourse/templates/users.hbs +++ b/app/assets/javascripts/discourse/templates/users.hbs @@ -4,7 +4,7 @@
    {{plugin-outlet name="users-top" connectorTagName='div' args=(hash model=model)}}
    - {{period-chooser period=period}} + {{period-chooser period=period onChange=(action (mut period))}} {{text-field value=nameInput placeholderKey="directory.filter_name" class="filter-name no-blur"}}
    diff --git a/app/assets/javascripts/discourse/widgets/component-connector.js.es6 b/app/assets/javascripts/discourse/widgets/component-connector.js.es6 index db53cc314f2..6221d61f97d 100644 --- a/app/assets/javascripts/discourse/widgets/component-connector.js.es6 +++ b/app/assets/javascripts/discourse/widgets/component-connector.js.es6 @@ -23,16 +23,16 @@ export default class ComponentConnector { .lookupFactory(`component:${componentName}`) .create(opts); - // component connector is not triggering didReceiveAttrs - // so we make sure to compute the component attrs - if (view.selectKitComponent) { - view._compute(); - } - if (setOwner) { setOwner(view, getOwner(mounted)); } + // component connector is not triggering didReceiveAttrs + // we force it for selectKit components + if (view.selectKit) { + view.didReceiveAttrs(); + } + mounted._connected.push(view); view.renderer.appendTo(view, $elem[0]); }); diff --git a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 index 1455e5cc098..7342e8f9557 100644 --- a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 +++ b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 @@ -386,7 +386,15 @@ createWidget("timeline-footer-controls", { { value: notificationLevel, topic, - showFullTitle: false + options: { + showFullTitle: false, + placement: "bottom-end" + }, + onChange: newNotificationLevel => { + if (newNotificationLevel !== notificationLevel) { + topic.details.updateNotifications(newNotificationLevel); + } + } }, ["value"] ) diff --git a/app/assets/javascripts/select-kit/components/admin-group-selector.js.es6 b/app/assets/javascripts/select-kit/components/admin-group-selector.js.es6 index 2d99404c0af..1b02a45d280 100644 --- a/app/assets/javascripts/select-kit/components/admin-group-selector.js.es6 +++ b/app/assets/javascripts/select-kit/components/admin-group-selector.js.es6 @@ -1,34 +1,9 @@ import MultiSelectComponent from "select-kit/components/multi-select"; -import discourseComputed from "discourse-common/utils/decorators"; - -const { makeArray } = Ember; export default MultiSelectComponent.extend({ pluginApiIdentifiers: ["admin-group-selector"], - classNames: "admin-group-selector", - selected: null, - available: null, - allowAny: false, - buffer: null, - - @discourseComputed("buffer") - values(buffer) { - return buffer === null - ? makeArray(this.selected).map(s => this.valueForContentItem(s)) - : buffer; - }, - - computeContent() { - return makeArray(this.available); - }, - - computeContentItem(contentItem, name) { - let computedContentItem = this._super(contentItem, name); - computedContentItem.locked = contentItem.automatic; - return computedContentItem; - }, - - mutateValues(values) { - this.set("buffer", values); + classNames: ["admin-group-selector"], + selectKitOptions: { + allowAny: false } }); diff --git a/app/assets/javascripts/select-kit/components/categories-admin-dropdown.js.es6 b/app/assets/javascripts/select-kit/components/categories-admin-dropdown.js.es6 index 5b1dc108e07..f56b2705239 100644 --- a/app/assets/javascripts/select-kit/components/categories-admin-dropdown.js.es6 +++ b/app/assets/javascripts/select-kit/components/categories-admin-dropdown.js.es6 @@ -1,20 +1,20 @@ import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; +import { computed } from "@ember/object"; +import { setting } from "discourse/lib/computed"; export default DropdownSelectBoxComponent.extend({ pluginApiIdentifiers: ["categories-admin-dropdown"], - classNames: "categories-admin-dropdown", - showFullTitle: false, - allowInitialValueMutation: false, + classNames: ["categories-admin-dropdown"], + fixedCateoryPositions: setting("fixed_category_positions"), - init() { - this._super(...arguments); - - this.headerIcon = ["bars"]; + selectKitOptions: { + icon: "bars", + showFullTitle: false, + autoFilterable: false, + filterable: false }, - autoHighlight() {}, - - computeContent() { + content: computed(function() { const items = [ { id: "create", @@ -24,8 +24,7 @@ export default DropdownSelectBoxComponent.extend({ } ]; - const includeReorder = this.get("siteSettings.fixed_category_positions"); - if (includeReorder) { + if (this.fixedCateoryPositions) { items.push({ id: "reorder", name: I18n.t("categories.reorder.title"), @@ -35,9 +34,5 @@ export default DropdownSelectBoxComponent.extend({ } return items; - }, - - mutateValue(value) { - this.get(value)(); - } + }) }); diff --git a/app/assets/javascripts/select-kit/components/category-chooser.js.es6 b/app/assets/javascripts/select-kit/components/category-chooser.js.es6 index 07872a02ce5..9b475bc5749 100644 --- a/app/assets/javascripts/select-kit/components/category-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/category-chooser.js.es6 @@ -1,145 +1,150 @@ import ComboBoxComponent from "select-kit/components/combo-box"; -import discourseComputed from "discourse-common/utils/decorators"; import PermissionType from "discourse/models/permission-type"; import Category from "discourse/models/category"; import { categoryBadgeHTML } from "discourse/helpers/category-link"; -const { get, isPresent, isEmpty } = Ember; +import { computed, set } from "@ember/object"; +import { isNone } from "@ember/utils"; +import { setting } from "discourse/lib/computed"; export default ComboBoxComponent.extend({ pluginApiIdentifiers: ["category-chooser"], - classNames: "category-chooser", - filterable: true, - castInteger: true, - allowUncategorized: false, - rowComponent: "category-row", - noneRowComponent: "none-category-row", - allowSubCategories: true, - permissionType: PermissionType.FULL, + classNames: ["category-chooser"], + allowUncategorizedTopics: setting("allow_uncategorized_topics"), + fixedCategoryPositionsOnCreate: setting("fixed_category_positions_on_create"), - init() { - this._super(...arguments); - - this.rowComponentOptions.setProperties({ - allowUncategorized: this.allowUncategorized - }); + selectKitOptions: { + filterable: true, + allowUncategorized: false, + allowSubCategories: true, + permissionType: PermissionType.FULL, + excludeCategoryId: null, + scopedCategoryId: null }, - filterComputedContent(computedContent, computedValue, filter) { - if (isEmpty(filter)) { - return computedContent; - } + modifyComponentForRow() { + return "category-row"; + }, - if (this.scopedCategoryId) { - computedContent = this.categoriesByScope(this.scopedCategoryId).map(c => - this.computeContentItem(c) + modifyNoSelection() { + if (!isNone(this.selectKit.options.none)) { + const none = this.selectKit.options.none; + const isString = typeof none === "string"; + return this.defaultItem( + null, + I18n.t( + isString ? this.selectKit.options.none : "category.none" + ).htmlSafe() ); - } - - const _matchFunction = (f, text) => { - return this._normalize(text).indexOf(f) > -1; - }; - - return computedContent.filter(c => { - const category = Category.findById(get(c, "value")); - const text = get(c, "name"); - if (category && category.get("parentCategory")) { - const categoryName = category.get("parentCategory.name"); - return ( - _matchFunction(filter, text) || _matchFunction(filter, categoryName) - ); - } else { - return _matchFunction(filter, text); - } - }); - }, - - @discourseComputed("rootNone", "rootNoneLabel") - none(rootNone, rootNoneLabel) { - if (isPresent(rootNone)) { - return rootNoneLabel || "category.none"; } else if ( - this.siteSettings.allow_uncategorized_topics || - this.allowUncategorized + this.allowUncategorizedTopics || + this.selectKit.options.allowUncategorized ) { return Category.findUncategorized(); } else { - return "category.choose"; + return this.defaultItem(null, I18n.t("category.choose").htmlSafe()); } }, - computeHeaderContent() { - let content = this._super(...arguments); + modifySelection(content) { + if (this.selectKit.hasSelection) { + const category = Category.findById(this.value); - if (this.hasSelection) { - const category = Category.findById(content.value); - content.label = categoryBadgeHTML(category, { - link: false, - hideParent: !!category.parent_category_id, - allowUncategorized: true, - recursive: true - }).htmlSafe(); + set( + content, + "label", + categoryBadgeHTML(category, { + link: false, + hideParent: !!category.parent_category_id, + allowUncategorized: true, + recursive: true + }).htmlSafe() + ); } return content; }, - didSelect(computedContentItem) { - if (this.attrs.onChooseCategory) { - this.attrs.onChooseCategory(computedContentItem.originalContent); + search(filter) { + if (filter) { + let content = this.content; + + if (this.selectKit.options.scopedCategoryId) { + content = this.categoriesByScope( + this.selectKit.options.scopedCategoryId + ); + } + + return content.filter(item => { + const category = Category.findById(this.getValue(item)); + const categoryName = this.getName(item); + + if (category && category.parentCategory) { + const parentCategoryName = this.getName(category.parentCategory); + return ( + this._matchCategory(filter, categoryName) || + this._matchCategory(filter, parentCategoryName) + ); + } else { + return this._matchCategory(filter, categoryName); + } + }); + } else { + return this.content; } }, - didClearSelection() { - if (this.attrs.onChooseCategory) { - this.attrs.onChooseCategory(null); - } - }, - - computeContent() { - return this.categoriesByScope(this.scopedCategoryId); - }, + content: computed("selectKit.options.scopedCategoryId", function() { + return this.categoriesByScope(this.selectKit.options.scopedCategoryId); + }), categoriesByScope(scopedCategoryId = null) { - const categories = Discourse.SiteSettings.fixed_category_positions_on_create + const categories = this.fixedCategoryPositionsOnCreate ? Category.list() : Category.listByActivity(); if (scopedCategoryId) { const scopedCat = Category.findById(scopedCategoryId); - scopedCategoryId = - scopedCat.get("parent_category_id") || scopedCat.get("id"); + scopedCategoryId = scopedCat.parent_category_id || scopedCat.id; } - const excludeCategoryId = this.excludeCategoryId; + const excludeCategoryId = this.selectKit.options.excludeCategoryId; - return categories.filter(c => { - const categoryId = this.valueForContentItem(c); + return categories.filter(category => { + const categoryId = this.getValue(category); if ( scopedCategoryId && categoryId !== scopedCategoryId && - get(c, "parent_category_id") !== scopedCategoryId + category.parent_category_id !== scopedCategoryId ) { return false; } - if (this.allowSubCategories === false && c.get("parentCategory")) { + if ( + this.selectKit.options.allowSubCategories === false && + category.parentCategory + ) { return false; } if ( - (this.allowUncategorized === false && - get(c, "isUncategorizedCategory")) || + (this.selectKit.options.allowUncategorized === false && + category.isUncategorizedCategory) || excludeCategoryId === categoryId ) { return false; } - if (this.permissionType) { - return this.permissionType === get(c, "permission"); + const permissionType = this.selectKit.options.permissionType; + if (permissionType) { + return permissionType === category.permission; } return true; }); + }, + + _matchCategory(filter, categoryName) { + return this._normalize(categoryName).indexOf(filter) > -1; } }); diff --git a/app/assets/javascripts/select-kit/components/category-drop.js.es6 b/app/assets/javascripts/select-kit/components/category-drop.js.es6 index f2ad904d3e8..b5dcb7162a9 100644 --- a/app/assets/javascripts/select-kit/components/category-drop.js.es6 +++ b/app/assets/javascripts/select-kit/components/category-drop.js.es6 @@ -1,180 +1,145 @@ -import { alias, not } from "@ember/object/computed"; +import { readOnly } from "@ember/object/computed"; +import { computed } from "@ember/object"; import ComboBoxComponent from "select-kit/components/combo-box"; import DiscourseURL from "discourse/lib/url"; -import discourseComputed from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; import { categoryBadgeHTML } from "discourse/helpers/category-link"; -import Site from "discourse/models/site"; -const { isEmpty } = Ember; +export const NO_CATEGORIES_ID = "no-categories"; +export const ALL_CATEGORIES_ID = "all-categories"; export default ComboBoxComponent.extend({ pluginApiIdentifiers: ["category-drop"], classNameBindings: ["categoryStyle"], - classNames: "category-drop", - verticalOffset: 3, - content: alias("categoriesWithShortcuts"), - rowComponent: "category-row", - headerComponent: "category-drop/category-drop-header", - allowAutoSelectFirst: false, + classNames: ["category-drop"], + value: readOnly("category.id"), + content: readOnly("categoriesWithShortcuts.[]"), tagName: "li", - categoryStyle: alias("siteSettings.category_style"), + categoryStyle: readOnly("siteSettings.category_style"), noCategoriesLabel: I18n.t("categories.no_subcategory"), - fullWidthOnMobile: true, - caretDownIcon: "caret-right", - caretUpIcon: "caret-down", - subCategory: false, - isAsync: not("subCategory"), - @discourseComputed( - "categories", - "hasSelection", - "subCategory", - "noSubcategories" - ) - categoriesWithShortcuts( - categories, - hasSelection, - subCategory, - noSubcategories - ) { - const shortcuts = []; - - if (hasSelection || (noSubcategories && subCategory)) { - shortcuts.push({ - name: this.allCategoriesLabel, - __sk_row_type: "noopRow", - id: "all-categories" - }); - } - - if (subCategory && (hasSelection || !noSubcategories)) { - shortcuts.push({ - name: this.noCategoriesLabel, - __sk_row_type: "noopRow", - id: "no-categories" - }); - } - - return shortcuts.concat(categories); + selectKitOptions: { + filterable: true, + none: "category.all", + caretDownIcon: "caret-right", + caretUpIcon: "caret-down", + fullWidthOnMobile: true, + noSubcategories: false, + subCategory: false, + clearable: false, + hideParentCategory: "hideParentCategory", + allowUncategorized: true, + countSubcategories: false, + autoInsertNoneItem: false, + displayCategoryDescription: "displayCategoryDescription", + headerComponent: "category-drop/category-drop-header" }, - init() { - this._super(...arguments); - - this.rowComponentOptions.setProperties({ - hideParentCategory: this.subCategory, - allowUncategorized: true, - countSubcategories: this.countSubcategories, - displayCategoryDescription: !( - this.currentUser && - (this.currentUser.get("staff") || this.currentUser.trust_level > 0) - ) - }); + modifyComponentForRow() { + return "category-row"; }, - didReceiveAttrs() { - if (!this.categories) this.set("categories", []); - this.forceValue(this.get("category.id")); - }, - - @discourseComputed("content") - filterable(content) { - const contentLength = (content && content.length) || 0; - return ( - contentLength >= 15 || - (this.isAsync && contentLength < Category.list().length) + displayCategoryDescription: computed(function() { + return !( + this.get("currentUser.staff") || this.get("currentUser.trust_level") > 0 ); + }), + + hideParentCategory: computed(function() { + return this.options.subCategory || false; + }), + + categoriesWithShortcuts: computed( + "categories.[]", + "value", + "selectKit.options.{subCategory,noSubcategories}", + function() { + const shortcuts = []; + + if ( + this.value || + (this.selectKit.options.noSubcategories && + this.selectKit.options.subCategory) + ) { + shortcuts.push({ + id: ALL_CATEGORIES_ID, + name: this.allCategoriesLabel + }); + } + + if ( + this.selectKit.options.subCategory && + (this.value || !this.selectKit.options.noSubcategories) + ) { + shortcuts.push({ + id: NO_CATEGORIES_ID, + name: this.noCategoriesLabel + }); + } + + const results = this._filterUncategorized(this.categories || []); + return shortcuts.concat(results); + } + ), + + modifyNoSelection() { + if (this.selectKit.options.noSubcategories) { + return this.defaultItem(NO_CATEGORIES_ID, this.noCategoriesLabel); + } else { + return this.defaultItem(ALL_CATEGORIES_ID, this.allCategoriesLabel); + } }, - computeHeaderContent() { - let content = this._super(...arguments); - - if (this.hasSelection) { - const category = Category.findById(content.value); + modifySelection(content) { + if (this.value) { + const category = Category.findById(this.value); content.title = category.title; content.label = categoryBadgeHTML(category, { link: false, - allowUncategorized: true, + allowUncategorized: this.selectKit.options.allowUncategorized, hideParent: true }).htmlSafe(); - } else { - if (this.noSubcategories) { - content.label = `${this.get( - "noCategoriesLabel" - )}`; - content.title = this.noCategoriesLabel; - } else { - content.label = `${this.get( - "allCategoriesLabel" - )}`; - content.title = this.allCategoriesLabel; - } } return content; }, - @discourseComputed("parentCategory.name", "subCategory") - allCategoriesLabel(categoryName, subCategory) { - if (subCategory) { - return I18n.t("categories.all_subcategories", { categoryName }); - } - return I18n.t("categories.all"); - }, + parentCategoryName: readOnly("selectKit.options.parentCategory.name"), - @discourseComputed("parentCategory.url", "subCategory") - allCategoriesUrl(parentCategoryUrl, subCategory) { - return Discourse.getURL(subCategory ? parentCategoryUrl || "/" : "/"); - }, + parentCategoryUrl: readOnly("selectKit.options.parentCategory.url"), - @discourseComputed("parentCategory.url") - noCategoriesUrl(parentCategoryUrl) { - return Discourse.getURL(`${parentCategoryUrl}/none`); - }, - - actions: { - onSelect(categoryId) { - let categoryURL; - - if (categoryId === "all-categories") { - categoryURL = Discourse.getURL(this.allCategoriesUrl); - } else if (categoryId === "no-categories") { - categoryURL = Discourse.getURL(this.noCategoriesUrl); - } else { - const category = Category.findById(parseInt(categoryId, 10)); - const slug = Category.slugFor(category); - categoryURL = Discourse.getURL(`/c/${slug}/${categoryId}`); - } - - DiscourseURL.routeTo(categoryURL); - }, - - onExpand() { - if (this.isAsync && isEmpty(this.asyncContent)) { - this.set("asyncContent", this.content); - } - }, - - onFilter(filter) { - if (!this.isAsync) { - return; - } - - if (isEmpty(filter)) { - this.set("asyncContent", this.content); - return; - } - - let results = Category.search(filter); - - if (!this.siteSettings.allow_uncategorized_topics) { - results = results.filter(result => { - return result.id !== Site.currentProp("uncategorized_category_id"); + allCategoriesLabel: computed( + "parentCategoryName", + "selectKit.options.subCategory", + function() { + if (this.selectKit.options.subCategory) { + return I18n.t("categories.all_subcategories", { + categoryName: this.parentCategoryName }); } - results = results.sort((a, b) => { + return I18n.t("categories.all"); + } + ), + + allCategoriesUrl: computed( + "parentCategoryUrl", + "selectKit.options.subCategory", + function() { + return Discourse.getURL( + this.selectKit.options.subCategory ? this.parentCategoryUrl || "/" : "/" + ); + } + ), + + noCategoriesUrl: computed("parentCategoryUrl", function() { + return Discourse.getURL(`${this.parentCategoryUrl}/none`); + }), + + search(filter) { + if (filter) { + let results = Discourse.Category.search(filter); + results = this._filterUncategorized(results).sort((a, b) => { if (a.parent_category_id && !b.parent_category_id) { return 1; } else if (!a.parent_category_id && b.parent_category_id) { @@ -183,9 +148,43 @@ export default ComboBoxComponent.extend({ return 0; } }); - - this.set("asyncContent", results); - this.autoHighlight(); + return results; + } else { + return this._filterUncategorized(this.content); } + }, + + actions: { + onChange(value) { + let categoryURL; + + if (value === ALL_CATEGORIES_ID) { + categoryURL = this.allCategoriesUrl; + } else if (value === NO_CATEGORIES_ID) { + categoryURL = this.noCategoriesUrl; + } else { + const categoryId = parseInt(value, 10); + const category = Category.findById(categoryId); + const slug = Discourse.Category.slugFor(category); + categoryURL = `/c/${slug}`; + } + + DiscourseURL.routeToUrl(categoryURL); + + return false; + } + }, + + _filterUncategorized(content) { + if ( + !this.siteSettings.allow_uncategorized_topics || + !this.selectKit.options.allowUncategorized + ) { + content = content.filter( + c => c.id !== this.site.uncategorized_category_id + ); + } + + return content; } }); diff --git a/app/assets/javascripts/select-kit/components/category-drop/category-drop-header.js.es6 b/app/assets/javascripts/select-kit/components/category-drop/category-drop-header.js.es6 index c84b7189d0d..7f641fb14fd 100644 --- a/app/assets/javascripts/select-kit/components/category-drop/category-drop-header.js.es6 +++ b/app/assets/javascripts/select-kit/components/category-drop/category-drop-header.js.es6 @@ -1,40 +1,30 @@ -import { alias } from "@ember/object/computed"; -import { isEmpty } from "@ember/utils"; +import { readOnly } from "@ember/object/computed"; +import { schedule } from "@ember/runloop"; import ComboBoxSelectBoxHeaderComponent from "select-kit/components/combo-box/combo-box-header"; import discourseComputed from "discourse-common/utils/decorators"; -import Category from "discourse/models/category"; export default ComboBoxSelectBoxHeaderComponent.extend({ layoutName: "select-kit/templates/components/category-drop/category-drop-header", - classNames: "category-drop-header", - + classNames: ["category-drop-header"], classNameBindings: ["categoryStyleClass"], - categoryStyleClass: alias("site.category_style"), + categoryStyleClass: readOnly("site.category_style"), - @discourseComputed("computedContent.value", "computedContent.name") - category(value, name) { - if (isEmpty(value)) { - const uncat = Category.findUncategorized(); - if (uncat && uncat.get("name") === name) { - return uncat; - } - } else { - return Category.findById(parseInt(value, 10)); - } - }, - - @discourseComputed("category.color") + @discourseComputed("selectedContent.color") categoryBackgroundColor(categoryColor) { return categoryColor || "#e9e9e9"; }, - @discourseComputed("category.text_color") + @discourseComputed("selectedContent.text_color") categoryTextColor(categoryTextColor) { return categoryTextColor || "#333"; }, - @discourseComputed("category", "categoryBackgroundColor", "categoryTextColor") + @discourseComputed( + "selectedContent", + "categoryBackgroundColor", + "categoryTextColor" + ) categoryStyle(category, categoryBackgroundColor, categoryTextColor) { const categoryStyle = this.siteSettings.category_style; @@ -56,12 +46,16 @@ export default ComboBoxSelectBoxHeaderComponent.extend({ } }, - didRender() { + didInsertElement() { this._super(...arguments); - this.element.setAttribute("style", this.categoryStyle); - this.element - .querySelector(".caret-icon") - .setAttribute("style", this.categoryStyle); + schedule("afterRender", () => { + if (this.categoryStyle) { + this.element.setAttribute("style", this.categoryStyle); + this.element + .querySelector(".caret-icon") + .setAttribute("style", this.categoryStyle); + } + }); } }); diff --git a/app/assets/javascripts/select-kit/components/category-notifications-button.js.es6 b/app/assets/javascripts/select-kit/components/category-notifications-button.js.es6 index ecc000d8bdb..b9e959f7b5b 100644 --- a/app/assets/javascripts/select-kit/components/category-notifications-button.js.es6 +++ b/app/assets/javascripts/select-kit/components/category-notifications-button.js.es6 @@ -1,18 +1,15 @@ -import { or, alias } from "@ember/object/computed"; +import { or } from "@ember/object/computed"; import NotificationOptionsComponent from "select-kit/components/notifications-button"; export default NotificationOptionsComponent.extend({ pluginApiIdentifiers: ["category-notifications-button"], - classNames: "category-notifications-button", + classNames: ["category-notifications-button"], isHidden: or("category.deleted"), - headerIcon: alias("iconForSelectedDetails"), - i18nPrefix: "category.notifications", - showFullTitle: false, - allowInitialValueMutation: false, - mutateValue(value) { - this.category.setNotification(value); + selectKitOptions: { + i18nPrefix: "i18nPrefix", + showFullTitle: false }, - deselect() {} + i18nPrefix: "category.notifications" }); diff --git a/app/assets/javascripts/select-kit/components/category-row.js.es6 b/app/assets/javascripts/select-kit/components/category-row.js.es6 index 452aad22d53..fe3639e20e7 100644 --- a/app/assets/javascripts/select-kit/components/category-row.js.es6 +++ b/app/assets/javascripts/select-kit/components/category-row.js.es6 @@ -1,109 +1,122 @@ -import { bool } from "@ember/object/computed"; -import { isEmpty } from "@ember/utils"; +import { reads, bool } from "@ember/object/computed"; import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row"; -import discourseComputed from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; import { categoryBadgeHTML } from "discourse/helpers/category-link"; -import { isNone } from "@ember/utils"; +import { isEmpty, isNone } from "@ember/utils"; +import { computed } from "@ember/object"; +import { setting } from "discourse/lib/computed"; export default SelectKitRowComponent.extend({ layoutName: "select-kit/templates/components/category-row", - classNames: "category-row", + classNames: ["category-row"], + hideParentCategory: bool("selectKit.options.hideParentCategory"), + allowUncategorized: bool("selectKit.options.allowUncategorized"), + categoryLink: bool("selectKit.options.categoryLink"), + countSubcategories: bool("selectKit.options.countSubcategories"), + allowUncategorizedTopics: setting("allow_uncategorized_topics"), - hideParentCategory: bool("options.hideParentCategory"), - allowUncategorized: bool("options.allowUncategorized"), - categoryLink: bool("options.categoryLink"), + displayCategoryDescription: computed( + "selectKit.options.displayCategoryDescription", + function() { + const option = this.selectKit.options.displayCategoryDescription; + if (isNone(option)) { + return true; + } - @discourseComputed("options.displayCategoryDescription") - displayCategoryDescription(displayCategoryDescription) { - if (isNone(displayCategoryDescription)) { - return true; + return option; } + ), - return displayCategoryDescription; - }, + title: computed("descriptionText", "description", "categoryName", function() { + return this.descriptionText || this.description || this.categoryName; + }), - @discourseComputed("descriptionText", "description", "category.name") - title(descriptionText, description, name) { - return descriptionText || description || name; - }, + categoryName: reads("category.name"), - @discourseComputed("computedContent.value", "computedContent.name") - category(value, name) { - if (isEmpty(value)) { + categoryDescription: reads("category.description"), + + categoryDescriptionText: reads("category.description_text"), + + category: computed("rowValue", "rowName", function() { + if (isEmpty(this.rowValue)) { const uncat = Category.findUncategorized(); - if (uncat && uncat.get("name") === name) { + if (uncat && uncat.name === this.rowName) { return uncat; } } else { - return Category.findById(parseInt(value, 10)); + return Category.findById(parseInt(this.rowValue, 10)); } - }, + }), - @discourseComputed("category", "parentCategory") - badgeForCategory(category, parentCategory) { - return categoryBadgeHTML(category, { + badgeForCategory: computed("category", "parentCategory", function() { + return categoryBadgeHTML(this.category, { link: this.categoryLink, - allowUncategorized: this.allowUncategorized, - hideParent: !!parentCategory + allowUncategorized: + this.allowUncategorizedTopics || this.allowUncategorized, + hideParent: !!this.parentCategory }).htmlSafe(); - }, + }), - @discourseComputed("parentCategory") - badgeForParentCategory(parentCategory) { - return categoryBadgeHTML(parentCategory, { + badgeForParentCategory: computed("parentCategory", function() { + return categoryBadgeHTML(this.parentCategory, { link: this.categoryLink, - allowUncategorized: this.allowUncategorized, + allowUncategorized: + this.allowUncategorizedTopics || this.allowUncategorized, recursive: true }).htmlSafe(); - }, + }), - @discourseComputed("parentCategoryid") - parentCategory(parentCategoryId) { - return Category.findById(parentCategoryId); - }, + parentCategory: computed("parentCategoryId", function() { + return Category.findById(this.parentCategoryId); + }), - @discourseComputed("parentCategoryid") - hasParentCategory(parentCategoryid) { - return !isNone(parentCategoryid); - }, + hasParentCategory: bool("parentCategoryId"), - @discourseComputed("category") - parentCategoryid(category) { - return category.get("parent_category_id"); - }, + parentCategoryId: reads("category.parent_category_id"), - @discourseComputed( - "category.totalTopicCount", - "category.topic_count", - "options.countSubcategories" - ) - topicCount(totalCount, topicCount, countSubcats) { - return countSubcats ? totalCount : topicCount; - }, + categoryTotalTopicCount: reads("category.totalTopicCount"), - @discourseComputed("displayCategoryDescription", "category.description") - shouldDisplayDescription(displayCategoryDescription, description) { - return displayCategoryDescription && description && description !== "null"; - }, + categoryTopicCount: reads("category.topic_count"), - @discourseComputed("category.description_text") - descriptionText(descriptionText) { - if (descriptionText) { - return this._formatCategoryDescription(descriptionText); + topicCount: computed( + "categoryTotalTopicCount", + "categoryTopicCount", + "countSubcategories", + function() { + return this.countSubcategories + ? this.categoryTotalTopicCount + : this.categoryTopicCount; } - }, + ), - @discourseComputed("category.description") - description(description) { - if (description) { - return this._formatCategoryDescription(description); + shouldDisplayDescription: computed( + "displayCategoryDescription", + "categoryDescription", + function() { + return ( + this.displayCategoryDescription && + this.categoryDescription && + this.categoryDescription !== "null" + ); } - }, + ), - _formatCategoryDescription(description) { - return `${description.substr(0, 200)}${ - description.length > 200 ? "…" : "" + descriptionText: computed("categoryDescriptionText", function() { + if (this.categoryDescriptionText) { + return this._formatDescription(this.categoryDescriptionText); + } + }), + + description: computed("categoryDescription", function() { + if (this.categoryDescription) { + return this._formatDescription(this.categoryDescription); + } + }), + + _formatDescription(description) { + const limit = 200; + return `${description.substr(0, limit)}${ + description.length > limit ? "…" : "" }`; } }); diff --git a/app/assets/javascripts/select-kit/components/category-selector.js.es6 b/app/assets/javascripts/select-kit/components/category-selector.js.es6 index 11ed11bacd5..20475d7570c 100644 --- a/app/assets/javascripts/select-kit/components/category-selector.js.es6 +++ b/app/assets/javascripts/select-kit/components/category-selector.js.es6 @@ -1,58 +1,54 @@ -import { get } from "@ember/object"; +import { get, computed } from "@ember/object"; +import { mapBy } from "@ember/object/computed"; import { makeArray } from "discourse-common/lib/helpers"; import MultiSelectComponent from "select-kit/components/multi-select"; import Category from "discourse/models/category"; export default MultiSelectComponent.extend({ pluginApiIdentifiers: ["category-selector"], - classNames: "category-selector", - filterable: true, - allowAny: false, - rowComponent: "category-row", + classNames: ["category-selector"], categories: null, blacklist: null, - allowUncategorized: true, + + selectKitOptions: { + filterable: true, + allowAny: false, + allowUncategorized: "allowUncategorized", + displayCategoryDescription: false, + selectedNameComponent: "multi-select/selected-category" + }, init() { this._super(...arguments); if (!this.categories) this.set("categories", []); if (!this.blacklist) this.set("blacklist", []); - - this.headerComponentOptions.setProperties({ - selectedNameComponent: "multi-select/selected-category" - }); - - this.rowComponentOptions.setProperties({ - allowUncategorized: this.allowUncategorized, - displayCategoryDescription: false - }); }, - computeValues() { - return makeArray(this.categories).map(c => c.id); - }, - - mutateValues(values) { - this.set( - "categories", - values.map(v => Category.findById(v)) - ); - }, - - filterComputedContent(computedContent, computedValues, filter) { - const regex = new RegExp(filter, "i"); - return computedContent.filter(category => - this._normalize(get(category, "name")).match(regex) - ); - }, - - computeContent() { + content: computed("categories.[]", "blacklist.[]", function() { const blacklist = makeArray(this.blacklist); return Category.list().filter(category => { return ( this.categories.includes(category) || !blacklist.includes(category) ); }); + }), + + value: mapBy("categories", "id"), + + filterComputedContent(computedContent, filter) { + const regex = new RegExp(filter, "i"); + return computedContent.filter(category => + this._normalize(get(category, "name")).match(regex) + ); + }, + + actions: { + onChange(values) { + this.attrs.onChange( + values.map(v => Category.findById(v)).filter(Boolean) + ); + return false; + } } }); diff --git a/app/assets/javascripts/select-kit/components/combo-box.js.es6 b/app/assets/javascripts/select-kit/components/combo-box.js.es6 index e68dab3c5fc..af20aa8c119 100644 --- a/app/assets/javascripts/select-kit/components/combo-box.js.es6 +++ b/app/assets/javascripts/select-kit/components/combo-box.js.es6 @@ -1,31 +1,17 @@ import SingleSelectComponent from "select-kit/components/single-select"; -import discourseComputed, { on } from "discourse-common/utils/decorators"; +import { computed } from "@ember/object"; export default SingleSelectComponent.extend({ pluginApiIdentifiers: ["combo-box"], - classNames: "combobox combo-box", - autoFilterable: true, - headerComponent: "combo-box/combo-box-header", + classNames: ["combobox", "combo-box"], - caretUpIcon: "caret-up", - caretDownIcon: "caret-down", - clearable: false, - - computeHeaderContent() { - let content = this._super(...arguments); - content.hasSelection = this.hasSelection; - return content; + selectKitOptions: { + caretUpIcon: "caret-up", + caretDownIcon: "caret-down", + autoFilterable: "autoFilterable", + clearable: false, + headerComponent: "combo-box/combo-box-header" }, - @discourseComputed("isExpanded", "caretUpIcon", "caretDownIcon") - caretIcon(isExpanded, caretUpIcon, caretDownIcon) { - return isExpanded ? caretUpIcon : caretDownIcon; - }, - - @on("didUpdateAttrs", "init") - _setComboBoxOptions() { - this.headerComponentOptions.setProperties({ - clearable: this.clearable - }); - } + autoFilterable: computed.gte("content.length", 5) }); diff --git a/app/assets/javascripts/select-kit/components/combo-box/combo-box-header.js.es6 b/app/assets/javascripts/select-kit/components/combo-box/combo-box-header.js.es6 index 4dbe7d22c51..3c7472d5258 100644 --- a/app/assets/javascripts/select-kit/components/combo-box/combo-box-header.js.es6 +++ b/app/assets/javascripts/select-kit/components/combo-box/combo-box-header.js.es6 @@ -1,12 +1,21 @@ -import { alias, and } from "@ember/object/computed"; -import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header"; +import { reads, and } from "@ember/object/computed"; +import SingleSelectHeaderComponent from "select-kit/components/select-kit/single-select-header"; +import { computed } from "@ember/object"; -export default SelectKitHeaderComponent.extend({ +export default SingleSelectHeaderComponent.extend({ layoutName: "select-kit/templates/components/combo-box/combo-box-header", - classNames: "combo-box-header", + classNames: ["combo-box-header"], + clearable: reads("selectKit.options.clearable"), + caretUpIcon: reads("selectKit.options.caretUpIcon"), + caretDownIcon: reads("selectKit.options.caretDownIcon"), + shouldDisplayClearableButton: and("clearable", "value"), - clearable: alias("options.clearable"), - caretUpIcon: alias("options.caretUpIcon"), - caretDownIcon: alias("options.caretDownIcon"), - shouldDisplayClearableButton: and("clearable", "computedContent.hasSelection") + caretIcon: computed( + "selectKit.isExpanded", + "caretUpIcon", + "caretDownIcon", + function() { + return this.selectKit.isExpanded ? this.caretUpIcon : this.caretDownIcon; + } + ) }); diff --git a/app/assets/javascripts/select-kit/components/composer-actions.js.es6 b/app/assets/javascripts/select-kit/components/composer-actions.js.es6 index f42643f03c0..7b8b461d35f 100644 --- a/app/assets/javascripts/select-kit/components/composer-actions.js.es6 +++ b/app/assets/javascripts/select-kit/components/composer-actions.js.es6 @@ -1,14 +1,12 @@ import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; -import discourseComputed from "discourse-common/utils/decorators"; import { PRIVATE_MESSAGE, CREATE_TOPIC, CREATE_SHARED_DRAFT, - REPLY, - EDIT + REPLY } from "discourse/models/composer"; +import { computed } from "@ember/object"; import { camelize } from "@ember/string"; -import { empty } from "@ember/object/computed"; // Component can get destroyed and lose state let _topicSnapshot = null; @@ -21,14 +19,13 @@ export function _clearSnapshots() { export default DropdownSelectBoxComponent.extend({ pluginApiIdentifiers: ["composer-actions"], - classNames: "composer-actions", - fullWidthOnMobile: true, - autofilterable: false, - filterable: false, - allowInitialValueMutation: false, - allowAutoSelectFirst: false, - showFullTitle: false, - isHidden: empty("content"), + classNames: ["composer-actions"], + + selectKitOptions: { + icon: "share", + filterable: false, + showFullTitle: false + }, didReceiveAttrs() { this._super(...arguments); @@ -37,7 +34,7 @@ export default DropdownSelectBoxComponent.extend({ if ( this.get("composerModel.topic") && (!_topicSnapshot || - this.get("composerModel.topic.id") !== _topicSnapshot.get("id")) + this.get("composerModel.topic.id") !== _topicSnapshot.id) ) { _topicSnapshot = this.get("composerModel.topic"); _postSnapshot = this.get("composerModel.post"); @@ -46,43 +43,26 @@ export default DropdownSelectBoxComponent.extend({ // if we hit reply on a different post we want to change postSnapshot if ( this.get("composerModel.post") && - (!_postSnapshot || - this.get("composerModel.post.id") !== _postSnapshot.get("id")) + (!_postSnapshot || this.get("composerModel.post.id") !== _postSnapshot.id) ) { _postSnapshot = this.get("composerModel.post"); } - }, - computeHeaderContent() { - let content = this._super(...arguments); - - switch (this.action) { - case PRIVATE_MESSAGE: - case CREATE_TOPIC: - case REPLY: - content.icon = "share"; - content.title = I18n.t("composer.composer_actions.reply"); - break; - case EDIT: - content.icon = "pencil-alt"; - content.title = I18n.t("composer.composer_actions.edit"); - break; - case CREATE_SHARED_DRAFT: - content.icon = "far-clipboard"; - content.title = I18n.t("composer.composer_actions.draft"); - break; + if (Ember.isEmpty(this.content)) { + this.set("selectKit.isHidden", true); } - - return content; }, - @discourseComputed("options", "canWhisper", "action") - content(options, canWhisper, action) { + modifySelection() { + return {}; + }, + + content: computed(function() { let items = []; if ( - action !== CREATE_TOPIC && - action !== CREATE_SHARED_DRAFT && + this.action !== CREATE_TOPIC && + this.action !== CREATE_SHARED_DRAFT && _topicSnapshot ) { items.push({ @@ -96,15 +76,15 @@ export default DropdownSelectBoxComponent.extend({ } if ( - (action !== REPLY && _postSnapshot) || - (action === REPLY && + (this.action !== REPLY && _postSnapshot) || + (this.action === REPLY && _postSnapshot && - !(options.userAvatar && options.userLink)) + !(this.replyOptions.userAvatar && this.replyOptions.userLink)) ) { items.push({ name: I18n.t("composer.composer_actions.reply_to_post.label", { - postNumber: _postSnapshot.get("post_number"), - postUsername: _postSnapshot.get("username") + postNumber: _postSnapshot.post_number, + postUsername: _postSnapshot.username }), description: I18n.t("composer.composer_actions.reply_to_post.desc"), icon: "share", @@ -114,7 +94,7 @@ export default DropdownSelectBoxComponent.extend({ if ( this.siteSettings.enable_personal_messages && - action !== PRIVATE_MESSAGE + this.action !== PRIVATE_MESSAGE ) { items.push({ name: I18n.t( @@ -129,12 +109,12 @@ export default DropdownSelectBoxComponent.extend({ } if ( - (action !== REPLY && _topicSnapshot) || - (action === REPLY && + (this.action !== REPLY && _topicSnapshot) || + (this.action === REPLY && _topicSnapshot && - options.userAvatar && - options.userLink && - options.topicLink) + this.replyOptions.userAvatar && + this.replyOptions.userLink && + this.replyOptions.topicLink) ) { items.push({ name: I18n.t("composer.composer_actions.reply_to_topic.label"), @@ -146,7 +126,7 @@ export default DropdownSelectBoxComponent.extend({ // if answered post is a whisper, we can only answer with a whisper so no need for toggle if ( - canWhisper && + this.canWhisper && (!_postSnapshot || (_postSnapshot && _postSnapshot.post_type !== this.site.post_types.whisper)) @@ -160,11 +140,11 @@ export default DropdownSelectBoxComponent.extend({ } let showCreateTopic = false; - if (action === CREATE_SHARED_DRAFT) { + if (this.action === CREATE_SHARED_DRAFT) { showCreateTopic = true; } - if (action === CREATE_TOPIC) { + if (this.action === CREATE_TOPIC) { if (this.site.shared_drafts_category_id) { // Shared Drafts Choice items.push({ @@ -198,7 +178,7 @@ export default DropdownSelectBoxComponent.extend({ this.get("currentUser.staff") || this.get("currentUser.trust_level") === 4; - if (action === REPLY && showToggleTopicBump) { + if (this.action === REPLY && showToggleTopicBump) { items.push({ name: I18n.t("composer.composer_actions.toggle_topic_bump.label"), description: I18n.t("composer.composer_actions.toggle_topic_bump.desc"), @@ -208,7 +188,7 @@ export default DropdownSelectBoxComponent.extend({ } return items; - }, + }), _replyFromExisting(options, post, topic) { this.closeComposer(); @@ -295,18 +275,17 @@ export default DropdownSelectBoxComponent.extend({ }, actions: { - onSelect(value) { - let action = `${camelize(value)}Selected`; + onChange(value) { + const action = `${camelize(value)}Selected`; if (this[action]) { - let model = this.composerModel; this[action]( - model.getProperties( + this.composerModel.getProperties( "draftKey", "draftSequence", "reply", "disableScopedCategory" ), - model + this.composerModel ); } else { // eslint-disable-next-line no-console diff --git a/app/assets/javascripts/select-kit/components/create-color-row.js.es6 b/app/assets/javascripts/select-kit/components/create-color-row.js.es6 new file mode 100644 index 00000000000..9c4f5aac070 --- /dev/null +++ b/app/assets/javascripts/select-kit/components/create-color-row.js.es6 @@ -0,0 +1,17 @@ +import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row"; +import { escapeExpression } from "discourse/lib/utilities"; +import { schedule } from "@ember/runloop"; + +export default SelectKitRowComponent.extend({ + layoutName: "select-kit/templates/components/create-color-row", + classNames: ["create-color-row"], + + didReceiveAttrs() { + this._super(...arguments); + + schedule("afterRender", () => { + const color = escapeExpression(this.rowValue); + this.element.style.borderLeftColor = `#${color}`; + }); + } +}); diff --git a/app/assets/javascripts/select-kit/components/dropdown-select-box.js.es6 b/app/assets/javascripts/select-kit/components/dropdown-select-box.js.es6 index 77c1927e96c..171215aac8f 100644 --- a/app/assets/javascripts/select-kit/components/dropdown-select-box.js.es6 +++ b/app/assets/javascripts/select-kit/components/dropdown-select-box.js.es6 @@ -1,32 +1,17 @@ import SingleSelectComponent from "select-kit/components/single-select"; -import { on } from "discourse-common/utils/decorators"; export default SingleSelectComponent.extend({ pluginApiIdentifiers: ["dropdown-select-box"], - classNames: "dropdown-select-box", - verticalOffset: 3, - fullWidthOnMobile: true, - filterable: false, - autoFilterable: false, - headerComponent: "dropdown-select-box/dropdown-select-box-header", - rowComponent: "dropdown-select-box/dropdown-select-box-row", - showFullTitle: true, - allowInitialValueMutation: false, + classNames: ["dropdown-select-box"], - @on("didUpdateAttrs", "init") - _setDropdownSelectBoxComponentOptions() { - this.headerComponentOptions.setProperties({ - showFullTitle: this.showFullTitle - }); + selectKitOptions: { + autoFilterable: false, + filterable: false, + showFullTitle: true, + headerComponent: "dropdown-select-box/dropdown-select-box-header" }, - didClickOutside() { - if (!this.isExpanded) return; - this.close(); - }, - - didSelect() { - this._super(...arguments); - this.close(); + modifyComponentForRow() { + return "dropdown-select-box/dropdown-select-box-row"; } }); diff --git a/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-header.js.es6 b/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-header.js.es6 index d83156c0185..2b04e4094ad 100644 --- a/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-header.js.es6 +++ b/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-header.js.es6 @@ -1,16 +1,18 @@ -import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header"; -import discourseComputed from "discourse-common/utils/decorators"; +import SingleSelectHeaderComponent from "select-kit/components/select-kit/single-select-header"; +import { computed } from "@ember/object"; +import { readOnly } from "@ember/object/computed"; -export default SelectKitHeaderComponent.extend({ +export default SingleSelectHeaderComponent.extend({ layoutName: "select-kit/templates/components/dropdown-select-box/dropdown-select-box-header", - classNames: "btn-default dropdown-select-box-header", + classNames: ["btn-default", "dropdown-select-box-header"], tagName: "button", - classNameBindings: ["btnClassName"], + showFullTitle: readOnly("selectKit.options.showFullTitle"), + attributeBindings: ["buttonType:type"], + buttonType: "button", - @discourseComputed("options.showFullTitle") - btnClassName(showFullTitle) { - return `btn ${showFullTitle ? "btn-icon-text" : "no-text btn-icon"}`; - } + btnClassName: computed("showFullTitle", function() { + return `btn ${this.showFullTitle ? "btn-icon-text" : "no-text btn-icon"}`; + }) }); diff --git a/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-row.js.es6 b/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-row.js.es6 index 8169762e9a2..0944404e08b 100644 --- a/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-row.js.es6 +++ b/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-row.js.es6 @@ -1,10 +1,10 @@ -import { alias } from "@ember/object/computed"; +import { readOnly } from "@ember/object/computed"; import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row"; export default SelectKitRowComponent.extend({ layoutName: "select-kit/templates/components/dropdown-select-box/dropdown-select-box-row", - classNames: "dropdown-select-box-row", + classNames: ["dropdown-select-box-row"], - description: alias("computedContent.originalContent.description") + description: readOnly("item.description") }); diff --git a/app/assets/javascripts/select-kit/components/future-date-input-selector.js.es6 b/app/assets/javascripts/select-kit/components/future-date-input-selector.js.es6 index 9fec1611c14..0c588dbdfed 100644 --- a/app/assets/javascripts/select-kit/components/future-date-input-selector.js.es6 +++ b/app/assets/javascripts/select-kit/components/future-date-input-selector.js.es6 @@ -1,3 +1,4 @@ +import { computed } from "@ember/object"; import { equal } from "@ember/object/computed"; import { isEmpty } from "@ember/utils"; import ComboBoxComponent from "select-kit/components/combo-box"; @@ -191,31 +192,20 @@ export default ComboBoxComponent.extend(DatetimeMixin, { classNames: ["future-date-input-selector"], isCustom: equal("value", "pick_date_and_time"), isBasedOnLastPost: equal("value", "set_based_on_last_post"), - rowComponent: "future-date-input-selector/future-date-input-selector-row", - headerComponent: - "future-date-input-selector/future-date-input-selector-header", - computeHeaderContent() { - let content = this._super(...arguments); - content.datetime = this._computeDatetimeForValue(this.computedValue); - content.name = this.get("selection.name") || content.name; - content.hasSelection = this.hasSelection; - content.icons = this._computeIconsForValue(this.computedValue); - return content; + selectKitOptions: { + autoInsertNoneItem: false, + headerComponent: + "future-date-input-selector/future-date-input-selector-header" }, - computeContentItem(contentItem, name) { - let computedContentItem = this._super(contentItem, name); - computedContentItem.datetime = this._computeDatetimeForValue( - contentItem.id - ); - computedContentItem.icons = this._computeIconsForValue(contentItem.id); - return computedContentItem; + modifyComponentForRow() { + return "future-date-input-selector/future-date-input-selector-row"; }, - computeContent() { - let now = moment(); - let opts = { + content: computed(function() { + const now = moment(); + const opts = { now, day: now.day(), includeWeekend: this.includeWeekend, @@ -229,23 +219,24 @@ export default ComboBoxComponent.extend(DatetimeMixin, { return TIMEFRAMES.filter(tf => tf.enabled(opts)).map(tf => { return { id: tf.id, - name: I18n.t(`topic.auto_update_input.${tf.id}`) + name: I18n.t(`topic.auto_update_input.${tf.id}`), + datetime: this._computeDatetimeForValue(tf.id), + icons: this._computeIconsForValue(tf.id) }; }); - }, + }), - mutateValue(value) { - if (value === "pick_date_and_time" || this.isBasedOnLastPost) { - this.set("value", value); - } else { - let input = null; - const { time } = this._updateAt(value); - - if (time && !isEmpty(value)) { - input = time.locale("en").format(FORMAT); + actions: { + onChange(value) { + if (value !== "pick_date_and_time" || !this.isBasedOnLastPost) { + const { time } = this._updateAt(value); + if (time && !isEmpty(value)) { + this.attrs.onChangeInput && + this.attrs.onChangeInput(time.locale("en").format(FORMAT)); + } } - this.setProperties({ input, value }); + this.attrs.onChange && this.attrs.onChange(value); } } }); diff --git a/app/assets/javascripts/select-kit/components/future-date-input-selector/future-date-input-selector-row.js.es6 b/app/assets/javascripts/select-kit/components/future-date-input-selector/future-date-input-selector-row.js.es6 index 0438dff50ca..69836359d73 100644 --- a/app/assets/javascripts/select-kit/components/future-date-input-selector/future-date-input-selector-row.js.es6 +++ b/app/assets/javascripts/select-kit/components/future-date-input-selector/future-date-input-selector-row.js.es6 @@ -3,5 +3,5 @@ import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-r export default SelectKitRowComponent.extend({ layoutName: "select-kit/templates/components/future-date-input-selector/future-date-input-selector-row", - classNames: "future-date-input-selector-row" + classNames: ["future-date-input-selector-row"] }); diff --git a/app/assets/javascripts/select-kit/components/future-date-input-selector/mixin.js.es6 b/app/assets/javascripts/select-kit/components/future-date-input-selector/mixin.js.es6 index 9aa6cf56fe2..705e8081ab4 100644 --- a/app/assets/javascripts/select-kit/components/future-date-input-selector/mixin.js.es6 +++ b/app/assets/javascripts/select-kit/components/future-date-input-selector/mixin.js.es6 @@ -33,7 +33,7 @@ export default Mixin.create({ }, _updateAt(selection) { - let details = timeframeDetails(selection); + const details = timeframeDetails(selection); if (details) { return { diff --git a/app/assets/javascripts/select-kit/components/group-dropdown.js.es6 b/app/assets/javascripts/select-kit/components/group-dropdown.js.es6 index 2abd0a0bb9d..57f7a8ff748 100644 --- a/app/assets/javascripts/select-kit/components/group-dropdown.js.es6 +++ b/app/assets/javascripts/select-kit/components/group-dropdown.js.es6 @@ -1,50 +1,42 @@ -import { alias } from "@ember/object/computed"; +import { reads, gte } from "@ember/object/computed"; import ComboBoxComponent from "select-kit/components/combo-box"; import DiscourseURL from "discourse/lib/url"; -import discourseComputed from "discourse-common/utils/decorators"; +import { computed } from "@ember/object"; +import { setting } from "discourse/lib/computed"; export default ComboBoxComponent.extend({ pluginApiIdentifiers: ["group-dropdown"], - classNames: "group-dropdown", - content: alias("groups"), + classNames: ["group-dropdown"], + content: reads("groupsWithShortcut"), tagName: "li", - caretDownIcon: "caret-right", - caretUpIcon: "caret-down", - allowAutoSelectFirst: false, - valueAttribute: "name", + valueProperty: null, + nameProperty: null, + hasManyGroups: gte("content.length", 10), + enableGroupDirectory: setting("enable_group_directory"), - @discourseComputed("content") - filterable(content) { - return content && content.length >= 10; + selectKitOptions: { + caretDownIcon: "caret-right", + caretUpIcon: "caret-down", + filterable: "hasManyGroups" }, - computeHeaderContent() { - let content = this._super(...arguments); + groupsWithShortcut: computed("groups.[]", function() { + const shortcuts = []; - if (!this.hasSelection) { - content.label = `${I18n.t("groups.index.all")}`; + if (this.enableGroupDirectory || this.get("currentUser.staff")) { + shortcuts.push(I18n.t("groups.index.all").toLowerCase()); } - return content; - }, - - @discourseComputed - collectionHeader() { - if ( - this.siteSettings.enable_group_directory || - (this.currentUser && this.currentUser.get("staff")) - ) { - return ` - - ${I18n.t("groups.index.all").toLowerCase()} - - `.htmlSafe(); - } - }, + return shortcuts.concat(this.groups); + }), actions: { - onSelect(groupName) { - DiscourseURL.routeTo(Discourse.getURL(`/g/${groupName}`)); + onChange(groupName) { + if ((this.groups || []).includes(groupName)) { + DiscourseURL.routeToUrl(`/g/${groupName}`); + } else { + DiscourseURL.routeToUrl(`/g`); + } } } }); diff --git a/app/assets/javascripts/select-kit/components/group-members-dropdown.js.es6 b/app/assets/javascripts/select-kit/components/group-members-dropdown.js.es6 index 5dc5549c585..39d1e927537 100644 --- a/app/assets/javascripts/select-kit/components/group-members-dropdown.js.es6 +++ b/app/assets/javascripts/select-kit/components/group-members-dropdown.js.es6 @@ -1,19 +1,15 @@ import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; +import { computed } from "@ember/object"; export default DropdownSelectBoxComponent.extend({ - classNames: "group-members-dropdown", - showFullTitle: false, - allowInitialValueMutation: false, + classNames: ["group-members-dropdown"], - init() { - this._super(...arguments); - - this.headerIcon = ["bars"]; + selectKitOptions: { + icon: "bars", + showFullTitle: false }, - autoHighlight() {}, - - computeContent() { + content: computed(function() { const items = [ { id: "showAddMembersModal", @@ -31,9 +27,5 @@ export default DropdownSelectBoxComponent.extend({ } return items; - }, - - mutateValue(value) { - this.get(value)(); - } + }) }); diff --git a/app/assets/javascripts/select-kit/components/group-notifications-button.js.es6 b/app/assets/javascripts/select-kit/components/group-notifications-button.js.es6 index 7c94a73bae8..7e137afc950 100644 --- a/app/assets/javascripts/select-kit/components/group-notifications-button.js.es6 +++ b/app/assets/javascripts/select-kit/components/group-notifications-button.js.es6 @@ -1,12 +1,12 @@ import NotificationOptionsComponent from "select-kit/components/notifications-button"; export default NotificationOptionsComponent.extend({ - pluginApiIdentifiers: ["grouo-notifications-button"], + pluginApiIdentifiers: ["group-notifications-button"], classNames: ["group-notifications-button"], - i18nPrefix: "groups.notifications", - allowInitialValueMutation: false, - mutateValue(value) { - this.group.setNotification(value, this.get("user.id")); - } + selectKitOptions: { + i18nPrefix: "i18nPrefix" + }, + + i18nPrefix: "groups.notifications" }); diff --git a/app/assets/javascripts/select-kit/components/list-setting.js.es6 b/app/assets/javascripts/select-kit/components/list-setting.js.es6 index 66b252bc488..b22f8dc535f 100644 --- a/app/assets/javascripts/select-kit/components/list-setting.js.es6 +++ b/app/assets/javascripts/select-kit/components/list-setting.js.es6 @@ -1,53 +1,44 @@ import MultiSelectComponent from "select-kit/components/multi-select"; -const { isNone, makeArray } = Ember; +import { MAIN_COLLECTION } from "select-kit/components/select-kit"; +import { computed } from "@ember/object"; +import { readOnly } from "@ember/object/computed"; +import { makeArray } from "discourse-common/lib/helpers"; export default MultiSelectComponent.extend({ pluginApiIdentifiers: ["list-setting"], - classNames: "list-setting", - tokenSeparator: "|", - settingValue: "", + classNames: ["list-setting"], choices: null, - filterable: true, + nameProperty: null, + valueProperty: null, + content: readOnly("choices"), + + selectKitOptions: { + filterable: true, + selectedNameComponent: "selectedNameComponent" + }, + + modifyComponentForRow(collection) { + if ( + collection === MAIN_COLLECTION && + this.settingName && + this.settingName.indexOf("color") > -1 + ) { + return "create-color-row"; + } + }, + + selectedNameComponent: computed("settingName", function() { + if (this.settingName && this.settingName.indexOf("color") > -1) { + return "selected-color"; + } else { + return "selected-name"; + } + }), + + deselect(value) { + this.onChangeChoices && + this.onChangeChoices([...new Set([value, ...makeArray(this.choices)])]); - init() { this._super(...arguments); - - if (!isNone(this.settingName)) { - this.set("nameProperty", this.settingName); - } - - if (this.nameProperty.indexOf("color") > -1) { - this.headerComponentOptions.setProperties({ - selectedNameComponent: "multi-select/selected-color" - }); - } - }, - - computeContent() { - let content; - if (isNone(this.choices)) { - content = this.settingValue.split(this.tokenSeparator); - } else { - content = this.choices; - } - - return makeArray(content).filter(c => c); - }, - - mutateValues(values) { - this.set("settingValue", values.join(this.tokenSeparator)); - }, - - computeValues() { - return this.settingValue.split(this.tokenSeparator).filter(c => c); - }, - - _handleTabOnKeyDown(event) { - if (this.$highlightedRow().length === 1) { - this._super(event); - } else { - this.close(); - return false; - } } }); diff --git a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 index dc8518edb98..6284181bbeb 100644 --- a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 @@ -1,294 +1,186 @@ -import { empty, alias } from "@ember/object/computed"; -import Category from "discourse/models/category"; +import { empty, or } from "@ember/object/computed"; import ComboBox from "select-kit/components/combo-box"; import TagsMixin from "select-kit/mixins/tags"; -import discourseComputed from "discourse-common/utils/decorators"; -import renderTag from "discourse/lib/render-tag"; -import { escapeExpression } from "discourse/lib/utilities"; import { makeArray } from "discourse-common/lib/helpers"; -import { iconHTML } from "discourse-common/lib/icon-library"; -import { get } from "@ember/object"; -import { isEmpty } from "@ember/utils"; -import { debounce } from "@ember/runloop"; +import { computed } from "@ember/object"; +import { setting } from "discourse/lib/computed"; + +const SELECTED_TAGS_COLLECTION = "MINI_TAG_CHOOSER_SELECTED_TAGS"; +import { ERRORS_COLLECTION } from "select-kit/components/select-kit"; export default ComboBox.extend(TagsMixin, { - allowContentReplacement: true, headerComponent: "mini-tag-chooser/mini-tag-chooser-header", pluginApiIdentifiers: ["mini-tag-chooser"], - attributeBindings: ["categoryId"], + attributeBindings: ["selectKit.options.categoryId:category-id"], classNames: ["mini-tag-chooser"], classNameBindings: ["noTags"], - verticalOffset: 3, - filterable: true, - noTags: empty("selection"), - allowCreate: null, - allowAny: alias("allowCreate"), - caretUpIcon: alias("caretIcon"), - caretDownIcon: alias("caretIcon"), - isAsync: true, - fullWidthOnMobile: true, + noTags: empty("value"), + maxTagSearchResults: setting("max_tag_search_results"), + maxTagsPerTopic: setting("max_tags_per_topic"), + highlightedTag: null, + + collections: computed( + "mainCollection.[]", + "errorsCollection.[]", + "highlightedTag", + function() { + return this._super(...arguments); + } + ), + + selectKitOptions: { + fullWidthOnMobile: true, + filterable: true, + caretDownIcon: "caretIcon", + caretUpIcon: "caretIcon", + termMatchesForbidden: false, + categoryId: null, + everyTag: false, + none: "tagging.choose_for_topic", + closeOnChange: false, + maximum: 10, + autoInsertNoneItem: false + }, + + modifyComponentForRow(collection, item) { + if (this.getValue(item) === this.selectKit.filter) { + return "select-kit/select-kit-row"; + } + + return "tag-row"; + }, + + modifyComponentForCollection(collection) { + if (collection === SELECTED_TAGS_COLLECTION) { + return "mini-tag-chooser/selected-collection"; + } + }, + + modifyContentForCollection(collection) { + if (collection === SELECTED_TAGS_COLLECTION) { + return { + selectedTags: this.value, + highlightedTag: this.highlightedTag + }; + } + }, + + allowAnyTag: or("allowCreate", "site.can_create_tag"), + + maximumSelectedTags: computed(function() { + return parseInt( + this.options.limit || this.options.maximum || this.maxTagsPerTopic, + 10 + ); + }), init() { this._super(...arguments); - this.set("termMatchesForbidden", false); - this.selectionSelector = ".selected-tag"; - - if (this.allowCreate !== false) { - this.set("allowCreate", this.site.get("can_create_tag")); - } - - this.set("templateForRow", rowComponent => { - const tag = rowComponent.get("computedContent"); - return renderTag(get(tag, "value"), { - count: get(tag, "originalContent.count"), - noHref: true - }); - }); - - this.set( - "maximum", - parseInt( - this.limit || - this.maximum || - this.get("siteSettings.max_tags_per_topic"), - 10 - ) - ); + this.insertAfterCollection(ERRORS_COLLECTION, SELECTED_TAGS_COLLECTION); }, - @discourseComputed( - "computedValue", - "filter", - "collectionComputedContent.[]", - "hasReachedMaximum", - "hasReachedMinimum", - "categoryId" - ) - shouldDisplayCreateRow() { - if (this.categoryId) { - const category = Category.findById(this.categoryId); - if ( - (category.allowed_tags && category.allowed_tags.length > 0) || - (category.allowed_tag_groups && category.allowed_tag_groups.length > 0) - ) { - return category.allow_global_tags && this._super(...arguments); - } - } - return this._super(...arguments); - }, + caretIcon: computed("selectKit.hasReachedMaximum", function() { + return this.selectKit.hasReachedMaximum ? null : "plus"; + }), - didInsertElement() { - this._super(...arguments); + modifySelection(content) { + let joinedTags = this.value.join(", "); - $(this.element.querySelector(".select-kit-body")).on( - "mousedown touchstart", - ".selected-tag", - event => { - const $button = $(event.target).closest(".selected-tag"); - this._destroyEvent(event); - this.destroyTags(this.computeContentItem($button.attr("data-value"))); - } - ); - }, - - willDestroyElement() { - this._super(...arguments); - - $(this.element.querySelector(".select-kit-body")).off( - "mousedown touchstart" - ); - }, - - @discourseComputed("hasReachedMaximum") - caretIcon(hasReachedMaximum) { - return hasReachedMaximum ? null : "plus"; - }, - - @discourseComputed("tags") - selection(tags) { - return makeArray(tags).map(c => this.computeContentItem(c)); - }, - - filterComputedContent(computedContent) { - return computedContent; - }, - - // we are directly mutatings tags to define the current selection - mutateValue() {}, - - didPressTab(event) { - if (this.isLoading) { - this._destroyEvent(event); - return false; - } - - if (isEmpty(this.filter) && !this.highlighted) { - this.$header().focus(); - this.close(event); - return true; - } - - if (this.highlighted && this.isExpanded) { - this._destroyEvent(event); - this.focus(); - this.select(this.highlighted); - return false; - } else { - this.close(event); - } - - return true; - }, - - @discourseComputed("tags.[]", "filter", "highlightedSelection.[]") - collectionHeader(tags, filter, highlightedSelection) { - if (!isEmpty(tags)) { - let output = ""; - - // if we have more than x tags we will also filter the selection - if (tags.length >= 20) { - tags = tags.filter(t => t.indexOf(filter) >= 0); - } - - tags.map(tag => { - tag = escapeExpression(tag); - const isHighlighted = highlightedSelection - .map(s => get(s, "value")) - .includes(tag); - output += ` - - `; - }); - - return `
    ${output}
    `; - } - }, - - computeHeaderContent() { - let content = this._super(...arguments); - - const joinedTags = this.selection - .map(s => Ember.get(s, "value")) - .join(", "); - - if (isEmpty(this.selection)) { - content.label = I18n.t("tagging.choose_for_topic"); - } else { - content.label = joinedTags; - } - - if (!this.hasReachedMinimum && isEmpty(this.selection)) { - const key = this.minimumLabel || "select_kit.min_content_not_reached"; - const label = I18n.t(key, { count: this.minimum }); + if (!this.selectKit.hasReachedMinimum) { + const key = + this.selectKit.options.minimumLabel || + "select_kit.min_content_not_reached"; + const label = I18n.t(key, { count: this.selectKit.options.minimum }); content.title = content.name = content.label = label; } - content.title = content.name = content.value = joinedTags; + content.title = content.name = content.value = content.label = joinedTags; + + if (content.label.length > 32) { + content.label = `${content.label.slice(0, 32)}...`; + } return content; }, - _prepareSearch(query, options) { + search(filter) { const data = { - q: query, - limit: this.get("siteSettings.max_tag_search_results"), - categoryId: this.categoryId + q: filter || "", + limit: this.maxTagSearchResults, + categoryId: this.selectKit.options.categoryId }; - if (this.selection) { - data.selected_tags = this.selection - .map(s => Ember.get(s, "value")) - .slice(0, 100); + if (this.value) { + data.selected_tags = this.value.slice(0, 100); } - if (!this.everyTag) data.filterForInput = true; + if (!this.selectKit.options.everyTag) data.filterForInput = true; - this.searchTags("/tags/filter/search", data, this._transformJson, options); + return this.searchTags("/tags/filter/search", data, this._transformJson); }, _transformJson(context, json) { let results = json.results; - context.set("termMatchesForbidden", json.forbidden ? true : false); - context.set("termMatchErrorMessage", json.forbidden_message); + context.setProperties({ + termMatchesForbidden: json.forbidden ? true : false, + termMatchErrorMessage: json.forbidden_message + }); if (context.get("siteSettings.tags_sort_alphabetically")) { results = results.sort((a, b) => a.text.localeCompare(b.text)); } - results = results.filter(r => !context.get("selection").includes(r.id)); - - results = results.map(result => { - return { id: result.text, name: result.text, count: result.count }; - }); + results = results + .filter(r => !makeArray(context.tags).includes(r.id)) + .map(result => { + return { id: result.text, name: result.text, count: result.count }; + }); return results; }, - destroyTags(tags) { - tags = makeArray(tags).map(c => get(c, "value")); + select(value) { + this._reset(); - // work around usage with buffered proxy - // it does not listen on array changes, similar hack already on select - // TODO: FIX buffered-proxy.js to support arrays - this.tags.removeObjects(tags); - this.set("tags", this.tags.slice(0)); - this._tagsChanged(); - - this.set( - "searchDebounce", - debounce(this, this._prepareSearch, this.filter, 350) - ); - }, - - didDeselect(tags) { - this.destroyTags(tags); - }, - - didUpdateAttrs() { - this._super(...arguments); - - this._prepareSearch(this.filter, { background: true }); - }, - - _tagsChanged() { - if (this.attrs.onChangeTags) { - this.attrs.onChangeTags({ target: { value: this.tags } }); + if (!this.validateSelect(value)) { + return; } + + const tags = [...new Set(makeArray(this.value).concat(value))]; + this.selectKit.change(tags, tags); }, - actions: { - onSelect(tag) { - this.set("tags", makeArray(this.tags).concat(tag)); - this._tagsChanged(); + deselect(value) { + this._reset(); - this._prepareSearch(this.filter); - this.autoHighlight(); - }, + const tags = [...new Set(makeArray(this.value).removeObject(value))]; + this.selectKit.change(tags, tags); + }, - onExpand() { - if (isEmpty(this.collectionComputedContent)) { - this.set( - "searchDebounce", - debounce(this, this._prepareSearch, this.filter, 350) - ); + _reset() { + this.clearErrors(); + this.set("highlightedTag", null); + }, + + _onKeydown(event) { + if (event.keyCode === 8) { + this._onBackspace(this.value, this.highlightedTag); + } else { + this.set("highlightedTag", null); + } + + return true; + }, + + _onBackspace(value, highlightedTag) { + if (value && value.length) { + if (!highlightedTag) { + this.set("highlightedTag", value.lastObject); + } else { + this.deselect(highlightedTag); } - }, - - onFilter(filter) { - // we start loading right away so we avoid updating createRow multiple times - this.startLoading(); - - filter = isEmpty(filter) ? null : filter; - this.set( - "searchDebounce", - debounce(this, this._prepareSearch, filter, 350) - ); } } }); diff --git a/app/assets/javascripts/select-kit/components/mini-tag-chooser/mini-tag-chooser-header.js.es6 b/app/assets/javascripts/select-kit/components/mini-tag-chooser/mini-tag-chooser-header.js.es6 index 38ef91b9a83..5c634dba571 100644 --- a/app/assets/javascripts/select-kit/components/mini-tag-chooser/mini-tag-chooser-header.js.es6 +++ b/app/assets/javascripts/select-kit/components/mini-tag-chooser/mini-tag-chooser-header.js.es6 @@ -1,7 +1,7 @@ -import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header"; +import ComboBoxSelectBoxHeaderComponent from "select-kit/components/combo-box/combo-box-header"; -export default SelectKitHeaderComponent.extend({ +export default ComboBoxSelectBoxHeaderComponent.extend({ layoutName: "select-kit/templates/components/mini-tag-chooser/mini-tag-chooser-header", - classNames: "mini-tag-chooser-header" + classNames: ["mini-tag-chooser-header"] }); diff --git a/app/assets/javascripts/select-kit/components/mini-tag-chooser/selected-collection.js.es6 b/app/assets/javascripts/select-kit/components/mini-tag-chooser/selected-collection.js.es6 new file mode 100644 index 00000000000..7b4ebf9259c --- /dev/null +++ b/app/assets/javascripts/select-kit/components/mini-tag-chooser/selected-collection.js.es6 @@ -0,0 +1,50 @@ +import Component from "@ember/component"; +import { computed } from "@ember/object"; +import { reads, notEmpty } from "@ember/object/computed"; + +export default Component.extend({ + layoutName: + "select-kit/templates/components/mini-tag-chooser/selected-collection", + classNames: ["mini-tag-chooser-selected-collection", "selected-tags"], + isVisible: notEmpty("selectedTags.[]"), + selectedTags: reads("collection.content.selectedTags.[]"), + highlightedTag: reads("collection.content.highlightedTag"), + + tags: computed( + "selectedTags.[]", + "highlightedTag", + "selectKit.filter", + function() { + if (!this.selectedTags) { + return []; + } + + let tags = this.selectedTags; + if (tags.length >= 20 && this.selectKit.filter) { + tags = tags.filter(t => t.indexOf(this.selectKit.filter) >= 0); + } else if (tags.length >= 20) { + tags = tags.slice(0, 20); + } + + tags = tags.map(selectedTag => { + const classNames = ["selected-tag"]; + if (selectedTag === this.highlightedTag) { + classNames.push("is-highlighted"); + } + + return { + value: selectedTag, + classNames: classNames.join(" ") + }; + }); + + return tags; + } + ), + + actions: { + deselectTag(tag) { + return this.selectKit.deselect(tag); + } + } +}); diff --git a/app/assets/javascripts/select-kit/components/multi-select.js.es6 b/app/assets/javascripts/select-kit/components/multi-select.js.es6 index 9a28a53cbc2..3b82012cab7 100644 --- a/app/assets/javascripts/select-kit/components/multi-select.js.es6 +++ b/app/assets/javascripts/select-kit/components/multi-select.js.es6 @@ -1,363 +1,157 @@ +import deprecated from "discourse-common/lib/deprecated"; import SelectKitComponent from "select-kit/components/select-kit"; -import discourseComputed, { on } from "discourse-common/utils/decorators"; -const { get, isNone, isEmpty, makeArray, run } = Ember; -import { - applyOnSelectPluginApiCallbacks, - applyOnSelectNonePluginApiCallbacks -} from "select-kit/mixins/plugin-api"; +import { computed } from "@ember/object"; +const { makeArray } = Ember; export default SelectKitComponent.extend({ pluginApiIdentifiers: ["multi-select"], layoutName: "select-kit/templates/components/multi-select", - classNames: "multi-select", - headerComponent: "multi-select/multi-select-header", - headerText: "select_kit.default_header_text", - allowAny: true, - allowInitialValueMutation: false, - autoFilterable: true, - selectedNameComponent: "multi-select/selected-name", - filterIcon: null, - filterComponent: "multi-select/multi-select-filter", - computedValues: null, - values: null, + classNames: ["multi-select"], + multiSelect: true, - init() { - this._super(...arguments); - - this.set("computedValues", []); - - if (isNone(this.values)) { - this.set("values", []); - } - - this.headerComponentOptions.setProperties({ - selectedNameComponent: this.selectedNameComponent - }); + selectKitOptions: { + none: "select_kit.default_header_text", + clearable: true, + filterable: true, + filterIcon: null, + clearOnClick: true, + closeOnChange: false, + autoInsertNoneItem: false, + headerComponent: "multi-select/multi-select-header", + filterComponent: "multi-select/multi-select-filter" }, - @on("didRender") - _setChoicesMaxWidth() { - const width = this.$body().outerWidth(false); - if (width > 0) { - this.element.querySelector(".choices").style.maxWidth = `${width}px`; - } + search(filter) { + return this._super(filter).filter( + content => !makeArray(this.selectedContent).includes(content) + ); }, - @on("didUpdateAttrs", "init") - _compute() { - run.scheduleOnce("afterRender", () => { - this.willComputeAttributes(); - let content = this.content || []; - let asyncContent = this.asyncContent || []; - content = this.willComputeContent(content); - asyncContent = this.willComputeAsyncContent(asyncContent); - let values = this._beforeWillComputeValues(this.values); - content = this.computeContent(content); - asyncContent = this.computeAsyncContent(asyncContent); - content = this._beforeDidComputeContent(content); - asyncContent = this._beforeDidComputeAsyncContent(asyncContent); - values = this.willComputeValues(values); - values = this.computeValues(values); - values = this._beforeDidComputeValues(values); - this.didComputeContent(content); - this.didComputeAsyncContent(asyncContent); - this.didComputeValues(values); - this.didComputeAttributes(); - }); + deselect(item) { + this.clearErrors(); + + const newContent = this.selectedContent.filter( + content => this.getValue(item) !== this.getValue(content) + ); + + this.selectKit.change( + this.valueProperty ? newContent.mapBy(this.valueProperty) : newContent, + newContent + ); }, - @discourseComputed("filter", "shouldDisplayCreateRow") - createRowComputedContent(filter, shouldDisplayCreateRow) { - if (shouldDisplayCreateRow) { - let content = this.createContentFromInput(filter); - return this.computeContentItem(content, { created: true }); - } - }, + select(value, item) { + if (!value || !value.length) { + if (!this.validateSelect(this.selectKit.highlighted)) { + return; + } - @discourseComputed("filter", "computedValues") - shouldDisplayCreateRow(filter, computedValues) { - return this._super() && !computedValues.includes(filter); - }, - - @discourseComputed - shouldDisplayFilter() { - return true; - }, - - _beforeWillComputeValues(values) { - return values.map(v => this._cast(v === "" ? null : v)); - }, - willComputeValues(values) { - return values; - }, - computeValues(values) { - return values; - }, - _beforeDidComputeValues(values) { - this.setProperties({ computedValues: values }); - return values; - }, - didComputeValues(values) { - return values; - }, - - mutateAttributes() { - run.next(() => { - if (this.isDestroyed || this.isDestroying) return; - - this.mutateContent(this.computedContent); - this.mutateValues(this.computedValues); - }); - }, - mutateValues(computedValues) { - this.set("values", computedValues); - }, - mutateContent() {}, - - forceValues(values) { - this.mutateValues(values); - this._compute(); - }, - - filterComputedContent(computedContent, computedValues, filter) { - return computedContent.filter(c => { - return this._normalize(get(c, "name")).indexOf(filter) > -1; - }); - }, - - @discourseComputed("computedAsyncContent.[]", "computedValues.[]") - filteredAsyncComputedContent(computedAsyncContent, computedValues) { - computedAsyncContent = computedAsyncContent.filter(c => { - return !computedValues.includes(get(c, "value")); - }); - - if (this.limitMatches) { - return computedAsyncContent.slice(0, this.limitMatches); - } - - return computedAsyncContent; - }, - - @discourseComputed("computedContent.[]", "computedValues.[]", "filter") - filteredComputedContent(computedContent, computedValues, filter) { - computedContent = computedContent.filter(c => { - return !computedValues.includes(get(c, "value")); - }); - - if (this.shouldFilter) { - computedContent = this.filterComputedContent( - computedContent, - computedValues, - this._normalize(filter) + this.selectKit.change( + makeArray(this.value).concat( + makeArray(this.getValue(this.selectKit.highlighted)) + ), + makeArray(this.selectedContent).concat( + makeArray(this.selectKit.highlighted) + ) ); - } - - if (this.limitMatches) { - return computedContent.slice(0, this.limitMatches); - } - - return computedContent; - }, - - computeHeaderContent() { - let content = { - title: this.title, - selection: this.selection - }; - - if (this.noneLabel) { - if (!this.hasSelection) { - content.title = content.name = content.label = I18n.t(this.noneLabel); - } } else { - if (!this.hasReachedMinimum) { - const key = this.minimumLabel || "select_kit.min_content_not_reached"; - content.title = content.name = content.label = I18n.t(key, { - count: this.minimum - }); - } - } - - return content; - }, - - @discourseComputed("filter") - templateForCreateRow() { - return rowComponent => { - return I18n.t("select_kit.create", { - content: rowComponent.get("computedContent.name") - }); - }; - }, - - validateSelect() { - return this._super() && !this.hasReachedMaximum; - }, - - @discourseComputed("computedValues.[]", "computedContent.[]") - selection(computedValues, computedContent) { - const selected = []; - - computedValues.forEach(v => { - const value = computedContent.findBy("value", v); - if (value) selected.push(value); - }); - - return selected; - }, - - @discourseComputed("selection.[]") - hasSelection(selection) { - return !isEmpty(selection); - }, - - didPressTab(event) { - if (isEmpty(this.filter) && !this.highlighted) { - this.$header().focus(); - this.close(event); - return true; - } - - if (this.highlighted && this.isExpanded) { - this._destroyEvent(event); - this.focus(); - this.select(this.highlighted); - return false; - } else { - this.close(event); - } - - return true; - }, - - autoHighlight() { - run.schedule("afterRender", () => { - if (!this.isExpanded) return; - if (!this.renderedBodyOnce) return; - if (this.highlighted) return; - - if (isEmpty(this.collectionComputedContent)) { - if (this.createRowComputedContent) { - this.highlight(this.createRowComputedContent); - } else if (this.noneRowComputedContent && this.hasSelection) { - this.highlight(this.noneRowComputedContent); + const existingItem = this.findValue( + this.mainCollection, + this.selectKit.valueProperty ? item : value + ); + if (existingItem) { + if (!this.validateSelect(item)) { + return; } - } else { - this.highlight(this.get("collectionComputedContent.firstObject")); } - }); - }, - select(computedContentItem) { - if ( - !computedContentItem || - computedContentItem.__sk_row_type === "noneRow" - ) { - applyOnSelectNonePluginApiCallbacks(this.pluginApiIdentifiers, this); - this._boundaryActionHandler("onSelectNone"); - this.clearSelection(); - return; - } - - if (computedContentItem.__sk_row_type === "noopRow") { - applyOnSelectPluginApiCallbacks( - this.pluginApiIdentifiers, - computedContentItem.value, - this + const newValues = makeArray(this.value).concat(makeArray(value)); + const newContent = makeArray(this.selectedContent).concat( + makeArray(item) ); - this._boundaryActionHandler("onSelect", computedContentItem.value); - return; + this.selectKit.change( + newValues, + newContent.length + ? newContent + : makeArray(this.defaultItem(value, value)) + ); } + }, - if (computedContentItem.__sk_row_type === "createRow") { - if ( - !this.computedValues.includes(computedContentItem.value) && - this.validateCreate(computedContentItem.value) - ) { - this.willCreate(computedContentItem); + selectedContent: computed("value.[]", "content.[]", function() { + if (this.value && this.value.length) { + let content = []; - computedContentItem.__sk_row_type = null; - this.computedContent.pushObject(computedContentItem); + this.value.forEach(v => { + if (this.selectKit.valueProperty) { + const c = makeArray(this.content).findBy( + this.selectKit.valueProperty, + v + ); + if (c) { + content.push(c); + } + } else { + if (makeArray(this.content).includes(v)) { + content.push(v); + } + } + }); - run.schedule("afterRender", () => { - this.didCreate(computedContentItem); - this._boundaryActionHandler("onCreate"); - }); - - this.select(computedContentItem); - return; - } else { - this._boundaryActionHandler("onCreateFailure"); - return; - } + return this.selectKit.modifySelection(content || []); + } else { + return this.selectKit.noneItem; } + }), - if (this.validateSelect(computedContentItem)) { - this.willSelect(computedContentItem); - this.clearFilter(); - this.setProperties({ highlighted: null }); - this.computedValues.pushObject(computedContentItem.value); + _onKeydown(event) { + if (event.keyCode === 8) { + event.stopPropagation(); - run.next(() => this.mutateAttributes()); - - run.schedule("afterRender", () => { - this.didSelect(computedContentItem); - - applyOnSelectPluginApiCallbacks( - this.pluginApiIdentifiers, - computedContentItem.value, - this + const input = this.getFilterInput(); + if (input && !input.value.length >= 1) { + const selected = this.element.querySelectorAll( + ".select-kit-header .choice.select-kit-selected-name" ); - this.autoHighlight(); - - this._boundaryActionHandler("onSelect", computedContentItem.value); - }); - } else { - this._boundaryActionHandler("onSelectFailure"); - } - }, - - deselect(rowComputedContentItems) { - this.willDeselect(rowComputedContentItems); - - rowComputedContentItems = makeArray(rowComputedContentItems); - const generatedComputedContents = this._filterRemovableComputedContents( - makeArray(rowComputedContentItems) - ); - this.setProperties({ highlighted: null, highlightedSelection: [] }); - this.computedValues.removeObjects( - rowComputedContentItems.map(r => r.value) - ); - this.computedContent.removeObjects([ - ...rowComputedContentItems, - ...generatedComputedContents - ]); - - run.next(() => { - this.mutateAttributes(); - - run.schedule("afterRender", () => { - this.didDeselect(rowComputedContentItems); - this.autoHighlight(); - - if (!this.isDestroying && !this.isDestroyed) { - this._positionWrapper(); + if (selected.length) { + const lastSelected = selected[selected.length - 1]; + if (lastSelected) { + if (lastSelected.classList.contains("is-highlighted")) { + this.deselect(this.selectedContent.lastObject); + } else { + lastSelected.classList.add("is-highlighted"); + } + } } - }); - }); + } + } else { + const selected = this.element.querySelectorAll( + ".select-kit-header .choice.select-kit-selected-name" + ); + selected.forEach(s => s.classList.remove("is-highlighted")); + } + + return true; }, - close(event) { - this.clearHighlightSelection(); + handleDeprecations() { + this._super(...arguments); - this._super(event); + this._deprecateValues(); }, - unfocus(event) { - this.clearHighlightSelection(); + _deprecateValues() { + if (this.values && !this.value) { + deprecated( + "The `values` property is deprecated for multi-select. Use `value` instead", + { + since: "v2.4.0" + } + ); - this._super(event); + this.set("value", this.values); + } } }); diff --git a/app/assets/javascripts/select-kit/components/multi-select/multi-select-filter.js.es6 b/app/assets/javascripts/select-kit/components/multi-select/multi-select-filter.js.es6 index 4224d8d6b42..aa514938166 100644 --- a/app/assets/javascripts/select-kit/components/multi-select/multi-select-filter.js.es6 +++ b/app/assets/javascripts/select-kit/components/multi-select/multi-select-filter.js.es6 @@ -6,7 +6,7 @@ export default SelectKitFilterComponent.extend({ layoutName: "select-kit/templates/components/select-kit/select-kit-filter", classNames: ["multi-select-filter"], - @discourseComputed("placeholder", "hasSelection") + @discourseComputed("placeholder", "selectKit.hasSelection") computedPlaceholder(placeholder, hasSelection) { if (hasSelection) return ""; return isEmpty(placeholder) ? "" : I18n.t(placeholder); diff --git a/app/assets/javascripts/select-kit/components/multi-select/multi-select-header.js.es6 b/app/assets/javascripts/select-kit/components/multi-select/multi-select-header.js.es6 index 9b6c22f5274..b9c500dc07a 100644 --- a/app/assets/javascripts/select-kit/components/multi-select/multi-select-header.js.es6 +++ b/app/assets/javascripts/select-kit/components/multi-select/multi-select-header.js.es6 @@ -1,55 +1,24 @@ -import { alias, or } from "@ember/object/computed"; -import { makeArray } from "discourse-common/lib/helpers"; -import { on } from "discourse-common/utils/decorators"; -import discourseComputed from "discourse-common/utils/decorators"; import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header"; +import { computed } from "@ember/object"; export default SelectKitHeaderComponent.extend({ - attributeBindings: [ - "label:title", - "label:aria-label", - "names:data-name", - "values:data-value" - ], - classNames: "multi-select-header", + classNames: ["multi-select-header"], layoutName: "select-kit/templates/components/multi-select/multi-select-header", - selectedNameComponent: alias("options.selectedNameComponent"), - forceEscape: alias("options.forceEscape"), + selectedNames: computed("selectedContent", function() { + return Ember.makeArray(this.selectedContent).map(c => this.getName(c)); + }), - ariaLabel: or("computedContent.ariaLabel", "title", "names"), + selectedValue: computed("selectedContent", function() { + return Ember.makeArray(this.selectedContent) + .map(c => { + if (this.getName(c) !== this.getName(this.selectKit.noneItem)) { + return this.getValue(c); + } - title: or("computedContent.title", "names"), - - @on("didRender") - _positionFilter() { - if (!this.shouldDisplayFilter) return; - - const $filter = $(this.element.querySelector(".filter")); - $filter.width(0); - - const leftHeaderOffset = $(this.element).offset().left; - const leftFilterOffset = $filter.offset().left; - const offset = leftFilterOffset - leftHeaderOffset; - const width = $(this.element).outerWidth(false); - const availableSpace = width - offset; - const $choices = $filter.parent(".choices"); - const parentRightPadding = parseInt($choices.css("padding-right"), 10); - $filter.width(availableSpace - parentRightPadding * 4); - }, - - @discourseComputed("computedContent.selection.[]") - names(selection) { - return makeArray(selection) - .map(s => s.name) - .join(","); - }, - - @discourseComputed("computedContent.selection.[]") - values(selection) { - return makeArray(selection) - .map(s => s.value) - .join(","); - } + return null; + }) + .filter(Boolean); + }) }); diff --git a/app/assets/javascripts/select-kit/components/multi-select/selected-category.js.es6 b/app/assets/javascripts/select-kit/components/multi-select/selected-category.js.es6 index 155506095b8..b3f3d4fb15e 100644 --- a/app/assets/javascripts/select-kit/components/multi-select/selected-category.js.es6 +++ b/app/assets/javascripts/select-kit/components/multi-select/selected-category.js.es6 @@ -1,16 +1,15 @@ -import SelectedNameComponent from "select-kit/components/multi-select/selected-name"; -import discourseComputed from "discourse-common/utils/decorators"; +import SelectedNameComponent from "select-kit/components/selected-name"; import { categoryBadgeHTML } from "discourse/helpers/category-link"; +import { computed } from "@ember/object"; export default SelectedNameComponent.extend({ - classNames: "selected-category", + classNames: ["selected-category"], layoutName: "select-kit/templates/components/multi-select/selected-category", - @discourseComputed("computedContent.originalContent") - badge(category) { - return categoryBadgeHTML(category, { + badge: computed("item", function() { + return categoryBadgeHTML(this.item, { allowUncategorized: true, link: false }).htmlSafe(); - } + }) }); diff --git a/app/assets/javascripts/select-kit/components/multi-select/selected-color.js.es6 b/app/assets/javascripts/select-kit/components/multi-select/selected-color.js.es6 index 3574550c203..a109d3df2bf 100644 --- a/app/assets/javascripts/select-kit/components/multi-select/selected-color.js.es6 +++ b/app/assets/javascripts/select-kit/components/multi-select/selected-color.js.es6 @@ -1,8 +1,8 @@ -import SelectedNameComponent from "select-kit/components/multi-select/selected-name"; +import SelectedNameComponent from "select-kit/components/selected-name"; import discourseComputed from "discourse-common/utils/decorators"; export default SelectedNameComponent.extend({ - classNames: "selected-color", + classNames: ["select-kit-selected-color"], @discourseComputed("name") footerContent(name) { diff --git a/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 b/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 deleted file mode 100644 index ab6a36c36cd..00000000000 --- a/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 +++ /dev/null @@ -1,56 +0,0 @@ -import { or, alias } from "@ember/object/computed"; -import Component from "@ember/component"; -import discourseComputed from "discourse-common/utils/decorators"; -import { computed } from "@ember/object"; - -export default Component.extend({ - attributeBindings: [ - "tabindex", - "ariaLabel:aria-label", - "title", - "name:data-name", - "value:data-value", - "guid:data-guid" - ], - classNames: ["selected-name", "choice"], - classNameBindings: ["isHighlighted", "isLocked"], - layoutName: "select-kit/templates/components/multi-select/selected-name", - tagName: "span", - tabindex: -1, - - @discourseComputed("computedContent") - guid(computedContent) { - return Ember.guidFor(computedContent); - }, - - ariaLabel: or("computedContent.ariaLabel", "title"), - - @discourseComputed("computedContent.title", "name") - title(computedContentTitle, name) { - if (computedContentTitle) return computedContentTitle; - if (name) return name; - - return null; - }, - - label: or("computedContent.label", "title", "name"), - - name: alias("computedContent.name"), - - value: alias("computedContent.value"), - - isLocked: computed("computedContent.locked", function() { - return this.getWithDefault("computedContent.locked", false); - }), - - @discourseComputed("computedContent", "highlightedSelection.[]") - isHighlighted(computedContent, highlightedSelection) { - return highlightedSelection.includes(this.computedContent); - }, - - click() { - if (this.isLocked) return false; - this.onClickSelectionItem([this.computedContent]); - return false; - } -}); diff --git a/app/assets/javascripts/select-kit/components/notifications-button.js.es6 b/app/assets/javascripts/select-kit/components/notifications-button.js.es6 index e6eeb542f64..f52b5446d30 100644 --- a/app/assets/javascripts/select-kit/components/notifications-button.js.es6 +++ b/app/assets/javascripts/select-kit/components/notifications-button.js.es6 @@ -1,54 +1,37 @@ -import { alias } from "@ember/object/computed"; import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; -import discourseComputed, { - observes, - on -} from "discourse-common/utils/decorators"; -import { buttonDetails } from "discourse/lib/notification-levels"; -import { allLevels } from "discourse/lib/notification-levels"; +import { allLevels, buttonDetails } from "discourse/lib/notification-levels"; +import { computed, setProperties } from "@ember/object"; export default DropdownSelectBoxComponent.extend({ - classNames: "notifications-button", - nameProperty: "key", - fullWidthOnMobile: true, + pluginApiIdentifiers: ["notifications-button"], + classNames: ["notifications-button"], content: allLevels, - castInteger: true, - autofilterable: false, - filterable: false, - rowComponent: "notifications-button/notifications-button-row", - allowInitialValueMutation: false, - i18nPrefix: "", - i18nPostfix: "", + nameProperty: "key", - @discourseComputed("iconForSelectedDetails") - headerIcon(iconForSelectedDetails) { - return iconForSelectedDetails; + selectKitOptions: { + autoFilterable: false, + filterable: false, + i18nPrefix: "", + i18nPostfix: "" }, - @on("init") - @observes("i18nPostfix") - _setNotificationsButtonComponentOptions() { - this.rowComponentOptions.setProperties({ - i18nPrefix: this.i18nPrefix, - i18nPostfix: this.i18nPostfix + modifyComponentForRow() { + return "notifications-button/notifications-button-row"; + }, + + modifySelection(content) { + content = content || {}; + const { i18nPrefix, i18nPostfix } = this.selectKit.options; + setProperties(content, { + label: I18n.t( + `${i18nPrefix}.${this.buttonForValue.key}${i18nPostfix}.title` + ), + icon: this.buttonForValue.icon }); - }, - - iconForSelectedDetails: alias("selectedDetails.icon"), - - computeHeaderContent() { - let content = this._super(...arguments); - content.name = I18n.t( - `${this.i18nPrefix}.${this.get("selectedDetails.key")}${this.get( - "i18nPostfix" - )}.title` - ); - content.hasSelection = this.hasSelection; return content; }, - @discourseComputed("computedValue") - selectedDetails(computedValue) { - return buttonDetails(computedValue); - } + buttonForValue: computed("value", function() { + return buttonDetails(this.value); + }) }); diff --git a/app/assets/javascripts/select-kit/components/notifications-button/notifications-button-row.js.es6 b/app/assets/javascripts/select-kit/components/notifications-button/notifications-button-row.js.es6 index 5d62a82e4bc..3638bb6169a 100644 --- a/app/assets/javascripts/select-kit/components/notifications-button/notifications-button-row.js.es6 +++ b/app/assets/javascripts/select-kit/components/notifications-button/notifications-button-row.js.es6 @@ -1,45 +1,32 @@ -import { alias } from "@ember/object/computed"; -import DropdownSelectBoxRoxComponent from "select-kit/components/dropdown-select-box/dropdown-select-box-row"; -import { buttonDetails } from "discourse/lib/notification-levels"; -import discourseComputed from "discourse-common/utils/decorators"; -import { iconHTML } from "discourse-common/lib/icon-library"; +import { readOnly } from "@ember/object/computed"; +import { computed } from "@ember/object"; +import DropdownSelectBoxRowComponent from "select-kit/components/dropdown-select-box/dropdown-select-box-row"; +import { escapeExpression } from "discourse/lib/utilities"; -export default DropdownSelectBoxRoxComponent.extend({ - classNames: "notifications-button-row", +export default DropdownSelectBoxRowComponent.extend({ + classNames: ["notifications-button-row"], + i18nPrefix: readOnly("selectKit.options.i18nPrefix"), + i18nPostfix: readOnly("selectKit.options.i18nPostfix"), - i18nPrefix: alias("options.i18nPrefix"), - i18nPostfix: alias("options.i18nPostfix"), + label: computed("_start", function() { + return escapeExpression(I18n.t(`${this._start}.title`)); + }), - @discourseComputed("computedContent.value", "i18nPrefix", "i18nPostfix") - title(value, prefix, postfix) { - const key = buttonDetails(value).key; - return I18n.t(`${prefix}.${key}${postfix}.title`); - }, + title: readOnly("label"), - @discourseComputed( - "computedContent.name", - "computedContent.originalContent.icon" - ) - icon(contentName, icon) { - return iconHTML(icon, { class: contentName.dasherize() }); - }, + icons: computed("title", "item.icon", function() { + return [escapeExpression(this.item.icon)]; + }), - @discourseComputed("_start") - description(_start) { + description: computed("_start", function() { if (this.site && this.site.mobileView) { return null; } - return Handlebars.escapeExpression(I18n.t(`${_start}.description`)); - }, + return escapeExpression(I18n.t(`${this._start}.description`)); + }), - @discourseComputed("_start") - name(_start) { - return Handlebars.escapeExpression(I18n.t(`${_start}.title`)); - }, - - @discourseComputed("i18nPrefix", "i18nPostfix", "computedContent.name") - _start(prefix, postfix, contentName) { - return `${prefix}.${contentName}${postfix}`; - } + _start: computed("i18nPrefix", "i18nPostfix", "rowName", function() { + return `${this.i18nPrefix}.${this.rowName}${this.i18nPostfix}`; + }) }); diff --git a/app/assets/javascripts/select-kit/components/period-chooser.js.es6 b/app/assets/javascripts/select-kit/components/period-chooser.js.es6 index 60c6cd476a4..8431089252d 100644 --- a/app/assets/javascripts/select-kit/components/period-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/period-chooser.js.es6 @@ -1,34 +1,37 @@ -import { oneWay, alias } from "@ember/object/computed"; +import { oneWay, readOnly } from "@ember/object/computed"; import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; -import discourseComputed, { on } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default DropdownSelectBoxComponent.extend({ classNames: ["period-chooser"], - rowComponent: "period-chooser/period-chooser-row", - headerComponent: "period-chooser/period-chooser-header", content: oneWay("site.periods"), - value: alias("period"), - isHidden: alias("showPeriods"), + value: readOnly("period"), + isVisible: readOnly("showPeriods"), + valueProperty: null, + nameProperty: null, + + modifyComponentForRow() { + return "period-chooser/period-chooser-row"; + }, @discourseComputed("isExpanded") caretIcon(isExpanded) { return isExpanded ? "caret-up" : "caret-down"; }, - @on("didUpdateAttrs", "init") - _setFullDay() { - this.headerComponentOptions.setProperties({ - fullDay: this.fullDay - }); - this.rowComponentOptions.setProperties({ - fullDay: this.fullDay - }); + selectKitOptions: { + filterable: false, + autoFilterable: false, + fullDay: "fullDay", + headerComponent: "period-chooser/period-chooser-header" }, actions: { - onSelect() { + onChange(value) { if (this.action) { - this.action(this.computedValue); + this.action(value); + } else { + this.attrs.onChange && this.attrs.onChange(value); } } } diff --git a/app/assets/javascripts/select-kit/components/period-chooser/period-chooser-header.js.es6 b/app/assets/javascripts/select-kit/components/period-chooser/period-chooser-header.js.es6 index f1ee7712a25..0f553cd1569 100644 --- a/app/assets/javascripts/select-kit/components/period-chooser/period-chooser-header.js.es6 +++ b/app/assets/javascripts/select-kit/components/period-chooser/period-chooser-header.js.es6 @@ -3,5 +3,5 @@ import DropdownSelectBoxHeaderomponent from "select-kit/components/dropdown-sele export default DropdownSelectBoxHeaderomponent.extend({ layoutName: "select-kit/templates/components/period-chooser/period-chooser-header", - classNames: "period-chooser-header" + classNames: ["period-chooser-header"] }); diff --git a/app/assets/javascripts/select-kit/components/period-chooser/period-chooser-row.js.es6 b/app/assets/javascripts/select-kit/components/period-chooser/period-chooser-row.js.es6 index 4cb25af58e7..4a2e42a0639 100644 --- a/app/assets/javascripts/select-kit/components/period-chooser/period-chooser-row.js.es6 +++ b/app/assets/javascripts/select-kit/components/period-chooser/period-chooser-row.js.es6 @@ -4,10 +4,10 @@ import discourseComputed from "discourse-common/utils/decorators"; export default DropdownSelectBoxRowComponent.extend({ layoutName: "select-kit/templates/components/period-chooser/period-chooser-row", - classNames: "period-chooser-row", + classNames: ["period-chooser-row"], - @discourseComputed("computedContent") - title(computedContent) { - return I18n.t(`filters.top.${computedContent.name || "this_week"}`).title; + @discourseComputed("rowName") + title(rowName) { + return I18n.t(`filters.top.${rowName || "this_week"}`).title; } }); diff --git a/app/assets/javascripts/select-kit/components/pinned-options.js.es6 b/app/assets/javascripts/select-kit/components/pinned-options.js.es6 index c96856a804a..3d88220e9ce 100644 --- a/app/assets/javascripts/select-kit/components/pinned-options.js.es6 +++ b/app/assets/javascripts/select-kit/components/pinned-options.js.es6 @@ -1,20 +1,16 @@ import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; -import { on } from "discourse-common/utils/decorators"; import { iconHTML } from "discourse-common/lib/icon-library"; +import { computed } from "@ember/object"; export default DropdownSelectBoxComponent.extend({ pluginApiIdentifiers: ["pinned-options"], - classNames: "pinned-options", - allowInitialValueMutation: false, + classNames: ["pinned-options"], - autoHighlight() {}, - - computeHeaderContent() { - let content = this._super(...arguments); + modifySelection(content) { const pinnedGlobally = this.get("topic.pinned_globally"); - const pinned = this.computedValue; + const pinned = this.value; const globally = pinnedGlobally ? "_globally" : ""; - const state = pinned ? `pinned${globally}` : "unpinned"; + const state = pinned === "pinned" ? `pinned${globally}` : "unpinned"; const title = I18n.t(`topic_statuses.${state}.title`); content.label = `${title}${iconHTML("caret-down")}`.htmlSafe(); @@ -24,15 +20,14 @@ export default DropdownSelectBoxComponent.extend({ return content; }, - @on("init") - _setContent() { - const globally = this.get("topic.pinned_globally") ? "_globally" : ""; + content: computed(function() { + const globally = this.topic.pinned_globally ? "_globally" : ""; - this.set("content", [ + return [ { id: "pinned", - name: I18n.t("topic_statuses.pinned" + globally + ".title"), - description: I18n.t("topic_statuses.pinned" + globally + ".help"), + name: I18n.t(`topic_statuses.pinned${globally}.title`), + description: I18n.t(`topic_statuses.pinned${globally}.help`), icon: "thumbtack" }, { @@ -41,14 +36,14 @@ export default DropdownSelectBoxComponent.extend({ icon: "thumbtack unpinned", description: I18n.t("topic_statuses.unpinned.help") } - ]); - }, + ]; + }), actions: { - onSelect() { + onSelect(value) { const topic = this.topic; - if (this.computedValue === "unpinned") { + if (value === "unpinned") { topic.clearPin(); } else { topic.rePin(); diff --git a/app/assets/javascripts/select-kit/components/search-advanced-category-chooser.js.es6 b/app/assets/javascripts/select-kit/components/search-advanced-category-chooser.js.es6 index 831507a3b8b..77e46499ba0 100644 --- a/app/assets/javascripts/select-kit/components/search-advanced-category-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/search-advanced-category-chooser.js.es6 @@ -1,32 +1,14 @@ import CategoryChooserComponent from "select-kit/components/category-chooser"; -import Category from "discourse/models/category"; export default CategoryChooserComponent.extend({ pluginApiIdentifiers: ["search-advanced-category-chooser"], classNames: ["search-advanced-category-chooser"], - rootNone: true, - rootNoneLabel: "category.all", - allowUncategorized: true, - clearable: true, - permissionType: null, - init() { - this._super(...arguments); - - this.rowComponentOptions.setProperties({ - displayCategoryDescription: false - }); - }, - - mutateValue(value) { - if (value) { - this.set("value", Category.findById(value)); - } else { - this.set("value", null); - } - }, - - computeValue(category) { - if (category) return category.id; + selectKitOptions: { + allowUncategorized: true, + clearable: true, + none: "category.all", + displayCategoryDescription: false, + permissionType: null } }); diff --git a/app/assets/javascripts/select-kit/components/select-kit.js.es6 b/app/assets/javascripts/select-kit/components/select-kit.js.es6 index 6c2199a5b71..06d1ecf15df 100644 --- a/app/assets/javascripts/select-kit/components/select-kit.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit.js.es6 @@ -1,521 +1,988 @@ -import discourseComputed from "discourse-common/utils/decorators"; -import EmberObject from "@ember/object"; +import { computed, default as EmberObject } from "@ember/object"; import Component from "@ember/component"; -const { get, isNone, run, isEmpty, makeArray } = Ember; - +import deprecated from "discourse-common/lib/deprecated"; +const { get, isNone, makeArray } = Ember; import UtilsMixin from "select-kit/mixins/utils"; -import DomHelpersMixin from "select-kit/mixins/dom-helpers"; -import EventsMixin from "select-kit/mixins/events"; import PluginApiMixin from "select-kit/mixins/plugin-api"; import { - applyContentPluginApiCallbacks, + next, + debounce, + cancel, + throttle, + bind, + schedule +} from "@ember/runloop"; +import { Promise } from "rsvp"; +import { applyHeaderContentPluginApiCallbacks, - applyCollectionHeaderCallbacks + applyModifyNoSelectionPluginApiCallbacks, + applyContentPluginApiCallbacks, + applyOnOpenPluginApiCallbacks, + applyOnClosePluginApiCallbacks, + applyOnInputPluginApiCallbacks } from "select-kit/mixins/plugin-api"; +export const MAIN_COLLECTION = "MAIN_COLLECTION"; +export const ERRORS_COLLECTION = "ERRORS_COLLECTION"; + +const EMPTY_OBJECT = Object.freeze({}); +const SELECT_KIT_OPTIONS = Ember.Mixin.create({ + mergedProperties: ["selectKitOptions"], + selectKitOptions: EMPTY_OBJECT +}); + export default Component.extend( - UtilsMixin, + SELECT_KIT_OPTIONS, PluginApiMixin, - DomHelpersMixin, - EventsMixin, + UtilsMixin, { pluginApiIdentifiers: ["select-kit"], layoutName: "select-kit/templates/components/select-kit", classNames: ["select-kit"], classNameBindings: [ - "isLoading", - "isFocused", - "isExpanded", - "isDisabled", - "isHidden", - "hasSelection", - "hasReachedMaximum", - "hasReachedMinimum" + "selectKit.isLoading:is-loading", + "selectKit.isExpanded:is-expanded", + "selectKit.isDisabled:is-disabled", + "selectKit.isHidden:is-hidden", + "selectKit.hasSelection:has-selection", + "selectKit.hasReachedMaximum:has-reached-maximum", + "selectKit.hasReachedMinimum:has-reached-minimum" ], - isDisabled: false, - isExpanded: false, - isFocused: false, - isHidden: false, - isLoading: false, - isAsync: false, - renderedBodyOnce: false, - renderedFilterOnce: false, tabindex: 0, - none: null, - highlighted: null, - valueAttribute: "id", - nameProperty: "name", - autoFilterable: false, - filterable: false, - filter: "", - previousFilter: "", - filterIcon: "search", - headerIcon: null, - rowComponent: "select-kit/select-kit-row", - rowComponentOptions: null, - noneRowComponent: "select-kit/select-kit-none-row", - createRowComponent: "select-kit/select-kit-create-row", - filterComponent: "select-kit/select-kit-filter", - headerComponent: "select-kit/select-kit-header", - headerComponentOptions: null, - headerComputedContent: null, - collectionHeaderComputedContent: null, - collectionComponent: "select-kit/select-kit-collection", - verticalOffset: 0, - horizontalOffset: 0, - fullWidthOnMobile: false, - castInteger: false, - castBoolean: false, - allowAny: false, - allowInitialValueMutation: false, content: null, - computedContent: null, - limitMatches: null, - nameChanges: false, - allowContentReplacement: false, - collectionHeader: null, - allowAutoSelectFirst: true, - highlightedSelection: null, - maximum: null, - minimum: null, - minimumLabel: null, - maximumLabel: null, - forceEscape: false, + value: null, + selectKit: null, + mainCollection: null, + errorsCollection: null, + options: null, + valueProperty: "id", + nameProperty: "name", init() { this._super(...arguments); - this.selectKitComponent = true; - this.noneValue = "__none__"; + this._searchPromise = null; + + this.set("errorsCollection", []); + this._collections = [ERRORS_COLLECTION, MAIN_COLLECTION]; + + !this.options && this.set("options", EmberObject.create({})); + + this.handleDeprecations(); + this.set( - "headerComponentOptions", - EmberObject.create({ forceEscape: this.forceEscape }) - ); - this.set( - "rowComponentOptions", + "selectKit", EmberObject.create({ - forceEscape: this.forceEscape + uniqueID: Ember.guidFor(this), + valueProperty: this.valueProperty, + nameProperty: this.nameProperty, + options: EmberObject.create(), + + isLoading: false, + isHidden: false, + isExpanded: false, + isFilterExpanded: false, + hasSelection: false, + hasNoContent: true, + highlighted: null, + noneItem: null, + filter: null, + + modifyContent: bind(this, this._modifyContentWrapper), + modifySelection: bind(this, this._modifySelectionWrapper), + modifyComponentForRow: bind(this, this._modifyComponentForRowWrapper), + modifyContentForCollection: bind( + this, + this._modifyContentForCollectionWrapper + ), + modifyComponentForCollection: bind( + this, + this._modifyComponentForCollectionWrapper + ), + + toggle: bind(this, this._toggle), + close: bind(this, this._close), + open: bind(this, this._open), + highlightNext: bind(this, this._highlightNext), + highlightPrevious: bind(this, this._highlightPrevious), + change: bind(this, this._onChangeWrapper), + select: bind(this, this.select), + deselect: bind(this, this.deselect), + + onOpen: bind(this, this._onOpenWrapper), + onClose: bind(this, this._onCloseWrapper), + onInput: bind(this, this._onInput), + onClearSelection: bind(this, this._onClearSelection), + onHover: bind(this, this._onHover), + onKeydown: bind(this, this._onKeydownWrapper) }) ); - this.set("computedContent", []); - this.set("highlightedSelection", []); - - if (this.nameChanges) { - this.addObserver( - `content.@each.${this.nameProperty}`, - this, - this._compute - ); - } - - if (this.allowContentReplacement) { - this.addObserver(`content.[]`, this, this._compute); - } - - if (this.isAsync) { - this.addObserver(`asyncContent.[]`, this, this._compute); - } }, - keyDown(event) { - if (!isEmpty(this.filter)) return true; + _modifyComponentForRowWrapper(collection, item) { + let component = this.modifyComponentForRow(collection, item); + return component || "select-kit/select-kit-row"; + }, - const keyCode = event.keyCode || event.which; + modifyComponentForRow() {}, - if (event.metaKey === true && keyCode === this.keys.A) { - this.didPressSelectAll(); - return false; + _modifyContentForCollectionWrapper(identifier) { + let collection = this.modifyContentForCollection(identifier); + + if (!collection) { + switch (identifier) { + case ERRORS_COLLECTION: + collection = this.errorsCollection; + break; + default: + collection = this.mainCollection; + break; + } } - if (keyCode === this.keys.BACKSPACE) { - this.didPressBackspace(); - return false; + return collection; + }, + + modifyContentForCollection() {}, + + _modifyComponentForCollectionWrapper(identifier) { + let component = this.modifyComponentForCollection(identifier); + + if (!component) { + switch (identifier) { + case ERRORS_COLLECTION: + component = "select-kit/errors-collection"; + break; + default: + component = "select-kit/select-kit-collection"; + break; + } } + + return component; + }, + + modifyComponentForCollection() {}, + + didUpdateAttrs() { + this._super(...arguments); + + this.set("selectKit.isDisabled", this.isDisabled || false); + + this.handleDeprecations(); }, willDestroyElement() { - this.removeObserver( - `content.@each.${this.nameProperty}`, - this, - "_compute" - ); - this.removeObserver(`content.[]`, this, "_compute"); - this.removeObserver(`asyncContent.[]`, this, "_compute"); + this._super(...arguments); + + this._searchPromise && cancel(this._searchPromise); + + if (this.popper) { + this.popper.destroy(); + this.popper = null; + } }, - willComputeAttributes() {}, - didComputeAttributes() {}, + didReceiveAttrs() { + this._super(...arguments); - willComputeContent(content) { - return applyContentPluginApiCallbacks( - this.pluginApiIdentifiers, - content, - this + const computedOptions = {}; + Object.keys(this.selectKitOptions).forEach(key => { + const value = this.selectKitOptions[key]; + + if ( + key === "componentForRow" || + key === "contentForCollection" || + key === "componentForCollection" + ) { + if (typeof value === "string") { + computedOptions[key] = () => value; + } else { + computedOptions[key] = bind(this, value); + } + + return; + } + + if ( + typeof value === "string" && + value.indexOf(".") < 0 && + value in this + ) { + const computedValue = get(this, value); + if (typeof computedValue !== "function") { + computedOptions[key] = get(this, value); + return; + } + } + computedOptions[key] = value; + }); + this.selectKit.options.setProperties( + Object.assign(computedOptions, this.options || {}) ); - }, - computeContent(content) { - return content; - }, - _beforeDidComputeContent(content) { - let existingCreatedComputedContent = []; - if (!this.allowContentReplacement) { - existingCreatedComputedContent = this.computedContent.filterBy( - "created", - true + + this.selectKit.setProperties({ + hasSelection: !Ember.isEmpty(this.value), + noneItem: this._modifyNoSelectionWrapper() + }); + + if (this.selectKit.isExpanded) { + if (this._searchPromise) { + cancel(this._searchPromise); + } + this._searchPromise = this._searchWrapper(this.selectKit.filter); + } + + if (this.computeContent) { + this._deprecated( + `The \`computeContent()\` function is deprecated pass a \`content\` attribute or define a \`content\` computed property in your component.` ); + + this.set("content", this.computeContent()); } + }, - this.setProperties({ - computedContent: content - .map(c => this.computeContentItem(c)) - .concat(existingCreatedComputedContent) - }); - return content; + selectKitOptions: { + showFullTitle: true, + none: null, + translatedNone: null, + filterable: false, + autoFilterable: "autoFilterable", + filterIcon: "search", + filterPlaceholder: "filterPlaceholder", + translatedfilterPlaceholder: null, + icon: null, + icons: null, + maximum: null, + minimum: null, + minimumLabel: null, + maximumLabel: null, + autoInsertNoneItem: true, + clearOnClick: false, + closeOnChange: true, + limitMatches: null, + placement: "bottom-start", + filterComponent: "select-kit/select-kit-filter", + selectedNameComponent: "selected-name", + hasReachedMaximum: "hasReachedMaximum", + hasReachedMinimum: "hasReachedMinimum" }, - didComputeContent() {}, - willComputeAsyncContent(content) { - return content; - }, - computeAsyncContent(content) { - return content; - }, - _beforeDidComputeAsyncContent(content) { - content = applyContentPluginApiCallbacks( - this.pluginApiIdentifiers, - content, - this + autoFilterable: computed("content.[]", "selectKit.filter", function() { + return ( + this.selectKit.filter && + this.options.autoFilterable && + this.content.length > 15 ); - this.setProperties({ - computedAsyncContent: content.map(c => this.computeAsyncContentItem(c)) - }); - return content; - }, - didComputeAsyncContent() {}, + }), - computeContentItem(contentItem, options) { - let originalContent; - options = options || {}; - const name = options.name; - - if (typeof contentItem === "string" || typeof contentItem === "number") { - originalContent = {}; - originalContent[this.valueAttribute] = contentItem; - originalContent[this.nameProperty] = name || contentItem; - } else { - originalContent = contentItem; - } - - let computedContentItem = { - value: this._cast(this.valueForContentItem(contentItem)), - name: name || this._nameForContent(contentItem), - locked: false, - created: options.created || false, - __sk_row_type: options.created - ? "createRow" - : contentItem.__sk_row_type, - originalContent - }; - - return computedContentItem; - }, - - computeAsyncContentItem(contentItem, options) { - return this.computeContentItem(contentItem, options); - }, - - @discourseComputed( - "isAsync", - "isLoading", - "filteredAsyncComputedContent.[]", - "filteredComputedContent.[]" - ) - collectionComputedContent( - isAsync, - isLoading, - filteredAsyncComputedContent, - filteredComputedContent - ) { - if (isAsync) { - return isLoading ? [] : filteredAsyncComputedContent; - } else { - return filteredComputedContent; - } - }, - - validateCreate(created) { - return !this.hasReachedMaximum && created.length > 0; - }, - - validateSelect() { - return !this.hasReachedMaximum; - }, - - @discourseComputed("maximum", "selection.[]") - hasReachedMaximum(maximum, selection) { - if (!maximum) return false; - selection = makeArray(selection); - return selection.length >= maximum; - }, - - @discourseComputed("minimum", "selection.[]") - hasReachedMinimum(minimum, selection) { - if (!minimum) return true; - selection = makeArray(selection); - return selection.length >= minimum; - }, - - @discourseComputed("shouldFilter", "allowAny") - shouldDisplayFilter(shouldFilter, allowAny) { - if (shouldFilter) return true; - if (allowAny) return true; - return false; - }, - - @discourseComputed("filter", "collectionComputedContent.[]", "isLoading") - noContentRow(filter, collectionComputedContent, isLoading) { - if ( - filter.length > 0 && - collectionComputedContent.length === 0 && - !isLoading - ) { - return this.termMatchErrorMessage || I18n.t("select_kit.no_content"); - } - }, - - @discourseComputed("hasReachedMaximum", "hasReachedMinimum", "isExpanded") - validationMessage(hasReachedMaximum, hasReachedMinimum) { - if (hasReachedMaximum && this.maximum) { - const key = this.maximumLabel || "select_kit.max_content_reached"; - return I18n.t(key, { count: this.maximum }); - } - - if (!hasReachedMinimum && this.minimum) { - const key = this.minimumLabel || "select_kit.min_content_not_reached"; - return I18n.t(key, { count: this.minimum }); - } - }, - - @discourseComputed("allowAny") - filterPlaceholder(allowAny) { - return allowAny + filterPlaceholder: computed("options.allowAny", function() { + return this.options.allowAny ? "select_kit.filter_placeholder_with_any" : "select_kit.filter_placeholder"; - }, + }), - @discourseComputed( - "filter", - "filterable", - "autoFilterable", - "renderedFilterOnce" - ) - shouldFilter(filter, filterable, autoFilterable, renderedFilterOnce) { - if (renderedFilterOnce && filterable) return true; - if (filterable) return true; - if (autoFilterable && filter.length > 0) return true; - return false; - }, + collections: computed( + "selectedContent.[]", + "mainCollection.[]", + "errorsCollection.[]", + function() { + return this._collections.map(identifier => { + return { + identifier, + content: this.selectKit.modifyContentForCollection(identifier) + }; + }); + } + ), + + hasReachedMaximum: computed( + "selectKit.options.maximum", + "value", + function() { + const maximum = parseInt(this.selectKit.options.maximum, 10); + + if (maximum && makeArray(this.value).length >= maximum) { + return true; + } - @discourseComputed( - "computedValue", - "filter", - "collectionComputedContent.[]", - "hasReachedMaximum", - "isLoading" - ) - shouldDisplayCreateRow( - computedValue, - filter, - collectionComputedContent, - hasReachedMaximum, - isLoading - ) { - if (isLoading || hasReachedMaximum) return false; - if (collectionComputedContent.map(c => c.value).includes(filter)) return false; - if (this.allowAny && this.validateCreate(filter)) return true; - return false; - }, - - @discourseComputed("filter", "shouldDisplayCreateRow") - createRowComputedContent(filter, shouldDisplayCreateRow) { - if (shouldDisplayCreateRow) { - let content = this.createContentFromInput(filter); - let computedContentItem = this.computeContentItem(content, { - created: true - }); - computedContentItem.__sk_row_type = "createRow"; - return computedContentItem; } - }, + ), - @discourseComputed - templateForRow() { - return () => null; - }, + hasReachedMinimum: computed( + "selectKit.options.minimum", + "value", + function() { + const minimum = parseInt(this.selectKit.options.minimum, 10); - @discourseComputed - templateForNoneRow() { - return () => null; - }, + if (!minimum || makeArray(this.value).length >= minimum) { + return true; + } - @discourseComputed("filter") - templateForCreateRow() { - return rowComponent => { - return I18n.t("select_kit.create", { - content: rowComponent.get("computedContent.name") - }); - }; - }, - - @discourseComputed("none") - noneRowComputedContent(none) { - if (isNone(none)) return null; - - let noneRowComputedContent; - - switch (typeof none) { - case "string": - noneRowComputedContent = this.computeContentItem(this.noneValue, { - name: (I18n.t(none) || "").htmlSafe() - }); - break; - default: - noneRowComputedContent = this.computeContentItem(none); + return false; } - - noneRowComputedContent.__sk_row_type = "noneRow"; - - return noneRowComputedContent; - }, + ), createContentFromInput(input) { return input; }, - highlightSelection(items) { - this.set("highlightedSelection", makeArray(items)); - this.notifyPropertyChange("highlightedSelection"); + validateCreate(filter, content) { + this.clearErrors(); + + return ( + filter.length > 0 && + content && + !content.map(c => this.getValue(c)).includes(filter) && + !makeArray(this.value).includes(filter) + ); }, - clearHighlightSelection() { - this.highlightSelection([]); - }, + validateSelect() { + this.clearErrors(); - willSelect() {}, - didSelect() {}, + const selection = Ember.makeArray(this.value); - didClearSelection() {}, + const maximum = this.selectKit.options.maximum; - willCreate() {}, - didCreate() {}, - - willDeselect() {}, - didDeselect() {}, - - clearFilter() { - this.$filterInput().val(""); - this.setProperties({ filter: "", previousFilter: "" }); - }, - - startLoading() { - this.set("isLoading", true); - this.set("highlighted", null); - this._boundaryActionHandler("onStartLoading"); - }, - - stopLoading() { - if (this.site && !this.site.isMobileDevice) { - this.focusFilterOrHeader(); + if (maximum && selection.length >= maximum) { + const key = + this.selectKit.options.maximumLabel || + "select_kit.max_content_reached"; + this.addError(I18n.t(key, { count: maximum })); + return false; } - this.set("isLoading", false); - this._boundaryActionHandler("onStopLoading"); + const minimum = this.selectKit.options.minimum; + if (minimum && selection.length <= minimum) { + const key = + this.selectKit.options.minimumLabel || + "select_kit.min_content_not_reached"; + this.addError(I18n.t(key, { count: minimum })); + return false; + } + + return true; }, - @discourseComputed( - "selection.[]", - "isExpanded", - "filter", - "highlightedSelection.[]" - ) - collectionHeaderComputedContent() { - return applyCollectionHeaderCallbacks( + addError(error) { + this.errorsCollection.pushObject(error); + + this._safeAfterRender(() => this.popper && this.popper.update()); + }, + + clearErrors() { + if (!this.element || this.isDestroyed || this.isDestroying) { + return; + } + + this.set("errorsCollection", []); + }, + + prependCollection(identifier) { + this._collections.unshift(identifier); + }, + + appendCollection(identifier) { + this._collections.push(identifier); + }, + + insertCollectionAtIndex(identifier, index) { + this._collections.insertAt(index, identifier); + }, + + insertBeforeCollection(identifier, insertedIdentifier) { + const index = this._collections.indexOf(identifier); + this.insertCollectionAtIndex(insertedIdentifier, index - 1); + }, + + insertAfterCollection(identifier, insertedIdentifier) { + const index = this._collections.indexOf(identifier); + this.insertCollectionAtIndex(insertedIdentifier, index + 1); + }, + + _onInput(event) { + this.popper && this.popper.update(); + + if (this._searchPromise) { + cancel(this._searchPromise); + } + + const input = applyOnInputPluginApiCallbacks( this.pluginApiIdentifiers, - this.collectionHeader, - this + event, + this.selectKit + ); + + if (input) { + this.selectKit.set("isLoading", true); + debounce(this, this._debouncedInput, event.target.value, 200); + } + }, + + _debouncedInput(filter) { + this.selectKit.set("filter", filter); + this._searchPromise = this._searchWrapper(filter); + }, + + _onChangeWrapper(value, items) { + this.selectKit.set("filter", null); + + return new Promise(resolve => { + if ( + !this.selectKit.valueProperty && + this.selectKit.noneItem === value + ) { + value = null; + items = []; + } + + this._boundaryActionHandler("onChange", value, items); + resolve(items); + }).finally(() => { + if (!this.isDestroying && !this.isDestroyed) { + if (this.selectKit.options.closeOnChange) { + this.selectKit.close(); + } + + this._safeAfterRender(() => { + this._focusFilter(); + this.popper && this.popper.update(); + }); + } + }); + }, + + _modifyContentWrapper(content) { + content = this.modifyContent(content); + + return applyContentPluginApiCallbacks( + this.pluginApiIdentifiers, + content, + this.selectKit ); }, - @discourseComputed("selection.[]", "isExpanded", "headerIcon") - headerComputedContent() { - return applyHeaderContentPluginApiCallbacks( + modifyContent(content) { + return content; + }, + + _modifyNoSelectionWrapper() { + let none = this.modifyNoSelection(); + + return applyModifyNoSelectionPluginApiCallbacks( this.pluginApiIdentifiers, - this.computeHeaderContent(), - this + none, + this.selectKit ); }, + modifyNoSelection() { + if (this.selectKit.options.translatedNone) { + return this.defaultItem(null, this.selectKit.options.translatedNone); + } + + let none = this.selectKit.options.none; + if (isNone(none) && !this.selectKit.options.allowAny) return null; + + if ( + isNone(none) && + this.selectKit.options.allowAny && + !this.selectKit.isExpanded + ) { + return this.defaultItem( + null, + I18n.t("select_kit.filter_placeholder_with_any") + ); + } + + let item; + switch (typeof none) { + case "string": + item = this.defaultItem(null, I18n.t(none)); + break; + default: + item = none; + } + + return item; + }, + + _modifySelectionWrapper(item) { + applyHeaderContentPluginApiCallbacks( + this.pluginApiIdentifiers, + item, + this.selectKit + ); + + return this.modifySelection(item); + }, + + modifySelection(item) { + return item; + }, + + _onKeydownWrapper(event) { + return this._boundaryActionHandler("onKeydown", event); + }, + + _onHover(value, item) { + throttle(this, this._highlight, item, 25, true); + }, + + _highlight(item) { + this.selectKit.set("highlighted", item); + }, + _boundaryActionHandler(actionName, ...params) { - if (get(this.actions, actionName)) { - run.next(() => this.send(actionName, ...params)); - } else if (this.get(actionName)) { - run.next(() => this.get(actionName)(...params)); + if (!this.element || this.isDestroying || this.isDestroyed) { + return; } - }, - highlight(computedContent) { - this.set("highlighted", computedContent); - this._boundaryActionHandler("onHighlight", computedContent); - }, + let boundaryAction = true; - clearSelection() { - this.deselect(this.selection); - this.focusFilterOrHeader(); - this.didClearSelection(); - }, + const privateActionName = `_${actionName}`; + const privateAction = get(this, privateActionName); + if (privateAction) { + boundaryAction = privateAction.call(this, ...params); + } - actions: { - onToggle() { - this.clearHighlightSelection(); - - if (this.isExpanded) { - this.collapse(); - } else { - this.expand(); + if (this.actions) { + const componentAction = get(this.actions, actionName); + if (boundaryAction && componentAction) { + boundaryAction = componentAction.call(this, ...params); } - }, - - onClickRow(computedContentItem) { - this.didClickRow(computedContentItem); - }, - - onClickSelectionItem(computedContentItem) { - this.didClickSelectionItem(computedContentItem); - }, - - onClearSelection() { - this.clearSelection(); - }, - - onMouseoverRow(computedContentItem) { - this.highlight(computedContentItem); - }, - - onFilterComputedContent(filter) { - if (filter === this.previousFilter) return; - - this.clearHighlightSelection(); - - this.setProperties({ - highlighted: null, - renderedFilterOnce: true, - previousFilter: filter, - filter - }); - this.autoHighlight(); - this._boundaryActionHandler("onFilter", filter); } + + const action = get(this, actionName); + if (boundaryAction && action) { + boundaryAction = action.call(this, ...params); + } + + return boundaryAction; + }, + + deselect() { + this.clearErrors(); + this.selectKit.change(null, null); + }, + + search(filter) { + let content = this.content || []; + if (filter) { + filter = this._normalize(filter); + content = content.filter(c => { + const name = this._normalize(this.getName(c)); + return name && name.indexOf(filter) > -1; + }); + } + return content; + }, + + _searchWrapper(filter) { + this.clearErrors(); + this.setProperties({ mainCollection: [], "selectKit.isLoading": true }); + this._safeAfterRender(() => this.popper && this.popper.update()); + + let content = []; + + return Promise.resolve(this.search(filter)).then(result => { + content = content.concat(makeArray(result)); + content = this.selectKit.modifyContent(content).filter(Boolean); + + if (this.selectKit.valueProperty) { + content = content.uniqBy(this.selectKit.valueProperty); + } else { + content = content.uniq(); + } + + if (this.selectKit.options.limitMatches) { + content = content.slice(0, this.selectKit.options.limitMatches); + } + + const noneItem = this.selectKit.noneItem; + + if ( + this.selectKit.options.allowAny && + filter && + this.getName(noneItem) !== filter + ) { + filter = this.createContentFromInput(filter); + if (this.validateCreate(filter, content)) { + content.unshift(this.defaultItem(filter, filter)); + } + } + + const hasNoContent = Ember.isEmpty(content); + + if ( + this.selectKit.hasSelection && + noneItem && + this.selectKit.options.autoInsertNoneItem + ) { + content.unshift(noneItem); + } + + this.set("mainCollection", content); + + this.selectKit.setProperties({ + highlighted: + this.singleSelect && this.value + ? this.itemForValue(this.value) + : this.mainCollection.firstObject, + isLoading: false, + hasNoContent + }); + + this._safeAfterRender(() => { + this.popper && this.popper.update(); + this._focusFilter(); + }); + }); + }, + + _safeAfterRender(fn) { + next(() => { + schedule("afterRender", () => { + if (!this.element || this.isDestroyed || this.isDestroying) { + return; + } + + fn(); + }); + }); + }, + + _scrollToRow(rowItem) { + const value = this.getValue(rowItem); + const rowContainer = this.element.querySelector( + `.select-kit-row[data-value="${value}"]` + ); + + if (rowContainer) { + const $collection = $( + this.element.querySelector(".select-kit-collection") + ); + + const collectionTop = $collection.position().top; + + $collection.scrollTop( + $collection.scrollTop() + + $(rowContainer).position().top - + collectionTop + ); + } + }, + + _highlightNext() { + const highlightedIndex = this.mainCollection.indexOf( + this.selectKit.highlighted + ); + let newHighlightedIndex = highlightedIndex; + const count = this.mainCollection.length; + + if (highlightedIndex < count - 1) { + newHighlightedIndex = highlightedIndex + 1; + } else { + newHighlightedIndex = 0; + } + + const highlighted = this.mainCollection.objectAt(newHighlightedIndex); + if (highlighted) { + this._scrollToRow(highlighted); + this.set("selectKit.highlighted", highlighted); + } + }, + + _highlightPrevious() { + const highlightedIndex = this.mainCollection.indexOf( + this.selectKit.highlighted + ); + let newHighlightedIndex = highlightedIndex; + const count = this.mainCollection.length; + + if (highlightedIndex > 0) { + newHighlightedIndex = highlightedIndex - 1; + } else { + newHighlightedIndex = count - 1; + } + + const highlighted = this.mainCollection.objectAt(newHighlightedIndex); + if (highlighted) { + this._scrollToRow(highlighted); + this.set("selectKit.highlighted", highlighted); + } + }, + + select(value, item) { + if (!value) { + if (!this.validateSelect(this.selectKit.highlighted)) { + return; + } + + this.selectKit.change( + this.getValue(this.selectKit.highlighted), + this.selectKit.highlighted + ); + } else { + const existingItem = this.findValue(this.mainCollection, item); + if (existingItem) { + if (!this.validateSelect(item)) { + return; + } + } + + this.selectKit.change(value, item || this.defaultItem(value, value)); + } + }, + + _onClearSelection() { + this.selectKit.change(null, null); + }, + + _onOpenWrapper(event) { + let boundaryAction = this._boundaryActionHandler("onOpen"); + + boundaryAction = applyOnOpenPluginApiCallbacks( + this.pluginApiIdentifiers, + this.selectKit, + event + ); + + return boundaryAction; + }, + + _onCloseWrapper(event) { + this._focusFilter(this.multiSelect); + + this.set("selectKit.highlighted", null); + + let boundaryAction = this._boundaryActionHandler("onClose"); + + boundaryAction = applyOnClosePluginApiCallbacks( + this.pluginApiIdentifiers, + this.selectKit, + event + ); + + return boundaryAction; + }, + + _toggle(event) { + if (this.selectKit.isExpanded) { + this._close(event); + } else { + this._open(event); + } + }, + + _close(event) { + if (!this.selectKit.isExpanded) { + return; + } + + this.clearErrors(); + + if (!this.selectKit.onClose(event)) { + return; + } + + this.selectKit.setProperties({ + isExpanded: false, + filter: null + }); + }, + + _open(event) { + if (this.selectKit.isExpanded) { + return; + } + + this.clearErrors(); + + if (!this.selectKit.onOpen(event)) { + return; + } + + if (!this.popper) { + const anchor = document.querySelector( + `[data-select-kit-id=${this.selectKit.uniqueID}-header]` + ); + const popper = document.querySelector( + `[data-select-kit-id=${this.selectKit.uniqueID}-body]` + ); + + if (!this.site.mobileView && popper.offsetWidth < anchor.offsetWidth) { + popper.style.minWidth = `${anchor.offsetWidth}px`; + } + + const inModal = $(this.element).parents("#discourse-modal").length; + + if (!this.site.mobileView && inModal) { + popper.style.width = `${anchor.offsetWidth}px`; + } + + /* global Popper:true */ + this.popper = Popper.createPopper(anchor, popper, { + eventsEnabled: false, + strategy: inModal ? "fixed" : "absolute", + placement: this.selectKit.options.placement, + modifiers: [ + { + name: "positionWrapper", + phase: "afterWrite", + enabled: true, + fn: data => { + const wrapper = this.element.querySelector( + ".select-kit-wrapper" + ); + if (wrapper) { + let height = this.element.offsetHeight; + + const body = this.element.querySelector(".select-kit-body"); + if (body) { + height += body.offsetHeight; + } + + const popperElement = data.state.elements.popper; + if ( + popperElement && + popperElement.getAttribute("data-popper-placement") === + "top-start" + ) { + this.element.classList.remove("is-under"); + this.element.classList.add("is-above"); + } else { + this.element.classList.remove("is-above"); + this.element.classList.add("is-under"); + } + + wrapper.style.width = `${this.element.offsetWidth}px`; + wrapper.style.height = `${height}px`; + } + } + } + ] + }); + } + + this.selectKit.setProperties({ + isExpanded: true, + isFilterExpanded: + this.selectKit.options.filterable || this.selectKit.options.allowAny + }); + + if (this._searchPromise) { + cancel(this._searchPromise); + } + this._searchPromise = this._searchWrapper(); + + this._safeAfterRender(() => { + this._focusFilter(); + this.popper && this.popper.update(); + }); + }, + + _focusFilter(forceHeader = false) { + this._safeAfterRender(() => { + const input = this.getFilterInput(); + if (!forceHeader && input) { + input.focus({ preventScroll: true }); + } else { + const headerContainer = this.getHeader(); + headerContainer && headerContainer.focus({ preventScroll: true }); + } + }); + }, + + getFilterInput() { + return document.querySelector( + `[data-select-kit-id=${this.selectKit.uniqueID}-filter] input` + ); + }, + + getHeader() { + return document.querySelector( + `[data-select-kit-id=${this.selectKit.uniqueID}-header]` + ); + }, + + handleDeprecations() { + this._deprecateValueAttribute(); + this._deprecateMutations(); + this._deprecateOptions(); + }, + + _deprecated(text) { + const discourseSetup = document.getElementById("data-discourse-setup"); + if ( + discourseSetup && + discourseSetup.getAttribute("data-environment") === "development" + ) { + deprecated(text, { since: "v2.4.0" }); + } + }, + + _deprecateValueAttribute() { + if (this.valueAttribute) { + this._deprecated( + "The `valueAttribute` is deprecated. Use `valueProperty` instead" + ); + + this.set("valueProperty", this.valueAttribute); + } + }, + + _deprecateMutations() { + this.actions = this.actions || {}; + this.attrs = this.attrs || {}; + + if (!this.attrs.onChange && !this.actions.onChange) { + this._deprecated( + "Implicit mutation has been deprecated, please use `onChange` handler" + ); + + this.actions.onChange = + this.attrs.onSelect || + this.actions.onSelect || + (value => this.set("value", value)); + } + }, + + _deprecateOptions() { + const migrations = { + headerIcon: "icon", + onExpand: "onOpen", + onCollapse: "onClose", + allowAny: "options.allowAny", + allowCreate: "options.allowAny", + filterable: "options.filterable", + excludeCategoryId: "options.excludeCategoryId", + scopedCategoryId: "options.scopedCategoryId", + allowUncategorized: "options.allowUncategorized", + none: "options.none", + rootNone: "options.none", + isDisabled: "options.isDisabled", + rootNoneLabel: "options.none", + showFullTitle: "options.showFullTitle", + title: "options.translatedNone", + maximum: "options.maximum", + minimum: "options.minimum", + i18nPostfix: "options.i18nPostfix", + i18nPrefix: "options.i18nPrefix" + }; + + Object.keys(migrations).forEach(from => { + const to = migrations[from]; + if (this.get(from) && !this.get(to)) { + this._deprecated( + `The \`${from}\` attribute is deprecated. Use \`${to}\` instead` + ); + + this.set(to, this.get(from)); + } + }); } } ); diff --git a/app/assets/javascripts/select-kit/components/select-kit/errors-collection.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/errors-collection.js.es6 new file mode 100644 index 00000000000..ed4c78ae0b0 --- /dev/null +++ b/app/assets/javascripts/select-kit/components/select-kit/errors-collection.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; +import { notEmpty } from "@ember/object/computed"; + +export default Component.extend({ + layoutName: "select-kit/templates/components/select-kit/errors-collection", + classNames: ["select-kit-errors-collection"], + tagName: "ul", + isVisible: notEmpty("collection.content") +}); diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-body.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-body.js.es6 new file mode 100644 index 00000000000..dc4bfa62e3c --- /dev/null +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-body.js.es6 @@ -0,0 +1,61 @@ +import Component from "@ember/component"; +import { computed } from "@ember/object"; + +export default Component.extend({ + layoutName: "select-kit/templates/components/select-kit/select-kit-body", + classNames: ["select-kit-body"], + attributeBindings: ["selectKitId:data-select-kit-id"], + selectKitId: computed("selectKit.uniqueID", function() { + return `${this.selectKit.uniqueID}-body`; + }), + rootEventType: "click", + + init() { + this._super(...arguments); + + this.handleRootMouseDownHandler = Ember.run.bind( + this, + this.handleRootMouseDown + ); + }, + + didInsertElement() { + this._super(...arguments); + + document.addEventListener( + this.rootEventType, + this.handleRootMouseDownHandler, + true + ); + }, + + willDestroyElement() { + this._super(...arguments); + + document.removeEventListener( + this.rootEventType, + this.handleRootMouseDownHandler, + true + ); + }, + + handleRootMouseDown(event) { + if (!this.selectKit.isExpanded) { + return; + } + + const headerElement = document.querySelector( + `[data-select-kit-id=${this.selectKit.uniqueID}-header]` + ); + + if (headerElement && headerElement.contains(event.target)) { + return; + } + + if (this.element.contains(event.target)) { + return; + } + + this.selectKit.close(event); + } +}); diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-collection.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-collection.js.es6 index cc2ff90a15d..661a43d7b5e 100644 --- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-collection.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-collection.js.es6 @@ -1,7 +1,10 @@ import Component from "@ember/component"; +import { notEmpty } from "@ember/object/computed"; + export default Component.extend({ layoutName: "select-kit/templates/components/select-kit/select-kit-collection", classNames: ["select-kit-collection"], - tagName: "ul" + tagName: "ul", + isVisible: notEmpty("collection") }); diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 index 9212f52fda8..a86b01378c6 100644 --- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 @@ -1,17 +1,96 @@ -import { not } from "@ember/object/computed"; +import { fmt } from "discourse/lib/computed"; import Component from "@ember/component"; import discourseComputed from "discourse-common/utils/decorators"; +import { isEmpty } from "@ember/utils"; +import { computed } from "@ember/object"; +import { not } from "@ember/object/computed"; +import UtilsMixin from "select-kit/mixins/utils"; -const { isEmpty } = Ember; - -export default Component.extend({ +export default Component.extend(UtilsMixin, { layoutName: "select-kit/templates/components/select-kit/select-kit-filter", classNames: ["select-kit-filter"], - classNameBindings: ["isFocused", "isHidden"], - isHidden: not("shouldDisplayFilter"), + classNameBindings: ["isExpanded:is-expanded"], + attributeBindings: ["selectKitId:data-select-kit-id"], + selectKitId: fmt("selectKit.uniqueID", "%@-filter"), - @discourseComputed("placeholder") - computedPlaceholder(placeholder) { - return isEmpty(placeholder) ? "" : I18n.t(placeholder); + isHidden: computed( + "selectKit.options.{filterable,allowAny,autoFilterable}", + "content.[]", + function() { + return ( + !this.selectKit.options.filterable && + !this.selectKit.options.allowAny && + !this.selectKit.options.autoFilterable + ); + } + ), + + isExpanded: not("isHidden"), + + @discourseComputed( + "selectKit.options.filterPlaceholder", + "selectKit.options.translatedfilterPlaceholder" + ) + placeholder(placeholder, translatedPlaceholder) { + return isEmpty(placeholder) + ? translatedPlaceholder + ? translatedPlaceholder + : "" + : I18n.t(placeholder); + }, + + actions: { + onInput(event) { + this.selectKit.onInput(event); + }, + + onKeydown(event) { + if (!this.selectKit.onKeydown(event)) { + return false; + } + + // Do nothing for left/right arrow + if (event.keyCode === 37 || event.keyCode === 39) { + return true; + } + + // Up arrow + if (event.keyCode === 38) { + this.selectKit.highlightPrevious(); + return false; + } + + // Down arrow + if (event.keyCode === 40) { + this.selectKit.highlightNext(); + return false; + } + + // Escape + if (event.keyCode === 27) { + this.selectKit.close(event); + return false; + } + + // Enter + if (event.keyCode === 13 && this.selectKit.highlighted) { + this.selectKit.select(this.getValue(this.selectKit.highlighted)); + return false; + } + + if (event.keyCode === 13 && !this.selectKit.highlighted) { + this.element.querySelector("input").focus(); + return false; + } + + // Tab + if (event.keyCode === 9) { + if (this.selectKit.highlighted && this.selectKit.isExpanded) { + this.selectKit.select(this.getValue(this.selectKit.highlighted)); + } + this.selectKit.close(event); + return; + } + } } }); diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6 index 525c9b69366..4cee1a0bbc4 100644 --- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6 @@ -1,59 +1,157 @@ -import { alias, none, or } from "@ember/object/computed"; +import { computed } from "@ember/object"; import Component from "@ember/component"; -import discourseComputed from "discourse-common/utils/decorators"; +import UtilsMixin from "select-kit/mixins/utils"; +import { schedule } from "@ember/runloop"; +import { makeArray } from "discourse-common/lib/helpers"; -const { isEmpty, makeArray } = Ember; +export default Component.extend(UtilsMixin, { + eventType: "click", + + click(event) { + if (typeof document === "undefined") return; + if (this.isDestroyed || !this.selectKit || this.selectKit.isDisabled) + return; + if (this.eventType !== "click" || event.button !== 0) return; + this.selectKit.toggle(event); + }, -export default Component.extend({ - layoutName: "select-kit/templates/components/select-kit/select-kit-header", classNames: ["select-kit-header"], - classNameBindings: ["isFocused", "isNone"], + classNameBindings: ["isFocused"], attributeBindings: [ "tabindex", - "ariaLabel:aria-label", + "ariaOwns:aria-owns", "ariaHasPopup:aria-haspopup", - "sanitizedTitle:title", - "value:data-value", - "name:data-name" + "ariaIsExpanded:aria-expanded", + "selectKitId:data-select-kit-id", + "roleButton:role", + "selectedValue:data-value", + "selectedNames:data-name", + "serializedNames:title" ], - forceEscape: alias("options.forceEscape"), + selectedValue: computed("value", function() { + return this.value === this.getValue(this.selectKit.noneItem) + ? null + : makeArray(this.value).join(","); + }), - isNone: none("computedContent.value"), + selectedNames: computed("selectedContent.[]", function() { + return makeArray(this.selectedContent) + .map(s => this.getName(s)) + .join(","); + }), - ariaHasPopup: "true", + icons: computed("selectKit.options.{icon,icons}", function() { + const icon = makeArray(this.selectKit.options.icon); + const icons = makeArray(this.selectKit.options.icons); + return icon.concat(icons).filter(Boolean); + }), - ariaLabel: or("computedContent.ariaLabel", "sanitizedTitle"), + selectKitId: computed("selectKit.uniqueID", function() { + return `${this.selectKit.uniqueID}-header`; + }), - @discourseComputed("computedContent.title", "name") - title(computedContentTitle, name) { - if (computedContentTitle) return computedContentTitle; - if (name) return name; + ariaIsExpanded: computed("selectKit.isExpanded", function() { + return this.selectKit.isExpanded ? "true" : "false"; + }), - return ""; + ariaHasPopup: true, + + ariaOwns: computed("selectKit.uniqueID", function() { + return `[data-select-kit-id=${this.selectKit.uniqueID}-body]`; + }), + + roleButton: "button", + + tabindex: 0, + + keyDown(event) { + if (this.selectKit.isDisabled) { + return; + } + + if (!this.selectKit.onKeydown(event)) { + return false; + } + + const onlyShiftKey = event.shiftKey && event.keyCode === 16; + if (event.metaKey || onlyShiftKey) { + return; + } + + if (event.keyCode === 13) { + // Enter + if (this.selectKit.isExpanded && this.selectKit.highlighted) { + this.selectKit.select(this.getValue(this.selectKit.highlighted)); + return false; + } else { + this.selectKit.toggle(event); + } + } else if (event.keyCode === 38) { + // Up arrow + if (this.selectKit.isExpanded) { + this.selectKit.highlightPrevious(); + } else { + this.selectKit.open(event); + } + return false; + } else if (event.keyCode === 40) { + // Down arrow + if (this.selectKit.isExpanded) { + this.selectKit.highlightNext(); + } else { + this.selectKit.open(event); + } + return false; + } else if (event.keyCode === 37 || event.keyCode === 39) { + // Do nothing for left/right arrow + return true; + } else if (event.keyCode === 32) { + // Space + event.preventDefault(); // prevents the space to trigger a scroll page-next + this.selectKit.toggle(event); + } else if (event.keyCode === 27) { + // Escape + this.selectKit.close(event); + } else if (event.keyCode === 8) { + // Backspace + this._focusFilterInput(); + } else if (event.keyCode === 9) { + // Tab + if (this.selectKit.highlighted && this.selectKit.isExpanded) { + this.selectKit.select(this.getValue(this.selectKit.highlighted)); + } + this.selectKit.close(event); + } else if ( + this.selectKit.options.filterable || + this.selectKit.options.autoFilterable || + this.selectKit.options.allowAny + ) { + if (this.selectKit.isExpanded) { + this._focusFilterInput(); + } else { + this.selectKit.open(event); + schedule("afterRender", () => this._focusFilterInput()); + } + } else { + if (this.selectKit.isExpanded) { + return false; + } else { + return true; + } + } }, - // this might need a more advanced solution - // but atm it's the only case we have to handle - @discourseComputed("title") - sanitizedTitle(title) { - return String(title).replace("…", ""); - }, + _focusFilterInput() { + const filterContainer = document.querySelector( + `[data-select-kit-id=${this.selectKit.uniqueID}-filter]` + ); - label: or("computedContent.label", "title", "name"), + if (filterContainer) { + filterContainer.style.display = "flex"; - name: alias("computedContent.name"), - - value: alias("computedContent.value"), - - @discourseComputed("computedContent.icon", "computedContent.icons") - icons(icon, icons) { - return makeArray(icon) - .concat(icons) - .filter(i => !isEmpty(i)); - }, - - click() { - this.onToggle(); + const filterInput = filterContainer.querySelector(".filter-input"); + filterInput && filterInput.focus(); + } } }); diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 index 7ab8ff112fd..a4a7fb6d1a1 100644 --- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 @@ -1,8 +1,7 @@ -import { alias, or } from "@ember/object/computed"; import Component from "@ember/component"; -import discourseComputed, { on } from "discourse-common/utils/decorators"; - -const { run, isPresent, makeArray, isEmpty } = Ember; +import { propertyEqual } from "discourse/lib/computed"; +import { computed } from "@ember/object"; +import { makeArray } from "discourse-common/lib/helpers"; import UtilsMixin from "select-kit/mixins/utils"; export default Component.extend(UtilsMixin, { @@ -13,85 +12,80 @@ export default Component.extend(UtilsMixin, { attributeBindings: [ "tabIndex", "title", - "value:data-value", - "name:data-name", + "rowValue:data-value", + "rowName:data-name", "ariaLabel:aria-label", "guid:data-guid" ], classNameBindings: [ "isHighlighted", "isSelected", - "computedContent.originalContent.classNames" + "isNone", + "isNone:none", + "item.classNames" ], - forceEscape: alias("options.forceEscape"), + isNone: computed("rowValue", function() { + return this.rowValue === this.getValue(this.selectKit.noneItem); + }), - ariaLabel: or("computedContent.ariaLabel", "title"), + guid: computed("item", function() { + return Ember.guidFor(this.item); + }), - @discourseComputed("computedContent.title", "name") - title(computedContentTitle, name) { - if (computedContentTitle) return computedContentTitle; - if (name) return name; + ariaLabel: computed("item.ariaLabel", "title", function() { + return this.getProperty(this.item, "ariaLabel") || this.title; + }), - return null; - }, + title: computed("item.title", "rowName", function() { + return this.getProperty(this.item, "title") || this.rowName; + }), - @discourseComputed("computedContent") - guid(computedContent) { - return Ember.guidFor(computedContent); - }, + label: computed("item.label", "title", "rowName", function() { + const label = + this.getProperty(this.item, "label") || this.title || this.rowName; + if ( + this.selectKit.options.allowAny && + this.rowValue === this.selectKit.filter && + this.getName(this.selectKit.noneItem) !== this.rowName + ) { + return I18n.t("select_kit.create", { content: label }); + } + return label; + }), - label: or("computedContent.label", "title", "name"), + didReceiveAttrs() { + this._super(...arguments); - name: alias("computedContent.name"), - - value: alias("computedContent.value"), - - @discourseComputed("templateForRow") - template(templateForRow) { - return templateForRow(this); - }, - - @on("didReceiveAttrs") - _setSelectionState() { this.setProperties({ - isSelected: this.computedValue === this.value, - isHighlighted: this.get("highlighted.value") === this.value + rowName: this.getName(this.item), + rowValue: this.getValue(this.item) }); }, - @on("willDestroyElement") - _clearDebounce() { - const hoverDebounce = this.hoverDebounce; - if (isPresent(hoverDebounce)) { - run.cancel(hoverDebounce); - } - }, + icons: computed("item.{icon,icons}", function() { + const icon = makeArray(this.getProperty(this.item, "icon")); + const icons = makeArray(this.getProperty(this.item, "icons")); + return icon.concat(icons).filter(Boolean); + }), - @discourseComputed( - "computedContent.icon", - "computedContent.icons", - "computedContent.originalContent.icon" - ) - icons(icon, icons, originalIcon) { - return makeArray(icon) - .concat(icons) - .concat(makeArray(originalIcon)) - .filter(i => !isEmpty(i)); - }, + highlightedValue: computed("selectKit.highlighted", function() { + return this.getValue(this.selectKit.highlighted); + }), + + isHighlighted: propertyEqual("rowValue", "highlightedValue"), + + isSelected: propertyEqual("rowValue", "value"), mouseEnter() { - this.set( - "hoverDebounce", - run.debounce(this, this._sendMouseoverAction, 32) - ); + if (!this.isDestroying || !this.isDestroyed) { + this.selectKit.onHover(this.rowValue, this.item); + } + return false; }, click() { - this.onClickRow(this.computedContent); - }, - - _sendMouseoverAction() { - this.onMouseoverRow(this.computedContent); + this.selectKit.select(this.rowValue, this.item); + return false; } }); diff --git a/app/assets/javascripts/select-kit/components/select-kit/single-select-header.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/single-select-header.js.es6 new file mode 100644 index 00000000000..1ff4a8c8a9b --- /dev/null +++ b/app/assets/javascripts/select-kit/components/select-kit/single-select-header.js.es6 @@ -0,0 +1,7 @@ +import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header"; +import UtilsMixin from "select-kit/mixins/utils"; + +export default SelectKitHeaderComponent.extend(UtilsMixin, { + layoutName: "select-kit/templates/components/select-kit/single-select-header", + classNames: ["single-select-header"] +}); diff --git a/app/assets/javascripts/select-kit/components/selected-color.js.es6 b/app/assets/javascripts/select-kit/components/selected-color.js.es6 new file mode 100644 index 00000000000..48e03ed8205 --- /dev/null +++ b/app/assets/javascripts/select-kit/components/selected-color.js.es6 @@ -0,0 +1,16 @@ +import SelectedNameComponent from "select-kit/components/selected-name"; +import { escapeExpression } from "discourse/lib/utilities"; +import { schedule } from "@ember/runloop"; + +export default SelectedNameComponent.extend({ + classNames: ["select-kit-selected-color"], + + didReceiveAttrs() { + this._super(...arguments); + + schedule("afterRender", () => { + const color = escapeExpression(this.name); + this.element.style.borderBottomColor = `#${color}`; + }); + } +}); diff --git a/app/assets/javascripts/select-kit/components/selected-name.js.es6 b/app/assets/javascripts/select-kit/components/selected-name.js.es6 new file mode 100644 index 00000000000..1f69a5e2eee --- /dev/null +++ b/app/assets/javascripts/select-kit/components/selected-name.js.es6 @@ -0,0 +1,64 @@ +import { computed } from "@ember/object"; +import Component from "@ember/component"; +import { makeArray } from "discourse-common/lib/helpers"; +import UtilsMixin from "select-kit/mixins/utils"; +import { get } from "@ember/object"; + +export default Component.extend(UtilsMixin, { + layoutName: "select-kit/templates/components/selected-name", + classNames: ["select-kit-selected-name", "selected-name", "choice"], + name: null, + value: null, + tabindex: 0, + attributeBindings: ["title", "value:data-value", "name:data-name"], + + click() { + if (this.selectKit.options.clearOnClick) { + this.selectKit.deselect(this.item); + return false; + } + }, + + didReceiveAttrs() { + this._super(...arguments); + + // we can't listen on `item.nameProperty` given it's variable + this.setProperties({ + name: this.getName(this.item), + value: + this.item === this.selectKit.noneItem ? null : this.getValue(this.item) + }); + }, + + ariaLabel: computed("item", "sanitizedTitle", function() { + return this._safeProperty("ariaLabel", this.item) || this.sanitizedTitle; + }), + + // this might need a more advanced solution + // but atm it's the only case we have to handle + sanitizedTitle: computed("title", function() { + return String(this.title).replace("…", ""); + }), + + title: computed("item", function() { + return this._safeProperty("title", this.item) || this.name || ""; + }), + + label: computed("title", "name", function() { + return this._safeProperty("label", this.item) || this.title || this.name; + }), + + icons: computed("item.{icon,icons}", function() { + const icon = makeArray(this._safeProperty("icon", this.item)); + const icons = makeArray(this._safeProperty("icons", this.item)); + return icon.concat(icons).filter(Boolean); + }), + + _safeProperty(name, content) { + if (!content) { + return null; + } + + return get(content, name); + } +}); diff --git a/app/assets/javascripts/select-kit/components/single-select.js.es6 b/app/assets/javascripts/select-kit/components/single-select.js.es6 index 42ccc4fa668..18f2ede6fb8 100644 --- a/app/assets/javascripts/select-kit/components/single-select.js.es6 +++ b/app/assets/javascripts/select-kit/components/single-select.js.es6 @@ -1,319 +1,36 @@ import SelectKitComponent from "select-kit/components/select-kit"; -import discourseComputed, { on } from "discourse-common/utils/decorators"; -const { get, isNone, isEmpty, isPresent, run, makeArray } = Ember; - -import { - applyOnSelectPluginApiCallbacks, - applyOnSelectNonePluginApiCallbacks -} from "select-kit/mixins/plugin-api"; +import { computed } from "@ember/object"; +import { isEmpty } from "@ember/utils"; export default SelectKitComponent.extend({ pluginApiIdentifiers: ["single-select"], layoutName: "select-kit/templates/components/single-select", - classNames: "single-select", - computedValue: null, - value: null, - allowInitialValueMutation: false, + classNames: ["single-select"], + singleSelect: true, - @on("didUpdateAttrs", "init") - _compute() { - run.scheduleOnce("afterRender", () => { - this.willComputeAttributes(); - let content = this.content || []; - let asyncContent = this.asyncContent || []; - content = this.willComputeContent(content); - asyncContent = this.willComputeAsyncContent(asyncContent); - let value = this._beforeWillComputeValue(this.value); - content = this.computeContent(content); - asyncContent = this.computeAsyncContent(asyncContent); - content = this._beforeDidComputeContent(content); - asyncContent = this._beforeDidComputeAsyncContent(asyncContent); - value = this.willComputeValue(value); - value = this.computeValue(value); - value = this._beforeDidComputeValue(value); - this.didComputeContent(content); - this.didComputeAsyncContent(asyncContent); - this.didComputeValue(value); - this.didComputeAttributes(); - - if (this.allowInitialValueMutation) this.mutateAttributes(); - }); + selectKitOptions: { + headerComponent: "select-kit/single-select-header" }, - mutateAttributes() { - run.next(() => { - if (this.isDestroyed || this.isDestroying) return; + selectedContent: computed("value", "content.[]", function() { + if (!isEmpty(this.value)) { + let content; - this.mutateContent(this.computedContent); - this.mutateValue(this.computedValue); - }); - }, - mutateContent() {}, - mutateValue(computedValue) { - this.set("value", computedValue); - }, - - forceValue(value) { - this.mutateValue(value); - this._compute(); - }, - - _beforeWillComputeValue(value) { - if ( - !isEmpty(this.content) && - isEmpty(value) && - isNone(this.none) && - this.allowAutoSelectFirst - ) { - value = this.valueForContentItem(get(this.content, "firstObject")); - } - - switch (typeof value) { - case "string": - case "number": - return this._cast(value === "" ? null : value); - default: - return value; - } - }, - willComputeValue(value) { - return value; - }, - computeValue(value) { - return value; - }, - _beforeDidComputeValue(value) { - this.setProperties({ computedValue: value }); - return value; - }, - didComputeValue(value) { - return value; - }, - - filterComputedContent(computedContent, computedValue, filter) { - return computedContent.filter(c => { - return this._normalize(get(c, "name")).indexOf(filter) > -1; - }); - }, - - computeHeaderContent() { - let content = { - title: this.title, - icons: makeArray(this.getWithDefault("headerIcon", [])), - value: this.get("selection.value"), - name: - this.get("selection.name") || this.get("noneRowComputedContent.name") - }; - - if (this.noneLabel && !this.hasSelection) { - content.title = content.name = I18n.t(this.noneLabel); - } - - return content; - }, - - @discourseComputed("computedAsyncContent.[]", "computedValue") - filteredAsyncComputedContent(computedAsyncContent, computedValue) { - computedAsyncContent = (computedAsyncContent || []).filter(c => { - return computedValue !== get(c, "value"); - }); - - if (this.limitMatches) { - return computedAsyncContent.slice(0, this.limitMatches); - } - - return computedAsyncContent; - }, - - @discourseComputed( - "computedContent.[]", - "computedValue", - "filter", - "shouldFilter" - ) - filteredComputedContent( - computedContent, - computedValue, - filter, - shouldFilter - ) { - if (shouldFilter) { - computedContent = this.filterComputedContent( - computedContent, - computedValue, - this._normalize(filter) - ); - } - - if (this.limitMatches) { - return computedContent.slice(0, this.limitMatches); - } - - return computedContent; - }, - - @discourseComputed("computedValue", "computedContent.[]") - selection(computedValue, computedContent) { - return computedContent.findBy("value", computedValue); - }, - - @discourseComputed("selection") - hasSelection(selection) { - return selection !== this.noneRowComputedContent && !isNone(selection); - }, - - @discourseComputed( - "computedValue", - "filter", - "collectionComputedContent.[]", - "hasReachedMaximum", - "hasReachedMinimum" - ) - shouldDisplayCreateRow(computedValue, filter) { - return this._super() && computedValue !== filter; - }, - - autoHighlight() { - run.schedule("afterRender", () => { - if (this.shouldDisplayCreateRow) { - this.highlight(this.createRowComputedContent); - return; - } - - if (!isEmpty(this.filter) && !isEmpty(this.collectionComputedContent)) { - this.highlight(this.get("collectionComputedContent.firstObject")); - return; - } - - if (!this.isAsync && this.hasSelection && isEmpty(this.filter)) { - this.highlight(get(makeArray(this.selection), "firstObject")); - return; - } - - if ( - !this.isAsync && - !this.hasSelection && - isEmpty(this.filter) && - !isEmpty(this.collectionComputedContent) - ) { - this.highlight(this.get("collectionComputedContent.firstObject")); - return; - } - - if (isPresent(this.noneRowComputedContent)) { - this.highlight(this.noneRowComputedContent); - return; - } - }); - }, - - select(computedContentItem) { - if (computedContentItem.__sk_row_type === "noopRow") { - applyOnSelectPluginApiCallbacks( - this.pluginApiIdentifiers, - computedContentItem.value, - this - ); - - this._boundaryActionHandler("onSelect", computedContentItem.value); - this._boundaryActionHandler("onSelectAny", computedContentItem); - return; - } - - if (this.hasSelection) { - this.deselect(this.get("selection.value")); - } - - if ( - !computedContentItem || - computedContentItem.__sk_row_type === "noneRow" - ) { - applyOnSelectNonePluginApiCallbacks(this.pluginApiIdentifiers, this); - this._boundaryActionHandler("onSelectNone"); - this._boundaryActionHandler("onSelectAny", computedContentItem); - this.clearSelection(); - return; - } - - if (computedContentItem.__sk_row_type === "createRow") { - if ( - this.computedValue !== computedContentItem.value && - this.validateCreate(computedContentItem.value) - ) { - this.willCreate(computedContentItem); - computedContentItem.__sk_row_type = null; - this.computedContent.pushObject(computedContentItem); - - run.schedule("afterRender", () => { - this.didCreate(computedContentItem); - this._boundaryActionHandler("onCreate"); - }); - - this.select(computedContentItem); - return; - } else { - this._boundaryActionHandler("onCreateFailure"); - return; - } - } - - if (this.validateSelect(computedContentItem)) { - this.willSelect(computedContentItem); - this.clearFilter(); - - const action = computedContentItem.originalContent.action; - if (action) { - action(); - } else { - this.setProperties({ - highlighted: null, - computedValue: computedContentItem.value - }); - - run.next(() => this.mutateAttributes()); - } - - run.schedule("afterRender", () => { - this.didSelect(computedContentItem); - - applyOnSelectPluginApiCallbacks( - this.pluginApiIdentifiers, - computedContentItem.value, - this + if (this.selectKit.valueProperty) { + content = (this.content || []).findBy( + this.selectKit.valueProperty, + this.value ); - - this._boundaryActionHandler( - "onSelect", - computedContentItem.value, - computedContentItem.originalContent + return this.selectKit.modifySelection( + content || this.defaultItem(this.value, this.value) ); - this._boundaryActionHandler("onSelectAny", computedContentItem); - - this.autoHighlight(); - }); + } else { + return this.selectKit.modifySelection( + (this.content || []).filter(c => c === this.value) + ); + } } else { - this._boundaryActionHandler("onSelectFailure"); + return this.selectKit.noneItem; } - }, - - deselect(computedContentItem) { - makeArray(computedContentItem).forEach(item => { - this.willDeselect(item); - - this.clearFilter(); - - this.setProperties({ - computedValue: null, - highlighted: null, - highlightedSelection: [] - }); - - run.next(() => this.mutateAttributes()); - run.schedule("afterRender", () => { - this.didDeselect(item); - this._boundaryActionHandler("onDeselect", item); - this.autoHighlight(); - }); - }); - } + }) }); diff --git a/app/assets/javascripts/select-kit/components/tag-chooser-row.js.es6 b/app/assets/javascripts/select-kit/components/tag-chooser-row.js.es6 new file mode 100644 index 00000000000..3013c3df1ee --- /dev/null +++ b/app/assets/javascripts/select-kit/components/tag-chooser-row.js.es6 @@ -0,0 +1,6 @@ +import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row"; + +export default SelectKitRowComponent.extend({ + layoutName: "select-kit/templates/components/tag-chooser-row", + classNames: ["tag-chooser-row"] +}); diff --git a/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 b/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 index 103a294060d..ff43a16b660 100644 --- a/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 @@ -1,110 +1,78 @@ -import { alias } from "@ember/object/computed"; +import { computed } from "@ember/object"; import MultiSelectComponent from "select-kit/components/multi-select"; import TagsMixin from "select-kit/mixins/tags"; -import renderTag from "discourse/lib/render-tag"; -import discourseComputed from "discourse-common/utils/decorators"; import { makeArray } from "discourse-common/lib/helpers"; -const { get, run } = Ember; export default MultiSelectComponent.extend(TagsMixin, { pluginApiIdentifiers: ["tag-chooser"], - classNames: "tag-chooser", - isAsync: true, - filterable: true, - filterPlaceholder: "tagging.choose_for_topic", - limit: null, + classNames: ["tag-chooser"], + + selectKitOptions: { + filterable: true, + filterPlaceholder: "tagging.choose_for_topic", + limit: null, + allowAny: "canCreateTag", + maximum: "maximumTagCount" + }, + + modifyComponentForRow() { + return "tag-chooser-row"; + }, + blacklist: null, attributeBindings: ["categoryId"], - allowCreate: null, - allowAny: alias("allowCreate"), excludeSynonyms: false, excludeHasSynonyms: false, + canCreateTag: computed("site.can_create_tag", "allowCreate", function() { + return this.allowCreate || this.site.can_create_tag; + }), + + maximumTagCount: computed( + "siteSettings.max_tags_per_topic", + "unlimitedTagCount", + function() { + if (!this.unlimitedTagCount) { + return parseInt( + this.options.limit || + this.options.maximum || + this.get("siteSettings.max_tags_per_topic"), + 10 + ); + } + + return null; + } + ), + init() { this._super(...arguments); - if (this.allowCreate !== false) { - this.set("allowCreate", this.site.get("can_create_tag")); - } - - if (!this.blacklist) { - this.set("blacklist", []); - } - - this.set("termMatchesForbidden", false); - this.set("termMatchErrorMessage", null); - - this.set("templateForRow", rowComponent => { - const tag = rowComponent.get("computedContent"); - return renderTag(get(tag, "value"), { - count: get(tag, "originalContent.count"), - noHref: true - }); + this.setProperties({ + blacklist: this.blacklist || [], + termMatchesForbidden: false, + termMatchErrorMessage: null }); - - if (!this.unlimitedTagCount) { - this.set( - "maximum", - parseInt( - this.limit || - this.maximum || - this.get("siteSettings.max_tags_per_topic"), - 10 - ) - ); - } }, - mutateValues(values) { - this.set( - "tags", - values.filter(v => v) - ); - }, + value: computed("tags.[]", function() { + return makeArray(this.tags).uniq(); + }), - @discourseComputed("tags") - values(tags) { - return makeArray(tags); - }, - - @discourseComputed("tags") - content(tags) { - return makeArray(tags); - }, + content: computed("tags.[]", function() { + return makeArray(this.tags) + .uniq() + .map(t => this.defaultItem(t, t)); + }), actions: { - onFilter(filter) { - this.expand(); - this.set( - "searchDebounce", - run.debounce(this, this._prepareSearch, filter, 200) - ); - }, - - onExpand() { - this.set( - "searchDebounce", - run.debounce(this, this._prepareSearch, this.filter, 200) - ); - }, - - onDeselect() { - this.set( - "searchDebounce", - run.debounce(this, this._prepareSearch, this.filter, 200) - ); - }, - - onSelect() { - this.set( - "searchDebounce", - run.debounce(this, this._prepareSearch, this.filter, 50) - ); + onChange(value) { + this.set("tags", value); } }, - _prepareSearch(query) { - const selectedTags = makeArray(this.values).filter(t => t); + search(query) { + const selectedTags = makeArray(this.tags).filter(Boolean); const data = { q: query, @@ -113,28 +81,30 @@ export default MultiSelectComponent.extend(TagsMixin, { }; if (selectedTags.length || this.blacklist.length) { - data.selected_tags = _.uniq(selectedTags.concat(this.blacklist)).slice( - 0, - 100 - ); + data.selected_tags = selectedTags + .concat(this.blacklist) + .uniq() + .slice(0, 100); } if (!this.everyTag) data.filterForInput = true; if (this.excludeSynonyms) data.excludeSynonyms = true; if (this.excludeHasSynonyms) data.excludeHasSynonyms = true; - this.searchTags("/tags/filter/search", data, this._transformJson); + return this.searchTags("/tags/filter/search", data, this._transformJson); }, _transformJson(context, json) { let results = json.results; - context.set("termMatchesForbidden", json.forbidden ? true : false); - context.set("termMatchErrorMessage", json.forbidden_message); + context.setProperties({ + termMatchesForbidden: json.forbidden ? true : false, + termMatchErrorMessage: json.forbidden_message + }); - if (context.get("blacklist")) { + if (context.blacklist) { results = results.filter(result => { - return !context.get("blacklist").includes(result.id); + return !context.blacklist.includes(result.id); }); } @@ -142,10 +112,8 @@ export default MultiSelectComponent.extend(TagsMixin, { results = results.sort((a, b) => a.id > b.id); } - results = results.map(result => { + return results.uniqBy("text").map(result => { return { id: result.text, name: result.text, count: result.count }; }); - - return results; } }); diff --git a/app/assets/javascripts/select-kit/components/tag-drop.js.es6 b/app/assets/javascripts/select-kit/components/tag-drop.js.es6 index 3e80f3ce8a0..fa25372b911 100644 --- a/app/assets/javascripts/select-kit/components/tag-drop.js.es6 +++ b/app/assets/javascripts/select-kit/components/tag-drop.js.es6 @@ -1,227 +1,175 @@ -import { computed } from "@ember/object"; -import { alias } from "@ember/object/computed"; -import { makeArray } from "discourse-common/lib/helpers"; +import Category from "discourse/models/category"; +import { readOnly, or, equal, gte } from "@ember/object/computed"; +import { i18n, setting } from "discourse/lib/computed"; import ComboBoxComponent from "select-kit/components/combo-box"; import DiscourseURL from "discourse/lib/url"; import TagsMixin from "select-kit/mixins/tags"; -import discourseComputed from "discourse-common/utils/decorators"; -const { isEmpty, run } = Ember; -import Category from "discourse/models/category"; -import deprecated from "discourse-common/lib/deprecated"; +import { computed } from "@ember/object"; +import { isEmpty } from "@ember/utils"; +import { makeArray } from "discourse-common/lib/helpers"; + +export const NO_TAG_ID = "no-tags"; +export const ALL_TAGS_ID = "all-tags"; +export const NONE_TAG_ID = "none"; export default ComboBoxComponent.extend(TagsMixin, { pluginApiIdentifiers: ["tag-drop"], classNameBindings: ["categoryStyle", "tagClass"], - classNames: "tag-drop", - verticalOffset: 3, - value: alias("tagId"), - headerComponent: "tag-drop/tag-drop-header", - allowAutoSelectFirst: false, + classNames: ["tag-drop"], + value: readOnly("tagId"), tagName: "li", - showFilterByTag: alias("siteSettings.show_filter_by_tag"), - tagId: null, - categoryStyle: alias("siteSettings.category_style"), - mutateAttributes() {}, - fullWidthOnMobile: true, - caretDownIcon: "caret-right", - caretUpIcon: "caret-down", - allowContentReplacement: true, - isAsync: true, - - currentCategory: computed("secondCategory", "firstCategory", { - set(key, value) { - this.currentCategoryRaw = value; - return value; - }, - - get() { - if (this.currentCategoryRaw) { - return this.currentCategoryRaw; - } - - const result = this.secondCategory || this.firstCategory; - if (result) { - deprecated( - "Setting firstCategory and secondCategory on tag-drop directly is deprecated. Please use currentCategory instead." - ); - return result; - } + currentCategory: or("secondCategory", "firstCategory"), + showFilterByTag: setting("show_filter_by_tag"), + categoryStyle: setting("category_style"), + maxTagSearchResults: setting("max_tag_search_results"), + sortTagsAlphabetically: setting("tags_sort_alphabetically"), + isVisible: computed("showFilterByTag", "content.[]", function() { + if (this.showFilterByTag && !isEmpty(this.content)) { + return true; } + + return false; }), - @discourseComputed("tagId") - noTagsSelected() { - return this.tagId === "none"; + selectKitOptions: { + allowAny: false, + caretDownIcon: "caret-right", + caretUpIcon: "caret-down", + fullWidthOnMobile: true, + filterable: true, + headerComponent: "tag-drop/tag-drop-header", + autoInsertNoneItem: false }, - @discourseComputed("showFilterByTag", "content") - isHidden(showFilterByTag, content) { - if (showFilterByTag && !isEmpty(content)) return false; - return true; - }, + noTagsSelected: equal("tagId", NONE_TAG_ID), - @discourseComputed("content") - filterable(content) { - return content && content.length >= 15; - }, + filterable: gte("content.length", 15), - computeHeaderContent() { - let content = this._super(...arguments); - - if (!content.value) { - if (this.tagId) { - if (this.tagId === "none") { - content.title = this.noTagsLabel; - } else { - content.title = this.tagId; - } - } else if (this.noTagsSelected) { - content.title = this.noTagsLabel; - } else { - content.title = this.allTagsLabel; - } + modifyNoSelection() { + if (this.noTagsSelected) { + return this.defaultItem(NO_TAG_ID, this.noTagsLabel); } else { - content.title = content.value; + return this.defaultItem(ALL_TAGS_ID, this.allTagsLabel); + } + }, + + modifySelection(content) { + if (this.tagId) { + if (this.noTagsSelected) { + content = this.defaultItem(NO_TAG_ID, this.noTagsLabel); + } else { + content = this.defaultItem(this.tagId, this.tagId); + } } return content; }, - @discourseComputed("tagId") - tagClass(tagId) { - return tagId ? `tag-${tagId}` : "tag_all"; - }, + tagClass: computed("tagId", function() { + return this.tagId ? `tag-${this.tagId}` : "tag_all"; + }), - @discourseComputed("currentCategory") - allTagsUrl() { + currentCategoryUrl: readOnly("currentCategory.url"), + + allTagsUrl: computed("firstCategory", "secondCategory", function() { if (this.currentCategory) { - return Discourse.getURL(this.get("currentCategory.url") + "?allTags=1"); + return Discourse.getURL(`${this.currentCategoryUrl}?allTags=1`); } else { return Discourse.getURL("/"); } - }, + }), - @discourseComputed("currentCategory") - noTagsUrl(currentCategory) { + noTagsUrl: computed("firstCategory", "secondCategory", function() { let url = "/tags"; - - if (currentCategory) { - url += `/c/${Category.slugFor(currentCategory)}/${currentCategory.id}`; + if (this.currentCategory) { + url += `/c/${Category.slugFor(this.currentCategory)}/${ + this.currentCategory.id + }`; } + return Discourse.getURL(`${url}/${NONE_TAG_ID}`); + }), - return Discourse.getURL(`${url}/none`); - }, + allTagsLabel: i18n("tagging.selector_all_tags"), - @discourseComputed("tag") - allTagsLabel() { - return I18n.t("tagging.selector_all_tags"); - }, + noTagsLabel: i18n("tagging.selector_no_tags"), - @discourseComputed("tag") - noTagsLabel() { - return I18n.t("tagging.selector_no_tags"); - }, - - @discourseComputed("tagId", "allTagsLabel", "noTagsLabel") - shortcuts(tagId, allTagsLabel, noTagsLabel) { + shortcuts: computed("tagId", function() { const shortcuts = []; - if (tagId !== "none") { - shortcuts.push({ - name: noTagsLabel, - __sk_row_type: "noopRow", - id: "no-tags" - }); + if (this.tagId !== NONE_TAG_ID) { + shortcuts.push(NO_TAG_ID); } - if (tagId) { - shortcuts.push({ - name: allTagsLabel, - __sk_row_type: "noopRow", - id: "all-tags" - }); + if (this.tagId) { + shortcuts.push(ALL_TAGS_ID); } return shortcuts; - }, + }), - @discourseComputed("site.top_tags", "shortcuts") - content(topTags, shortcuts) { - if (this.siteSettings.tags_sort_alphabetically && topTags) { - return shortcuts.concat(topTags.sort()); + topTags: readOnly("site.top_tags.[]"), + + content: computed("topTags.[]", "shortcuts.[]", function() { + if (this.sortTagsAlphabetically && this.topTags) { + return this.shortcuts.concat(this.topTags.sort()); } else { - return shortcuts.concat(makeArray(topTags)); + return this.shortcuts.concat(makeArray(this.topTags)); } - }, + }), - _prepareSearch(query) { - const data = { - q: query, - limit: this.get("siteSettings.max_tag_search_results") - }; + search(filter) { + if (filter) { + const data = { + q: filter, + limit: this.maxTagSearchResults + }; - this.searchTags("/tags/filter/search", data, this._transformJson); + return this.searchTags("/tags/filter/search", data, this._transformJson); + } else { + return (this.content || []).map(tag => this.defaultItem(tag, tag)); + } }, _transformJson(context, json) { - let results = json.results; - results = results.sort((a, b) => a.id > b.id); - - return results.map(r => { - return { - id: r.id, - name: r.text, - targetTagId: r.target_tag || r.id - }; - }); + return json.results + .sort((a, b) => a.id > b.id) + .map(r => { + const content = context.defaultItem(r.id, r.text); + content.targetTagId = r.target_tag || r.id; + content.count = r.count; + content.pmCount = r.pm_count; + return content; + }); }, actions: { - onSelect(tagId, tag) { + onChange(tagId, tag) { let url; - if (tagId === "all-tags") { - url = Discourse.getURL(this.allTagsUrl); - } else if (tagId === "no-tags") { - url = Discourse.getURL(this.noTagsUrl); - } else { - if (this.currentCategory) { - url = `/tags/c/${Category.slugFor(this.currentCategory)}/${ - this.currentCategory.id - }`; - } else { - url = "/tag"; - } + switch (tagId) { + case ALL_TAGS_ID: + url = this.allTagsUrl; + break; + case NO_TAG_ID: + url = this.noTagsUrl; + break; + default: + if (this.currentCategory) { + url = `/tags/c/${Category.slugFor(this.currentCategory)}/${ + this.currentCategory.id + }`; + } else { + url = "/tag"; + } - if (tag && tag.targetTagId) { - url += `/${tag.targetTagId.toLowerCase()}`; - } else { - url += `/${tagId.toLowerCase()}`; - } - url = Discourse.getURL(url); + if (tag && tag.targetTagId) { + url += `/${tag.targetTagId.toLowerCase()}`; + } else { + url += `/${tagId.toLowerCase()}`; + } } - DiscourseURL.routeTo(url); - }, - - onExpand() { - if (isEmpty(this.asyncContent)) { - this.set("asyncContent", this.content); - } - }, - - onFilter(filter) { - if (isEmpty(filter)) { - this.set("asyncContent", this.content); - return; - } - - this.startLoading(); - - this.set( - "searchDebounce", - run.debounce(this, this._prepareSearch, filter, 350) - ); + DiscourseURL.routeTo(Discourse.getURL(url)); } } }); diff --git a/app/assets/javascripts/select-kit/components/tag-group-chooser.js.es6 b/app/assets/javascripts/select-kit/components/tag-group-chooser.js.es6 index 2843ee1e0c3..8857d8cd63f 100644 --- a/app/assets/javascripts/select-kit/components/tag-group-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/tag-group-chooser.js.es6 @@ -1,97 +1,63 @@ import MultiSelectComponent from "select-kit/components/multi-select"; import TagsMixin from "select-kit/mixins/tags"; -import renderTag from "discourse/lib/render-tag"; -import discourseComputed from "discourse-common/utils/decorators"; - -const { get, isEmpty, run, makeArray } = Ember; +import { makeArray } from "discourse-common/lib/helpers"; +import { computed } from "@ember/object"; export default MultiSelectComponent.extend(TagsMixin, { pluginApiIdentifiers: ["tag-group-chooser"], classNames: ["tag-group-chooser", "tag-chooser"], - isAsync: true, - filterable: true, - filterPlaceholder: "category.tag_groups_placeholder", - limit: null, - allowAny: false, - init() { - this._super(...arguments); - - this.set("templateForRow", rowComponent => { - const tag = rowComponent.get("computedContent"); - return renderTag(get(tag, "value"), { - count: get(tag, "originalContent.count"), - noHref: true - }); - }); + selectKitOptions: { + allowAny: false, + filterable: true, + filterPlaceholder: "category.tag_groups_placeholder", + limit: null }, - mutateValues(values) { - this.set( - "tagGroups", - values.filter(v => v) - ); + modifyComponentForRow() { + return "tag-chooser-row"; }, - @discourseComputed("tagGroups") - values(tagGroups) { - return makeArray(tagGroups); - }, + value: computed("tagGroups.[]", function() { + return makeArray(this.tagGroups).uniq(); + }), - @discourseComputed("tagGroups") - content(tagGroups) { - return makeArray(tagGroups); - }, + content: computed("tagGroups.[]", function() { + return makeArray(this.tagGroups) + .uniq() + .map(t => this.defaultItem(t, t)); + }), actions: { - onFilter(filter) { - this.expand(); - this.set( - "searchDebounce", - run.debounce(this, this._prepareSearch, filter, 200) - ); - }, - - onExpand() { - if (isEmpty(this.collectionComputedContent)) { - this.set( - "searchDebounce", - run.debounce(this, this._prepareSearch, this.filter, 200) - ); - } - }, - - onDeselect() { - this.set( - "searchDebounce", - run.debounce(this, this._prepareSearch, this.filter, 200) - ); - }, - - onSelect() { - this.set( - "searchDebounce", - run.debounce(this, this._prepareSearch, this.filter, 50) - ); + onChange(value) { + this.set("tagGroups", value); } }, - _prepareSearch(query) { + search(query) { const data = { q: query, limit: this.get("siteSettings.max_tag_search_results") }; - this.searchTags("/tag_groups/filter/search", data, this._transformJson); + return this.searchTags( + "/tag_groups/filter/search", + data, + this._transformJson + ).then(results => { + if (results && results.length) { + return results.filter(r => { + return !this.tagGroups.includes(this.getValue(r)); + }); + } + }); }, _transformJson(context, json) { - let results = json.results.sort((a, b) => a.id > b.id); - - results = results.map(result => { - return { id: result.text, name: result.text, count: result.count }; - }); - - return results; + return json.results + .sort((a, b) => a.id > b.id) + .map(result => { + return { id: result.text, name: result.text, count: result.count }; + }); } }); diff --git a/app/assets/javascripts/select-kit/components/tag-notifications-button.js.es6 b/app/assets/javascripts/select-kit/components/tag-notifications-button.js.es6 index c163c3d5d92..67f961fbe36 100644 --- a/app/assets/javascripts/select-kit/components/tag-notifications-button.js.es6 +++ b/app/assets/javascripts/select-kit/components/tag-notifications-button.js.es6 @@ -1,23 +1,13 @@ -import NotificationOptionsComponent from "select-kit/components/notifications-button"; -import discourseComputed from "discourse-common/utils/decorators"; +import NotificationsButtonComponent from "select-kit/components/notifications-button"; -export default NotificationOptionsComponent.extend({ +export default NotificationsButtonComponent.extend({ pluginApiIdentifiers: ["tag-notifications-button"], - classNames: "tag-notifications-button", - i18nPrefix: "tagging.notifications", - showFullTitle: false, - allowInitialValueMutation: false, + classNames: ["tag-notifications-button"], - mutateValue(value) { - this.action(value); + selectKitOptions: { + showFullTitle: false, + i18nPrefix: "i18nPrefix" }, - computeValue() { - return this.notificationLevel; - }, - - @discourseComputed("iconForSelectedDetails") - headerIcon(iconForSelectedDetails) { - return iconForSelectedDetails; - } + i18nPrefix: "tagging.notifications" }); diff --git a/app/assets/javascripts/select-kit/components/tag-row.js.es6 b/app/assets/javascripts/select-kit/components/tag-row.js.es6 new file mode 100644 index 00000000000..7ababa8f148 --- /dev/null +++ b/app/assets/javascripts/select-kit/components/tag-row.js.es6 @@ -0,0 +1,6 @@ +import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row"; + +export default SelectKitRowComponent.extend({ + layoutName: "select-kit/templates/components/tag-row", + classNames: ["tag-row"] +}); diff --git a/app/assets/javascripts/select-kit/components/timezone-input.js.es6 b/app/assets/javascripts/select-kit/components/timezone-input.js.es6 index 8c52564f052..10a5c3ea2c7 100644 --- a/app/assets/javascripts/select-kit/components/timezone-input.js.es6 +++ b/app/assets/javascripts/select-kit/components/timezone-input.js.es6 @@ -1,31 +1,25 @@ import ComboBoxComponent from "select-kit/components/combo-box"; -import discourseComputed from "discourse-common/utils/decorators"; +import { computed } from "@ember/object"; export default ComboBoxComponent.extend({ pluginApiIdentifiers: ["timezone-input"], - classNames: "timezone-input", - allowAutoSelectFirst: false, - fullWidthOnMobile: true, - filterable: true, - allowAny: false, + classNames: ["timezone-input"], + nameProperty: null, + valueProperty: null, - @discourseComputed - content() { - let timezones; + selectKitOptions: { + filterable: true, + allowAny: false + }, + content: computed(function() { if ( moment.locale() !== "en" && typeof moment.tz.localizedNames === "function" ) { - timezones = moment.tz.localizedNames(); + return moment.tz.localizedNames().mapBy("value"); + } else { + return moment.tz.names(); } - timezones = moment.tz.names(); - - return timezones.map(t => { - return { - id: t, - name: t - }; - }); - } + }) }); diff --git a/app/assets/javascripts/select-kit/components/toolbar-popup-menu-options.js.es6 b/app/assets/javascripts/select-kit/components/toolbar-popup-menu-options.js.es6 index 8e25330942a..9c3c619bf19 100644 --- a/app/assets/javascripts/select-kit/components/toolbar-popup-menu-options.js.es6 +++ b/app/assets/javascripts/select-kit/components/toolbar-popup-menu-options.js.es6 @@ -1,35 +1,46 @@ -import { empty } from "@ember/object/computed"; import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; -import discourseComputed from "discourse-common/utils/decorators"; + +const HEADING_COLLECTION = "HEADING_COLLECTION"; export default DropdownSelectBoxComponent.extend({ pluginApiIdentifiers: ["toolbar-popup-menu-options"], classNames: ["toolbar-popup-menu-options"], - isHidden: empty("computedContent"), - showFullTitle: false, - @discourseComputed("title") - collectionHeader(title) { - return `

    ${title}

    `; + init() { + this._super(...arguments); + + this.prependCollection(HEADING_COLLECTION); }, - autoHighlight() {}, + selectKitOptions: { + showFullTitle: false, + filterable: false, + autoFilterable: false + }, - computeContent(content) { - return content - .map(contentItem => { - if (contentItem.condition) { + modifyContentForCollection(collection) { + if (collection === HEADING_COLLECTION) { + return { title: this.selectKit.options.popupTitle }; + } + }, + + modifyComponentForCollection(collection) { + if (collection === HEADING_COLLECTION) { + return "toolbar-popup-menu-options/toolbar-popup-menu-options-heading"; + } + }, + + modifyContent(contents) { + return contents + .map(content => { + if (content.condition) { return { - icon: contentItem.icon, - name: I18n.t(contentItem.label), - id: contentItem.action + icon: content.icon, + name: I18n.t(content.label), + id: content.action }; } }) - .filter(contentItem => contentItem); - }, - - // composer is triggering a focus on textarea, we avoid instantly closing - // popup menu by tweaking the focus out behavior - onFilterInputFocusout() {} + .filter(Boolean); + } }); diff --git a/app/assets/javascripts/select-kit/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.js.es6 b/app/assets/javascripts/select-kit/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.js.es6 new file mode 100644 index 00000000000..1625cf3e5b6 --- /dev/null +++ b/app/assets/javascripts/select-kit/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.js.es6 @@ -0,0 +1,10 @@ +import Component from "@ember/component"; +import { reads } from "@ember/object/computed"; + +export default Component.extend({ + tagName: "h3", + layoutName: + "select-kit/templates/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading", + classNames: ["toolbar-popup-menu-options-heading"], + heading: reads("collection.content.title") +}); diff --git a/app/assets/javascripts/select-kit/components/topic-footer-mobile-dropdown.js.es6 b/app/assets/javascripts/select-kit/components/topic-footer-mobile-dropdown.js.es6 index 07d665486c6..b924b924955 100644 --- a/app/assets/javascripts/select-kit/components/topic-footer-mobile-dropdown.js.es6 +++ b/app/assets/javascripts/select-kit/components/topic-footer-mobile-dropdown.js.es6 @@ -1,36 +1,18 @@ -import { empty } from "@ember/object/computed"; import ComboBoxComponent from "select-kit/components/combo-box"; export default ComboBoxComponent.extend({ pluginApiIdentifiers: ["topic-footer-mobile-dropdown"], - classNames: "topic-footer-mobile-dropdown", - filterable: false, - autoFilterable: false, - allowInitialValueMutation: false, - allowAutoSelectFirst: false, - nameProperty: "label", - isHidden: empty("content"), + classNames: ["topic-footer-mobile-dropdown"], - computeHeaderContent() { - const content = this._super(...arguments); - - content.name = I18n.t("topic.controls"); - return content; + selectKitOptions: { + none: "topic.controls", + filterable: false, + autoFilterable: false }, - mutateAttributes() {}, - - willComputeContent(content) { - content = this._super(content); - - // TODO: this is for backward compat reasons, should be removed - // when plugins have been updated for long enough - content.forEach(c => { - if (c.name) { - c.label = c.name; - } - }); - - return content; + actions: { + onChange(value, item) { + item.action && item.action(); + } } }); diff --git a/app/assets/javascripts/select-kit/components/topic-notifications-button.js.es6 b/app/assets/javascripts/select-kit/components/topic-notifications-button.js.es6 index 399e9b0e2f4..efca6b77c8a 100644 --- a/app/assets/javascripts/select-kit/components/topic-notifications-button.js.es6 +++ b/app/assets/javascripts/select-kit/components/topic-notifications-button.js.es6 @@ -1,7 +1,16 @@ import Component from "@ember/component"; + export default Component.extend({ layoutName: "select-kit/templates/components/topic-notifications-button", - classNames: "topic-notifications-button", + classNames: ["topic-notifications-button"], + appendReason: true, showFullTitle: true, - appendReason: true + + actions: { + changeTopicNotificationLevel(newNotificationLevel) { + if (newNotificationLevel !== this.notificationLevel) { + this.topic.details.updateNotifications(newNotificationLevel); + } + } + } }); diff --git a/app/assets/javascripts/select-kit/components/topic-notifications-options.js.es6 b/app/assets/javascripts/select-kit/components/topic-notifications-options.js.es6 index b81c7a43a82..a1f98fa6b57 100644 --- a/app/assets/javascripts/select-kit/components/topic-notifications-options.js.es6 +++ b/app/assets/javascripts/select-kit/components/topic-notifications-options.js.es6 @@ -1,40 +1,32 @@ -import NotificationOptionsComponent from "select-kit/components/notifications-button"; -import discourseComputed, { on } from "discourse-common/utils/decorators"; +import NotificationsButtonComponent from "select-kit/components/notifications-button"; import { topicLevels } from "discourse/lib/notification-levels"; +import { computed } from "@ember/object"; -export default NotificationOptionsComponent.extend({ +export default NotificationsButtonComponent.extend({ pluginApiIdentifiers: ["topic-notifications-options"], - classNames: "topic-notifications-options", + classNames: ["topic-notifications-options"], content: topicLevels, + + selectKitOptions: { + i18nPrefix: "i18nPrefix", + i18nPostfix: "i18nPostfix" + }, + i18nPrefix: "topic.notifications", - allowInitialValueMutation: false, - @discourseComputed("topic.archetype") - i18nPostfix(archetype) { - return archetype === "private_message" ? "_pm" : ""; + i18nPostfix: computed("topic.archetype", function() { + return this.topic.archetype === "private_message" ? "_pm" : ""; + }), + + didInsertElement() { + this._super(...arguments); + + this.appEvents.on("topic-notifications-button:changed", this, "onSelect"); }, - _changed(msg) { - if (this.computedValue !== msg.id) { - this.get("topic.details").updateNotifications(msg.id); - } - }, + willDestroyElement() { + this._super(...arguments); - @on("didInsertElement") - _bindGlobalLevelChanged() { - this.appEvents.on("topic-notifications-button:changed", this, "_changed"); - }, - - @on("willDestroyElement") - _unbindGlobalLevelChanged() { - this.appEvents.off("topic-notifications-button:changed", this, "_changed"); - }, - - mutateValue(value) { - if (value !== this.value) { - this.get("topic.details").updateNotifications(value); - } - }, - - deselect() {} + this.appEvents.off("topic-notifications-button:changed", this, "onSelect"); + } }); diff --git a/app/assets/javascripts/select-kit/components/user-notifications-dropdown.js.es6 b/app/assets/javascripts/select-kit/components/user-notifications-dropdown.js.es6 index 1141395a6d3..11546965c9d 100644 --- a/app/assets/javascripts/select-kit/components/user-notifications-dropdown.js.es6 +++ b/app/assets/javascripts/select-kit/components/user-notifications-dropdown.js.es6 @@ -1,27 +1,37 @@ import DropdownSelectBox from "select-kit/components/dropdown-select-box"; import { popupAjaxError } from "discourse/lib/ajax-error"; import showModal from "discourse/lib/show-modal"; -import discourseComputed from "discourse-common/utils/decorators"; +import { computed } from "@ember/object"; export default DropdownSelectBox.extend({ classNames: ["user-notifications", "user-notifications-dropdown"], - nameProperty: "label", - computeContent() { + selectKitOptions: { + headerIcon: "userNotificationicon" + }, + + userNotificationicon: computed("mainCollection.[]", "value", function() { + return ( + this.mainCollection && + this.mainCollection.find(row => row.id === this.value).icon + ); + }), + + content: computed(function() { const content = []; content.push({ icon: "user", id: "changeToNormal", description: I18n.t("user.user_notifications.normal_option_title"), - label: I18n.t("user.user_notifications.normal_option") + name: I18n.t("user.user_notifications.normal_option") }); content.push({ icon: "times-circle", id: "changeToMuted", description: I18n.t("user.user_notifications.mute_option_title"), - label: I18n.t("user.user_notifications.mute_option") + name: I18n.t("user.user_notifications.mute_option") }); if (this.get("user.can_ignore_user")) { @@ -29,17 +39,12 @@ export default DropdownSelectBox.extend({ icon: "far-eye-slash", id: "changeToIgnored", description: I18n.t("user.user_notifications.ignore_option_title"), - label: I18n.t("user.user_notifications.ignore_option") + name: I18n.t("user.user_notifications.ignore_option") }); } return content; - }, - - @discourseComputed("value") - headerIcon(value) { - return this.computeContent().find(row => row.id === value).icon; - }, + }), changeToNormal() { this.updateNotificationLevel("normal").catch(popupAjaxError); @@ -53,26 +58,13 @@ export default DropdownSelectBox.extend({ }); }, - @discourseComputed("user.ignored", "user.muted") - value() { - if (this.get("user.ignored")) { - return "changeToIgnored"; - } else if (this.get("user.muted")) { - return "changeToMuted"; - } else { - return "changeToNormal"; - } - }, - - _select(id) { - this.select( - this.collectionComputedContent.find(c => c.originalContent.id === id) - ); - }, - actions: { - onSelect(level) { + onChange(level) { this[level](); + + // hack but model.ignored/muted is not + // getting updated after updateNotificationLevel + this.set("value", level); } } }); diff --git a/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 b/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 deleted file mode 100644 index 3cebc129c91..00000000000 --- a/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 +++ /dev/null @@ -1,352 +0,0 @@ -import { next } from "@ember/runloop"; -import { schedule } from "@ember/runloop"; -import { on } from "discourse-common/utils/decorators"; -import Mixin from "@ember/object/mixin"; - -export default Mixin.create({ - init() { - this._super(...arguments); - - this._previousScrollParentOverflow = null; - this._previousCSSContext = null; - this.selectionSelector = ".choice"; - this.filterInputSelector = ".filter-input"; - this.rowSelector = ".select-kit-row"; - this.collectionSelector = ".select-kit-collection"; - this.headerSelector = ".select-kit-header"; - this.bodySelector = ".select-kit-body"; - this.wrapperSelector = ".select-kit-wrapper"; - this.scrollableParentSelector = ".modal-body"; - this.fixedPlaceholderSelector = `.select-kit-fixed-placeholder-${this.elementId}`; - }, - - $findRowByValue(value) { - return $( - this.element.querySelector(`${this.rowSelector}[data-value='${value}']`) - ); - }, - - $header() { - return $(this.element && this.element.querySelector(this.headerSelector)); - }, - - $body() { - return $(this.element && this.element.querySelector(this.bodySelector)); - }, - - $wrapper() { - return $(this.element && this.element.querySelector(this.wrapperSelector)); - }, - - $collection() { - return $( - this.element && this.element.querySelector(this.collectionSelector) - ); - }, - - $scrollableParent() { - return $(this.scrollableParentSelector); - }, - - $fixedPlaceholder() { - return $(this.fixedPlaceholderSelector); - }, - - $rows() { - return this.$(`${this.rowSelector}:not(.no-content):not(.is-hidden)`); - }, - - $highlightedRow() { - return this.$rows().filter(".is-highlighted"); - }, - - $selectedRow() { - return this.$rows().filter(".is-selected"); - }, - - $filterInput() { - return $( - this.element && this.element.querySelector(this.filterInputSelector) - ); - }, - - _adjustPosition() { - this._applyDirection(); - this._applyFixedPosition(); - this._positionWrapper(); - }, - - @on("willDestroyElement") - _clearState() { - this.$fixedPlaceholder().remove(); - }, - - // use to collapse and remove focus - close(event) { - this.setProperties({ isFocused: false }); - this.collapse(event); - }, - - focus() { - this.focusFilterOrHeader(); - }, - - // try to focus filter and fallback to header if not present - focusFilterOrHeader() { - const context = this; - // next so we are sure it finised expand/collapse - next(() => { - schedule("afterRender", () => { - if ( - !context.$filterInput() || - !context.$filterInput().is(":visible") || - context - .$filterInput() - .parent() - .hasClass("is-hidden") - ) { - if (context.$header()) { - context.$header().focus(); - } else { - $(context.element).focus(); - } - } else { - context.$filterInput().focus(); - } - }); - }); - }, - - expand() { - if (this.isExpanded) return; - - this.setProperties({ - isExpanded: true, - renderedBodyOnce: true, - isFocused: true - }); - this.focusFilterOrHeader(); - this.autoHighlight(); - - next(() => { - this._boundaryActionHandler("onExpand", this); - schedule("afterRender", () => { - if (!this.isDestroying && !this.isDestroyed) { - this._adjustPosition(); - } - }); - }); - }, - - collapse() { - if (!this.isExpanded) return; - - this.set("isExpanded", false); - - next(() => { - this._boundaryActionHandler("onCollapse", this); - schedule("afterRender", () => { - if (!this.isDestroying && !this.isDestroyed) { - this._removeFixedPosition(); - } - }); - }); - }, - - // lose focus of the component in two steps - // first collapse and keep focus and then remove focus - unfocus(event) { - if (this.isExpanded) { - this.collapse(event); - this.focus(event); - } else { - this.close(event); - } - }, - - _destroyEvent(event) { - event.preventDefault(); - event.stopPropagation(); - }, - - _applyDirection() { - let options = { left: "auto", bottom: "auto", top: "auto" }; - - const discourseHeader = $(".d-header")[0]; - const discourseHeaderHeight = discourseHeader - ? discourseHeader.getBoundingClientRect().top + - this._computedStyle(discourseHeader, "height") - : 0; - const bodyHeight = this._computedStyle(this.$body()[0], "height"); - const componentHeight = this._computedStyle(this.element, "height"); - const offsetTop = this.element.getBoundingClientRect().top; - const offsetBottom = this.element.getBoundingClientRect().bottom; - const windowWidth = $(window).width(); - - if (this.fullWidthOnMobile && this.site && this.site.isMobileDevice) { - const margin = 10; - const relativeLeft = - $(this.element).offset().left - $(window).scrollLeft(); - options.left = margin - relativeLeft; - options.width = windowWidth - margin * 2; - options.maxWidth = options.minWidth = "unset"; - } else { - const parentWidth = this.$scrollableParent().length - ? this.$scrollableParent().width() - : windowWidth; - const bodyWidth = this._computedStyle(this.$body()[0], "width"); - - let spaceToLeftEdge; - if (this.$scrollableParent().length) { - spaceToLeftEdge = - $(this.element).offset().left - - this.$scrollableParent().offset().left; - } else { - spaceToLeftEdge = this.element.getBoundingClientRect().left; - } - - let isLeftAligned = true; - const spaceToRightEdge = parentWidth - spaceToLeftEdge; - const elementWidth = this.element.getBoundingClientRect().width; - if (spaceToRightEdge > spaceToLeftEdge + elementWidth) { - isLeftAligned = false; - } - - if (isLeftAligned) { - this.element.classList.add("is-left-aligned"); - this.element.classList.remove("is-right-aligned"); - - if (this._isRTL()) { - options.right = this.horizontalOffset; - } else { - options.left = -bodyWidth + elementWidth - this.horizontalOffset; - } - } else { - this.element.classList.add("is-right-aligned"); - this.element.classList.remove("is-left-aligned"); - - if (this._isRTL()) { - options.right = -bodyWidth + elementWidth - this.horizontalOffset; - } else { - options.left = this.horizontalOffset; - } - } - } - - const fullHeight = this.verticalOffset + bodyHeight + componentHeight; - const hasBelowSpace = $(window).height() - offsetBottom - fullHeight >= -1; - const hasAboveSpace = offsetTop - fullHeight - discourseHeaderHeight >= -1; - const headerHeight = this._computedStyle(this.$header()[0], "height"); - - if (hasBelowSpace || (!hasBelowSpace && !hasAboveSpace)) { - this.element.classList.add("is-below"); - this.element.classList.remove("is-above"); - options.top = headerHeight + this.verticalOffset; - } else { - this.element.classList.add("is-above"); - this.element.classList.remove("is-below"); - options.bottom = headerHeight + this.verticalOffset; - } - - this.$body().css(options); - }, - - _applyFixedPosition() { - if (this.isExpanded !== true) return; - if (this.$fixedPlaceholder().length) return; - if (!this.$scrollableParent().length) return; - - const width = this._computedStyle(this.element, "width"); - const height = this._computedStyle(this.element, "height"); - - this._previousScrollParentOverflow = - this._previousScrollParentOverflow || - this.$scrollableParent().css("overflow"); - - this._previousCSSContext = this._previousCSSContext || { - width, - minWidth: this.element.style.minWidth, - maxWidth: this.element.style.maxWidth, - top: this.element.style.top, - left: this.element.style.left, - marginLeft: this.element.style.marginLeft, - marginRight: this.element.style.marginRight, - position: this.element.style.position - }; - - const componentStyles = { - top: this.element.getBoundingClientRect().top, - width, - left: this.element.getBoundingClientRect().left, - marginLeft: 0, - marginRight: 0, - minWidth: "unset", - maxWidth: "unset", - position: "fixed" - }; - - const $placeholderTemplate = $( - `
    ` - ); - $placeholderTemplate.css({ - display: "inline-block", - width, - height, - "margin-bottom": this.element.style.marginBottom, - "vertical-align": "middle" - }); - - $(this.element) - .before($placeholderTemplate) - .css(componentStyles); - - this.$scrollableParent().css({ overflow: "hidden" }); - }, - - _removeFixedPosition() { - this.$fixedPlaceholder().remove(); - - if (!this.element || this.isDestroying || this.isDestroyed) return; - if (this.$scrollableParent().length === 0) return; - - $(this.element).css(this._previousCSSContext || {}); - this.$scrollableParent().css( - "overflow", - this._previousScrollParentOverflow || {} - ); - }, - - _positionWrapper() { - const elementWidth = this._computedStyle(this.element, "width"); - const headerHeight = this._computedStyle(this.$header()[0], "height"); - const bodyHeight = this._computedStyle(this.$body()[0], "height"); - - this.$wrapper().css({ - width: elementWidth, - height: headerHeight + bodyHeight - }); - }, - - _isRTL() { - return $("html").css("direction") === "rtl"; - }, - - _computedStyle(element, style) { - if (!element) return 0; - - let value; - - if (window.getComputedStyle) { - value = window.getComputedStyle(element, null)[style]; - } else { - value = $(element).css(style); - } - - return this._getFloat(value); - }, - - _getFloat(value) { - value = parseFloat(value); - return $.isNumeric(value) ? value : 0; - } -}); diff --git a/app/assets/javascripts/select-kit/mixins/events.js.es6 b/app/assets/javascripts/select-kit/mixins/events.js.es6 deleted file mode 100644 index 4c54979d1f2..00000000000 --- a/app/assets/javascripts/select-kit/mixins/events.js.es6 +++ /dev/null @@ -1,442 +0,0 @@ -import { get } from "@ember/object"; -import { makeArray } from "discourse-common/lib/helpers"; -import { isEmpty } from "@ember/utils"; -import { throttle } from "@ember/runloop"; -import { schedule } from "@ember/runloop"; -import { on } from "discourse-common/utils/decorators"; -import Mixin from "@ember/object/mixin"; - -const { bind } = Ember.run; - -export default Mixin.create({ - @on("init") - _initKeys() { - this.keys = { - TAB: 9, - ENTER: 13, - ESC: 27, - UP: 38, - DOWN: 40, - BACKSPACE: 8, - LEFT: 37, - RIGHT: 39, - A: 65 - }; - - this._boundMouseDownHandler = bind(this, this._mouseDownHandler); - this._boundFocusHeaderHandler = bind(this, this._focusHeaderHandler); - this._boundKeydownHeaderHandler = bind(this, this._keydownHeaderHandler); - this._boundKeypressHeaderHandler = bind(this, this._keypressHeaderHandler); - this._boundChangeFilterInputHandler = bind( - this, - this._changeFilterInputHandler - ); - this._boundKeypressFilterInputHandler = bind( - this, - this._keypressFilterInputHandler - ); - this._boundFocusoutFilterInputHandler = bind( - this, - this._focusoutFilterInputHandler - ); - this._boundKeydownFilterInputHandler = bind( - this, - this._keydownFilterInputHandler - ); - }, - - @on("didInsertElement") - _setupEvents() { - $(document).on("mousedown.select-kit", this._boundMouseDownHandler); - - this.$header() - .on("blur.select-kit", this._boundBlurHeaderHandler) - .on("focus.select-kit", this._boundFocusHeaderHandler) - .on("keydown.select-kit", this._boundKeydownHeaderHandler) - .on("keypress.select-kit", this._boundKeypressHeaderHandler); - - this.$filterInput() - .on("change.select-kit", this._boundChangeFilterInputHandler) - .on("keypress.select-kit", this._boundKeypressFilterInputHandler) - .on("focusout.select-kit", this._boundFocusoutFilterInputHandler) - .on("keydown.select-kit", this._boundKeydownFilterInputHandler); - }, - - @on("willDestroyElement") - _cleanUpEvents() { - $(document).off("mousedown.select-kit", this._boundMouseDownHandler); - - if (this.$header()) { - this.$header() - .off("blur.select-kit", this._boundBlurHeaderHandler) - .off("focus.select-kit", this._boundFocusHeaderHandler) - .off("keydown.select-kit", this._boundKeydownHeaderHandler) - .off("keypress.select-kit", this._boundKeypressHeaderHandler); - } - - if (this.$filterInput()) { - this.$filterInput() - .off("change.select-kit", this._boundChangeFilterInputHandler) - .off("keypress.select-kit", this._boundKeypressFilterInputHandler) - .off("focusout.select-kit", this._boundFocusoutFilterInputHandler) - .off("keydown.select-kit", this._boundKeydownFilterInputHandler); - } - }, - - _mouseDownHandler(event) { - if (!this.element || this.isDestroying || this.isDestroyed) { - return true; - } - - if (this.element !== event.target && this.element.contains(event.target)) { - event.stopPropagation(); - if (!this.renderedBodyOnce) return; - if (!this.isFocused) return; - } else { - this.didClickOutside(event); - } - }, - - _blurHeaderHandler() { - if (!this.isExpanded && this.isFocused) { - this.close(); - } - }, - - _focusHeaderHandler(event) { - this.set("isFocused", true); - this._destroyEvent(event); - }, - - _keydownHeaderHandler(event) { - if (document.activeElement !== this.$header()[0]) return event; - - const keyCode = event.keyCode || event.which; - - if (keyCode === this.keys.TAB && event.shiftKey) { - this.unfocus(event); - } - if (keyCode === this.keys.TAB && !event.shiftKey) this.tabFromHeader(event); - if (isEmpty(this.filter) && keyCode === this.keys.BACKSPACE) - this.backspaceFromHeader(event); - if (keyCode === this.keys.ESC) this.escapeFromHeader(event); - if (keyCode === this.keys.ENTER) this.enterFromHeader(event); - if ([this.keys.UP, this.keys.DOWN].includes(keyCode)) - this.upAndDownFromHeader(event); - if ( - isEmpty(this.filter) && - [this.keys.LEFT, this.keys.RIGHT].includes(keyCode) - ) { - this.leftAndRightFromHeader(event); - } - return event; - }, - - _keypressHeaderHandler(event) { - const keyCode = event.keyCode || event.which; - - if (keyCode === this.keys.ENTER) return true; - if (keyCode === this.keys.TAB) return true; - - this.expand(event); - - if (this.filterable || this.autoFilterable) { - this.set("renderedFilterOnce", true); - } - - schedule("afterRender", () => { - this.$filterInput() - .focus() - .val(this.$filterInput().val() + String.fromCharCode(keyCode)); - }); - - return false; - }, - - _keydownFilterInputHandler(event) { - const keyCode = event.keyCode || event.which; - - if ( - isEmpty(this.filter) && - keyCode === this.keys.BACKSPACE && - typeof this.didPressBackspaceFromFilter === "function" - ) { - this.didPressBackspaceFromFilter(event); - } - - if (keyCode === this.keys.TAB && event.shiftKey) { - this.unfocus(event); - } - if (keyCode === this.keys.TAB && !event.shiftKey) this.tabFromFilter(event); - if (keyCode === this.keys.ESC) this.escapeFromFilter(event); - if (keyCode === this.keys.ENTER) this.enterFromFilter(event); - if ([this.keys.UP, this.keys.DOWN].includes(keyCode)) - this.upAndDownFromFilter(event); - - if ( - isEmpty(this.filter) && - [this.keys.LEFT, this.keys.RIGHT].includes(keyCode) - ) { - this.leftAndRightFromFilter(event); - } - }, - - _changeFilterInputHandler(event) { - this.send("onFilterComputedContent", $(event.target).val()); - }, - _keypressFilterInputHandler(event) { - event.stopPropagation(); - }, - _focusoutFilterInputHandler(event) { - this.onFilterInputFocusout(event); - }, - - didPressTab(event) { - if (this.$highlightedRow().length && this.isExpanded) { - this.close(event); - this.$header().focus(); - const guid = this.$highlightedRow().attr("data-guid"); - this.select(this._findComputedContentItemByGuid(guid)); - return true; - } - - if (isEmpty(this.filter)) { - this.close(event); - return true; - } - - return true; - }, - - didPressEnter(event) { - if (!this.isExpanded) { - this.expand(event); - } else if (this.$highlightedRow().length) { - this.close(event); - this.$header().focus(); - const guid = this.$highlightedRow().attr("data-guid"); - this.select(this._findComputedContentItemByGuid(guid)); - } - - return true; - }, - - didClickSelectionItem(computedContentItem) { - this.focus(); - this.deselect(computedContentItem); - }, - - didClickRow(computedContentItem) { - this.close(); - this.focus(); - this.select(computedContentItem); - }, - - didPressEscape(event) { - this._destroyEvent(event); - - if (this.highlightedSelection.length && this.isExpanded) { - this.clearHighlightSelection(); - } else { - this.unfocus(event); - } - }, - - didPressUpAndDownArrows(event) { - this._destroyEvent(event); - - this.clearHighlightSelection(); - - const keyCode = event.keyCode || event.which; - - if (!this.isExpanded) { - this.expand(event); - - if (this.$selectedRow().length === 1) { - this._highlightRow(this.$selectedRow()); - return; - } - - return; - } - - const $rows = this.$rows(); - - if (!$rows.length) { - return; - } - - if ($rows.length === 1) { - this._rowSelection($rows, 0); - return; - } - - const direction = keyCode === 38 ? -1 : 1; - - throttle(this, this._moveHighlight, direction, $rows, 32); - }, - - didPressBackspaceFromFilter(event) { - this.didPressBackspace(event); - }, - didPressBackspace(event) { - if (!this.isExpanded) { - this.expand(); - if (event) event.stopImmediatePropagation(); - return; - } - - if (!this.selection || !this.selection.length) return; - - if (!isEmpty(this.filter)) { - this.clearHighlightSelection(); - return; - } - - if (!this.highlightedSelection.length) { - // try to highlight the last non locked item from the current selection - makeArray(this.selection) - .slice() - .reverse() - .some(selection => { - if (!get(selection, "locked")) { - this.highlightSelection(selection); - return true; - } - }); - - if (event) event.stopImmediatePropagation(); - } else { - this.deselect(this.highlightedSelection); - if (event) event.stopImmediatePropagation(); - } - }, - - didPressSelectAll() { - this.highlightSelection(makeArray(this.selection)); - }, - - didClickOutside(event) { - if (this.isExpanded && $(event.target).parents(".select-kit").length) { - this.close(event); - return false; - } - - this.close(event); - return; - }, - - // make sure we don’t propagate a click outside component - // to avoid closing a modal containing the component for example - click(event) { - this._destroyEvent(event); - }, - - didPressLeftAndRightArrows(event) { - if (!this.isExpanded) { - this.expand(); - event.stopImmediatePropagation(); - return; - } - - if (isEmpty(this.selection)) return; - - const keyCode = event.keyCode || event.which; - - if (keyCode === this.keys.LEFT) { - const prev = this.get("highlightedSelection.lastObject"); - const indexOfPrev = this.selection.indexOf(prev); - - if (this.selection[indexOfPrev - 1]) { - this.highlightSelection(this.selection[indexOfPrev - 1]); - } else { - this.highlightSelection(this.get("selection.lastObject")); - } - } else { - const prev = this.get("highlightedSelection.firstObject"); - const indexOfNext = this.selection.indexOf(prev); - - if (this.selection[indexOfNext + 1]) { - this.highlightSelection(this.selection[indexOfNext + 1]); - } else { - this.highlightSelection(this.get("selection.firstObject")); - } - } - }, - - tabFromHeader(event) { - this.didPressTab(event); - }, - tabFromFilter(event) { - this.didPressTab(event); - }, - - escapeFromHeader(event) { - this.didPressEscape(event); - }, - escapeFromFilter(event) { - this.didPressEscape(event); - }, - - upAndDownFromHeader(event) { - this.didPressUpAndDownArrows(event); - }, - upAndDownFromFilter(event) { - this.didPressUpAndDownArrows(event); - }, - - leftAndRightFromHeader(event) { - this.didPressLeftAndRightArrows(event); - }, - leftAndRightFromFilter(event) { - this.didPressLeftAndRightArrows(event); - }, - - backspaceFromHeader(event) { - this.didPressBackspace(event); - }, - - enterFromHeader(event) { - this.didPressEnter(event); - }, - enterFromFilter(event) { - this.didPressEnter(event); - }, - - onFilterInputFocusout(event) { - if ( - !( - this.element !== event.relatedTarget && - this.element.contains(event.relatedTarget) - ) - ) { - this.close(event); - } - }, - - _moveHighlight(direction, $rows) { - const currentIndex = $rows.index(this.$highlightedRow()); - let nextIndex = currentIndex + direction; - - if (nextIndex < 0) { - nextIndex = $rows.length - 1; - } else if (nextIndex >= $rows.length) { - nextIndex = 0; - } - - this._rowSelection($rows, nextIndex); - }, - - _rowSelection($rows, nextIndex) { - const highlightableValue = $rows.eq(nextIndex).attr("data-value"); - const $highlightableRow = this.$findRowByValue(highlightableValue); - this._highlightRow($highlightableRow); - }, - - _highlightRow($row) { - schedule("afterRender", () => { - $row.trigger("mouseover").focus(); - this.focus(); - }); - } -}); diff --git a/app/assets/javascripts/select-kit/mixins/plugin-api.js.es6 b/app/assets/javascripts/select-kit/mixins/plugin-api.js.es6 index 9386c595cfa..6ff04393f27 100644 --- a/app/assets/javascripts/select-kit/mixins/plugin-api.js.es6 +++ b/app/assets/javascripts/select-kit/mixins/plugin-api.js.es6 @@ -19,6 +19,15 @@ function prependContent(pluginApiIdentifiers, contentFunction) { _prependContentCallbacks[pluginApiIdentifiers].push(contentFunction); } +let _filterContentCallbacks = {}; +function filterContent(pluginApiIdentifiers, contentFunction) { + if (isNone(_filterContentCallbacks[pluginApiIdentifiers])) { + _filterContentCallbacks[pluginApiIdentifiers] = []; + } + + _filterContentCallbacks[pluginApiIdentifiers].push(contentFunction); +} + let _modifyContentCallbacks = {}; function modifyContent(pluginApiIdentifiers, contentFunction) { if (isNone(_modifyContentCallbacks[pluginApiIdentifiers])) { @@ -39,6 +48,15 @@ function modifyHeaderComputedContent(pluginApiIdentifiers, contentFunction) { ); } +let _modifyNoSelectionCallbacks = {}; +function modifyNoSelection(pluginApiIdentifiers, contentFunction) { + if (isNone(_modifyNoSelectionCallbacks[pluginApiIdentifiers])) { + _modifyNoSelectionCallbacks[pluginApiIdentifiers] = []; + } + + _modifyNoSelectionCallbacks[pluginApiIdentifiers].push(contentFunction); +} + let _modifyCollectionHeaderCallbacks = {}; function modifyCollectionHeader(pluginApiIdentifiers, contentFunction) { if (isNone(_modifyCollectionHeaderCallbacks[pluginApiIdentifiers])) { @@ -48,15 +66,6 @@ function modifyCollectionHeader(pluginApiIdentifiers, contentFunction) { _modifyCollectionHeaderCallbacks[pluginApiIdentifiers].push(contentFunction); } -let _onSelectNoneCallbacks = {}; -function onSelectNone(pluginApiIdentifiers, mutationFunction) { - if (isNone(_onSelectNoneCallbacks[pluginApiIdentifiers])) { - _onSelectNoneCallbacks[pluginApiIdentifiers] = []; - } - - _onSelectNoneCallbacks[pluginApiIdentifiers].push(mutationFunction); -} - let _onSelectCallbacks = {}; function onSelect(pluginApiIdentifiers, mutationFunction) { if (isNone(_onSelectCallbacks[pluginApiIdentifiers])) { @@ -66,18 +75,57 @@ function onSelect(pluginApiIdentifiers, mutationFunction) { _onSelectCallbacks[pluginApiIdentifiers].push(mutationFunction); } -export function applyContentPluginApiCallbacks(identifiers, content, context) { +let _onOpenCallbacks = {}; +function onOpen(pluginApiIdentifiers, mutationFunction) { + if (isNone(_onOpenCallbacks[pluginApiIdentifiers])) { + _onOpenCallbacks[pluginApiIdentifiers] = []; + } + + _onOpenCallbacks[pluginApiIdentifiers].push(mutationFunction); +} + +let _onCloseCallbacks = {}; +function onClose(pluginApiIdentifiers, mutationFunction) { + if (isNone(_onCloseCallbacks[pluginApiIdentifiers])) { + _onCloseCallbacks[pluginApiIdentifiers] = []; + } + + _onCloseCallbacks[pluginApiIdentifiers].push(mutationFunction); +} + +let _onInputCallbacks = {}; +function onInput(pluginApiIdentifiers, mutationFunction) { + if (isNone(_onInputCallbacks[pluginApiIdentifiers])) { + _onInputCallbacks[pluginApiIdentifiers] = []; + } + + _onInputCallbacks[pluginApiIdentifiers].push(mutationFunction); +} + +export function applyContentPluginApiCallbacks( + identifiers, + content, + selectKit +) { identifiers.forEach(key => { (_prependContentCallbacks[key] || []).forEach(c => { - content = c() - .concat(content) - .uniqBy("id"); + content = Ember.makeArray(c(selectKit, content)).concat(content); }); (_appendContentCallbacks[key] || []).forEach(c => { - content = content.concat(c()).uniqBy("id"); + content = content.concat(Ember.makeArray(c(selectKit, content))); }); + const filterCallbacks = _filterContentCallbacks[key] || []; + if (filterCallbacks.length) { + content = content.filter(c => { + let kept = true; + filterCallbacks.forEach(cb => { + kept = cb(selectKit, c); + }); + return kept; + }); + } (_modifyContentCallbacks[key] || []).forEach(c => { - content = c(context, content).uniqBy("id"); + content = c(selectKit, content); }); }); @@ -97,10 +145,13 @@ export function applyHeaderContentPluginApiCallbacks( return content; } - -export function applyCollectionHeaderCallbacks(identifiers, content, context) { +export function applyModifyNoSelectionPluginApiCallbacks( + identifiers, + content, + context +) { identifiers.forEach(key => { - (_modifyCollectionHeaderCallbacks[key] || []).forEach(c => { + (_modifyNoSelectionCallbacks[key] || []).forEach(c => { content = c(context, content); }); }); @@ -108,16 +159,54 @@ export function applyCollectionHeaderCallbacks(identifiers, content, context) { return content; } -export function applyOnSelectPluginApiCallbacks(identifiers, val, context) { +export function applyCollectionHeaderCallbacks( + identifiers, + content, + selectKit +) { identifiers.forEach(key => { - (_onSelectCallbacks[key] || []).forEach(c => c(context, val)); + (_modifyCollectionHeaderCallbacks[key] || []).forEach(c => { + content = c(selectKit, content); + }); + }); + + return content; +} + +export function applyOnSelectPluginApiCallbacks(identifiers, val, selectKit) { + identifiers.forEach(key => { + (_onSelectCallbacks[key] || []).forEach(c => c(selectKit, val)); }); } -export function applyOnSelectNonePluginApiCallbacks(identifiers, context) { +export function applyOnOpenPluginApiCallbacks(identifiers, selectKit, event) { + let keepBubbling = true; identifiers.forEach(key => { - (_onSelectNoneCallbacks[key] || []).forEach(c => c(context)); + (_onOpenCallbacks[key] || []).forEach( + c => (keepBubbling = c(selectKit, event)) + ); }); + return keepBubbling; +} + +export function applyOnClosePluginApiCallbacks(identifiers, selectKit, event) { + let keepBubbling = true; + identifiers.forEach(key => { + (_onCloseCallbacks[key] || []).forEach( + c => (keepBubbling = c(selectKit, event)) + ); + }); + return keepBubbling; +} + +export function applyOnInputPluginApiCallbacks(identifiers, event, selectKit) { + let keepBubbling = true; + identifiers.forEach(key => { + (_onInputCallbacks[key] || []).forEach( + c => (keepBubbling = c(selectKit, event)) + ); + }); + return keepBubbling; } export function modifySelectKit(pluginApiIdentifiers) { @@ -134,6 +223,10 @@ export function modifySelectKit(pluginApiIdentifiers) { }); return modifySelectKit(pluginApiIdentifiers); }, + filterContent: filterFunction => { + filterContent(pluginApiIdentifiers, filterFunction); + return modifySelectKit(pluginApiIdentifiers); + }, modifyContent: callback => { modifyContent(pluginApiIdentifiers, callback); return modifySelectKit(pluginApiIdentifiers); @@ -142,6 +235,14 @@ export function modifySelectKit(pluginApiIdentifiers) { modifyHeaderComputedContent(pluginApiIdentifiers, callback); return modifySelectKit(pluginApiIdentifiers); }, + modifySelection: callback => { + modifyHeaderComputedContent(pluginApiIdentifiers, callback); + return modifySelectKit(pluginApiIdentifiers); + }, + modifyNoSelection: callback => { + modifyNoSelection(pluginApiIdentifiers, callback); + return modifySelectKit(pluginApiIdentifiers); + }, modifyCollectionHeader: callback => { modifyCollectionHeader(pluginApiIdentifiers, callback); return modifySelectKit(pluginApiIdentifiers); @@ -150,8 +251,16 @@ export function modifySelectKit(pluginApiIdentifiers) { onSelect(pluginApiIdentifiers, callback); return modifySelectKit(pluginApiIdentifiers); }, - onSelectNone: callback => { - onSelectNone(pluginApiIdentifiers, callback); + onClose: callback => { + onClose(pluginApiIdentifiers, callback); + return modifySelectKit(pluginApiIdentifiers); + }, + onOpen: callback => { + onOpen(pluginApiIdentifiers, callback); + return modifySelectKit(pluginApiIdentifiers); + }, + onInput: callback => { + onInput(pluginApiIdentifiers, callback); return modifySelectKit(pluginApiIdentifiers); } }; @@ -160,11 +269,15 @@ export function modifySelectKit(pluginApiIdentifiers) { export function clearCallbacks() { _appendContentCallbacks = {}; _prependContentCallbacks = {}; + _filterContentCallbacks = {}; + _modifyNoSelectionCallbacks = {}; _modifyContentCallbacks = {}; _modifyHeaderComputedContentCallbacks = {}; _modifyCollectionHeaderCallbacks = {}; _onSelectCallbacks = {}; - _onSelectNoneCallbacks = {}; + _onCloseCallbacks = {}; + _onOpenCallbacks = {}; + _onInputCallbacks = {}; } const EMPTY_ARRAY = Object.freeze([]); diff --git a/app/assets/javascripts/select-kit/mixins/tags.js.es6 b/app/assets/javascripts/select-kit/mixins/tags.js.es6 index a9749fa49ac..016c7330c69 100644 --- a/app/assets/javascripts/select-kit/mixins/tags.js.es6 +++ b/app/assets/javascripts/select-kit/mixins/tags.js.es6 @@ -1,67 +1,71 @@ -const { run, get } = Ember; +import { reads } from "@ember/object/computed"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import Mixin from "@ember/object/mixin"; export default Mixin.create({ - willDestroyElement() { - this._super(...arguments); - - const searchDebounce = this.searchDebounce; - if (searchDebounce) run.cancel(searchDebounce); - }, - - searchTags(url, data, callback, options) { - options = options || {}; - - if (!options.background) this.startLoading(); - + searchTags(url, data, callback) { return ajax(Discourse.getURL(url), { quietMillis: 200, cache: true, dataType: "json", data }) - .then(json => { - this.set("asyncContent", callback(this, json)); - if (!options.background) this.autoHighlight(); - }) - .catch(error => popupAjaxError(error)) - .finally(() => { - if (!options.background) this.stopLoading(); - }); + .then(json => callback(this, json)) + .catch(popupAjaxError); }, - validateCreate(term) { - if (this.hasReachedMaximum || !this.site.get("can_create_tag")) { + selectKitOptions: { + allowAny: "allowAnyTag" + }, + + allowAnyTag: reads("site.can_create_tag"), + + validateCreate(filter, content) { + if (this.selectKit.hasReachedMaximum) { + this.addError( + I18n.t("select_kit.max_content_reached", { + count: this.selectKit.limit + }) + ); return false; } const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); - term = term + filter = filter .replace(filterRegexp, "") .trim() .toLowerCase(); - if (!term.length || this.termMatchesForbidden) { + if (this.termMatchesForbidden) { return false; } - if (this.get("siteSettings.max_tag_length") < term.length) { + if ( + !filter.length || + this.get("siteSettings.max_tag_length") < filter.length + ) { + this.addError( + I18n.t("select_kit.invalid_selection_length", { + count: `[1 - ${this.get("siteSettings.max_tag_length")}]` + }) + ); return false; } const toLowerCaseOrUndefined = string => { - return string === undefined ? undefined : string.toLowerCase(); + return Ember.isEmpty(string) ? undefined : string.toLowerCase(); }; - const inCollection = this.collectionComputedContent - .map(c => toLowerCaseOrUndefined(get(c, "id"))) - .includes(term); + const inCollection = content + .map(c => toLowerCaseOrUndefined(this.getValue(c))) + .filter(Boolean) + .includes(filter); - const inSelection = this.selection - .map(s => toLowerCaseOrUndefined(get(s, "value"))) - .includes(term); + const inSelection = (this.value || []) + .map(s => toLowerCaseOrUndefined(s)) + .filter(Boolean) + .includes(filter); if (inCollection || inSelection) { return false; @@ -72,16 +76,16 @@ export default Mixin.create({ createContentFromInput(input) { // See lib/discourse_tagging#clean_tag. - let content = input + input = input .trim() .replace(/\s+/g, "-") .replace(/[\/\?#\[\]@!\$&'\(\)\*\+,;=\.%\\`^\s|\{\}"<>]+/g, "") .substring(0, this.siteSettings.max_tag_length); if (this.siteSettings.force_lowercase_tags) { - content = content.toLowerCase(); + input = input.toLowerCase(); } - return content; + return input; } }); diff --git a/app/assets/javascripts/select-kit/mixins/utils.js.es6 b/app/assets/javascripts/select-kit/mixins/utils.js.es6 index 5ee7b96c90d..d044416f22b 100644 --- a/app/assets/javascripts/select-kit/mixins/utils.js.es6 +++ b/app/assets/javascripts/select-kit/mixins/utils.js.es6 @@ -1,27 +1,76 @@ import Mixin from "@ember/object/mixin"; -const { get, isNone, guidFor } = Ember; +const { get } = Ember; export default Mixin.create({ - valueForContentItem(content) { - switch (typeof content) { - case "string": - case "number": - return content; - default: - return get(content, this.valueAttribute); + defaultItem(value, name) { + if (this.selectKit.valueProperty) { + const item = {}; + item[this.selectKit.valueProperty] = value; + item[this.selectKit.nameProperty] = name; + return item; + } else { + return name || value; } }, - _nameForContent(content) { - if (isNone(content)) { + itemForValue(value) { + if (this.selectKit.valueProperty) { + return this.mainCollection.findBy(this.selectKit.valueProperty, value); + } else { + return value; + } + }, + + getProperty(item, property, options = { definedOnly: true }) { + const { definedOnly } = options; + + if (item && typeof property === "string") { + const attempt = get(item, property); + if (attempt) { + return attempt; + } + } + + property = get(this.selectKit, property); + + if (!item) { return null; } - if (typeof content === "object") { - return get(content, this.nameProperty); + if (!property && definedOnly) { + return null; + } else if (!property) { + return item; + } else if (typeof property === "string") { + return get(item, property); + } else { + return property(item); } + }, - return content; + getValue(item) { + return this.getProperty(item, "valueProperty", { definedOnly: false }); + }, + + getName(item) { + return this.getProperty(item, "nameProperty", { definedOnly: false }); + }, + + findValue(content, item) { + const property = Ember.get(this.selectKit, "valueProperty"); + + if (!property) { + if (content.indexOf(item) > -1) { + return item; + } + } else if (typeof property === "string") { + return content.findBy(property, this.getValue(item)); + } else { + const value = this.getValue(item); + return content.find(contentItem => { + return this.getValue(contentItem) === value; + }); + } }, _isNumeric(input) { @@ -29,55 +78,14 @@ export default Mixin.create({ }, _normalize(input) { - input = input.toLowerCase(); + if (input) { + input = input.toLowerCase(); - if (typeof input.normalize === "function") { - input = input.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + if (typeof input.normalize === "function") { + input = input.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + } } return input; - }, - - _cast(value) { - if (value === this.noneValue) return value; - return this._castInteger(this._castBoolean(value)); - }, - - _castBoolean(value) { - if ( - this.castBoolean && - Ember.isPresent(value) && - typeof value === "string" - ) { - return value === "true"; - } - - return value; - }, - - _castInteger(value) { - if (this.castInteger && Ember.isPresent(value) && this._isNumeric(value)) { - return parseInt(value, 10); - } - - return value; - }, - - _findComputedContentItemByGuid(guid) { - if (guidFor(this.createRowComputedContent) === guid) { - return this.createRowComputedContent; - } - - if (guidFor(this.noneRowComputedContent) === guid) { - return this.noneRowComputedContent; - } - - return this.collectionComputedContent.find(c => { - return guidFor(c) === guid; - }); - }, - - _filterRemovableComputedContents(computedContent) { - return computedContent.filter(c => c.created); } }); diff --git a/app/assets/javascripts/select-kit/templates/components/category-drop/category-drop-header.hbs b/app/assets/javascripts/select-kit/templates/components/category-drop/category-drop-header.hbs index 7928a3b94b9..a800565e6d1 100644 --- a/app/assets/javascripts/select-kit/templates/components/category-drop/category-drop-header.hbs +++ b/app/assets/javascripts/select-kit/templates/components/category-drop/category-drop-header.hbs @@ -1,5 +1,8 @@ - - {{{label}}} - +{{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit + shouldDisplayClearableButton=shouldDisplayClearableButton +}} {{d-icon caretIcon class="caret-icon"}} diff --git a/app/assets/javascripts/select-kit/templates/components/category-row.hbs b/app/assets/javascripts/select-kit/templates/components/category-row.hbs index b35247b47eb..e9356359dca 100644 --- a/app/assets/javascripts/select-kit/templates/components/category-row.hbs +++ b/app/assets/javascripts/select-kit/templates/components/category-row.hbs @@ -1,19 +1,19 @@ {{#if category}} -
    - {{#if hasParentCategory}} - {{#unless hideParentCategory}} - {{badgeForParentCategory}} - {{/unless}} - {{/if}} - {{badgeForCategory}} - - × {{topicCount}} - -
    - - {{#if shouldDisplayDescription}} -
    {{{dir-span description}}}
    +
    + {{#if hasParentCategory}} + {{#unless hideParentCategory}} + {{badgeForParentCategory}} + {{/unless}} {{/if}} + {{badgeForCategory}} + + × {{topicCount}} + +
    + + {{#if shouldDisplayDescription}} +
    {{{dir-span description}}}
    + {{/if}} {{else}} {{{label}}} {{/if}} diff --git a/app/assets/javascripts/select-kit/templates/components/combo-box/combo-box-header.hbs b/app/assets/javascripts/select-kit/templates/components/combo-box/combo-box-header.hbs index fbd9610f00a..5213bceac9d 100644 --- a/app/assets/javascripts/select-kit/templates/components/combo-box/combo-box-header.hbs +++ b/app/assets/javascripts/select-kit/templates/components/combo-box/combo-box-header.hbs @@ -1,18 +1,17 @@ {{#each icons as |icon|}} {{d-icon icon}} {{/each}} - - {{#if forceEscape}} - {{label}} - {{else}} - {{{label}}} - {{/if}} - +{{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit +}} {{#if shouldDisplayClearableButton}} {{d-button class="btn-clear" - action=(action onClearSelection bubbles=false) - icon="times"}} + icon="times" + action=selectKit.onClearSelection + }} {{/if}} {{d-icon caretIcon class="caret-icon"}} diff --git a/app/assets/javascripts/select-kit/templates/components/create-color-row.hbs b/app/assets/javascripts/select-kit/templates/components/create-color-row.hbs new file mode 100644 index 00000000000..838b39e22e9 --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/create-color-row.hbs @@ -0,0 +1 @@ +{{label}} diff --git a/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-header.hbs b/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-header.hbs index 5a1ba341646..0baa85400a8 100644 --- a/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-header.hbs +++ b/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-header.hbs @@ -1,7 +1,10 @@ -{{#each icons as |icon|}} {{d-icon icon}} {{/each}} - -{{#if options.showFullTitle}} - - {{{label}}} - +{{#if selectKit.options.icon}} + {{d-icon selectKit.options.icon}} {{/if}} + +{{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit + shouldDisplayClearableButton=shouldDisplayClearableButton +}} diff --git a/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-row.hbs b/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-row.hbs index d6884bd75ad..1c070006add 100644 --- a/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-row.hbs +++ b/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-row.hbs @@ -1,15 +1,13 @@ -{{#if template}} - {{{template}}} -{{else}} - {{#if icons}} -
    - - {{#each icons as |icon|}} {{d-icon icon}} {{/each}} -
    - {{/if}} - -
    - {{{label}}} - {{{description}}} +{{#if icons}} +
    + + {{#each icons as |icon|}} + {{d-icon icon translatedtitle=(dasherize title)}} + {{/each}}
    {{/if}} + +
    + {{{label}}} + {{{description}}} +
    diff --git a/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-header.hbs b/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-header.hbs index b0bd2ca02fe..171ef3f3a63 100644 --- a/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-header.hbs +++ b/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-header.hbs @@ -4,25 +4,16 @@
    {{/if}} - - {{#if forceEscape}} - {{label}} - {{else}} - {{{label}}} - {{/if}} - +{{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit +}} -{{#if computedContent.datetime}} +{{#if selectedContent.datetime}} - {{computedContent.datetime}} + {{selectedContent.datetime}} {{/if}} -{{#if shouldDisplayClearableButton}} - {{d-button - class="btn-clear" - action=(action onClearSelection bubbles=false) - icon="times"}} -{{/if}} - {{d-icon caretIcon class="caret-icon"}} diff --git a/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-row.hbs b/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-row.hbs index a0217afca1f..9a4a708c959 100644 --- a/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-row.hbs +++ b/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-row.hbs @@ -1,13 +1,13 @@ -{{#if computedContent.icons}} +{{#if item.icons}}
    - {{#each computedContent.icons as |icon|}} {{d-icon icon}} {{/each}} + {{#each item.icons as |icon|}} {{d-icon icon}} {{/each}}
    {{/if}} {{label}} -{{#if computedContent.datetime}} +{{#if item.datetime}} - {{computedContent.datetime}} + {{item.datetime}} {{/if}} diff --git a/app/assets/javascripts/select-kit/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs b/app/assets/javascripts/select-kit/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs index c50e9e371f6..a2826cffb83 100644 --- a/app/assets/javascripts/select-kit/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs +++ b/app/assets/javascripts/select-kit/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs @@ -1,2 +1,7 @@ -{{label}} +{{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit +}} + {{d-icon caretIcon class="caret-icon"}} diff --git a/app/assets/javascripts/select-kit/templates/components/mini-tag-chooser/selected-collection.hbs b/app/assets/javascripts/select-kit/templates/components/mini-tag-chooser/selected-collection.hbs new file mode 100644 index 00000000000..a5d613354d0 --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/mini-tag-chooser/selected-collection.hbs @@ -0,0 +1,11 @@ +{{#each tags as |tag|}} + {{#d-button + translatedTitle=tag.value + icon="times" + action=(action "deselectTag") + actionParam=tag.value + class=tag.classNames + }} + {{discourse-tag tag.value noHref=true}} + {{/d-button}} +{{/each}} diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select.hbs index d541cc2c1c4..3d8399f48dd 100644 --- a/app/assets/javascripts/select-kit/templates/components/multi-select.hbs +++ b/app/assets/javascripts/select-kit/templates/components/multi-select.hbs @@ -1,50 +1,34 @@ -{{#component headerComponent - tabindex=tabindex - isFocused=isFocused - isExpanded=isExpanded - highlightedSelection=highlightedSelection - onClickSelectionItem=(action "onClickSelectionItem") - computedContent=headerComputedContent - onToggle=(action "onToggle") - options=headerComponentOptions -}} - {{component filterComponent - icon=filterIcon - placeholder=filterPlaceholder - filter=filter - hasSelection=hasSelection - isLoading=isLoading - shouldDisplayFilter=shouldDisplayFilter - isFocused=isFocused - onFilterComputedContent=(action "onFilterComputedContent") +{{#unless isHidden}} + {{component selectKit.options.headerComponent + tabindex=tabindex + value=value + selectedContent=selectedContent + selectKit=selectKit }} -{{/component}} -
    - {{#if renderedBodyOnce}} - {{#unless isLoading}} - {{component collectionComponent - collectionHeaderComputedContent=collectionHeaderComputedContent - hasSelection=hasSelection - noneRowComputedContent=noneRowComputedContent - createRowComputedContent=createRowComputedContent - collectionComputedContent=collectionComputedContent - rowComponent=rowComponent - noneRowComponent=noneRowComponent - createRowComponent=createRowComponent - templateForRow=templateForRow - templateForNoneRow=templateForNoneRow - templateForCreateRow=templateForCreateRow - onClickRow=(action "onClickRow") - onMouseoverRow=(action "onMouseoverRow") - highlighted=highlighted - computedValue=computedValue - rowComponentOptions=rowComponentOptions - noContentRow=noContentRow - validationMessage=validationMessage - }} - {{/unless}} - {{/if}} -
    + {{#select-kit/select-kit-body selectKit=selectKit}} + {{#unless selectKit.isLoading}} + {{#if selectKit.filter}} + {{#if selectKit.hasNoContent}} + + {{i18n "select_kit.no_content"}} + + {{/if}} + {{/if}} -
    + {{#each collections as |collection|}} + {{component (component-for-collection collection.identifier selectKit) + collection=collection + selectKit=selectKit + value=value + }} + {{/each}} + {{else}} + + {{loading-spinner size="small"}} + + {{/unless}} + {{/select-kit/select-kit-body}} + +
    +{{/unless}} diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs index e95d429d27f..78f0d8f6aad 100644 --- a/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs +++ b/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs @@ -1,12 +1,15 @@
    - {{#each computedContent.selection as |selection|}} - {{component selectedNameComponent - onClickSelectionItem=onClickSelectionItem - highlightedSelection=highlightedSelection - forceEscape=forceEscape - computedContent=selection}} + {{#each selectedContent as |item|}} + {{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=item + selectKit=selectKit + }} {{/each}} - - {{yield}} - + +
    + {{component selectKit.options.filterComponent + selectKit=selectKit + }} +
    diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs deleted file mode 100644 index b39d272a045..00000000000 --- a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs +++ /dev/null @@ -1,14 +0,0 @@ -{{#if headerContent}}
    {{headerContent}}
    {{/if}} - -
    - - {{#if forceEscape}} - {{label}} - {{else}} - {{{label}}} - {{/if}} - - {{d-icon 'times'}} -
    - -{{#if footerContent}}{{/if}} diff --git a/app/assets/javascripts/select-kit/templates/components/period-chooser/period-chooser-header.hbs b/app/assets/javascripts/select-kit/templates/components/period-chooser/period-chooser-header.hbs index 698c054c930..ae393d67c0c 100644 --- a/app/assets/javascripts/select-kit/templates/components/period-chooser/period-chooser-header.hbs +++ b/app/assets/javascripts/select-kit/templates/components/period-chooser/period-chooser-header.hbs @@ -1,5 +1,8 @@

    - {{period-title value showDateRange=true fullDay=options.fullDay}} + {{period-title value + showDateRange=true + fullDay=selectKit.options.fullDay + }}

    {{d-icon caretIcon class="caret-icon"}} diff --git a/app/assets/javascripts/select-kit/templates/components/period-chooser/period-chooser-row.hbs b/app/assets/javascripts/select-kit/templates/components/period-chooser/period-chooser-row.hbs index d37693289a1..a0a5977c831 100644 --- a/app/assets/javascripts/select-kit/templates/components/period-chooser/period-chooser-row.hbs +++ b/app/assets/javascripts/select-kit/templates/components/period-chooser/period-chooser-row.hbs @@ -1,5 +1,8 @@ - {{period-title value showDateRange=true fullDay=options.fullDay}} + {{period-title rowValue + showDateRange=true + fullDay=selectKit.options.fullDay + }} diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/errors-collection.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/errors-collection.hbs new file mode 100644 index 00000000000..71befd8decb --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/errors-collection.hbs @@ -0,0 +1,5 @@ +{{#if collection}} + {{#each collection.content as |item|}} +
  • {{item}}
  • + {{/each}} +{{/if}} diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-body.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-body.hbs new file mode 100644 index 00000000000..cc9c19b3b4f --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-body.hbs @@ -0,0 +1,3 @@ +{{#if selectKit.isExpanded}} + {{yield}} +{{/if}} diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs index b673bd18d24..3a3cae0cecf 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs @@ -1,54 +1,7 @@ -{{#if collectionHeaderComputedContent}} -
    - {{{collectionHeaderComputedContent}}} -
    -{{/if}} - -{{#if hasSelection}} - {{#if noneRowComputedContent}} - {{component noneRowComponent - computedContent=noneRowComputedContent - templateForRow=templateForNoneRow - highlighted=highlighted - onMouseoverRow=onMouseoverRow - computedValue=computedValue - options=rowComponentOptions - onClickRow=onClickRow - }} - {{/if}} -{{/if}} - -{{#if createRowComputedContent}} - {{component createRowComponent - computedContent=createRowComputedContent - highlighted=highlighted - computedValue=computedValue - templateForRow=templateForCreateRow - onMouseoverRow=onMouseoverRow - onClickRow=onClickRow - options=rowComponentOptions +{{#each collection.content as |item|}} + {{component (component-for-row collection.identifier item selectKit) + item=item + value=value + selectKit=selectKit }} -{{/if}} - -{{#if noContentRow}} -
  • - {{noContentRow}} -
  • -{{else}} - {{#if validationMessage}} -
    - {{validationMessage}} -
    - {{/if}} - {{#each collectionComputedContent as |computedContent|}} - {{component rowComponent - computedContent=computedContent - highlighted=highlighted - computedValue=computedValue - templateForRow=templateForRow - onClickRow=onClickRow - onMouseoverRow=onMouseoverRow - options=rowComponentOptions - }} - {{/each}} -{{/if}} +{{/each}} diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs index 4e0a519d58c..30965ec78eb 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs @@ -1,19 +1,18 @@ -{{input - tabindex=-1 - class="filter-input" - placeholder=computedPlaceholder - key-up=onFilterComputedContent - autocomplete="off" - autocorrect="off" - autocapitalize="off" - spellcheck=false - value=filter -}} +{{#unless isHidden}} + {{input + tabindex=-1 + class="filter-input" + placeholder=placeholder + autocomplete="off" + autocorrect="off" + autocapitalize="off" + spellcheck=false + value=(readonly selectKit.filter) + input=(action "onInput") + keyDown=(action "onKeydown") + }} -{{#if isLoading}} - {{loading-spinner size="small"}} -{{else}} - {{#if icon}} - {{d-icon icon class="filter-icon"}} + {{#if selectKit.options.filterIcon}} + {{d-icon selectKit.options.filterIcon class="filter-icon"}} {{/if}} -{{/if}} +{{/unless}} diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-header.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-header.hbs deleted file mode 100644 index 77ea5328166..00000000000 --- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-header.hbs +++ /dev/null @@ -1,9 +0,0 @@ -{{#each icons as |icon|}} {{d-icon icon}} {{/each}} - - - {{#if forceEscape}} - {{label}} - {{else}} - {{{label}}} - {{/if}} - diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-row.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-row.hbs index 984d7091516..c5cd814c4e9 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-row.hbs +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-row.hbs @@ -1,12 +1,7 @@ -{{#if template}} - {{{template}}} -{{else}} - {{#each icons as |icon|}} {{d-icon icon}} {{/each}} - - {{#if forceEscape}} - {{label}} - {{else}} - {{{label}}} - {{/if}} - -{{/if}} +{{#each icons as |icon|}} + {{d-icon icon title=(dasherize title)}} +{{/each}} + + + {{label}} + diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/single-select-header.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/single-select-header.hbs new file mode 100644 index 00000000000..37bb407decc --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/single-select-header.hbs @@ -0,0 +1,7 @@ +{{#each icons as |icon|}} {{d-icon icon}} {{/each}} + +{{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit +}} diff --git a/app/assets/javascripts/select-kit/templates/components/selected-name.hbs b/app/assets/javascripts/select-kit/templates/components/selected-name.hbs new file mode 100644 index 00000000000..b1b1aa9ae3d --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/selected-name.hbs @@ -0,0 +1,26 @@ +{{#if selectKit.options.showFullTitle}} + {{#if item.icon}} + {{d-icon item.icon}} + {{/if}} + + + {{label}} + + + {{#if selectKit.options.clearOnClick}} + {{d-icon "times"}} + {{else}} + {{#if shouldDisplayClearableButton}} + {{d-button + class="btn-clear" + icon="times" + action=selectKit.deselect + actionParam=item + }} + {{/if}} + {{/if}} +{{else}} + {{#if item.icon}} + {{d-icon item.icon}} + {{/if}} +{{/if}} diff --git a/app/assets/javascripts/select-kit/templates/components/single-select.hbs b/app/assets/javascripts/select-kit/templates/components/single-select.hbs index e01241d3190..65b79a814d7 100644 --- a/app/assets/javascripts/select-kit/templates/components/single-select.hbs +++ b/app/assets/javascripts/select-kit/templates/components/single-select.hbs @@ -1,48 +1,36 @@ -{{component headerComponent - caretIcon=caretIcon - tabindex=tabindex - isFocused=isFocused - isExpanded=isExpanded - computedContent=headerComputedContent - onToggle=(action "onToggle") - onClearSelection=(action "onClearSelection") - options=headerComponentOptions -}} - -
    - {{component filterComponent - filter=filter - isLoading=isLoading - icon=filterIcon - hasSelection=hasSelection - shouldDisplayFilter=shouldDisplayFilter - placeholder=filterPlaceholder - isFocused=isFocused - onFilterComputedContent=(action "onFilterComputedContent") +{{#unless isHidden}} + {{component selectKit.options.headerComponent + tabindex=tabindex + value=value + selectedContent=selectedContent + selectKit=selectKit }} - {{#if renderedBodyOnce}} - {{component collectionComponent - collectionHeaderComputedContent=collectionHeaderComputedContent - hasSelection=hasSelection - noneRowComputedContent=noneRowComputedContent - createRowComputedContent=createRowComputedContent - collectionComputedContent=collectionComputedContent - rowComponent=rowComponent - noneRowComponent=noneRowComponent - createRowComponent=createRowComponent - templateForRow=templateForRow - templateForNoneRow=templateForNoneRow - templateForCreateRow=templateForCreateRow - onClickRow=(action "onClickRow") - onMouseoverRow=(action "onMouseoverRow") - highlighted=highlighted - computedValue=computedValue - rowComponentOptions=rowComponentOptions - noContentRow=noContentRow - validationMessage=validationMessage - }} - {{/if}} -
    + {{#select-kit/select-kit-body selectKit=selectKit}} + {{component selectKit.options.filterComponent selectKit=selectKit}} -
    + {{#unless selectKit.isLoading}} + {{#if selectKit.filter}} + {{#if selectKit.hasNoContent}} + + {{i18n "select_kit.no_content"}} + + {{/if}} + {{/if}} + + {{#each collections as |collection|}} + {{component (component-for-collection collection.identifier selectKit) + collection=collection + selectKit=selectKit + value=value + }} + {{/each}} + {{else}} + + {{loading-spinner size="small"}} + + {{/unless}} + {{/select-kit/select-kit-body}} + +
    +{{/unless}} diff --git a/app/assets/javascripts/select-kit/templates/components/tag-chooser-row.hbs b/app/assets/javascripts/select-kit/templates/components/tag-chooser-row.hbs new file mode 100644 index 00000000000..ef4dbda96b5 --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/tag-chooser-row.hbs @@ -0,0 +1 @@ +{{discourse-tag rowValue count=item.count noHref=true}} diff --git a/app/assets/javascripts/select-kit/templates/components/tag-drop/tag-drop-header.hbs b/app/assets/javascripts/select-kit/templates/components/tag-drop/tag-drop-header.hbs index 41e15b8653e..a800565e6d1 100644 --- a/app/assets/javascripts/select-kit/templates/components/tag-drop/tag-drop-header.hbs +++ b/app/assets/javascripts/select-kit/templates/components/tag-drop/tag-drop-header.hbs @@ -1,9 +1,8 @@ - - {{#if forceEscape}} - {{label}} - {{else}} - {{{label}}} - {{/if}} - +{{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit + shouldDisplayClearableButton=shouldDisplayClearableButton +}} {{d-icon caretIcon class="caret-icon"}} diff --git a/app/assets/javascripts/select-kit/templates/components/tag-row.hbs b/app/assets/javascripts/select-kit/templates/components/tag-row.hbs new file mode 100644 index 00000000000..da8360699bd --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/tag-row.hbs @@ -0,0 +1,4 @@ +{{discourse-tag rowValue + noHref=true + count=item.count +}} diff --git a/app/assets/javascripts/select-kit/templates/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.hbs b/app/assets/javascripts/select-kit/templates/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.hbs new file mode 100644 index 00000000000..0b82f33dcc4 --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.hbs @@ -0,0 +1 @@ +{{heading}} diff --git a/app/assets/javascripts/select-kit/templates/components/topic-notifications-button.hbs b/app/assets/javascripts/select-kit/templates/components/topic-notifications-button.hbs index 2569ea0c865..2f3a689c8c5 100644 --- a/app/assets/javascripts/select-kit/templates/components/topic-notifications-button.hbs +++ b/app/assets/javascripts/select-kit/templates/components/topic-notifications-button.hbs @@ -1,7 +1,11 @@ {{topic-notifications-options value=notificationLevel topic=topic - showFullTitle=showFullTitle}} + onChange=(action "changeTopicNotificationLevel") + options=(hash + showFullTitle=showFullTitle + ) +}} {{#if appendReason}}

    diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js index 6304b9816ac..0d1ee8b7297 100644 --- a/app/assets/javascripts/vendor.js +++ b/app/assets/javascripts/vendor.js @@ -8,6 +8,7 @@ //= require jquery.ui.widget.js //= require Markdown.Converter.js //= require bootbox.js +//= require popper.js //= require bootstrap-modal.js //= require caret_position //= require favcount.js diff --git a/app/assets/javascripts/wizard/components/invite-list.js.es6 b/app/assets/javascripts/wizard/components/invite-list.js.es6 index de04d70bab5..2c541d38c5d 100644 --- a/app/assets/javascripts/wizard/components/invite-list.js.es6 +++ b/app/assets/javascripts/wizard/components/invite-list.js.es6 @@ -17,6 +17,8 @@ export default Component.extend({ { id: "regular", label: I18n.t("wizard.invites.roles.regular") } ]); + this.set("inviteRole", this.get("roles.0.id")); + this.updateField(); }, diff --git a/app/assets/javascripts/wizard/templates/components/invite-list.hbs b/app/assets/javascripts/wizard/templates/components/invite-list.hbs index d37cc82e3fe..4a8ea1ffae7 100644 --- a/app/assets/javascripts/wizard/templates/components/invite-list.hbs +++ b/app/assets/javascripts/wizard/templates/components/invite-list.hbs @@ -12,7 +12,12 @@ {{input class="invite-email wizard-focusable" value=inviteEmail placeholder="user@example.com" tabindex="9"}}

    - {{combo-box allowInitialValueMutation=true value=inviteRole content=roles nameProperty="label"}} + {{combo-box + value=inviteRole + content=roles + nameProperty="label" + onChange=(action (mut inviteRole)) + }}