Introduces select-kit

* renames `select-box-kit` into `select-kit`
* introduces `single-select` and `multi-select` as base components
* introduces {{search-advanced-category-chooser}} as a better component for selecting category in advanced search
* improves events handling in select-kit
* recreates color selection inputs using {{multi-select}} and a custom {{selected-color}} component
* replaces category-selector by a component using select-kit and based on multi-select
* improves positioning of wrapper
* removes the need for offscreen, and instead use `select-kit-header` as a base focus point for all select-kit based components
* introduces a formal plugin api for select-kit based components
* introduces a formal pattern for loading and updating select-kit based components:

```
computeValue()
computeContent()
mutateValue()
```
This commit is contained in:
Joffrey JAFFEUX
2017-11-21 11:53:09 +01:00
committed by GitHub
parent edc4b30f82
commit 39f3dbd945
191 changed files with 3160 additions and 2788 deletions
@@ -0,0 +1,87 @@
import DropdownSelectBox from "select-kit/components/dropdown-select-box";
import computed from "ember-addons/ember-computed-decorators";
export default DropdownSelectBox.extend({
pluginApiIdentifiers: ["admin-agree-flag-dropdown"],
classNames: ["agree-flag", "admin-agree-flag-dropdown"],
adminTools: Ember.inject.service(),
nameProperty: "label",
allowInitialValueMutation: false,
headerIcon: "thumbs-o-up",
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
content.name = `${I18n.t("admin.flags.agree")}...`;
return content;
},
@computed("adminTools", "post.user")
spammerDetails(adminTools, user) {
return adminTools.spammerDetails(user);
},
canDeleteSpammer: Ember.computed.and("spammerDetails.canDelete", "post.flaggedForSpam"),
computeContent() {
const content = [];
const post = this.get("post");
const canDeleteSpammer = this.get("canDeleteSpammer");
if (post.user_deleted) {
content.push({
icon: "eye",
id: "confirm-agree-restore",
action: () => this.send("perform", "restore"),
label: I18n.t("admin.flags.agree_flag_restore_post"),
description: I18n.t("admin.flags.agree_flag_restore_post_title")
});
} else {
if (!post.get("postHidden")) {
content.push({
icon: "eye-slash",
action: () => this.send("perform", "hide"),
id: "confirm-agree-hide",
label: I18n.t("admin.flags.agree_flag_hide_post"),
description: I18n.t("admin.flags.agree_flag_hide_post_title")
});
}
}
content.push({
icon: "thumbs-o-up",
id: "confirm-agree-keep",
description: I18n.t('admin.flags.agree_flag_title'),
action: () => this.send("perform", "keep"),
label: I18n.t("admin.flags.agree_flag"),
});
if (canDeleteSpammer) {
content.push({
title: I18n.t("admin.flags.delete_spammer_title"),
icon: "exclamation-triangle",
id: "delete-spammer",
action: () => this.send("deleteSpammer"),
label: I18n.t("admin.flags.delete_spammer"),
});
}
return content;
},
mutateValue(value) {
const computedContentItem = this.get("computedContent").findBy("value", value);
Ember.get(computedContentItem, "originalContent.action")();
},
actions: {
deleteSpammer() {
let spammerDetails = this.get("spammerDetails");
this.attrs.removeAfter(spammerDetails.deleteUser());
},
perform(action) {
let flaggedPost = this.get("post");
this.attrs.removeAfter(flaggedPost.agreeFlags(action));
},
}
});
@@ -0,0 +1,77 @@
import DropdownSelectBox from "select-kit/components/dropdown-select-box";
import computed from "ember-addons/ember-computed-decorators";
export default DropdownSelectBox.extend({
classNames: ["delete-flag", "admin-delete-flag-dropdown"],
adminTools: Ember.inject.service(),
nameProperty: "label",
headerIcon: "trash-o",
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
content.name = I18n.t("admin.flags.delete");
return content;
},
@computed("adminTools", "post.user")
spammerDetails(adminTools, user) {
return adminTools.spammerDetails(user);
},
canDeleteSpammer: Ember.computed.and("spammerDetails.canDelete", "post.flaggedForSpam"),
computeContent() {
const content = [];
const canDeleteSpammer = this.get("canDeleteSpammer");
content.push({
icon: "external-link",
id: "delete-defer",
action: () => this.send("deletePostDeferFlag"),
label: I18n.t("admin.flags.delete_post_defer_flag"),
description: I18n.t("admin.flags.delete_post_defer_flag_title"),
});
content.push({
icon: "thumbs-o-up",
id: "delete-agree",
action: () => this.send("deletePostAgreeFlag"),
label: I18n.t("admin.flags.delete_post_agree_flag"),
description: I18n.t("admin.flags.delete_post_agree_flag_title"),
});
if (canDeleteSpammer) {
content.push({
title: I18n.t("admin.flags.delete_post_agree_flag_title"),
icon: "exclamation-triangle",
id: "delete-spammer",
action: () => this.send("deleteSpammer"),
label: I18n.t("admin.flags.delete_spammer")
});
}
return content;
},
mutateValue(value) {
const computedContentItem = this.get("computedContent").findBy("value", value);
Ember.get(computedContentItem, "originalContent.action")();
},
actions: {
deleteSpammer() {
let spammerDetails = this.get("spammerDetails");
this.attrs.removeAfter(spammerDetails.deleteUser());
},
deletePostDeferFlag() {
let flaggedPost = this.get('post');
this.attrs.removeAfter(flaggedPost.deferFlags(true));
},
deletePostAgreeFlag() {
let flaggedPost = this.get('post');
this.attrs.removeAfter(flaggedPost.agreeFlags('delete'));
}
}
});
@@ -0,0 +1,51 @@
import MultiSelectComponent from "select-kit/components/multi-select";
const { makeArray } = Ember;
export default MultiSelectComponent.extend({
pluginApiIdentifiers: ["admin-group-selector"],
classNames: "admin-group-selector",
selected: null,
available: null,
allowAny: false,
computeValues() {
return makeArray(this.get("selected"))
.map(s => this.valueForContentItem(s));
},
computeContent() {
return makeArray(this.get("available"));
},
computeContentItem(contentItem, name) {
let computedContent = this.baseComputedContentItem(contentItem, name);
computedContent.locked = contentItem.automatic;
return computedContent;
},
mutateValues(values) {
if (values.length > this.get("selected").length) {
const newValues = values
.filter(v => !this.get("selected")
.map(s => this.valueForContentItem(s))
.includes(v));
newValues.forEach(value => {
const actionContext = this.get("available")
.findBy(this.get("valueAttribute"), parseInt(value, 10));
this.triggerAction({ action: "groupAdded", actionContext });
});
} else if (values.length < this.get("selected").length) {
const selected = this.get("selected")
.filter(s => !values.includes(this.valueForContentItem(s)));
selected.forEach(s => {
this.triggerAction({
action: "groupRemoved",
actionContext: this.valueForContentItem(s)
});
});
}
}
});
@@ -0,0 +1,38 @@
import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box";
export default DropdownSelectBoxComponent.extend({
pluginApiIdentifiers: ["categories-admin-dropdown"],
classNames: "categories-admin-dropdown",
showFullTitle: false,
allowInitialValueMutation: false,
headerIcon: ["bars", "caret-down"],
autoHighlight() {},
computeContent() {
const items = [
{
id: "create",
name: I18n.t("category.create"),
description: I18n.t("category.create_long"),
icon: "plus"
}
];
const includeReorder = this.get("siteSettings.fixed_category_positions");
if (includeReorder) {
items.push({
id: "reorder",
name: I18n.t("categories.reorder.title"),
description: I18n.t("categories.reorder.title_long"),
icon: "random"
});
}
return items;
},
mutateValue(value) {
this.get(value)();
}
});
@@ -0,0 +1,92 @@
import ComboBoxComponent from "select-kit/components/combo-box";
import { on } from "ember-addons/ember-computed-decorators";
import computed from "ember-addons/ember-computed-decorators";
import PermissionType from "discourse/models/permission-type";
import Category from "discourse/models/category";
const { get, isNone, isEmpty } = Ember;
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,
filterComputedContent(computedContent, computedValue, filter) {
if (isEmpty(filter)) { return computedContent; }
const _matchFunction = (f, text) => {
return text.toLowerCase().indexOf(f) > -1;
};
const lowerFilter = filter.toLowerCase();
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(lowerFilter, text) || _matchFunction(lowerFilter, categoryName);
} else {
return _matchFunction(lowerFilter, text);
}
});
},
@computed("rootNone", "rootNoneLabel")
none(rootNone, rootNoneLabel) {
if (this.siteSettings.allow_uncategorized_topics || this.get("allowUncategorized")) {
if (!isNone(rootNone)) {
return rootNoneLabel || "category.none";
} else {
return Category.findUncategorized();
}
} else {
return "category.choose";
}
},
@on("didRender")
_bindComposerResizing() {
this.appEvents.on("composer:resized", this, this.applyDirection);
},
@on("willDestroyElement")
_unbindComposerResizing() {
this.appEvents.off("composer:resized");
},
computeContent() {
const categories = Discourse.SiteSettings.fixed_category_positions_on_create ?
Category.list() :
Category.listByActivity();
let scopedCategoryId = this.get("scopedCategoryId");
if (scopedCategoryId) {
const scopedCat = Category.findById(scopedCategoryId);
scopedCategoryId = scopedCat.get("parent_category_id") || scopedCat.get("id");
}
const excludeCategoryId = this.get("excludeCategoryId");
return categories.filter(c => {
const categoryId = this.valueForContentItem(c);
if (scopedCategoryId && categoryId !== scopedCategoryId && get(c, "parent_category_id") !== scopedCategoryId) {
return false;
}
if (this.get("allowSubCategories") === false && c.get("parentCategory") ) {
return false;
}
if ((this.get("allowUncategorized") === false && get(c, "isUncategorizedCategory")) || excludeCategoryId === categoryId) {
return false;
}
return get(c, "permission") === PermissionType.FULL;
});
}
});
@@ -0,0 +1,20 @@
import NotificationOptionsComponent from "select-kit/components/notifications-button";
import computed from "ember-addons/ember-computed-decorators";
export default NotificationOptionsComponent.extend({
pluginApiIdentifiers: ["category-notifications-button"],
classNames: "category-notifications-button",
isHidden: Ember.computed.or("category.deleted", "site.isMobileDevice"),
i18nPrefix: "category.notifications",
showFullTitle: false,
allowInitialValueMutation: false,
mutateValue(value) {
this.get("category").setNotification(value);
},
@computed("iconForSelectedDetails")
headerIcon(iconForSelectedDetails) {
return [iconForSelectedDetails, "caret-down"];
}
});
@@ -0,0 +1,63 @@
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
import computed from "ember-addons/ember-computed-decorators";
import Category from "discourse/models/category";
import { categoryBadgeHTML } from "discourse/helpers/category-link";
export default SelectKitRowComponent.extend({
layoutName: "select-kit/templates/components/category-row",
classNames: "category-row",
displayCategoryDescription: true,
@computed("computedContent.value", "computedContent.name")
category(value, name) {
if (Ember.isEmpty(value)) {
const uncat = Category.findUncategorized();
if (uncat && uncat.get("name") === name) {
return uncat;
}
} else {
return Category.findById(parseInt(value, 10));
}
},
@computed("category")
badgeForCategory(category) {
return categoryBadgeHTML(category, {
link: false,
allowUncategorized: true,
hideParent: true
}).htmlSafe();
},
@computed("parentCategory")
badgeForParentCategory(parentCategory) {
return categoryBadgeHTML(parentCategory, {link: false}).htmlSafe();
},
@computed("parentCategoryid")
parentCategory(parentCategoryId) {
return Category.findById(parentCategoryId);
},
@computed("parentCategoryid")
hasParentCategory(parentCategoryid) {
return !Ember.isNone(parentCategoryid);
},
@computed("category")
parentCategoryid(category) {
return category.get("parent_category_id");
},
topicCount: Ember.computed.alias("category.topic_count"),
@computed("options.displayCategoryDescription", "category.description")
hasDescription(displayCategoryDescription, description) {
return displayCategoryDescription && description && description !== "null";
},
@computed("category.description")
description(description) {
return `${description.substr(0, 200)}${description.length > 200 ? '&hellip;' : ''}`;
}
});
@@ -0,0 +1,40 @@
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",
init() {
this._super();
this.set("headerComponentOptions", Ember.Object.create({
selectedNameComponent: "multi-select/selected-category"
}));
this.set("rowComponentOptions", Ember.Object.create({
displayCategoryDescription: false
}));
},
computeValues() {
return Ember.makeArray(this.get("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.toLowerCase(), 'i');
return computedContent.filter(category => Ember.get(category, "name").match(regex));
},
computeContent() {
const blacklist = Ember.makeArray(this.get("blacklist"));
return Category.list().filter(category => !blacklist.includes(category));
}
});
@@ -0,0 +1,28 @@
import SingleSelectComponent from "select-kit/components/single-select";
import { on } from "ember-addons/ember-computed-decorators";
export default SingleSelectComponent.extend({
pluginApiIdentifiers: ["combo-box"],
classNames: "combobox combo-box",
autoFilterable: true,
headerComponent: "combo-box/combo-box-header",
caretUpIcon: "caret-up",
caretDownIcon: "caret-down",
clearable: false,
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
content.hasSelection = this.get("hasSelection");
return content;
},
@on("didReceiveAttrs")
_setComboBoxOptions() {
this.get("headerComponentOptions").setProperties({
caretUpIcon: this.get("caretUpIcon"),
caretDownIcon: this.get("caretDownIcon"),
clearable: this.get("clearable"),
});
}
});
@@ -0,0 +1,21 @@
import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header";
import { default as computed } from "ember-addons/ember-computed-decorators";
export default SelectKitHeaderComponent.extend({
layoutName: "select-kit/templates/components/combo-box/combo-box-header",
classNames: "combo-box-header",
clearable: Ember.computed.alias("options.clearable"),
caretUpIcon: Ember.computed.alias("options.caretUpIcon"),
caretDownIcon: Ember.computed.alias("options.caretDownIcon"),
@computed("isExpanded", "caretUpIcon", "caretDownIcon")
caretIcon(isExpanded, caretUpIcon, caretDownIcon) {
return isExpanded === true ? caretUpIcon : caretDownIcon;
},
@computed("clearable", "computedContent.hasSelection")
shouldDisplayClearableButton(clearable, hasSelection) {
return clearable === true && hasSelection === true;
}
});
@@ -0,0 +1,32 @@
import SingleSelectComponent from "select-kit/components/single-select";
import { on } from "ember-addons/ember-computed-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,
@on("didReceiveAttrs")
_setDropdownSelectBoxComponentOptions() {
this.get("headerComponentOptions").setProperties({
showFullTitle: this.get("showFullTitle")
});
},
didClickOutside() {
if (this.get("isExpanded") === false) { return; }
this.close();
},
didSelect() {
this._super();
this.close();
}
});
@@ -0,0 +1,15 @@
import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header";
import computed from "ember-addons/ember-computed-decorators";
export default SelectKitHeaderComponent.extend({
layoutName: "select-kit/templates/components/dropdown-select-box/dropdown-select-box-header",
classNames: "dropdown-select-box-header",
tagName: "button",
classNameBindings: ["btnClassName"],
@computed("options.showFullTitle")
btnClassName(showFullTitle) {
return `btn ${showFullTitle ? 'btn-icon-text' : 'no-text btn-icon'}`;
}
});
@@ -0,0 +1,9 @@
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",
name: Ember.computed.alias("computedContent.name"),
description: Ember.computed.alias("computedContent.originalContent.description")
});
@@ -0,0 +1,167 @@
import ComboBoxComponent from "select-kit/components/combo-box";
import { CLOSE_STATUS_TYPE } from "discourse/controllers/edit-topic-timer";
import DatetimeMixin from "select-kit/components/future-date-input-selector/mixin";
const TIMEFRAME_BASE = {
enabled: () => true,
when: () => null,
icon: 'briefcase',
displayWhen: true,
};
function buildTimeframe(opts) {
return jQuery.extend({}, TIMEFRAME_BASE, opts);
}
export const TIMEFRAMES = [
buildTimeframe({
id: 'later_today',
format: "h a",
enabled: opts => opts.canScheduleToday,
when: (time) => time.hour(18).minute(0),
icon: 'moon-o'
}),
buildTimeframe({
id: "tomorrow",
format: "ddd, h a",
when: (time, timeOfDay) => time.add(1, 'day').hour(timeOfDay).minute(0),
icon: 'sun-o'
}),
buildTimeframe({
id: "later_this_week",
format: "ddd, h a",
enabled: opts => !opts.canScheduleToday && opts.day < 4,
when: (time, timeOfDay) => time.add(2, 'day').hour(timeOfDay).minute(0),
}),
buildTimeframe({
id: "this_weekend",
format: "ddd, h a",
enabled: opts => opts.day < 5 && opts.includeWeekend,
when: (time, timeOfDay) => time.day(6).hour(timeOfDay).minute(0),
icon: 'bed'
}),
buildTimeframe({
id: "next_week",
format: "ddd, h a",
enabled: opts => opts.day !== 7,
when: (time, timeOfDay) => time.add(1, 'week').day(1).hour(timeOfDay).minute(0),
icon: 'briefcase'
}),
buildTimeframe({
id: "two_weeks",
format: "MMM D",
when: (time, timeOfDay) => time.add(2, 'week').hour(timeOfDay).minute(0),
icon: 'briefcase'
}),
buildTimeframe({
id: "next_month",
format: "MMM D",
enabled: opts => opts.now.date() !== moment().endOf("month").date(),
when: (time, timeOfDay) => time.add(1, 'month').startOf('month').hour(timeOfDay).minute(0),
icon: 'briefcase'
}),
buildTimeframe({
id: "three_months",
format: "MMM D",
enabled: opts => opts.includeFarFuture,
when: (time, timeOfDay) => time.add(3, 'month').startOf('month').hour(timeOfDay).minute(0),
icon: 'briefcase'
}),
buildTimeframe({
id: "six_months",
format: "MMM D",
enabled: opts => opts.includeFarFuture,
when: (time, timeOfDay) => time.add(6, 'month').startOf('month').hour(timeOfDay).minute(0),
icon: 'briefcase'
}),
buildTimeframe({
id: "one_year",
format: "MMM D",
enabled: opts => opts.includeFarFuture,
when: (time, timeOfDay) => time.add(1, 'year').startOf('day').hour(timeOfDay).minute(0),
icon: 'briefcase'
}),
buildTimeframe({
id: "forever",
enabled: opts => opts.includeFarFuture,
when: (time, timeOfDay) => time.add(1000, 'year').hour(timeOfDay).minute(0),
icon: 'gavel',
displayWhen: false
}),
buildTimeframe({
id: "pick_date_and_time",
icon: 'calendar-plus-o'
}),
buildTimeframe({
id: "set_based_on_last_post",
enabled: opts => opts.includeBasedOnLastPost,
icon: 'clock-o'
}),
];
let _timeframeById = null;
export function timeframeDetails(id) {
if (!_timeframeById) {
_timeframeById = {};
TIMEFRAMES.forEach(t => _timeframeById[t.id] = t);
}
return _timeframeById[id];
}
export const FORMAT = "YYYY-MM-DD HH:mm";
export default ComboBoxComponent.extend(DatetimeMixin, {
pluginApiIdentifiers: ["future-date-input-selector"],
classNames: ["future-date-input-selector"],
isCustom: Ember.computed.equal("value", "pick_date_and_time"),
clearable: true,
rowComponent: "future-date-input-selector/future-date-input-selector-row",
headerComponent: "future-date-input-selector/future-date-input-selector-header",
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
content.datetime = this._computeDatetimeForValue(this.get("computedValue"));
content.name = this.get("selectedComputedContent.name") || content.name;
content.hasSelection = this.get("hasSelection");
content.icons = this._computeIconsForValue(this.get("computedValue"));
return content;
},
computeContentItem(contentItem, name) {
let item = this.baseComputedContentItem(contentItem, name);
item.datetime = this._computeDatetimeForValue(contentItem.id);
item.icons = this._computeIconsForValue(contentItem.id);
return item;
},
computeContent() {
let now = moment();
let opts = {
now,
day: now.day(),
includeWeekend: this.get('includeWeekend'),
includeFarFuture: this.get('includeFarFuture'),
includeBasedOnLastPost: this.get("statusType") === CLOSE_STATUS_TYPE,
canScheduleToday: (24 - now.hour()) > 6,
};
return TIMEFRAMES.filter(tf => tf.enabled(opts)).map(tf => {
return {
id: tf.id,
name: I18n.t(`topic.auto_update_input.${tf.id}`)
};
});
},
mutateValue(value) {
if (this.get("isCustom")) return;
let input = null;
const { time } = this._updateAt(value);
if (time && !Ember.isEmpty(value)) {
input = time.format(FORMAT);
}
this.setProperties({ input, value });
},
});
@@ -0,0 +1,6 @@
import ComboBoxHeaderComponent from "select-kit/components/combo-box/combo-box-header";
export default ComboBoxHeaderComponent.extend({
layoutName: "select-kit/templates/components/future-date-input-selector/future-date-input-selector-header",
classNames: "future-date-input-selector-header"
});
@@ -0,0 +1,6 @@
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
export default SelectKitRowComponent.extend({
layoutName: "select-kit/templates/components/future-date-input-selector/future-date-input-selector-row",
classNames: "future-date-input-selector-row"
});
@@ -0,0 +1,45 @@
import { CLOSE_STATUS_TYPE } from 'discourse/controllers/edit-topic-timer';
import { timeframeDetails } from 'select-kit/components/future-date-input-selector';
export default Ember.Mixin.create({
_computeIconsForValue(value) {
let {icon} = this._updateAt(value);
if (icon) {
return icon.split(",");
}
return [];
},
_computeDatetimeForValue(value) {
if (Ember.isNone(value)) {
return null;
}
let {time} = this._updateAt(value);
if (time) {
let details = timeframeDetails(value);
if (!details.displayWhen) {
time = null;
}
if (time && details.format) {
return time.format(details.format);
}
}
return time;
},
_updateAt(selection) {
let details = timeframeDetails(selection);
if (details) {
return {
time: details.when(moment(), this.get('statusType') !== CLOSE_STATUS_TYPE ? 8 : 18),
icon: details.icon
};
}
return { time: moment() };
},
});
@@ -0,0 +1,12 @@
import NotificationOptionsComponent from "select-kit/components/notifications-button";
export default NotificationOptionsComponent.extend({
pluginApiIdentifiers: ["grouo-notifications-button"],
classNames: ["group-notifications-button"],
i18nPrefix: "groups.notifications",
allowInitialValueMutation: false,
mutateValue(value) {
this.get("group").setNotification(value, this.get("user.id"));
}
});
@@ -0,0 +1,54 @@
import MultiSelectComponent from "select-kit/components/multi-select";
export default MultiSelectComponent.extend({
pluginApiIdentifiers: ["list-setting"],
classNames: "list-setting",
tokenSeparator: "|",
settingValue: "",
choices: null,
filterable: true,
init() {
this._super();
if (!Ember.isNone(this.get("settingName"))) {
this.set("nameProperty", this.get("settingName"));
}
if (this.get("nameProperty").indexOf("color") > -1) {
this.set("headerComponentOptions", Ember.Object.create({
selectedNameComponent: "multi-select/selected-color"
}));
}
},
computeContent() {
let content;
if (Ember.isNone(this.get("choices"))) {
content = this.get("settingValue").split(this.get("tokenSeparator"));;
} else {
content = this.get("choices");
}
return Ember.makeArray(content).filter(c => c);
},
mutateValues(values) {
this.set("settingValue", values.join(this.get("tokenSeparator")));
},
computeValues() {
return this.get("settingValue")
.split(this.get("tokenSeparator"))
.filter(c => c);
},
_handleTabOnKeyDown(event) {
if (this.$highlightedRow().length === 1) {
this._super(event);
} else {
this.close();
return false;
}
}
});
@@ -0,0 +1,255 @@
import SelectKitComponent from "select-kit/components/select-kit";
import computed from "ember-addons/ember-computed-decorators";
import { on } from "ember-addons/ember-computed-decorators";
const { get, isNone, isEmpty, makeArray } = Ember;
export default SelectKitComponent.extend({
pluginApiIdentifiers: ["multi-select"],
classNames: "multi-select",
headerComponent: "multi-select/multi-select-header",
filterComponent: null,
headerText: "select_kit.default_header_text",
allowAny: true,
allowInitialValueMutation: false,
autoFilterable: true,
selectedNameComponent: "multi-select/selected-name",
init() {
this._super();
this.set("computedValues", []);
if (isNone(this.get("values"))) { this.set("values", []); }
this.set("headerComponentOptions", Ember.Object.create({
selectedNameComponent: this.get("selectedNameComponent")
}));
},
@on("didRender")
_setChoicesMaxWidth() {
const width = this.$body().outerWidth(false);
this.$(".choices").css({ maxWidth: width, width });
},
@on("didReceiveAttrs")
_compute() {
Ember.run.scheduleOnce("afterRender", () => {
this.willComputeAttributes();
let content = this._beforeWillComputeContent(this.get("content"));
content = this.willComputeContent(content);
let values = this._beforeWillComputeValues(this.get("values"));
content = this.computeContent(content);
content = this._beforeDidComputeContent(content);
values = this.willComputeValues(values);
values = this.computeValues(values);
values = this._beforeDidComputeValues(values);
this.set("headerComputedContent", this.computeHeaderContent());
this.didComputeContent(content);
this.didComputeValues(values);
this.didComputeAttributes();
});
},
@computed("filter", "shouldDisplayCreateRow")
createRowComputedContent(filter, shouldDisplayCreateRow) {
if (shouldDisplayCreateRow === true) {
let content = this.createContentFromInput(filter);
return this.computeContentItem(content, { created: true });
}
},
@computed("filter", "computedValues")
shouldDisplayCreateRow(filter, computedValues) {
return this._super() && !computedValues.includes(filter);
},
_beforeWillComputeValues(values) {
return values.map(v => this._castInteger(v === "" ? null : v));
},
willComputeValues(values) { return values; },
computeValues(values) { return values; },
_beforeDidComputeValues(values) {
this.setProperties({ computedValues: values });
return values;
},
didComputeValues(values) { return values; },
mutateAttributes() {
Ember.run.next(() => {
this.mutateContent(this.get("computedContent"));
this.mutateValues(this.get("computedValues"));
this.set("headerComputedContent", this.computeHeaderContent());
});
},
mutateValues(computedValues) { this.set("values", computedValues); },
filterComputedContent(computedContent, computedValues, filter) {
const lowerFilter = filter.toLowerCase();
return computedContent.filter(c => {
return get(c, "name").toLowerCase().indexOf(lowerFilter) > -1;
});
},
@computed("computedContent.[]", "computedValues.[]", "filter")
filteredComputedContent(computedContent, computedValues, filter) {
computedContent = computedContent.filter(c => {
return !computedValues.includes(get(c, "value"));
});
if (this.get("shouldFilter") === true) {
computedContent = this.filterComputedContent(computedContent, computedValues, filter);
}
return computedContent.slice(0, this.get("limitMatches"));
},
baseHeaderComputedContent() {
return {
selectedComputedContents: this.get("selectedComputedContents")
};
},
@computed("filter")
templateForCreateRow() {
return (rowComponent) => {
return I18n.t("select_kit.create", { content: rowComponent.get("computedContent.name")});
};
},
didPressBackspace(event) {
this.expand();
this.keyDown(event);
this._destroyEvent(event);
},
didPressEscape(event) {
const $highlighted = this.$(".selected-name.is-highlighted");
if ($highlighted.length > 0) {
$highlighted.removeClass("is-highlighted");
}
this._super(event);
},
keyDown(event) {
if (!isEmpty(this.get("filter"))) return;
const keyCode = event.keyCode || event.which;
const $filterInput = this.$filterInput();
// select all choices
if (this.get("hasSelection") && event.metaKey === true && keyCode === 65) {
this.$(".choices .selected-name:not(.is-locked)").addClass("is-highlighted");
return false;
}
// clear selection when multiple
if (this.$(".selected-name.is-highlighted").length >= 1 && keyCode === this.keys.BACKSPACE) {
const highlightedComputedContents = [];
$.each(this.$(".selected-name.is-highlighted"), (i, el) => {
const computedContent = this._findComputedContentItemByGuid($(el).attr("data-guid"));
if (!Ember.isNone(computedContent)) { highlightedComputedContents.push(computedContent); }
});
this.send("onDeselect", highlightedComputedContents);
return;
}
// try to remove last item from the list
if (keyCode === this.keys.BACKSPACE) {
let $lastSelectedValue = $(this.$(".choices .selected-name:not(.is-locked)").last());
if ($lastSelectedValue.length === 0) { return; }
if ($filterInput.not(":visible") && $lastSelectedValue.length > 0) {
$lastSelectedValue.click();
return false;
}
if ($filterInput.val() === "") {
if ($filterInput.is(":focus")) {
if ($lastSelectedValue.length > 0) { $lastSelectedValue.click(); }
} else {
if ($lastSelectedValue.length > 0) {
$lastSelectedValue.click();
} else {
$filterInput.focus();
}
}
}
}
},
@computed("computedValues.[]", "computedContent.[]")
selectedComputedContents(computedValues, computedContent) {
const selected = [];
computedValues.forEach(v => selected.push(computedContent.findBy("value", v)) );
return selected;
},
@computed("selectedComputedContents.[]")
hasSelection(selectedComputedContents) { return !Ember.isEmpty(selectedComputedContents); },
autoHighlight() {
Ember.run.schedule("afterRender", () => {
if (this.get("isExpanded") === false) { return; }
if (this.get("renderedBodyOnce") === false) { return; }
if (!isNone(this.get("highlightedValue"))) { return; }
if (isEmpty(this.get("filteredComputedContent"))) {
if (this.get("createRowComputedContent")) {
this.send("onHighlight", this.get("createRowComputedContent"));
} else if (this.get("noneRowComputedContent") && this.get("hasSelection") === true) {
this.send("onHighlight", this.get("noneRowComputedContent"));
}
} else {
this.send("onHighlight", this.get("filteredComputedContent.firstObject"));
}
});
},
didSelect() {
this.focus();
this.autoHighlight();
},
didDeselect() {
this.focus();
this.autoHighlight();
},
validateComputedContentItem(computedContentItem) {
return !this.get("computedValues").includes(computedContentItem.value);
},
actions: {
onClear() {
this.get("selectedComputedContents").forEach(selectedComputedContent => {
this.send("onDeselect", selectedComputedContent);
});
},
onCreate(computedContentItem) {
if (this.validateComputedContentItem(computedContentItem)) {
this.get("computedContent").pushObject(computedContentItem);
this.send("onSelect", computedContentItem);
}
},
onSelect(computedContentItem) {
this.willSelect(computedContentItem);
this.get("computedValues").pushObject(computedContentItem.value);
Ember.run.next(() => this.mutateAttributes());
Ember.run.schedule("afterRender", () => this.didSelect(computedContentItem));
},
onDeselect(rowComputedContentItems) {
rowComputedContentItems = Ember.makeArray(rowComputedContentItems);
const generatedComputedContents = this._filterRemovableComputedContents(makeArray(rowComputedContentItems));
this.willDeselect(rowComputedContentItems);
this.get("computedValues").removeObjects(rowComputedContentItems.map(r => r.value));
this.get("computedContent").removeObjects(generatedComputedContents);
Ember.run.next(() => this.mutateAttributes());
Ember.run.schedule("afterRender", () => this.didDeselect(rowComputedContentItems));
}
}
});
@@ -0,0 +1,32 @@
import { on } from "ember-addons/ember-computed-decorators";
import computed from "ember-addons/ember-computed-decorators";
import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header";
export default SelectKitHeaderComponent.extend({
attributeBindings: ["names:data-name"],
classNames: "multi-select-header",
layoutName: "select-kit/templates/components/multi-select/multi-select-header",
selectedNameComponent: Ember.computed.alias("options.selectedNameComponent"),
@on("didRender")
_positionFilter() {
if (this.get("shouldDisplayFilter") === false) { return; }
const $filter = this.$(".filter");
$filter.width(0);
const leftHeaderOffset = this.$().offset().left;
const leftFilterOffset = $filter.offset().left;
const offset = leftFilterOffset - leftHeaderOffset;
const width = this.$().outerWidth(false);
const availableSpace = width - offset;
const $choices = $filter.parent(".choices");
const parentRightPadding = parseInt($choices.css("padding-right") , 10);
$filter.width(availableSpace - parentRightPadding * 4);
},
@computed("computedContent.selectedComputedContents.[]")
names(selectedComputedContents) {
return Ember.makeArray(selectedComputedContents).map(sc => sc.name).join(",");
}
});
@@ -0,0 +1,13 @@
import SelectedNameComponent from "select-kit/components/multi-select/selected-name";
import computed from "ember-addons/ember-computed-decorators";
import { categoryBadgeHTML } from "discourse/helpers/category-link";
export default SelectedNameComponent.extend({
classNames: "selected-category",
layoutName: "select-kit/templates/components/multi-select/selected-category",
@computed("content.originalContent")
badge(category) {
return categoryBadgeHTML(category, {allowUncategorized: true, link: false}).htmlSafe();
}
});
@@ -0,0 +1,11 @@
import SelectedNameComponent from "select-kit/components/multi-select/selected-name";
export default SelectedNameComponent.extend({
classNames: "selected-color",
layoutName: "select-kit/templates/components/multi-select/selected-color",
didRender() {
const name = this.get("content.name");
this.$(".color-preview").css("background", `#${name}`.htmlSafe());
}
});
@@ -0,0 +1,28 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
attributeBindings: [
"tabindex",
"content.name:data-name",
"content.value:data-value",
"guid:data-guid"
],
classNames: ["selected-name", "choice"],
classNameBindings: ["isHighlighted", "isLocked"],
layoutName: "select-kit/templates/components/multi-select/selected-name",
tagName: "li",
tabindex: -1,
@computed("content")
guid(content) { return Ember.guidFor(content); },
isLocked: Ember.computed("content.locked", function() {
return this.getWithDefault("content.locked", false);
}),
click() {
if (this.get("isLocked") === true) { return false; }
this.toggleProperty("isHighlighted");
return false;
}
});
@@ -0,0 +1,10 @@
import CategoryRowComponent from "select-kit/components/category-row";
export default CategoryRowComponent.extend({
layoutName: "select-kit/templates/components/category-row",
classNames: "none category-row",
click() {
this.sendAction("onClear");
}
});
@@ -0,0 +1,45 @@
import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box";
import { default as computed, on } from "ember-addons/ember-computed-decorators";
import { buttonDetails } from "discourse/lib/notification-levels";
import { allLevels } from "discourse/lib/notification-levels";
export default DropdownSelectBoxComponent.extend({
classNames: "notifications-button",
nameProperty: "key",
fullWidthOnMobile: true,
content: allLevels,
collectionHeight: "auto",
castInteger: true,
autofilterable: false,
filterable: false,
rowComponent: "notifications-button/notifications-button-row",
allowInitialValueMutation: false,
i18nPrefix: "",
i18nPostfix: "",
@computed("iconForSelectedDetails")
headerIcon(iconForSelectedDetails) { return iconForSelectedDetails; },
@computed("selectedDetails.icon")
iconForSelectedDetails(icon) { return icon; },
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
content.name = I18n.t(`${this.get("i18nPrefix")}.${this.get("selectedDetails.key")}.title`);
content.hasSelection = this.get("hasSelection");
return content;
},
@on("didReceiveAttrs")
_setNotificationsButtonComponentOptions() {
this.get("rowComponentOptions").setProperties({
i18nPrefix: this.get("i18nPrefix"),
i18nPostfix: this.get("i18nPostfix")
});
},
@computed("computedValue")
selectedDetails(computedValue) {
return buttonDetails(computedValue);
}
});
@@ -0,0 +1,37 @@
import DropdownSelectBoxRoxComponent from "select-kit/components/dropdown-select-box/dropdown-select-box-row";
import { buttonDetails } from "discourse/lib/notification-levels";
import computed from "ember-addons/ember-computed-decorators";
import { iconHTML } from 'discourse-common/lib/icon-library';
export default DropdownSelectBoxRoxComponent.extend({
classNames: "notifications-button-row",
i18nPrefix: Ember.computed.alias("options.i18nPrefix"),
i18nPostfix: Ember.computed.alias("options.i18nPostfix"),
@computed("computedContent.value", "i18nPrefix")
title(value, prefix) {
const key = buttonDetails(value).key;
return I18n.t(`${prefix}.${key}.title`);
},
@computed("computedContent.name", "computedContent.originalContent.icon")
icon(contentName, icon) {
return iconHTML(icon, { class: contentName.dasherize() });
},
@computed("_start")
description(_start) {
return Handlebars.escapeExpression(I18n.t(`${_start}.description`));
},
@computed("_start")
name(_start) {
return Handlebars.escapeExpression(I18n.t(`${_start}.title`));
},
@computed("i18nPrefix", "i18nPostfix", "computedContent.name")
_start(prefix, postfix, contentName) {
return `${prefix}.${contentName}${postfix}`;
},
});
@@ -0,0 +1,22 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
pluginApiIdentifiers: ["pinned-button"],
descriptionKey: "help",
classNames: "pinned-button",
classNameBindings: ["isHidden"],
layoutName: "select-kit/templates/components/pinned-button",
@computed("topic.pinned_globally", "pinned")
reasonText(pinnedGlobally, pinned) {
const globally = pinnedGlobally ? "_globally" : "";
const pinnedKey = pinned ? `pinned${globally}` : "unpinned";
const key = `topic_statuses.${pinnedKey}.help`;
return I18n.t(key);
},
@computed("pinned", "topic.deleted", "topic.unpinned")
isHidden(pinned, deleted, unpinned) {
return deleted || (!pinned && !unpinned);
}
});
@@ -0,0 +1,55 @@
import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box";
import { on } from "ember-addons/ember-computed-decorators";
import { iconHTML } from 'discourse-common/lib/icon-library';
export default DropdownSelectBoxComponent.extend({
pluginApiIdentifiers: ["pinned-options"],
classNames: "pinned-options",
allowInitialValueMutation: false,
autoHighlight() {},
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
const pinnedGlobally = this.get("topic.pinned_globally");
const pinned = this.get("computedValue");
const globally = pinnedGlobally ? "_globally" : "";
const state = pinned ? `pinned${globally}` : "unpinned";
const title = I18n.t(`topic_statuses.${state}.title`);
content.name = `${title}${iconHTML("caret-down")}`.htmlSafe();
content.dataName = title;
content.icon = `thumb-tack ${state === "unpinned" ? "unpinned" : null}`;
return content;
},
@on("init")
_setContent() {
const globally = this.get("topic.pinned_globally") ? "_globally" : "";
this.set("content", [
{
id: "pinned",
name: I18n.t("topic_statuses.pinned" + globally + ".title"),
description: I18n.t('topic_statuses.pinned' + globally + '.help'),
icon: "thumb-tack"
},
{
id: "unpinned",
name: I18n.t("topic_statuses.unpinned.title"),
icon: "thumb-tack unpinned",
description: I18n.t('topic_statuses.unpinned.help'),
}
]);
},
mutateValue(value) {
const topic = this.get("topic");
if (value === "unpinned") {
topic.clearPin();
} else {
topic.rePin();
}
}
});
@@ -0,0 +1,20 @@
import CategoryChooserComponent from "select-kit/components/category-chooser";
import Category from "discourse/models/category";
export default CategoryChooserComponent.extend({
pluginApiIdentifiers: ["advanced-search-category-chooser"],
rootNone: true,
rootNoneLabel: "category.all",
allowUncategorized: true,
clearable: true,
mutateValue(value) {
if (value) {
this.set("value", Category.findById(value));
} else {
this.set("value", null);
}
},
computeValue(category) { if (category) return category.id; }
});
@@ -0,0 +1,239 @@
const { isNone, run, makeArray } = Ember;
import computed from "ember-addons/ember-computed-decorators";
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 } from "select-kit/mixins/plugin-api";
export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixin, EventsMixin, {
pluginApiIdentifiers: ["select-kit"],
layoutName: "select-kit/templates/components/select-kit",
classNames: ["select-kit", "select-box-kit"],
classNameBindings: [
"isFocused",
"isExpanded",
"isDisabled",
"isHidden",
"isAbove",
"isBelow",
"isLeftAligned",
"isRightAligned"
],
isDisabled: false,
isExpanded: false,
isFocused: false,
isHidden: false,
renderedBodyOnce: false,
renderedFilterOnce: false,
tabindex: 0,
scrollableParentSelector: ".modal-body",
none: null,
highlightedValue: null,
noContentLabel: "select_kit.no_content",
valueAttribute: "id",
nameProperty: "name",
autoFilterable: false,
filterable: false,
filter: "",
filterPlaceholder: "select_kit.filter_placeholder",
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,
collectionComponent: "select-kit/select-kit-collection",
collectionHeight: 200,
verticalOffset: 0,
horizontalOffset: 0,
fullWidthOnMobile: false,
castInteger: false,
allowAny: false,
allowInitialValueMutation: false,
content: null,
computedContent: null,
limitMatches: 100,
init() {
this._super();
this.noneValue = "__none__";
this._previousScrollParentOverflow = "auto";
this._previousCSSContext = {};
this.set("headerComponentOptions", Ember.Object.create());
this.set("rowComponentOptions", Ember.Object.create());
this.set("computedContent", []);
if ($(window).outerWidth(false) <= 420) {
this.setProperties({ filterable: false, autoFilterable: false });
}
},
willComputeAttributes() {},
didComputeAttributes() {},
_beforeWillComputeContent(content) { return makeArray(content); },
willComputeContent(content) { return content; },
computeContent(content) { return content; },
_beforeDidComputeContent(content) {
content = applyContentPluginApiCallbacks(this.get("pluginApiIdentifiers"), content);
const existingCreatedComputedContent = this.get("computedContent").filterBy("created", true);
this.setProperties({
computedContent: content.map(c => this.computeContentItem(c)).concat(existingCreatedComputedContent)
});
return content;
},
didComputeContent() {},
mutateAttributes() {
run.next(() => {
this.mutateContent(this.get("computedContent"));
this.mutateValue(this.get("computedValue"));
this.set("headerComputedContent", this.computeHeaderContent());
});
},
mutateContent() {},
mutateValue(computedValue) { this.set("value", computedValue); },
computeHeaderContent() {
return this.baseHeaderComputedContent();
},
computeContentItem(contentItem, options) {
return this.baseComputedContentItem(contentItem, options);
},
baseComputedContentItem(contentItem, options) {
let originalContent;
options = options || {};
const name = options.name;
if (typeof contentItem === "string" || typeof contentItem === "number") {
originalContent = {};
originalContent[this.get("valueAttribute")] = contentItem;
originalContent[this.get("nameProperty")] = name || contentItem;
} else {
originalContent = contentItem;
}
return {
value: this._castInteger(this.valueForContentItem(contentItem)),
name: name || this._nameForContent(contentItem),
locked: false,
created: options.created || false,
originalContent
};
},
@computed("shouldFilter", "allowAny", "filter")
shouldDisplayFilter(shouldFilter, allowAny, filter) {
if (shouldFilter === true) return true;
if (allowAny === true && filter.length > 0) return true;
return false;
},
@computed("filter", "filteredComputedContent.[]")
shouldDisplayNoContentRow(filter, filteredComputedContent) {
return filter.length > 0 && filteredComputedContent.length === 0;
},
@computed("filter", "filterable", "autoFilterable", "renderedFilterOnce")
shouldFilter(filter, filterable, autoFilterable, renderedFilterOnce) {
if (renderedFilterOnce === true && filterable === true) return true;
if (filterable === true) return true;
if (autoFilterable === true && filter.length > 0) return true;
return false;
},
@computed("filter", "computedContent")
shouldDisplayCreateRow(filter, computedContent) {
if (computedContent.map(c => c.value).includes(filter)) return false;
if (this.get("allowAny") === true && filter.length > 0) return true;
return false;
},
@computed("filter", "shouldDisplayCreateRow")
createRowComputedContent(filter, shouldDisplayCreateRow) {
if (shouldDisplayCreateRow === true) {
let content = this.createContentFromInput(filter);
return this.computeContentItem(content, { created: true });
}
},
@computed
templateForRow() { return () => null; },
@computed
templateForNoneRow() { return () => null; },
@computed("filter")
templateForCreateRow() {
return (rowComponent) => {
return I18n.t("select_box.create", {
content: rowComponent.get("computedContent.name")
});
};
},
@computed("none")
noneRowComputedContent(none) {
if (isNone(none)) { return null; }
switch (typeof none) {
case "string":
return this.computeContentItem(this.noneValue, { name: I18n.t(none) });
default:
return this.computeContentItem(none);
}
},
createContentFromInput(input) { return input; },
willSelect() {
this.clearFilter();
this.set("highlightedValue", null);
},
didSelect() {
this.collapse();
this.focus();
},
willDeselect() {
this.clearFilter();
this.set("highlightedValue", null);
},
didDeselect() {
this.collapse();
this.focus();
},
clearFilter() {
this.$filterInput().val("");
this.setProperties({ filter: "" });
},
actions: {
onToggle() {
this.get("isExpanded") === true ? this.collapse() : this.expand();
},
onHighlight(rowComputedContent) {
this.set("highlightedValue", rowComputedContent.value);
},
onFilter(filter) {
this.setProperties({
highlightedValue: null,
renderedFilterOnce: true,
filter
});
this.autoHighlight();
}
}
});
@@ -0,0 +1,5 @@
export default Ember.Component.extend({
layoutName: "select-kit/templates/components/select-kit/select-kit-collection",
classNames: ["select-kit-collection", "select-box-kit-collection"],
tagName: "ul"
});
@@ -0,0 +1,10 @@
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
export default SelectKitRowComponent.extend({
layoutName: "select-kit/templates/components/select-kit/select-kit-row",
classNames: "create",
click() {
this.sendAction("onCreate", this.get("computedContent"));
},
});
@@ -0,0 +1,6 @@
export default Ember.Component.extend({
layoutName: "select-kit/templates/components/select-kit/select-kit-filter",
classNames: ["select-kit-filter", "select-box-kit-filter"],
classNameBindings: ["isFocused", "isHidden"],
isHidden: Ember.computed.not("shouldDisplayFilter")
});
@@ -0,0 +1,35 @@
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
layoutName: "select-kit/templates/components/select-kit/select-kit-header",
classNames: ["select-kit-header", "select-box-kit-header"],
classNameBindings: ["isFocused"],
attributeBindings: [
"dataName:data-name",
"tabindex",
"ariaLabel:aria-label",
"ariaHasPopup:aria-haspopup",
"title"
],
ariaHasPopup: true,
ariaLabel: Ember.computed.alias("title"),
name: Ember.computed.alias("computedContent.name"),
@computed("computedContent.icon", "computedContent.icons")
icons(icon, icons) {
return Ember.makeArray(icon).concat(icons).filter(i => !Ember.isEmpty(i));
},
@computed("computedContent.dataName", "name")
dataName(dataName, name) { return dataName || name; },
@computed("computedContent.title", "name")
title(title, name) { return title || name; },
click() {
this.sendAction("onToggle");
}
});
@@ -0,0 +1,10 @@
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
export default SelectKitRowComponent.extend({
layoutName: "select-kit/templates/components/select-kit/select-kit-row",
classNames: "none",
click() {
this.sendAction("onClear");
}
});
@@ -0,0 +1,58 @@
import { on } from 'ember-addons/ember-computed-decorators';
import computed from 'ember-addons/ember-computed-decorators';
const { run, isPresent, makeArray, isEmpty } = Ember;
import UtilsMixin from "select-kit/mixins/utils";
export default Ember.Component.extend(UtilsMixin, {
layoutName: "select-kit/templates/components/select-kit/select-kit-row",
classNames: ["select-kit-row", "select-box-kit-row"],
tagName: "li",
tabIndex: -1,
attributeBindings: [
"tabIndex",
"title",
"computedContent.value:data-value",
"computedContent.name:data-name"
],
classNameBindings: ["isHighlighted", "isSelected"],
@computed("computedContent.title", "computedContent.name")
title(title, name) { return title || name; },
@computed("templateForRow")
template(templateForRow) { return templateForRow(this); },
@on("didReceiveAttrs")
_setSelectionState() {
const contentValue = this.get("computedContent.value");
this.set("isSelected", this.get("computedValue") === contentValue);
this.set("isHighlighted", this.get("highlightedValue") === contentValue);
},
@on("willDestroyElement")
_clearDebounce() {
const hoverDebounce = this.get("hoverDebounce");
if (isPresent(hoverDebounce)) { run.cancel(hoverDebounce); }
},
@computed("computedContent.icon", "computedContent.icons", "computedContent.originalContent.icon")
icons(icon, icons, originalIcon) {
return makeArray(icon)
.concat(icons)
.concat(makeArray(originalIcon))
.filter(i => !isEmpty(i));
},
mouseEnter() {
this.set("hoverDebounce", run.debounce(this, this._sendOnHighlightAction, 32));
},
click() {
this.sendAction("onSelect", this.get("computedContent"));
},
_sendOnHighlightAction() {
this.sendAction("onHighlight", this.get("computedContent"));
}
});
@@ -0,0 +1,168 @@
import SelectKitComponent from "select-kit/components/select-kit";
import { on } from "ember-addons/ember-computed-decorators";
import computed from "ember-addons/ember-computed-decorators";
const { get, isNone, isEmpty, isPresent } = Ember;
export default SelectKitComponent.extend({
pluginApiIdentifiers: ["single-select"],
classNames: "single-select",
computedValue: null,
value: null,
allowInitialValueMutation: true,
init() {
this._super();
if (this.get("allowInitialValueMutation") === true) {
const none = isNone(this.get("none"));
const emptyValue = isEmpty(this.get("value"));
if (none && emptyValue) {
if (!isEmpty(this.get("content"))) {
const value = this.valueForContentItem(this.get("content.firstObject"));
Ember.run.next(() => this.mutateValue(value));
}
}
}
},
@on("didReceiveAttrs")
_compute() {
Ember.run.scheduleOnce("afterRender", () => {
this.willComputeAttributes();
let content = this._beforeWillComputeContent(this.get("content"));
content = this.willComputeContent(content);
let value = this._beforeWillComputeValue(this.get("value"));
content = this.computeContent(content);
content = this._beforeDidComputeContent(content);
value = this.willComputeValue(value);
value = this.computeValue(value);
value = this._beforeDidComputeValue(value);
this.didComputeContent(content);
this.didComputeValue(value);
this.set("headerComputedContent", this.computeHeaderContent());
this.didComputeAttributes();
});
},
_beforeWillComputeValue(value) {
switch (typeof value) {
case "string":
case "number":
return this._castInteger(value === "" ? null : value);
default:
return value;
}
},
willComputeValue(value) { return value; },
computeValue(value) { return value; },
_beforeDidComputeValue(value) {
if (!isEmpty(this.get("content")) && isNone(value) && isNone(this.get("none"))) {
value = this.valueForContentItem(get(this.get("content"), "firstObject"));
}
this.setProperties({ computedValue: value });
return value;
},
didComputeValue(value) { return value; },
filterComputedContent(computedContent, computedValue, filter) {
const lowerFilter = filter.toLowerCase();
return computedContent.filter(c => {
return get(c, "name").toLowerCase().indexOf(lowerFilter) > -1;
});
},
baseHeaderComputedContent() {
return {
icons: Ember.makeArray(this.getWithDefault("headerIcon", [])),
name: this.get("selectedComputedContent.name") || this.get("noneRowComputedContent.name")
};
},
@computed("computedContent.[]", "computedValue", "filter", "shouldFilter")
filteredComputedContent(computedContent, computedValue, filter, shouldFilter) {
if (shouldFilter === true) {
computedContent = this.filterComputedContent(computedContent, computedValue, filter);
}
return computedContent.slice(0, this.get("limitMatches"));
},
@computed("computedValue", "computedContent.[]")
selectedComputedContent(computedValue, computedContent) {
if (isNone(computedValue) || isNone(computedContent)) { return null; }
return computedContent.findBy("value", computedValue);
},
@computed("selectedComputedContent")
hasSelection(selectedComputedContent) {
return selectedComputedContent !== this.get("noneRowComputedContent") &&
!Ember.isNone(selectedComputedContent);
},
@computed("filter", "computedValue")
shouldDisplayCreateRow(filter, computedValue) {
return this._super() && computedValue !== filter;
},
autoHighlight() {
Ember.run.schedule("afterRender", () => {
if (!isNone(this.get("highlightedValue"))) { return; }
const filteredComputedContent = this.get("filteredComputedContent");
const displayCreateRow = this.get("shouldDisplayCreateRow");
const none = this.get("noneRowComputedContent");
if (this.get("hasSelection") && isEmpty(this.get("filter"))) {
this.send("onHighlight", this.get("selectedComputedContent"));
return;
}
if (isNone(this.get("highlightedValue")) && !isEmpty(filteredComputedContent)) {
this.send("onHighlight", get(filteredComputedContent, "firstObject"));
return;
}
if (displayCreateRow === true && isEmpty(filteredComputedContent)) {
this.send("onHighlight", this.get("createRowComputedContent"));
}
else if (!isEmpty(filteredComputedContent)) {
this.send("onHighlight", get(filteredComputedContent, "firstObject"));
}
else if (isEmpty(filteredComputedContent) && isPresent(none) && displayCreateRow === false) {
this.send("onHighlight", none);
}
});
},
validateComputedContentItem(computedContentItem) {
return this.get("computedValue") !== computedContentItem.value;
},
actions: {
onClear() {
this.send("onDeselect", this.get("selectedComputedContent"));
},
onCreate(computedContentItem) {
if (this.validateComputedContentItem(computedContentItem)) {
this.get("computedContent").pushObject(computedContentItem);
this.send("onSelect", computedContentItem);
}
},
onSelect(rowComputedContentItem) {
this.willSelect(rowComputedContentItem);
this.set("computedValue", rowComputedContentItem.value);
this.mutateAttributes();
Ember.run.schedule("afterRender", () => this.didSelect(rowComputedContentItem));
},
onDeselect(rowComputedContentItem) {
this.willDeselect(rowComputedContentItem);
this.set("computedValue", null);
this.mutateAttributes();
Ember.run.schedule("afterRender", () => this.didDeselect(rowComputedContentItem));
}
}
});
@@ -0,0 +1,23 @@
import NotificationOptionsComponent from "select-kit/components/notifications-button";
import computed from "ember-addons/ember-computed-decorators";
export default NotificationOptionsComponent.extend({
pluginApiIdentifiers: ["tag-notifications-button"],
classNames: "tag-notifications-button",
i18nPrefix: "tagging.notifications",
showFullTitle: false,
allowInitialValueMutation: false,
mutateValue(value) {
this.sendAction("action", value);
},
computeValue() {
return this.get("notificationLevel");
},
@computed("iconForSelectedDetails")
headerIcon(iconForSelectedDetails) {
return [iconForSelectedDetails, "caret-down"];
}
});
@@ -0,0 +1,68 @@
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,
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
content.name = I18n.t("topic.controls");
return content;
},
computeContent(content) {
const topic = this.get("topic");
const details = topic.get("details");
if (details.get("can_invite_to")) {
content.push({ id: "invite", icon: "users", name: I18n.t("topic.invite_reply.title") });
}
if (topic.get("bookmarked")) {
content.push({ id: "bookmark", icon: "bookmark", name: I18n.t("bookmarked.clear_bookmarks") });
} else {
content.push({ id: "bookmark", icon: "bookmark", name: I18n.t("bookmarked.title") });
}
content.push({ id: "share", icon: "link", name: I18n.t("topic.share.title") });
if (details.get("can_flag_topic")) {
content.push({ id: "flag", icon: "flag", name: I18n.t("topic.flag_topic.title") });
}
return content;
},
autoHighlight() {},
mutateValue(value) {
const topic = this.get("topic");
if (!topic.get("id")) {
return;
}
const refresh = () => this.send("onDeselect", value);
switch(value) {
case "invite":
this.attrs.showInvite();
refresh();
break;
case "bookmark":
topic.toggleBookmark().then(() => refresh() );
break;
case "share":
this.appEvents.trigger("share:url", topic.get("shareUrl"), $("#topic-footer-buttons"));
refresh();
break;
case "flag":
this.attrs.showFlagTopic();
refresh();
break;
}
}
});
@@ -0,0 +1,6 @@
export default Ember.Component.extend({
layoutName: "select-kit/templates/components/topic-notifications-button",
classNames: "topic-notifications-button",
showFullTitle: true,
appendReason: true
});
@@ -0,0 +1,33 @@
import NotificationOptionsComponent from "select-kit/components/notifications-button";
import { on } from "ember-addons/ember-computed-decorators";
import { topicLevels } from "discourse/lib/notification-levels";
export default NotificationOptionsComponent.extend({
pluginApiIdentifiers: ["topic-notifications-options"],
classNames: "topic-notifications-options",
content: topicLevels,
i18nPrefix: "topic.notifications",
allowInitialValueMutation: false,
@on("didInsertElement")
_bindGlobalLevelChanged() {
this.appEvents.on("topic-notifications-button:changed", (msg) => {
if (msg.type === "notification") {
if (this.get("computedValue") !== msg.id) {
this.get("topic.details").updateNotifications(msg.id);
}
}
});
},
@on("willDestroyElement")
_unbindGlobalLevelChanged() {
this.appEvents.off("topic-notifications-button:changed");
},
mutateValue(value) {
if (value !== this.get("value")) {
this.get("topic.details").updateNotifications(value);
}
}
});