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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
191 changed files with 3160 additions and 2788 deletions

View File

@ -42,12 +42,12 @@
"invisible":true,
"asyncRender":true,
"selectDropdown":true,
"selectBox":true,
"expandSelectBoxKit":true,
"collapseSelectBoxKit":true,
"selectBoxKitSelectRow":true,
"selectBoxKitSelectNoneRow":true,
"selectBoxKitFillInFilter":true,
"selectKit":true,
"expandSelectKit":true,
"collapseSelectKit":true,
"selectKitSelectRow":true,
"selectKitSelectNoneRow":true,
"selectKitFillInFilter":true,
"asyncTestDiscourse":true,
"fixture":true,
"find":true,

View File

@ -1,3 +1,3 @@
{{category-selector categories=selectedCategories blacklist=selectedCategories}}
{{category-selector categories=selectedCategories}}
<div class='desc'>{{{unbound setting.description}}}</div>
{{setting-validation-message message=validationMessage}}

View File

@ -48,7 +48,7 @@
<div class='filters'>
<div>
<label>{{d-icon 'circle' class='tracking'}}{{i18n 'admin.web_hooks.categories_filter'}}</label>
{{category-selector categories=model.categories blacklist=model.categories}}
{{category-selector categories=model.categories}}
<div class="instructions">{{i18n 'admin.web_hooks.categories_filter_instructions'}}</div>
</div>
<div>

View File

@ -4,7 +4,7 @@
//= require ./ember-addons/ember-computed-decorators
//= require ./ember-addons/fmt
//= require_tree ./discourse-common
//= require_tree ./select-box-kit
//= require_tree ./select-kit
//= require ./discourse
//= require ./deprecated

View File

@ -42,7 +42,8 @@ export function renderIcon(renderType, id, params) {
let rendererForType = renderer[renderType];
if (rendererForType) {
let result = rendererForType(REPLACEMENTS[id] || id, params || {});
const icon = { id, replacementId: REPLACEMENTS[id] };
let result = rendererForType(icon, params || {});
if (result) {
return result;
}
@ -68,8 +69,14 @@ export function registerIconRenderer(renderer) {
}
// Support for font awesome icons
function faClasses(id, params) {
let classNames = `fa fa-${id} d-icon d-icon-${id}`;
function faClasses(icon, params) {
let classNames;
if (typeof icon.replacementId !== "undefined") {
classNames = `fa fa-${icon.replacementId} d-icon ${icon.id}`;
} else {
classNames = `fa fa-${icon.id} d-icon d-${icon.id}`;
}
if (params) {
if (params.modifier) { classNames += " fa-" + params.modifier; }
if (params['class']) { classNames += ' ' + params['class']; }
@ -81,9 +88,9 @@ function faClasses(id, params) {
registerIconRenderer({
name: 'font-awesome',
string(id, params) {
string(icon, params) {
let tagName = params.tagName || 'i';
let html = `<${tagName} class='${faClasses(id, params)}'`;
let html = `<${tagName} class='${faClasses(icon, params)}'`;
if (params.title) { html += ` title='${I18n.t(params.title)}'`; }
if (params.label) { html += " aria-hidden='true'"; }
html += `></${tagName}>`;
@ -93,11 +100,11 @@ registerIconRenderer({
return html;
},
node(id, params) {
node(icon, params) {
let tagName = params.tagName || 'i';
const properties = {
className: faClasses(id, params),
className: faClasses(icon, params),
attributes: { "aria-hidden": true }
};

View File

@ -1,49 +0,0 @@
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
import Category from 'discourse/models/category';
import { on, observes } from 'ember-addons/ember-computed-decorators';
import { findRawTemplate } from 'discourse/lib/raw-templates';
export default Ember.Component.extend({
@observes('categories')
_update() {
if (this.get('canReceiveUpdates') === 'true')
this._initializeAutocomplete({updateData: true});
},
@on('didInsertElement')
_initializeAutocomplete(opts) {
const self = this,
regexp = new RegExp(`href=['\"]${Discourse.getURL('/c/')}([^'\"]+)`);
this.$('input').autocomplete({
items: this.get('categories'),
single: this.get('single'),
allowAny: false,
updateData: (opts && opts.updateData) ? opts.updateData : false,
dataSource(term) {
return Category.list().filter(category => {
const regex = new RegExp(term, 'i');
return category.get('name').match(regex) &&
!_.contains(self.get('blacklist') || [], category) &&
!_.contains(self.get('categories'), category) ;
});
},
onChangeItems(items) {
const categories = _.map(items, link => {
const slug = link.match(regexp)[1];
return Category.findSingleBySlug(slug);
});
Em.run.next(() => {
let existingCategory = _.isArray(self.get('categories')) ? self.get('categories') : [self.get('categories')];
const result = _.intersection(existingCategory.map(itm => itm.id), categories.map(itm => itm.id));
if (result.length !== categories.length || existingCategory.length !== categories.length)
self.set('categories', categories);
});
},
template: findRawTemplate('category-selector-autocomplete'),
transformComplete(category) {
return categoryBadgeHTML(category, {allowUncategorized: true});
}
});
}
});

View File

@ -1,7 +1,7 @@
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
import {
FORMAT,
} from "select-box-kit/components/future-date-input-selector";
} from "select-kit/components/future-date-input-selector";
import { PUBLISH_TO_CATEGORY_STATUS_TYPE } from 'discourse/controllers/edit-topic-timer';

View File

@ -185,18 +185,18 @@ export default Em.Component.extend({
const userInput = Discourse.Category.findBySlug(subcategories[1], subcategories[0]);
if ((!existingInput && userInput)
|| (existingInput && userInput && existingInput.id !== userInput.id))
this.set('searchedTerms.category', [userInput]);
this.set('searchedTerms.category', userInput);
} else
if (isNaN(subcategories)) {
const userInput = Discourse.Category.findSingleBySlug(subcategories[0]);
if ((!existingInput && userInput)
|| (existingInput && userInput && existingInput.id !== userInput.id))
this.set('searchedTerms.category', [userInput]);
this.set('searchedTerms.category', userInput);
} else {
const userInput = Discourse.Category.findById(subcategories[0]);
if ((!existingInput && userInput)
|| (existingInput && userInput && existingInput.id !== userInput.id))
this.set('searchedTerms.category', [userInput]);
this.set('searchedTerms.category', userInput);
}
} else
this.set('searchedTerms.category', '');
@ -303,11 +303,11 @@ export default Em.Component.extend({
const slugCategoryMatches = (match.length !== 0) ? match[0].match(REGEXP_CATEGORY_SLUG) : null;
const idCategoryMatches = (match.length !== 0) ? match[0].match(REGEXP_CATEGORY_ID) : null;
if (categoryFilter && categoryFilter[0]) {
const id = categoryFilter[0].id;
const slug = categoryFilter[0].slug;
if (categoryFilter[0].parentCategory) {
const parentSlug = categoryFilter[0].parentCategory.slug;
if (categoryFilter) {
const id = categoryFilter.id;
const slug = categoryFilter.slug;
if (categoryFilter.parentCategory) {
const parentSlug = categoryFilter.parentCategory.slug;
if (slugCategoryMatches)
searchTerm = searchTerm.replace(slugCategoryMatches[0], `#${parentSlug}:${slug}`);
else if (idCategoryMatches)

View File

@ -12,6 +12,7 @@ export default MountWidget.extend(Docking, {
buildArgs() {
let attrs = {
topic: this.get('topic'),
notificationLevel: this.get('notificationLevel'),
topicTrackingState: this.topicTrackingState,
enteredIndex: this.get('enteredIndex'),
dockAt: this.dockAt,

View File

@ -21,6 +21,7 @@ import { attachAdditionalPanel } from 'discourse/widgets/header';
import { registerIconRenderer, replaceIcon } from 'discourse-common/lib/icon-library';
import { addNavItem } from 'discourse/models/nav-item';
import { replaceFormatter } from 'discourse/lib/utilities';
import { modifySelectKit } from "select-kit/mixins/plugin-api";
// If you add any methods to the API ensure you bump up this number
const PLUGIN_API_VERSION = '0.8.12';
@ -589,6 +590,21 @@ class PluginApi {
formatUsername(fn) {
replaceFormatter(fn);
}
/**
*
* Access SelectKit plugin api
*
* Example:
*
* modifySelectKit("topic-footer-mobile-dropdown").appendContent(() => [{
* name: "discourse",
* id: 1
* }])
*/
modifySelectKit(pluginApiKey) {
return modifySelectKit(pluginApiKey);
}
}
let _pluginv01;

View File

@ -1 +0,0 @@
<input class='category-selector' type='text' name='categories'>

View File

@ -9,7 +9,7 @@
{{navigation-bar navItems=navItems filterMode=filterMode category=category}}
{{#if showCategoryNotifications}}
{{category-notifications-button category=category}}
{{category-notifications-button value=category.notification_level category=category}}
{{/if}}
{{create-topic-button

View File

@ -22,7 +22,9 @@
{{category-chooser
none="category.none"
value=category.parent_category_id
excludeCategoryId=category.id
categories=parentCategories
allowSubCategories=false
allowUncategorized=false}}
{{/if}}
</section>

View File

@ -13,7 +13,7 @@
<div class="control-group pull-left">
<label class="control-label" for="search-in-category">{{i18n "search.advanced.in_category.label"}}</label>
<div class="controls">
{{category-selector categories=searchedTerms.category single="true" canReceiveUpdates="true"}}
{{search-advanced-category-chooser value=searchedTerms.category}}
</div>
</div>
</div>

View File

@ -74,10 +74,10 @@
args=(hash topic=topic)
connectorTagName="span"}}
{{pinned-button topic=topic}}
{{pinned-button pinned=topic.pinned topic=topic}}
</div>
{{topic-notifications-button topic=topic}}
{{topic-notifications-button notificationLevel=topic.details.notification_level topic=topic}}
{{plugin-outlet name="after-topic-footer-buttons"
args=(hash topic=topic)

View File

@ -85,6 +85,7 @@
{{topic-timeline
topic=model
notificationLevel=model.details.notification_level
prevEvent=info.prevEvent
fullscreen=info.topicProgressExpanded
enteredIndex=enteredIndex

View File

@ -8,6 +8,6 @@
{{i18n "topic.unsubscribe.change_notification_state"}}
</p>
{{topic-notifications-button topic=model}}
{{topic-notifications-button notificationLevel=model.details.notification_level topic=model}}
</div>
</div>

View File

@ -73,7 +73,7 @@
{{/if}}
{{#if isGroup}}
{{group-notifications-button group=group user=model}}
{{group-notifications-button value=group.group_user.notification_level group=group user=model}}
{{/if}}
</div>

View File

@ -1,12 +1,13 @@
export default class ComponentConnector {
constructor(widget, componentName, opts) {
constructor(widget, componentName, opts, trackedProperties) {
this.widget = widget;
this.opts = opts;
this.componentName = componentName;
this.trackedProperties = trackedProperties || [];
}
init() {
const $elem = $('<div style="display: inline-block;" class="widget-component-connector"></div>');
const $elem = $('<div style="display: inline-flex;" class="widget-component-connector"></div>');
const elem = $elem[0];
const { opts, widget, componentName } = this;
@ -29,7 +30,18 @@ export default class ComponentConnector {
return elem;
}
update() { }
update(prev) {
let shouldInit = false;
this.trackedProperties.forEach(prop => {
if (prev.opts[prop] !== this.opts[prop]) {
shouldInit = true;
}
});
if (shouldInit === true) return this.init();
return null;
}
}
ComponentConnector.prototype.type = 'Widget';

View File

@ -291,7 +291,7 @@ createWidget('timeline-footer-controls', {
html(attrs) {
const controls = [];
const { currentUser, fullScreen, topic } = attrs;
const { currentUser, fullScreen, topic, notificationLevel } = attrs;
if (currentUser && !fullScreen) {
if (topic.get('details.can_create_post')) {
@ -315,12 +315,13 @@ createWidget('timeline-footer-controls', {
if (currentUser) {
controls.push(new ComponentConnector(this,
'topic-notifications-button',
'topic-notifications-options',
{
value: notificationLevel,
topic,
appendReason: false,
showFullTitle: false
}
},
["value"]
));
}

View File

@ -1,45 +0,0 @@
import MultiComboBoxComponent from "select-box-kit/components/multi-combo-box";
export default MultiComboBoxComponent.extend({
classNames: "admin-group-selector",
selected: null,
available: null,
allowAny: false,
didReceiveAttrs() {
this._super();
this.set("value", this.get("selected").map(s => this._valueForContent(s)));
this.set("content", this.get("available"));
},
formatRowContent(content) {
let formatedContent = this._super(content);
formatedContent.locked = content.automatic;
return formatedContent;
},
didUpdateAttrs() {
this._super();
this.set("highlightedValue", null);
Ember.run.schedule("afterRender", () => {
this.autoHighlightFunction();
});
},
selectValuesFunction(values) {
values.forEach(value => {
this.triggerAction({
action: "groupAdded",
actionContext: this.get("content").findBy("id", parseInt(value, 10))
});
});
},
deselectValuesFunction(values) {
values.forEach(value => {
this.triggerAction({ action: "groupRemoved", actionContext: value });
});
}
});

View File

@ -1,131 +0,0 @@
import ComboBoxComponent from "select-box-kit/components/combo-box";
import { categoryBadgeHTML } from "discourse/helpers/category-link";
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({
classNames: "category-chooser",
filterable: true,
castInteger: true,
allowUncategorized: null,
filteredContentFunction(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";
}
},
@computed
templateForRow() {
return rowComponent => this._rowContentTemplate(rowComponent.get("content"));
},
@computed
templateForNoneRow() {
return rowComponent => this._rowContentTemplate(rowComponent.get("content"));
},
@computed("scopedCategoryId", "content.[]")
computedContent(scopedCategoryId, categories) {
// Always scope to the parent of a category, if present
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 = get(c, "value");
if (scopedCategoryId && categoryId !== scopedCategoryId && get(c, "originalContent.parent_category_id") !== scopedCategoryId) {
return false;
}
if (get(c, 'originalContent.isUncategorizedCategory') || excludeCategoryId === categoryId) {
return false;
}
return get(c, 'originalContent.permission') === PermissionType.FULL;
});
},
@on("didRender")
_bindComposerResizing() {
this.appEvents.on("composer:resized", this, this.applyDirection);
},
@on("willDestroyElement")
_unbindComposerResizing() {
this.appEvents.off("composer:resized");
},
@computed("site.sortedCategories")
content() {
const categories = Discourse.SiteSettings.fixed_category_positions_on_create ?
Category.list() :
Category.listByActivity();
return this.formatContents(categories);
},
_rowContentTemplate(content) {
let category;
// If we have no id, but text with the uncategorized name, we can use that badge.
if (isEmpty(get(content, "value"))) {
const uncat = Category.findUncategorized();
if (uncat && uncat.get("name") === get(content, "name")) {
category = uncat;
}
} else {
category = Category.findById(parseInt(get(content, "value"), 10));
}
if (!category) return get(content, "name");
let result = categoryBadgeHTML(category, {link: false, allowUncategorized: true, hideParent: true});
const parentCategoryId = category.get("parent_category_id");
if (parentCategoryId) {
result = `<div class="category-status">${categoryBadgeHTML(Category.findById(parentCategoryId), {link: false})}&nbsp;${result}`;
} else {
result = `<div class="category-status">${result}`;
}
result += ` <span class="topic-count">&times; ${category.get("topic_count")}</span></div>`;
const description = category.get("description");
// TODO wtf how can this be null?;
if (description && description !== "null") {
result += `<div class="category-desc">${description.substr(0, 200)}${description.length > 200 ? '&hellip;' : ''}</div>`;
}
return result;
}
});

View File

@ -1,13 +0,0 @@
import NotificationOptionsComponent from "select-box-kit/components/notifications-button";
export default NotificationOptionsComponent.extend({
classNames: "category-notifications-button",
isHidden: Ember.computed.or("category.deleted", "site.isMobileDevice"),
i18nPrefix: "category.notifications",
value: Ember.computed.alias("category.notification_level"),
headerComponent: "category-notifications-button/category-notifications-button-header",
selectValueFunction(value) {
this.get("category").setNotification(value);
}
});

View File

@ -1,13 +0,0 @@
import NotificationButtonHeader from "select-box-kit/components/notifications-button/notifications-button-header";
import computed from "ember-addons/ember-computed-decorators";
import { iconHTML } from 'discourse-common/lib/icon-library';
export default NotificationButtonHeader.extend({
classNames: "category-notifications-button-header",
shouldDisplaySelectedName: false,
@computed("_selectedDetails.icon", "_selectedDetails.key")
icon() {
return `${this._super()}${iconHTML("caret-down")}`.htmlSafe();
}
});

View File

@ -1,39 +0,0 @@
import SelectBoxKitHeaderComponent from "select-box-kit/components/select-box-kit/select-box-kit-header";
import { default as computed } from "ember-addons/ember-computed-decorators";
export default SelectBoxKitHeaderComponent.extend({
layoutName: "select-box-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"),
selectedName: Ember.computed.alias("options.selectedName"),
@computed("isExpanded", "caretUpIcon", "caretDownIcon")
caretIcon(isExpanded, caretUpIcon, caretDownIcon) {
return isExpanded === true ? caretUpIcon : caretDownIcon;
},
@computed("clearable", "selectedContent")
shouldDisplayClearableButton(clearable, selectedContent) {
return clearable === true && !Ember.isEmpty(selectedContent);
},
@computed("options.selectedName", "selectedContent.firstObject.name", "none.name")
selectedName(selectedName, name, noneName) {
if (Ember.isPresent(selectedName)) {
return selectedName;
}
if (Ember.isNone(name)) {
if (Ember.isNone(noneName)) {
return this._super();
} else {
return noneName;
}
} else {
return name;
}
}
});

View File

@ -1,22 +0,0 @@
import SelectBoxKitComponent from "select-box-kit/components/select-box-kit";
export default SelectBoxKitComponent.extend({
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",
clickOutside() {
if (this.get("isExpanded") === false) { return; }
this.close();
},
didSelectValue() {
this._super();
this.blur();
}
});

View File

@ -1,6 +0,0 @@
import SelectBoxKitHeaderComponent from "select-box-kit/components/select-box-kit/select-box-kit-header";
export default SelectBoxKitHeaderComponent.extend({
layoutName: "select-box-kit/templates/components/dropdown-select-box/dropdown-select-box-header",
classNames: "dropdown-select-box-header",
});

View File

@ -1,9 +0,0 @@
import SelectBoxKitRowComponent from "select-box-kit/components/select-box-kit/select-box-kit-row";
export default SelectBoxKitRowComponent.extend({
layoutName: "select-box-kit/templates/components/dropdown-select-box/dropdown-select-box-row",
classNames: "dropdown-select-box-row",
name: Ember.computed.alias("content.name"),
description: Ember.computed.alias("content.originalContent.description")
});

View File

@ -1,14 +0,0 @@
import ComboBoxHeaderComponent from "select-box-kit/components/combo-box/combo-box-header";
import DatetimeMixin from "select-box-kit/components/future-date-input-selector/mixin";
import computed from "ember-addons/ember-computed-decorators";
export default ComboBoxHeaderComponent.extend(DatetimeMixin, {
layoutName: "select-box-kit/templates/components/future-date-input-selector/future-date-input-selector-header",
classNames: "future-date-input-selector-header",
@computed("selectedContent.firstObject.value")
datetime(value) { return this._computeDatetimeForValue(value); },
@computed("selectedContent.firstObject.value")
icon(value) { return this._computeIconForValue(value); }
});

View File

@ -1,14 +0,0 @@
import SelectBoxKitRowComponent from "select-box-kit/components/select-box-kit/select-box-kit-row";
import DatetimeMixin from "select-box-kit/components/future-date-input-selector/mixin";
import computed from "ember-addons/ember-computed-decorators";
export default SelectBoxKitRowComponent.extend(DatetimeMixin, {
layoutName: "select-box-kit/templates/components/future-date-input-selector/future-date-input-selector-row",
classNames: "future-date-input-selector-row",
@computed("content.value")
datetime(value) { return this._computeDatetimeForValue(value); },
@computed("content.value")
icon(value) { return this._computeIconForValue(value); }
});

View File

@ -1,55 +0,0 @@
import MultiComboBoxComponent from "select-box-kit/components/multi-combo-box";
import { observes } from 'ember-addons/ember-computed-decorators';
export default MultiComboBoxComponent.extend({
classNames: "list-setting",
tokenSeparator: "|",
settingValue: "",
choices: null,
filterable: true,
init() {
const valuesFromString = this.get("settingValue").split(this.get("tokenSeparator"));
this.set("value", valuesFromString.reject(v => Ember.isEmpty(v)));
if (Ember.isNone(this.get("choices"))) {
this.set("content", valuesFromString);
} else {
this.set("content", this.get("choices"));
}
if (!Ember.isNone(this.get("settingName"))) {
this.set("nameProperty", this.get("settingName"));
}
if (Ember.isEmpty(this.get("content"))) {
this.set("rowComponent", null);
this.set("noContentLabel", null);
}
this._super();
if (this.get("nameProperty").indexOf("color") > -1) {
this.set("headerComponentOptions", Ember.Object.create({
selectedNameComponent: "multi-combo-box/selected-color"
}));
}
},
@observes("value.[]")
setSettingValue() {
this.set("settingValue", this.get("value").join(this.get("tokenSeparator")));
},
@observes("content.[]")
setChoices() { this.set("choices", this.get("content")); },
_handleTabOnKeyDown(event) {
if (this.$highlightedRow().length === 1) {
this._super(event);
} else {
this.close();
return false;
}
}
});

View File

@ -1,240 +0,0 @@
import SelectBoxKitComponent from "select-box-kit/components/select-box-kit";
import computed from "ember-addons/ember-computed-decorators";
const { get, isNone, isEmpty } = Ember;
export default SelectBoxKitComponent.extend({
classNames: "multi-combo-box",
headerComponent: "multi-combo-box/multi-combo-box-header",
filterComponent: null,
headerText: "select_box.default_header_text",
allowAny: true,
allowValueMutation: false,
autoSelectFirst: false,
autoFilterable: true,
selectedNameComponent: "multi-combo-box/selected-name",
init() {
this._super();
if (isNone(this.get("value"))) { this.set("value", []); }
this.set("headerComponentOptions", Ember.Object.create({
selectedNameComponent: this.get("selectedNameComponent")
}));
},
@computed("filter")
templateForCreateRow() {
return (rowComponent) => {
return I18n.t("select_box.create", { content: rowComponent.get("content.name")});
};
},
keyDown(event) {
const keyCode = event.keyCode || event.which;
const $filterInput = this.$filterInput();
if (this.get("isFocused") === true && this.get("isExpanded") === false && keyCode === this.keys.BACKSPACE) {
this.expand();
return;
}
// select all choices
if (event.metaKey === true && keyCode === 65 && isEmpty(this.get("filter"))) {
this.$(".choices .selected-name:not(.is-locked)").addClass("is-highlighted");
return;
}
// clear selection when multiple
if (Ember.isEmpty(this.get("filter")) && this.$(".selected-name.is-highlighted").length >= 1 && keyCode === this.keys.BACKSPACE) {
const highlightedValues = [];
$.each(this.$(".selected-name.is-highlighted"), (i, el) => {
highlightedValues.push($(el).attr("data-value"));
});
this.send("onDeselect", highlightedValues);
return;
}
// try to remove last item from the list
if (Ember.isEmpty(this.get("filter")) && keyCode === this.keys.BACKSPACE) {
let $lastSelectedValue = $(this.$(".choices .selected-name:not(.is-locked)").last());
if ($lastSelectedValue.length === 0) { return; }
if ($lastSelectedValue.hasClass("is-highlighted") || $(document.activeElement).is($lastSelectedValue)) {
this.send("onDeselect", this.get("selectedContent.lastObject.value"));
$filterInput.focus();
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("value.[]")
computedValue(value) { return value.map(v => this._castInteger(v)); },
@computed("value.[]", "computedContent.[]")
selectedContent(value, computedContent) {
const contents = [];
value.forEach(v => {
const content = computedContent.findBy("value", v);
if (!isNone(content)) { contents.push(content); }
});
return contents;
},
filteredContentFunction(computedContent, computedValue, filter) {
computedContent = computedContent.filter(c => {
return !computedValue.includes(get(c, "value"));
});
if (isEmpty(filter)) { return computedContent; }
const lowerFilter = filter.toLowerCase();
return computedContent.filter(c => {
return get(c, "name").toLowerCase().indexOf(lowerFilter) > -1;
});
},
willCreateContent() {
this.set("highlightedValue", null);
},
didCreateContent() {
this.clearFilter();
this.autoHighlightFunction();
},
createContentFunction(input) {
if (!this.get("content").includes(input)) {
this.get("content").pushObject(input);
this.get("value").pushObject(input);
}
},
deselectValuesFunction(values) {
const contents = this._computeRemovableContentsForValues(values);
this.get("value").removeObjects(values);
this.get("content").removeObjects(contents);
},
highlightValueFunction(value) {
this.set("highlightedValue", value);
},
selectValuesFunction(values) {
this.get("value").pushObjects(values);
},
willSelectValues() {
this.expand();
this.set("highlightedValue", null);
},
didSelectValues() {
this.focus();
this.clearFilter();
this.autoHighlightFunction();
},
willDeselectValues() {
this.set("highlightedValue", null);
},
didDeselectValues() {
this.autoHighlightFunction();
},
willHighlightValue() {},
didHighlightValue() {},
autoHighlightFunction() {
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("filteredContent"))) {
if (!isEmpty(this.get("filter"))) {
this.send("onHighlight", this.get("filter"));
} else if (this.get("none") && !isEmpty(this.get("selectedContent"))) {
this.send("onHighlight", this.noneValue);
}
} else {
this.send("onHighlight", this.get("filteredContent.firstObject.value"));
}
});
},
actions: {
onClearSelection() {
const values = this.get("selectedContent").map(c => get(c, "value"));
this.send("onDeselect", values);
},
onHighlight(value) {
value = this._originalValueForValue(value);
this.willHighlightValue(value);
this.set("highlightedValue", value);
this.highlightValueFunction(value);
this.didHighlightValue(value);
},
onCreateContent(input) {
this.willCreateContent(input);
this.createContentFunction(input);
this.didCreateContent(input);
},
onSelect(values) {
values = Ember.makeArray(values).map(v => this._originalValueForValue(v));
this.willSelectValues(values);
this.selectValuesFunction(values);
this.didSelectValues(values);
},
onDeselect(values) {
values = Ember.makeArray(this._computeRemovableValues(values));
this.willDeselectValues(values);
this.deselectValuesFunction(values);
this.didSelectValues(values);
}
},
_computeRemovableContentsForValues(values) {
const removableContents = [];
values.forEach(v => {
if (!this.get("_initialValues").includes(v)) {
const content = this._contentForValue(v);
if (!isNone(content)) { removableContents.push(content); }
}
});
return removableContents;
},
_computeRemovableValues(values) {
return Ember.makeArray(values)
.map(v => this._originalValueForValue(v))
.filter(v => {
return get(this._computedContentForValue(v), "locked") !== true;
});
}
});

View File

@ -1,8 +0,0 @@
import SelectedNameComponent from "select-box-kit/components/multi-combo-box/selected-name";
export default SelectedNameComponent.extend({
didRender() {
const name = this.get("content.name");
this.$().css("border-bottom", Handlebars.Utils.escapeExpression(`7px solid #${name}`));
}
});

View File

@ -1,26 +0,0 @@
import DropdownSelectBoxHeaderComponent from "select-box-kit/components/dropdown-select-box/dropdown-select-box-header";
import computed from "ember-addons/ember-computed-decorators";
import { iconHTML } from 'discourse-common/lib/icon-library';
import { buttonDetails } from "discourse/lib/notification-levels";
export default DropdownSelectBoxHeaderComponent.extend({
classNames: "notifications-button-header",
i18nPrefix: Ember.computed.alias("options.i18nPrefix"),
shouldDisplaySelectedName: Ember.computed.alias("options.showFullTitle"),
@computed("_selectedDetails.icon", "_selectedDetails.key")
icon(icon, key) {
return iconHTML(icon, { class: key }).htmlSafe();
},
@computed("_selectedDetails.key", "i18nPrefix")
selectedName(key, prefix) {
return I18n.t(`${prefix}.${key}.title`);
},
@computed("selectedContent.firstObject.value")
_selectedDetails(value) {
return buttonDetails(value);
}
});

View File

@ -1,60 +0,0 @@
import DropdownSelectBoxComponent from "select-box-kit/components/dropdown-select-box";
import computed from "ember-addons/ember-computed-decorators";
import { observes } from "ember-addons/ember-computed-decorators";
import { on } from "ember-addons/ember-computed-decorators";
export default DropdownSelectBoxComponent.extend({
classNames: "pinned-options",
headerComponent: "pinned-options/pinned-options-header",
@on("didReceiveAttrs")
_setComponentOptions() {
this.set("headerComponentOptions", Ember.Object.create({
pinned: this.get("topic.pinned"),
pinnedGlobally: this.get("topic.pinned_globally")
}));
},
@computed("topic.pinned")
value(pinned) {
return pinned ? "pinned" : "unpinned";
},
@observes("topic.pinned")
_pinStateChanged() {
this.set("value", this.get("topic.pinned") ? "pinned" : "unpinned");
this._setComponentOptions();
},
@computed("topic.pinned_globally")
content(pinnedGlobally) {
const globally = pinnedGlobally ? "_globally" : "";
return [
{
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",
description: I18n.t('topic_statuses.unpinned.help'),
iconClass: "unpinned"
}
];
},
selectValueFunction(value) {
const topic = this.get("topic");
if (value === "unpinned") {
topic.clearPin();
} else {
topic.rePin();
}
}
});

View File

@ -1,30 +0,0 @@
import DropdownSelectBoxHeaderComponent from "select-box-kit/components/dropdown-select-box/dropdown-select-box-header";
import computed from "ember-addons/ember-computed-decorators";
import { iconHTML } from 'discourse-common/lib/icon-library';
export default DropdownSelectBoxHeaderComponent.extend({
classNames: "pinned-options-header",
pinnedGlobally: Ember.computed.alias("options.pinnedGlobally"),
pinned: Ember.computed.alias("options.pinned"),
@computed("pinned", "pinnedGlobally")
icon(pinned, pinnedGlobally) {
const globally = pinnedGlobally ? "_globally" : "";
const state = pinned ? `pinned${globally}` : "unpinned";
return iconHTML(
"thumb-tack",
{ class: (state === "unpinned" ? "unpinned" : null) }
);
},
@computed("pinned", "pinnedGlobally")
selectedName(pinned, pinnedGlobally) {
const globally = pinnedGlobally ? "_globally" : "";
const state = pinned ? `pinned${globally}` : "unpinned";
const title = I18n.t(`topic_statuses.${state}.title`);
return `${title}${iconHTML("caret-down")}`.htmlSafe();
},
});

View File

@ -1,328 +0,0 @@
const { get, isNone, isEmpty, isPresent } = Ember;
import { on } from "ember-addons/ember-computed-decorators";
import computed from "ember-addons/ember-computed-decorators";
import UtilsMixin from "select-box-kit/mixins/utils";
import DomHelpersMixin from "select-box-kit/mixins/dom-helpers";
import KeyboardMixin from "select-box-kit/mixins/keyboard";
export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin, {
layoutName: "select-box-kit/templates/components/select-box-kit",
classNames: "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",
value: null,
none: null,
highlightedValue: null,
noContentLabel: "select_box.no_content",
valueAttribute: "id",
nameProperty: "name",
autoFilterable: false,
filterable: false,
filter: "",
filterPlaceholder: "select_box.filter_placeholder",
filterIcon: "search",
rowComponent: "select-box-kit/select-box-kit-row",
rowComponentOptions: null,
noneRowComponent: "select-box-kit/select-box-kit-none-row",
createRowComponent: "select-box-kit/select-box-kit-create-row",
filterComponent: "select-box-kit/select-box-kit-filter",
headerComponent: "select-box-kit/select-box-kit-header",
headerComponentOptions: null,
collectionComponent: "select-box-kit/select-box-kit-collection",
collectionHeight: 200,
verticalOffset: 0,
horizontalOffset: 0,
fullWidthOnMobile: false,
castInteger: false,
allowAny: false,
allowValueMutation: true,
autoSelectFirst: true,
content: null,
_initialValues: null,
init() {
this._super();
this.noneValue = "__none__";
this._previousScrollParentOverflow = "auto";
this._previousCSSContext = {};
this.set("headerComponentOptions", Ember.Object.create());
this.set("rowComponentOptions", Ember.Object.create());
if ($(window).outerWidth(false) <= 420) {
this.setProperties({ filterable: false, autoFilterable: false });
}
if (isNone(this.get("content"))) { this.set("content", []); }
this.set("value", this._castInteger(this.get("value")));
this.setInitialValues();
},
setInitialValues() {
this.set("_initialValues", this.getWithDefault("content", []).map((c) => {
return this._valueForContent(c);
}));
},
@computed("computedContent.[]", "computedValue.[]", "filter")
filteredContent(computedContent, computedValue, filter) {
return this.filteredContentFunction(computedContent, computedValue, filter);
},
filteredContentFunction(computedContent, computedValue, filter) {
if (isEmpty(filter)) { return computedContent; }
const lowerFilter = filter.toLowerCase();
return computedContent.filter(c => {
return get(c, "name").toLowerCase().indexOf(lowerFilter) > -1;
});
},
formatRowContent(content) {
let originalContent;
if (typeof content === "string" || typeof content === "number") {
originalContent = {};
originalContent[this.get("valueAttribute")] = content;
originalContent[this.get("nameProperty")] = content;
} else {
originalContent = content;
}
return {
value: this._castInteger(this._valueForContent(content)),
name: this._nameForContent(content),
locked: false,
originalContent
};
},
formatContents(contents) {
return contents.map(content => this.formatRowContent(content));
},
@computed("filter", "filterable", "autoFilterable", "renderedFilterOnce")
shouldDisplayFilter(filter, filterable, autoFilterable, renderedFilterOnce) {
if (renderedFilterOnce === true || filterable === true) { return true; }
if (filter.length > 0 && autoFilterable === true) { return true; }
return false;
},
@computed("filter")
shouldDisplayCreateRow(filter) {
if (this.get("allowAny") === true && filter.length > 0) { return true; }
return false;
},
@computed("filter", "shouldDisplayCreateRow")
createRowContent(filter, shouldDisplayCreateRow) {
if (shouldDisplayCreateRow === true && !this.get("value").includes(filter)) {
return Ember.Object.create({ value: filter, name: filter });
}
},
@computed("content.[]", "value.[]")
computedContent(content) {
this._mutateValue();
return this.formatContents(content || []);
},
@computed("value", "none", "computedContent.firstObject.value")
computedValue(value, none, firstContentValue) {
if (isNone(value) && isNone(none) && this.get("autoSelectFirst") === true) {
return firstContentValue;
}
return value;
},
@computed
templateForRow() { return () => null; },
@computed
templateForNoneRow() { return () => null; },
@computed
templateForCreateRow() { return () => null; },
@computed("none")
computedNone(none) {
if (isNone(none)) { return null; }
switch (typeof none) {
case "string":
return Ember.Object.create({ name: I18n.t(none), value: this.noneValue });
default:
return this.formatRowContent(none);
}
},
@computed("computedValue", "computedContent.[]")
selectedContent(computedValue, computedContent) {
if (isNone(computedValue)) { return []; }
return [ computedContent.findBy("value", computedValue) ];
},
@on("didInsertElement")
_setupResizeListener() {
$(window).on("resize.select-box-kit", () => this.collapse() );
},
autoHighlightFunction() {
Ember.run.schedule("afterRender", () => {
if (!isNone(this.get("highlightedValue"))) { return; }
const filteredContent = this.get("filteredContent");
const display = this.get("shouldDisplayCreateRow");
const none = this.get("computedNone");
if (isNone(this.get("highlightedValue")) && !isEmpty(filteredContent)) {
this.send("onHighlight", get(filteredContent, "firstObject.value"));
return;
}
if (display === true && isEmpty(filteredContent)) {
this.send("onHighlight", this.get("filter"));
}
else if (!isEmpty(filteredContent)) {
this.send("onHighlight", get(filteredContent, "firstObject.value"));
}
else if (isEmpty(filteredContent) && isPresent(none) && display === false) {
this.send("onHighlight", get(none, "value"));
}
});
},
willFilterContent() {
this.expand();
this.set("highlightedValue", null);
},
didFilterContent() {
this.set("renderedFilterOnce", true);
this.autoHighlightFunction();
},
willCreateContent() { },
createContentFunction(input) {
this.get("content").pushObject(input);
this.send("onSelect", input);
},
didCreateContent() {
this.clearFilter();
this.autoHighlightFunction();
},
willHighlightValue() {},
highlightValueFunction(value) {
this.set("highlightedValue", value);
},
didHighlightValue() {},
willSelectValue() {
this.clearFilter();
this.set("highlightedValue", null);
},
selectValueFunction(value) {
this.set("value", value);
},
didSelectValue() {
this.collapse();
this.focus();
},
willDeselectValue() {
this.set("highlightedValue", null);
},
unsetValueFunction() {
this.set("value", null);
},
didDeselectValue() {
this.focus();
},
actions: {
onToggle() {
this.get("isExpanded") === true ? this.collapse() : this.expand();
},
onClearSelection() {
this.send("onDeselect", this.get("value"));
},
onHighlight(value) {
value = this._originalValueForValue(value);
this.willHighlightValue(value);
this.set("highlightedValue", value);
this.highlightValueFunction(value);
this.didHighlightValue(value);
},
onCreateContent(input) {
this.willCreateContent(input);
this.createContentFunction(input);
this.didCreateContent(input);
},
onSelect(value) {
if (value === "") { value = null; }
this.willSelectValue(value);
this.selectValueFunction(value);
this.didSelectValue(value);
},
onDeselect(value) {
value = this._originalValueForValue(value);
this.willDeselectValue(value);
this.unsetValueFunction(value);
this.didSelectValue(value);
},
onFilterChange(_filter) {
this.willFilterContent(_filter);
this.set("filter", _filter);
this.didFilterContent(_filter);
},
},
clearFilter() {
this.$filterInput().val("");
this.setProperties({ filter: "" });
},
@on("didReceiveAttrs")
_mutateValue() {
if (this.get("allowValueMutation") !== true) {
return;
}
const none = isNone(this.get("none"));
const emptyValue = isEmpty(this.get("value"));
if (none && emptyValue) {
Ember.run.scheduleOnce("sync", () => {
if (!isEmpty(this.get("computedContent"))) {
const firstValue = this.get("computedContent.firstObject.value");
this.set("value", firstValue);
}
});
}
}
});

View File

@ -1,5 +0,0 @@
export default Ember.Component.extend({
layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-collection",
classNames: "select-box-kit-collection",
tagName: "ul"
});

View File

@ -1,10 +0,0 @@
import SelectBoxKitRowComponent from "select-box-kit/components/select-box-kit/select-box-kit-row";
export default SelectBoxKitRowComponent.extend({
layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-row",
classNames: "create",
click() {
this.sendAction("onCreateContent", this.get("content.name"));
},
});

View File

@ -1,28 +0,0 @@
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-header",
classNames: "select-box-kit-header",
classNameBindings: ["isFocused"],
attributeBindings: ["selectedName:data-name"],
shouldDisplaySelectedName: true,
@computed("options.shouldDisplaySelectedName")
shouldDisplaySelectedName(should) {
if (Ember.isNone(should)) { return true; }
return should;
},
@computed("options.selectedName", "selectedContent.firstObject.name")
selectedName(optionsSelectedName, firstSelectedContentName) {
if (Ember.isNone(optionsSelectedName)) {
return firstSelectedContentName;
}
return optionsSelectedName;
},
@computed("options.icon")
icon(optionsIcon) { return optionsIcon; },
click() { this.sendAction("onToggle"); }
});

View File

@ -1,10 +0,0 @@
import SelectBoxKitRowComponent from "select-box-kit/components/select-box-kit/select-box-kit-row";
export default SelectBoxKitRowComponent.extend({
layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-row",
classNames: "none",
click() {
this.sendAction("onClearSelection");
}
});

View File

@ -1,70 +0,0 @@
import { iconHTML } from 'discourse-common/lib/icon-library';
import { on } from 'ember-addons/ember-computed-decorators';
import computed from 'ember-addons/ember-computed-decorators';
const { run, isPresent } = Ember;
import UtilsMixin from "select-box-kit/mixins/utils";
export default Ember.Component.extend(UtilsMixin, {
layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-row",
classNames: "select-box-kit-row",
tagName: "li",
tabIndex: -1,
attributeBindings: [
"tabIndex",
"title",
"content.value:data-value",
"content.name:data-name"
],
classNameBindings: ["isHighlighted", "isSelected"],
clicked: false,
@computed("content.originalContent.title", "content.name")
title(title, name) {
return title || name;
},
@computed("templateForRow")
template(templateForRow) { return templateForRow(this); },
@on("didReceiveAttrs")
_setSelectionState() {
const contentValue = this.get("content.value");
this.set("isSelected", this.get("value") === contentValue);
this.set("isHighlighted", this.get("highlightedValue") === contentValue);
},
@on("willDestroyElement")
_clearDebounce() {
const hoverDebounce = this.get("hoverDebounce");
if (isPresent(hoverDebounce)) { run.cancel(hoverDebounce); }
},
@computed("content.originalContent.icon", "content.originalContent.iconClass")
icon(icon, cssClass) {
if (icon) {
return iconHTML(icon, { class: cssClass });
}
return null;
},
mouseEnter() {
this.set("hoverDebounce", run.debounce(this, this._sendOnHighlightAction, 32));
},
click() {
this._sendOnSelectAction();
},
_sendOnSelectAction() {
if (this.get("clicked") === false) {
this.set("clicked", true);
this.sendAction("onSelect", this.get("content.value"));
}
},
_sendOnHighlightAction() {
this.sendAction("onHighlight", this.get("content.value"));
}
});

View File

@ -1,12 +0,0 @@
import NotificationOptionsComponent from "select-box-kit/components/notifications-button";
export default NotificationOptionsComponent.extend({
classNames: "tag-notifications-button",
i18nPrefix: "tagging.notifications",
showFullTitle: false,
headerComponent: "tag-notifications-button/tag-notifications-button-header",
selectValueFunction(value) {
this.sendAction("action", value);
}
});

View File

@ -1,13 +0,0 @@
import NotificationButtonHeader from "select-box-kit/components/notifications-button/notifications-button-header";
import computed from "ember-addons/ember-computed-decorators";
import { iconHTML } from 'discourse-common/lib/icon-library';
export default NotificationButtonHeader.extend({
classNames: "tag-notifications-button-header",
shouldDisplaySelectedName: false,
@computed("_selectedDetails.icon", "_selectedDetails.key")
icon() {
return `${this._super()}${iconHTML("caret-down")}`.htmlSafe();
}
});

View File

@ -1,222 +0,0 @@
export default Ember.Mixin.create({
init() {
this._super();
this.keys = {
TAB: 9,
ENTER: 13,
ESC: 27,
SPACE: 32,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
SHIFT: 16,
CTRL: 17,
ALT: 18,
PAGE_UP: 33,
PAGE_DOWN: 34,
HOME: 36,
END: 35,
BACKSPACE: 8
};
},
willDestroyElement() {
this._super();
$(document)
.off("mousedown.select-box-kit")
.off("touchstart.select-box-kit");
this.$offscreenInput()
.off("focus.select-box-kit")
.off("focusin.select-box-kit")
.off("blur.select-box-kit")
.off("keypress.select-box-kit")
.off("keydown.select-box-kit");
this.$filterInput()
.off("change.select-box-kit")
.off("keypress.select-box-kit")
.off("keydown.select-box-kit");
},
didInsertElement() {
this._super();
$(document)
.on("mousedown.select-box-kit, touchstart.select-box-kit", event => {
if (Ember.isNone(this.get("element"))) {
return;
}
if (this.get("element").contains(event.target)) { return; }
this.clickOutside(event);
});
this.$offscreenInput()
.on("blur.select-box-kit", () => {
if (this.get("isExpanded") === false && this.get("isFocused") === true) {
this.close();
}
})
.on("focus.select-box-kit", (event) => {
this.set("isFocused", true);
this._killEvent(event);
})
.on("focusin.select-box-kit", (event) => {
this.set("isFocused", true);
this._killEvent(event);
})
.on("keydown.select-box-kit", (event) => {
const keyCode = event.keyCode || event.which;
if (keyCode === this.keys.TAB) { this._handleTabOnKeyDown(event); }
if (keyCode === this.keys.ESC) { this._handleEscOnKeyDown(event); }
if (keyCode === this.keys.UP || keyCode === this.keys.DOWN) {
this._handleArrowKey(keyCode, event);
}
if (keyCode === this.keys.BACKSPACE) {
this.expand();
if (this.$filterInput().is(":visible")) {
this.$filterInput().focus().trigger(event).trigger("change");
}
return event;
}
return true;
})
.on("keypress.select-box-kit", (event) => {
const keyCode = event.keyCode || event.which;
switch (keyCode) {
case this.keys.ENTER:
if (this.get("isExpanded") === false) {
this.expand();
} else if (this.$highlightedRow().length === 1) {
this.$highlightedRow().click();
}
return false;
case this.keys.BACKSPACE:
return event;
}
if (this._isSpecialKey(keyCode) === false && event.metaKey === false) {
this.expand();
if (this.get("filterable") === true || this.get("autoFilterable")) {
this.set("renderedFilterOnce", true);
}
Ember.run.schedule("afterRender", () => {
this.$filterInput()
.focus()
.val(this.$filterInput().val() + String.fromCharCode(keyCode));
});
}
});
this.$filterInput()
.on("change.select-box-kit", (event) => {
this.send("onFilterChange", $(event.target).val());
})
.on("keydown.select-box-kit", (event) => {
const keyCode = event.keyCode || event.which;
if (keyCode === this.keys.TAB) { this._handleTabOnKeyDown(event); }
if (keyCode === this.keys.ESC) { this._handleEscOnKeyDown(event); }
if (keyCode === this.keys.UP || keyCode === this.keys.DOWN) {
this._handleArrowKey(keyCode, event);
}
})
.on("keypress.select-box-kit", (event) => {
const keyCode = event.keyCode || event.which;
if ([
this.keys.RIGHT,
this.keys.LEFT,
this.keys.BACKSPACE,
this.keys.SPACE,
].includes(keyCode) || event.metaKey === true) {
return true;
}
if (keyCode === this.keys.TAB && this.get("isExpanded") === false) {
return true;
}
if (this._isSpecialKey(keyCode) === true) {
this.$offscreenInput().focus().trigger(event);
return false;
}
return true;
});
},
_handleEscOnKeyDown(event) {
this.unfocus();
this._killEvent(event);
},
_handleTabOnKeyDown(event) {
if (this.get("isExpanded") === false) {
this.unfocus();
return true;
} else if (this.$highlightedRow().length === 1) {
this._killEvent(event);
this.$highlightedRow().click();
this.focus();
} else {
this.unfocus();
return true;
}
return false;
},
_handleArrowKey(keyCode, event) {
if (this.get("isExpanded") === false) { this.expand(); }
this._killEvent(event);
const $rows = this.$rows();
if ($rows.length <= 0) { return; }
if ($rows.length === 1) {
this._rowSelection($rows, 0);
return;
}
const direction = keyCode === 38 ? -1 : 1;
Ember.run.throttle(this, this._moveHighlight, direction, $rows, 32);
},
_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);
Ember.run.schedule("afterRender", () => {
$highlightableRow.trigger("mouseover").focus();
this.focus();
});
},
_isSpecialKey(keyCode) {
return _.values(this.keys).includes(keyCode);
},
});

View File

@ -1,15 +0,0 @@
{{#if icon}}
{{{icon}}}
{{/if}}
<span class="selected-name" title={{selectedName}}>
{{{selectedName}}}
</span>
{{#if shouldDisplayClearableButton}}
<button class="btn-clear" {{action onClearSelection bubbles=false}}>
{{d-icon 'times'}}
</button>
{{/if}}
{{d-icon caretIcon class="caret-icon"}}

View File

@ -1,18 +0,0 @@
<button
class="btn {{if shouldDisplaySelectedName 'btn-icon-text' 'no-text btn-icon'}}"
aria-label="{{selectedName}}"
type="button"
tabindex="-1"
title="{{selectedName}}">
{{#if icon}}
{{{icon}}}
{{/if}}
{{#if shouldDisplaySelectedName}}
<span class="d-button-label selected-name">
{{selectedName}}
</span>
{{/if}}
</button>

View File

@ -1,13 +0,0 @@
<ul class="choices">
{{#each selectedContent as |selectedContent|}}
{{component selectedNameComponent onDeselect=onDeselect content=selectedContent}}
{{/each}}
<li class="filter">
{{component "select-box-kit/select-box-kit-filter"
onFilterChange=onFilterChange
shouldDisplayFilter=shouldDisplayFilter
isFocused=isFocused
filter=filter
}}
</li>
</ul>

View File

@ -1,9 +0,0 @@
<span class="name">
{{#unless isLocked}}
<span class="delete-icon" {{action onDeselect content.value bubbles=false}}>
{{d-icon "times"}}
</span>
{{/unless}}
{{content.name}}
</span>

View File

@ -1,7 +0,0 @@
{{#if icon}}
{{{icon}}}
{{/if}}
<span class="selected-name" title={{selectedName}}>
{{{selectedName}}}
</span>

View File

@ -1,9 +0,0 @@
{{#if template}}
{{{template}}}
{{else}}
{{#if icon}}
{{{icon}}}
{{/if}}
<span class="name">{{content.name}}</span>
{{/if}}

View File

@ -1,21 +1,18 @@
import { iconHTML } from 'discourse-common/lib/icon-library';
import DropdownSelectBox from "select-box-kit/components/dropdown-select-box";
import DropdownSelectBox from "select-kit/components/dropdown-select-box";
import computed from "ember-addons/ember-computed-decorators";
import { on } from "ember-addons/ember-computed-decorators";
export default DropdownSelectBox.extend({
headerText: "admin.flags.agree",
headerIcon: "thumbs-o-up",
pluginApiIdentifiers: ["admin-agree-flag-dropdown"],
classNames: ["agree-flag", "admin-agree-flag-dropdown"],
adminTools: Ember.inject.service(),
nameProperty: "label",
allowInitialValueMutation: false,
headerIcon: "thumbs-o-up",
@on("didReceiveAttrs")
_setAdminAgreeDropdownOptions() {
this.get('headerComponentOptions').setProperties({
selectedName: `${I18n.t(this.get("headerText"))} ...`,
icon: iconHTML("thumbs-o-up")
});
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
content.name = `${I18n.t("admin.flags.agree")}...`;
return content;
},
@computed("adminTools", "post.user")
@ -25,9 +22,10 @@ export default DropdownSelectBox.extend({
canDeleteSpammer: Ember.computed.and("spammerDetails.canDelete", "post.flaggedForSpam"),
@computed("post", "canDeleteSpammer")
content(post, canDeleteSpammer) {
computeContent() {
const content = [];
const post = this.get("post");
const canDeleteSpammer = this.get("canDeleteSpammer");
if (post.user_deleted) {
content.push({
@ -70,8 +68,9 @@ export default DropdownSelectBox.extend({
return content;
},
selectValueFunction(value) {
Ember.get(this._contentForValue(value), "action")();
mutateValue(value) {
const computedContentItem = this.get("computedContent").findBy("value", value);
Ember.get(computedContentItem, "originalContent.action")();
},
actions: {

View File

@ -1,20 +1,16 @@
import { iconHTML } from 'discourse-common/lib/icon-library';
import DropdownSelectBox from "select-box-kit/components/dropdown-select-box";
import DropdownSelectBox from "select-kit/components/dropdown-select-box";
import computed from "ember-addons/ember-computed-decorators";
import { on } from "ember-addons/ember-computed-decorators";
export default DropdownSelectBox.extend({
headerText: "admin.flags.delete",
classNames: ["delete-flag", "admin-delete-flag-dropdown"],
adminTools: Ember.inject.service(),
nameProperty: "label",
headerIcon: "trash-o",
@on("didReceiveAttrs")
_setAdminDeleteDropdownOptions() {
this.get('headerComponentOptions').setProperties({
selectedName: `${I18n.t(this.get("headerText"))} ...`,
icon: iconHTML("trash-o")
});
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
content.name = I18n.t("admin.flags.delete");
return content;
},
@computed("adminTools", "post.user")
@ -24,9 +20,9 @@ export default DropdownSelectBox.extend({
canDeleteSpammer: Ember.computed.and("spammerDetails.canDelete", "post.flaggedForSpam"),
@computed("post", "canDeleteSpammer")
content(post, canDeleteSpammer) {
computeContent() {
const content = [];
const canDeleteSpammer = this.get("canDeleteSpammer");
content.push({
icon: "external-link",
@ -57,8 +53,9 @@ export default DropdownSelectBox.extend({
return content;
},
selectValueFunction(value) {
Ember.get(this._contentForValue(value), "action")();
mutateValue(value) {
const computedContentItem = this.get("computedContent").findBy("value", value);
Ember.get(computedContentItem, "originalContent.action")();
},
actions: {

View File

@ -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)
});
});
}
}
});

View File

@ -1,21 +1,15 @@
import DropdownSelectBoxComponent from "select-box-kit/components/dropdown-select-box";
import { iconHTML } from "discourse-common/lib/icon-library";
import computed from "ember-addons/ember-computed-decorators";
import { on } from "ember-addons/ember-computed-decorators";
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"],
@on("didReceiveAttrs")
_setComponentOptions() {
this.get("headerComponentOptions").setProperties({
shouldDisplaySelectedName: false,
icon: `${iconHTML('bars')}${iconHTML('caret-down')}`.htmlSafe(),
});
},
autoHighlight() {},
@computed
content() {
computeContent() {
const items = [
{
id: "create",
@ -38,8 +32,7 @@ export default DropdownSelectBoxComponent.extend({
return items;
},
selectValueFunction(value) {
mutateValue(value) {
this.get(value)();
this.set("value", null);
}
});

View File

@ -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;
});
}
});

View File

@ -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"];
}
});

View File

@ -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;' : ''}`;
}
});

View File

@ -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));
}
});

View File

@ -1,7 +1,8 @@
import SelectBoxKitComponent from "select-box-kit/components/select-box-kit";
import SingleSelectComponent from "select-kit/components/single-select";
import { on } from "ember-addons/ember-computed-decorators";
export default SelectBoxKitComponent.extend({
export default SingleSelectComponent.extend({
pluginApiIdentifiers: ["combo-box"],
classNames: "combobox combo-box",
autoFilterable: true,
headerComponent: "combo-box/combo-box-header",
@ -10,6 +11,12 @@ export default SelectBoxKitComponent.extend({
caretDownIcon: "caret-down",
clearable: false,
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
content.hasSelection = this.get("hasSelection");
return content;
},
@on("didReceiveAttrs")
_setComboBoxOptions() {
this.get("headerComponentOptions").setProperties({

View File

@ -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;
}
});

View File

@ -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();
}
});

View File

@ -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'}`;
}
});

View File

@ -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")
});

View File

@ -1,7 +1,6 @@
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
import ComboBoxComponent from "select-box-kit/components/combo-box";
import ComboBoxComponent from "select-kit/components/combo-box";
import { CLOSE_STATUS_TYPE } from "discourse/controllers/edit-topic-timer";
import DatetimeMixin from "select-box-kit/components/future-date-input-selector/mixin";
import DatetimeMixin from "select-kit/components/future-date-input-selector/mixin";
const TIMEFRAME_BASE = {
enabled: () => true,
@ -112,14 +111,30 @@ export function timeframeDetails(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",
@computed
content() {
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,
@ -138,21 +153,15 @@ export default ComboBoxComponent.extend(DatetimeMixin, {
});
},
@observes("value")
_updateInput() {
mutateValue(value) {
if (this.get("isCustom")) return;
let input = null;
const { time } = this.get("updateAt");
const { time } = this._updateAt(value);
if (time && !Ember.isEmpty(this.get("value"))) {
if (time && !Ember.isEmpty(value)) {
input = time.format(FORMAT);
}
this.set("input", input);
this.setProperties({ input, value });
},
@computed("value")
updateAt(value) {
return this._updateAt(value);
}
});

View File

@ -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"
});

View File

@ -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"
});

View File

@ -1,16 +1,15 @@
import { iconHTML } from 'discourse-common/lib/icon-library';
import { CLOSE_STATUS_TYPE } from 'discourse/controllers/edit-topic-timer';
import { timeframeDetails } from 'select-box-kit/components/future-date-input-selector';
import { timeframeDetails } from 'select-kit/components/future-date-input-selector';
export default Ember.Mixin.create({
_computeIconForValue(value) {
_computeIconsForValue(value) {
let {icon} = this._updateAt(value);
if (icon) {
return icon.split(",").map(i => iconHTML(i)).join(" ");
return icon.split(",");
}
return null;
return [];
},
_computeDatetimeForValue(value) {
@ -20,7 +19,6 @@ export default Ember.Mixin.create({
let {time} = this._updateAt(value);
if (time) {
let details = timeframeDetails(value);
if (!details.displayWhen) {
time = null;
@ -34,6 +32,7 @@ export default Ember.Mixin.create({
_updateAt(selection) {
let details = timeframeDetails(selection);
if (details) {
return {
time: details.when(moment(), this.get('statusType') !== CLOSE_STATUS_TYPE ? 8 : 18),

View File

@ -1,11 +1,12 @@
import NotificationOptionsComponent from "select-box-kit/components/notifications-button";
import NotificationOptionsComponent from "select-kit/components/notifications-button";
export default NotificationOptionsComponent.extend({
pluginApiIdentifiers: ["grouo-notifications-button"],
classNames: ["group-notifications-button"],
value: Ember.computed.alias("group.group_user.notification_level"),
i18nPrefix: "groups.notifications",
allowInitialValueMutation: false,
selectValueFunction(value) {
mutateValue(value) {
this.get("group").setNotification(value, this.get("user.id"));
}
});

View File

@ -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;
}
}
});

View File

@ -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));
}
}
});

View File

@ -1,11 +1,11 @@
import { on } from "ember-addons/ember-computed-decorators";
import computed from "ember-addons/ember-computed-decorators";
import SelectBoxKitHeaderComponent from "select-box-kit/components/select-box-kit/select-box-kit-header";
import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header";
export default SelectBoxKitHeaderComponent.extend({
export default SelectKitHeaderComponent.extend({
attributeBindings: ["names:data-name"],
classNames: "multi-combo-box-header",
layoutName: "select-box-kit/templates/components/multi-combo-box/multi-combo-box-header",
classNames: "multi-select-header",
layoutName: "select-kit/templates/components/multi-select/multi-select-header",
selectedNameComponent: Ember.computed.alias("options.selectedNameComponent"),
@on("didRender")
@ -25,6 +25,8 @@ export default SelectBoxKitHeaderComponent.extend({
$filter.width(availableSpace - parentRightPadding * 4);
},
@computed("selectedContent.[]")
names(selectedContent) { return selectedContent.map(sc => sc.name).join(","); }
@computed("computedContent.selectedComputedContents.[]")
names(selectedComputedContents) {
return Ember.makeArray(selectedComputedContents).map(sc => sc.name).join(",");
}
});

View File

@ -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();
}
});

View File

@ -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());
}
});

View File

@ -1,18 +1,27 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
attributeBindings: ["tabindex","content.name:data-name", "content.value:data-value"],
classNames: "selected-name",
attributeBindings: [
"tabindex",
"content.name:data-name",
"content.value:data-value",
"guid:data-guid"
],
classNames: ["selected-name", "choice"],
classNameBindings: ["isHighlighted", "isLocked"],
layoutName: "select-box-kit/templates/components/multi-combo-box/selected-name",
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;
}

View File

@ -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");
}
});

View File

@ -1,4 +1,4 @@
import DropdownSelectBoxComponent from "select-box-kit/components/dropdown-select-box";
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";
@ -9,24 +9,29 @@ export default DropdownSelectBoxComponent.extend({
fullWidthOnMobile: true,
content: allLevels,
collectionHeight: "auto",
value: Ember.computed.alias("notificationLevel"),
castInteger: true,
autofilterable: false,
filterable: false,
rowComponent: "notifications-button/notifications-button-row",
headerComponent: "notifications-button/notifications-button-header",
allowInitialValueMutation: false,
i18nPrefix: "",
i18nPostfix: "",
showFullTitle: true,
@on("didReceiveAttrs", "didUpdateAttrs")
_setComponentOptions() {
this.get("headerComponentOptions").setProperties({
i18nPrefix: this.get("i18nPrefix"),
showFullTitle: this.get("showFullTitle"),
});
@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")

View File

@ -1,4 +1,4 @@
import DropdownSelectBoxRoxComponent from "select-box-kit/components/dropdown-select-box/dropdown-select-box-row";
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';
@ -9,13 +9,13 @@ export default DropdownSelectBoxRoxComponent.extend({
i18nPrefix: Ember.computed.alias("options.i18nPrefix"),
i18nPostfix: Ember.computed.alias("options.i18nPostfix"),
@computed("content.value", "i18nPrefix")
@computed("computedContent.value", "i18nPrefix")
title(value, prefix) {
const key = buttonDetails(value).key;
return I18n.t(`${prefix}.${key}.title`);
},
@computed("content.name", "content.originalContent.icon")
@computed("computedContent.name", "computedContent.originalContent.icon")
icon(contentName, icon) {
return iconHTML(icon, { class: contentName.dasherize() });
},
@ -30,7 +30,7 @@ export default DropdownSelectBoxRoxComponent.extend({
return Handlebars.escapeExpression(I18n.t(`${_start}.title`));
},
@computed("i18nPrefix", "i18nPostfix", "content.name")
@computed("i18nPrefix", "i18nPostfix", "computedContent.name")
_start(prefix, postfix, contentName) {
return `${prefix}.${contentName}${postfix}`;
},

View File

@ -1,12 +1,13 @@
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-box-kit/templates/components/pinned-button",
layoutName: "select-kit/templates/components/pinned-button",
@computed("topic.pinned_globally", "topic.pinned")
@computed("topic.pinned_globally", "pinned")
reasonText(pinnedGlobally, pinned) {
const globally = pinnedGlobally ? "_globally" : "";
const pinnedKey = pinned ? `pinned${globally}` : "unpinned";
@ -14,7 +15,7 @@ export default Ember.Component.extend({
return I18n.t(key);
},
@computed("topic.pinned", "topic.deleted", "topic.unpinned")
@computed("pinned", "topic.deleted", "topic.unpinned")
isHidden(pinned, deleted, unpinned) {
return deleted || (!pinned && !unpinned);
}

View File

@ -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();
}
}
});

View File

@ -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; }
});

View File

@ -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();
}
}
});

View File

@ -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"
});

View File

@ -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"));
},
});

View File

@ -1,6 +1,6 @@
export default Ember.Component.extend({
layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-filter",
classNames: "select-box-kit-filter",
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")
});

View File

@ -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");
}
});

View File

@ -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");
}
});

View File

@ -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"));
}
});

View File

@ -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));
}
}
});

View File

@ -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"];
}
});

View File

@ -1,24 +1,21 @@
import computed from "ember-addons/ember-computed-decorators";
import ComboBoxComponent from "select-box-kit/components/combo-box";
import { on } from "ember-addons/ember-computed-decorators";
import ComboBoxComponent from "select-kit/components/combo-box";
export default ComboBoxComponent.extend({
headerText: "topic.controls",
pluginApiIdentifiers: ["topic-footer-mobile-dropdown"],
classNames: "topic-footer-mobile-dropdown",
filterable: false,
autoFilterable: false,
allowValueMutation: false,
autoSelectFirst: false,
allowInitialValueMutation: false,
@on("didReceiveAttrs")
_setTopicFooterMobileDropdownOptions() {
this.get("headerComponentOptions")
.set("selectedName", I18n.t(this.get("headerText")));
computeHeaderContent() {
let content = this.baseHeaderComputedContent();
content.name = I18n.t("topic.controls");
return content;
},
@computed("topic", "topic.details", "value")
content(topic, details) {
const 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") });
@ -39,16 +36,15 @@ export default ComboBoxComponent.extend({
return content;
},
selectValueFunction(value) {
autoHighlight() {},
mutateValue(value) {
const topic = this.get("topic");
// In case it"s not a valid topic
if (!topic.get("id")) {
return;
}
this.set("value", value);
const refresh = () => this.send("onDeselect", value);
switch(value) {

View File

@ -1,5 +1,5 @@
export default Ember.Component.extend({
layoutName: "select-box-kit/templates/components/topic-notifications-button",
layoutName: "select-kit/templates/components/topic-notifications-button",
classNames: "topic-notifications-button",
showFullTitle: true,
appendReason: true

View File

@ -1,12 +1,13 @@
import NotificationOptionsComponent from "select-box-kit/components/notifications-button";
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",
value: Ember.computed.alias("topic.details.notification_level"),
allowInitialValueMutation: false,
@on("didInsertElement")
_bindGlobalLevelChanged() {
@ -24,11 +25,9 @@ export default NotificationOptionsComponent.extend({
this.appEvents.off("topic-notifications-button:changed");
},
selectValueFunction(value) {
mutateValue(value) {
if (value !== this.get("value")) {
this.get("topic.details").updateNotifications(value);
}
this.set("value", value);
}
});

View File

@ -4,13 +4,12 @@ export default Ember.Mixin.create({
init() {
this._super();
this.offscreenInputSelector = ".select-box-kit-offscreen";
this.filterInputSelector = ".select-box-kit-filter-input";
this.rowSelector = ".select-box-kit-row";
this.collectionSelector = ".select-box-kit-collection";
this.headerSelector = ".select-box-kit-header";
this.bodySelector = ".select-box-kit-body";
this.wrapperSelector = ".select-box-kit-wrapper";
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";
},
$findRowByValue(value) { return this.$(`${this.rowSelector}[data-value='${value}']`); },
@ -34,45 +33,50 @@ export default Ember.Mixin.create({
$selectedRow() { return this.$rows().filter(".is-selected"); },
$offscreenInput() { return this.$(this.offscreenInputSelector); },
$filterInput() { return this.$(this.filterInputSelector); },
@on("didRender")
_ajustPosition() {
$(`.select-box-kit-fixed-placeholder-${this.elementId}`).remove();
$(`.select-kit-fixed-placeholder-${this.elementId}`).remove();
this.$collection().css("max-height", this.get("collectionHeight"));
this._applyFixedPosition();
this._applyDirection();
this._positionWrapper();
},
@on("didInsertElement")
_setupResizeListener() {
$(window).on("resize.select-kit", () => this.collapse() );
},
@on("willDestroyElement")
_clearState() {
$(window).off("resize.select-box-kit");
$(`.select-box-kit-fixed-placeholder-${this.elementId}`).remove();
$(window).off("resize.select-kit");
$(`.select-kit-fixed-placeholder-${this.elementId}`).remove();
},
// make sure we dont propagate a click outside component
// to avoid closing a modal containing the component for example
click(event) { this._killEvent(event); },
click(event) {
this._destroyEvent(event);
},
// use to collapse and remove focus
close() {
this.collapse();
close(event) {
this.collapse(event);
this.setProperties({ isFocused: false });
},
// force the component in a known default state
focus() {
Ember.run.schedule("afterRender", () => this.$offscreenInput().select() );
Ember.run.schedule("afterRender", () => this.$header().focus() );
},
expand() {
if (this.get("isExpanded") === true) { return; }
expand(event) {
if (this.get("isExpanded") === true) return;
this.setProperties({ isExpanded: true, renderedBodyOnce: true, isFocused: true });
this.focus();
this.autoHighlightFunction();
this.focus(event);
this.autoHighlight();
},
collapse() {
@ -80,35 +84,18 @@ export default Ember.Mixin.create({
Ember.run.schedule("afterRender", () => this._removeFixedPosition() );
},
// make sure we close/unfocus the component when clicked outside
clickOutside(event) {
if ($(event.target).parents(".select-box-kit").length === 1) {
this.close();
return false;
}
this.unfocus();
return;
},
// lose focus of the component in two steps
// first collapase and keep focus and then remove focus
unfocus() {
this.set("highlightedValue", null);
// first collapse and keep focus and then remove focus
unfocus(event) {
if (this.get("isExpanded") === true) {
this.collapse();
this.focus();
this.collapse(event);
this.focus(event);
} else {
this.close();
this.close(event);
}
},
blur() {
Ember.run.schedule("afterRender", () => this.$offscreenInput().blur() );
},
_killEvent(event) {
_destroyEvent(event) {
event.preventDefault();
event.stopPropagation();
},
@ -119,8 +106,8 @@ export default Ember.Mixin.create({
const dHeader = $(".d-header")[0];
const dHeaderBounds = dHeader ? dHeader.getBoundingClientRect() : {top: 0, height: 0};
const dHeaderHeight = dHeaderBounds.top + dHeaderBounds.height;
const headerHeight = this.$header().outerHeight(false);
const headerWidth = this.$header().outerWidth(false);
const componentHeight = this.$().outerHeight(false);
const componentWidth = this.$().outerWidth(false);
const bodyHeight = this.$body().outerHeight(false);
const windowWidth = $(window).width();
const windowHeight = $(window).height();
@ -145,7 +132,7 @@ export default Ember.Mixin.create({
options.left = bodyWidth + this.get("horizontalOffset");
} else {
this.setProperties({ isLeftAligned: false, isRightAligned: true });
options.right = - (bodyWidth - headerWidth + this.get("horizontalOffset"));
options.right = - (bodyWidth - componentWidth + this.get("horizontalOffset"));
}
} else {
const horizontalSpacing = boundingRect.left;
@ -160,15 +147,15 @@ export default Ember.Mixin.create({
}
}
const componentHeight = this.get("verticalOffset") + bodyHeight + headerHeight;
const hasBelowSpace = windowHeight - offsetBottom - componentHeight > 0;
const hasAboveSpace = offsetTop - componentHeight - dHeaderHeight > 0;
const fullHeight = this.get("verticalOffset") + bodyHeight + componentHeight;
const hasBelowSpace = windowHeight - offsetBottom - fullHeight > 0;
const hasAboveSpace = offsetTop - fullHeight - dHeaderHeight > 0;
if (hasBelowSpace || (!hasBelowSpace && !hasAboveSpace)) {
this.setProperties({ isBelow: true, isAbove: false });
options.top = headerHeight + this.get("verticalOffset");
options.top = componentHeight + this.get("verticalOffset") - 2;
} else {
this.setProperties({ isBelow: false, isAbove: true });
options.bottom = headerHeight + this.get("verticalOffset");
options.bottom = componentHeight + this.get("verticalOffset") - 1;
}
this.$body().css(options);
@ -182,7 +169,7 @@ export default Ember.Mixin.create({
const width = this.$().outerWidth(false);
const height = this.$().outerHeight(false);
const $placeholder = $(`<div class='select-box-kit-fixed-placeholder-${this.elementId}'></div>`);
const $placeholder = $(`<div class='select-kit-fixed-placeholder-${this.elementId}'></div>`);
this._previousScrollParentOverflow = this._previousScrollParentOverflow || scrollableParent.css("overflow");
scrollableParent.css({ overflow: "hidden" });
@ -212,7 +199,7 @@ export default Ember.Mixin.create({
},
_removeFixedPosition() {
$(`.select-box-kit-fixed-placeholder-${this.elementId}`).remove();
$(`.select-kit-fixed-placeholder-${this.elementId}`).remove();
if (!this.element || this.isDestroying || this.isDestroyed) { return; }
@ -236,11 +223,11 @@ export default Ember.Mixin.create({
},
_positionWrapper() {
const headerHeight = this.$header().outerHeight(false);
const componentHeight = this.$().outerHeight(false);
this.$(this.wrapperSelector).css({
width: this.$().outerWidth(false),
height: headerHeight + this.$body().outerHeight(false)
width: this.$().outerWidth(false) - 2,
height: componentHeight + this.$body().outerHeight(false)
});
},
});

View File

@ -0,0 +1,233 @@
export default Ember.Mixin.create({
init() {
this._super();
this.keys = {
TAB: 9,
ENTER: 13,
ESC: 27,
UP: 38,
DOWN: 40,
BACKSPACE: 8,
};
},
willDestroyElement() {
this._super();
$(document)
.off("mousedown.select-kit")
.off("touchstart.select-kit");
this.$header()
.off("focus.select-kit")
.off("blur.select-kit")
.off("keypress.select-kit")
.off("keydown.select-kit");
this.$filterInput()
.off("change.select-kit")
.off("keydown.select-kit")
.off("focus.select-kit")
.off("focusin.select-kit");
},
didInsertElement() {
this._super();
$(document)
.on("mousedown.select-kit, touchstart.select-kit", event => {
if (Ember.isNone(this.get("element"))) return;
if (this.get("element").contains(event.target)) return;
this.didClickOutside(event);
});
this.$header()
.on("blur.select-kit", () => {
if (this.get("isExpanded") === false && this.get("isFocused") === true) {
this.close();
}
})
.on("focus.select-kit", (event) => {
this.set("isFocused", true);
this._destroyEvent(event);
})
.on("keydown.select-kit", (event) => {
const keyCode = event.keyCode || event.which;
if (document.activeElement !== this.$header()[0]) return event;
if (keyCode === this.keys.TAB) this.tabFromHeader(event);
if (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);
return event;
})
.on("keypress.select-kit", (event) => {
const keyCode = event.keyCode || event.which;
if (keyCode === this.keys.ENTER) { return true; }
this.expand(event);
if (this.get("filterable") === true || this.get("autoFilterable")) {
this.set("renderedFilterOnce", true);
}
Ember.run.schedule("afterRender", () => {
let newVal = this.$filterInput().val();
const start = this.$filterInput()[0].selectionStart;
const end = this.$filterInput()[0].selectionEnd;
if (!Ember.isNone(start) && !Ember.isNone(end)) {
newVal = newVal.substr(0, start) +
String.fromCharCode(keyCode) +
newVal.substr(end, newVal.length);
} else {
newVal = newVal + String.fromCharCode(keyCode);
}
this.$filterInput().focus().val(newVal);
});
return false;
});
this.$filterInput()
.on("change.select-kit", (event) => {
this.send("onFilter", $(event.target).val());
})
.on("focus.select-kit focusin.select-kit", (event) => {
this.set("isFocused", true);
this._destroyEvent(event);
})
.on("keydown.select-kit", (event) => {
const keyCode = event.keyCode || event.which;
if (keyCode === this.keys.TAB) 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);
});
},
didPressTab(event) {
if (this.get("isExpanded") === false) {
this.unfocus(event);
} else if (this.$highlightedRow().length === 1) {
this._destroyEvent(event);
Ember.run.throttle(this, this._rowClick, this.$highlightedRow(), 150, 150, true);
this.focus(event);
} else {
this._destroyEvent(event);
this.unfocus(event);
}
return true;
},
didPressEscape(event) {
this._destroyEvent(event);
this.unfocus(event);
},
didPressUpAndDownArrows(event) {
this._destroyEvent(event);
const keyCode = event.keyCode || event.which;
const $rows = this.$rows();
if (this.get("isExpanded") === false) {
this.expand(event);
if (this.$selectedRow().length === 1) {
this._highlightRow(this.$selectedRow());
return;
}
}
if ($rows.length <= 0) { return; }
if ($rows.length === 1) {
this._rowSelection($rows, 0);
return;
}
const direction = keyCode === 38 ? -1 : 1;
Ember.run.throttle(this, this._moveHighlight, direction, $rows, 32);
},
didPressBackspace(event) {
this._destroyEvent(event);
this.expand(event);
if (this.$filterInput().is(":visible")) {
this.$filterInput().focus().trigger(event).trigger("change");
}
},
didPressEnter(event) {
this._destroyEvent(event);
if (this.get("isExpanded") === false) {
this.expand(event);
} else if (this.$highlightedRow().length === 1) {
Ember.run.throttle(this, this._rowClick, this.$highlightedRow(), 150, true);
}
},
didClickOutside(event) {
if ($(event.target).parents(".select-kit").length === 1) {
this.close(event);
return false;
}
this.unfocus(event);
return;
},
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); },
backspaceFromHeader(event) { this.didPressBackspace(event); },
enterFromHeader(event) { this.didPressEnter(event); },
enterFromFilter(event) { this.didPressEnter(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);
},
_rowClick($row) { $row.click(); },
_rowSelection($rows, nextIndex) {
const highlightableValue = $rows.eq(nextIndex).attr("data-value");
const $highlightableRow = this.$findRowByValue(highlightableValue);
this._highlightRow($highlightableRow);
},
_highlightRow($row) {
Ember.run.schedule("afterRender", () => {
$row.trigger("mouseover").focus();
this.focus();
});
}
});

View File

@ -0,0 +1,71 @@
let _appendContentCallbacks = {};
function appendContent(pluginApiIdentifiers, contentFunction) {
if (Ember.isNone(_appendContentCallbacks[pluginApiIdentifiers])) {
_appendContentCallbacks[pluginApiIdentifiers] = [];
}
_appendContentCallbacks[pluginApiIdentifiers].push(contentFunction);
}
let _prependContentCallbacks = {};
function prependContent(pluginApiIdentifiers, contentFunction) {
if (Ember.isNone(_prependContentCallbacks[pluginApiIdentifiers])) {
_prependContentCallbacks[pluginApiIdentifiers] = [];
}
_prependContentCallbacks[pluginApiIdentifiers].push(contentFunction);
}
let _modifyContentCallbacks = {};
function modifyContent(pluginApiIdentifiers, contentFunction) {
if (Ember.isNone(_modifyContentCallbacks[pluginApiIdentifiers])) {
_modifyContentCallbacks[pluginApiIdentifiers] = [];
}
_modifyContentCallbacks[pluginApiIdentifiers].push(contentFunction);
}
export function applyContentPluginApiCallbacks(identifiers, content) {
identifiers.forEach((key) => {
(_prependContentCallbacks[key] || []).forEach((c) => {
content = c().concat(content);
});
(_appendContentCallbacks[key] || []).forEach((c) => {
content = content.concat(c());
});
(_modifyContentCallbacks[key] || []).forEach((c) => {
content = c(content);
});
});
return content;
}
export function modifySelectKit(pluginApiIdentifiers) {
return {
appendContent: (content) => {
appendContent(pluginApiIdentifiers, () => {return content;} );
return modifySelectKit(pluginApiIdentifiers);
},
prependContent: (content) => {
prependContent(pluginApiIdentifiers, () => {return content;} );
return modifySelectKit(pluginApiIdentifiers);
},
modifyContent: (callback) => {
modifyContent(pluginApiIdentifiers, callback);
return modifySelectKit(pluginApiIdentifiers);
}
};
}
export function clearCallbacks() {
_appendContentCallbacks = {};
_prependContentCallbacks = {};
_modifyContentCallbacks = {};
}
const EMPTY_ARRAY = Object.freeze([]);
export default Ember.Mixin.create({
concatenatedProperties: ["pluginApiIdentifiers"],
pluginApiIdentifiers: EMPTY_ARRAY
});

View File

@ -1,6 +1,16 @@
const { get, isNone } = Ember;
const { get, isNone, guidFor } = Ember;
export default Ember.Mixin.create({
valueForContentItem(content) {
switch (typeof content) {
case "string":
case "number":
return content;
default:
return get(content, this.get("valueAttribute"));
}
},
_nameForContent(content) {
if (isNone(content)) {
return null;
@ -25,37 +35,13 @@ export default Ember.Mixin.create({
return value;
},
_valueForContent(content) {
switch (typeof content) {
case "string":
case "number":
return content;
default:
return get(content, this.get("valueAttribute"));
}
},
_contentForValue(value) {
return this.get("content").find(c => {
if (this._valueForContent(c) === value) { return true; }
});
},
_computedContentForValue(value) {
const searchedValue = value.toString();
_findComputedContentItemByGuid(guid) {
return this.get("computedContent").find(c => {
if (c.value.toString() === searchedValue) { return true; }
return guidFor(c) === guid;
});
},
_originalValueForValue(value) {
if (isNone(value)) { return null; }
if (value === this.noneValue) { return this.noneValue; }
const computedContent = this._computedContentForValue(value);
if (isNone(computedContent)) { return value; }
return get(computedContent.originalContent, this.get("valueAttribute"));
},
_filterRemovableComputedContents(computedContent) {
return computedContent.filter(c => c.created === true);
}
});

View File

@ -0,0 +1,19 @@
{{#if category}}
{{#if hasParentCategory}}
<div class="category-status">
{{badgeForParentCategory}}&nbsp;{{badgeForCategory}}
&nbsp;<span class="topic-count">&times; {{topicCount}}</span>
</div>
{{else}}
<div class="category-status">
{{badgeForCategory}}
&nbsp;<span class="topic-count">&times; {{topicCount}}</span>
</div>
{{/if}}
{{#if hasDescription}}
<div class="category-desc">{{description}}</div>
{{/if}}
{{else}}
{{computedContent.name}}
{{/if}}

View File

@ -0,0 +1,13 @@
{{#each icons as |icon|}} {{d-icon icon}} {{/each}}
<span class="selected-name" title={{title}}>
{{{name}}}
</span>
{{#if shouldDisplayClearableButton}}
<button class="btn-clear" {{action onClear bubbles=false}}>
{{d-icon 'times'}}
</button>
{{/if}}
{{d-icon caretIcon class="caret-icon"}}

View File

@ -0,0 +1,7 @@
{{#each icons as |icon|}} {{d-icon icon}} {{/each}}
{{#if options.showFullTitle}}
<span class="d-button-label selected-name">
{{name}}
</span>
{{/if}}

Some files were not shown because too many files have changed in this diff Show More