[WIP] select-box-kit refactoring

This commit is contained in:
Joffrey JAFFEUX 2017-10-19 12:51:08 -07:00 committed by GitHub
parent a9f718fe57
commit ae1743c61f
157 changed files with 3262 additions and 2404 deletions

View File

@ -46,6 +46,7 @@
"expandSelectBox":true,
"collapseSelectBox":true,
"selectBoxSelectRow":true,
"selectBoxSelectNoneRow":true,
"selectBoxFillInFilter":true,
"asyncTestDiscourse":true,
"fixture":true,

View File

@ -50,5 +50,3 @@ export default Ember.Component.extend({
});
}
});

View File

@ -26,9 +26,7 @@
{{combo-box name="badge_type_id"
value=buffered.badge_type_id
content=badgeTypes
optionValuePath="content.id"
optionLabelPath="content.name"
disabled=readOnly}}
isDisabled=readOnly}}
</div>
<div>
@ -36,8 +34,7 @@
{{combo-box name="badge_grouping_id"
value=buffered.badge_grouping_id
content=badgeGroupings
optionValuePath="content.id"
optionLabelPath="content.displayName"}}
nameProperty="name"}}
&nbsp;<button {{action "editGroupings"}} class='btn'>{{d-icon 'pencil'}}</button>
</div>

View File

@ -1,6 +1,6 @@
{{#if editing}}
{{#admin-form-row label="admin.user_fields.type"}}
{{combo-box content=fieldTypes valueAttribute="id" value=buffered.field_type}}
{{combo-box content=fieldTypes value=buffered.field_type}}
{{/admin-form-row}}
{{#admin-form-row label="admin.user_fields.name"}}

View File

@ -9,7 +9,7 @@
{{input value=buffered.path_whitelist placeholder="/blog/.*" enter="save" class="path-whitelist"}}
</td>
<td>
{{category-select-box value=categoryId class="small"}}
{{category-chooser value=categoryId class="small"}}
</td>
<td>
{{d-button icon="check" action="save" class="btn-primary" disabled=cantSave}}

View File

@ -36,8 +36,7 @@
<h3>{{i18n "admin.customize.theme.color_scheme"}}</h3>
<p>{{i18n "admin.customize.theme.color_scheme_select"}}</p>
<p>{{select-box content=colorSchemes
textKey="name"
<p>{{combo-box content=colorSchemes
filterable=true
value=colorSchemeId
icon="paint-brush"}}
@ -123,11 +122,8 @@
</ul>
{{/unless}}
{{#if selectableChildThemes}}
<p>{{combo-box content=selectableChildThemes
nameProperty="name"
value=selectedChildThemeId
valueAttribute="id"}}
<p>
{{combo-box content=selectableChildThemes value=selectedChildThemeId}}
{{#d-button action="addChildTheme" icon="plus"}}{{i18n "admin.customize.theme.add"}}{{/d-button}}
</p>
{{/if}}

View File

@ -6,7 +6,7 @@
</div>
<div class='control'>
{{combo-box content=groups valueAttribute="id" value=groupId none="admin.groups.bulk_select"}}
{{combo-box filterable=true content=groups value=groupId none="admin.groups.bulk_select"}}
</div>
<div class='control'>

View File

@ -30,7 +30,7 @@
{{/if}}
</div>
{{else}}
{{i18n "admin.logs.staff_actions.filter"}} {{combo-box content=userHistoryActions nameProperty="name" value=filterActionId none="admin.logs.staff_actions.all"}}
{{i18n "admin.logs.staff_actions.filter"}} {{combo-box content=userHistoryActions value=filterActionId none="admin.logs.staff_actions.all"}}
{{/if}}
<div class="pull-right">

View File

@ -2,7 +2,6 @@
{{#d-modal-body title="admin.customize.colors.select_base.title"}}
{{i18n "admin.customize.colors.select_base.description"}}
{{combo-box content=model
nameProperty="name"
value=selectedBaseThemeId
valueAttribute="base_scheme_id"}}
{{/d-modal-body}}

View File

@ -4,10 +4,10 @@
{{i18n 'admin.dashboard.reports.start_date'}} {{date-picker-past value=startDate defaultDate=startDate}}
{{i18n 'admin.dashboard.reports.end_date'}} {{date-picker-past value=endDate defaultDate=endDate}}
{{#if showCategoryOptions}}
{{combo-box valueAttribute="value" content=categoryOptions value=categoryId}}
{{combo-box filterable=true valueAttribute="value" content=categoryOptions value=categoryId}}
{{/if}}
{{#if showGroupOptions}}
{{combo-box valueAttribute="value" content=groupOptions value=groupId}}
{{combo-box filterable=true valueAttribute="value" content=groupOptions value=groupId}}
{{/if}}
{{d-button action="refreshReport" class="btn-primary" label="admin.dashboard.reports.refresh_report" icon="refresh"}}
{{d-button action="exportCsv" label="admin.export_csv.button_text" icon="download"}}

View File

@ -16,7 +16,7 @@
<form class="form-horizontal">
<div>
<label>{{i18n 'admin.badges.badge'}}</label>
{{combo-box valueAttribute="id" value=selectedBadgeId content=grantableBadges nameProperty="name"}}
{{combo-box filterable=true value=selectedBadgeId content=grantableBadges}}
</div>
<label>
<label>{{i18n 'admin.badges.reason'}}</label>

View File

@ -1,5 +1,4 @@
<section class="details {{unless model.active 'not-activated'}}">
<div class='user-controls'>
{{#if model.canViewProfile}}
{{#link-to 'user' model class="btn"}}
@ -379,7 +378,7 @@
<div class='controls'>
{{#if model.customGroups}}
{{i18n 'admin.groups.primary'}}
{{combo-box content=model.customGroups value=model.primary_group_id nameProperty="name" none="admin.groups.no_primary"}}
{{combo-box content=model.customGroups value=model.primary_group_id none="admin.groups.no_primary"}}
{{/if}}
{{#if primaryGroupDirty}}
{{d-button icon="check" class="ok" action="savePrimaryGroup"}}

View File

@ -16,8 +16,6 @@
<label for='content-type'>{{i18n 'admin.web_hooks.content_type'}}</label>
{{combo-box content=contentTypes
name="content-type"
nameProperty="name"
valueAttribute="id"
value=model.content_type}}
</div>

View File

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

View File

@ -1,146 +0,0 @@
import { bufferedRender } from 'discourse-common/lib/buffered-render';
import { on, observes } from 'ember-addons/ember-computed-decorators';
import { iconHTML } from 'discourse-common/lib/icon-library';
export default Ember.Component.extend(bufferedRender({
tagName: 'select',
attributeBindings: ['tabindex', 'disabled'],
classNames: ['combobox'],
valueAttribute: 'id',
nameProperty: 'name',
buildBuffer(buffer) {
const nameProperty = this.get('nameProperty');
const none = this.get('none');
let noneValue = null;
// Add none option if required
if (typeof none === "string") {
buffer.push('<option value="">' + I18n.t(none) + "</option>");
} else if (typeof none === "object") {
noneValue = Em.get(none, this.get('valueAttribute'));
buffer.push(`<option value="${noneValue}">${Em.get(none, nameProperty)}</option>`);
}
let selected = this.get('value');
if (!Em.isNone(selected)) { selected = selected.toString(); }
let selectedFound = false;
let firstVal = undefined;
const content = this.get('content');
if (content) {
let first = true;
content.forEach(o => {
let val = o[this.get('valueAttribute')];
if (typeof val === "undefined") { val = o; }
if (!Em.isNone(val)) { val = val.toString(); }
const selectedText = (val === selected) ? "selected" : "";
const name = Handlebars.Utils.escapeExpression(Ember.get(o, nameProperty) || o);
if (val === selected) {
selectedFound = true;
}
if (first) {
firstVal = val;
first = false;
}
buffer.push(`<option ${selectedText} value="${val}">${name}</option>`);
});
}
if (!selectedFound && !noneValue) {
if (none) {
this.set('value', null);
} else {
this.set('value', firstVal);
}
}
Ember.run.scheduleOnce('afterRender', this, this._updateSelect2);
},
@observes('value')
valueChanged() {
const $combo = this.$(),
val = this.get('value');
if (val !== undefined && val !== null) {
$combo.select2('val', val.toString());
} else {
$combo.select2('val', null);
}
},
@observes('content.[]')
_rerenderOnChange() {
this.rerenderBuffer();
},
didInsertElement() {
this._super();
// Workaround for https://github.com/emberjs/ember.js/issues/9813
// Can be removed when fixed. Without it, the wrong option is selected
this.$('option').each((i, o) => o.selected = !!$(o).attr('selected'));
// observer for item names changing (optional)
if (this.get('nameChanges')) {
this.addObserver('content.@each.' + this.get('nameProperty'), this.rerenderBuffer);
}
const $elem = this.$();
const caps = this.capabilities;
const minimumResultsForSearch = this.get('minimumResultsForSearch') || ((caps && caps.isIOS) ? -1 : 5);
if (!this.get("selectionTemplate") && this.get("selectionIcon")) {
this.selectionTemplate = (item) => {
let name = Em.get(item, 'text');
name = Handlebars.escapeExpression(name);
return iconHTML(this.get('selectionIcon')) + name;
};
}
const options = {
minimumResultsForSearch,
width: this.get('width') || 'resolve',
allowClear: true
};
if (this.comboTemplate) {
options.formatResult = this.comboTemplate.bind(this);
}
if (this.selectionTemplate) {
options.formatSelection = this.selectionTemplate.bind(this);
}
$elem.select2(options);
const castInteger = this.get('castInteger');
$elem.on("change", e => {
let val = $(e.target).val();
if (val && val.length && castInteger) {
val = parseInt(val, 10);
}
Ember.run(() => this.set('value', val));
});
Ember.run.scheduleOnce('afterRender', this, this._triggerChange);
},
_updateSelect2() {
this.$().trigger('change.select2');
},
_triggerChange() {
this.$().trigger('change');
},
@on('willDestroyElement')
_destroyDropdown() {
this.$().select2('destroy');
}
}));

View File

@ -1,49 +0,0 @@
import DropdownSelectBoxComponent from "discourse/components/dropdown-select-box";
import { iconHTML } from "discourse-common/lib/icon-library";
import computed from "ember-addons/ember-computed-decorators";
export default DropdownSelectBoxComponent.extend({
classNames: ["categories-admin-dropdown"],
icon: `${iconHTML('bars')}${iconHTML('caret-down')}`.htmlSafe(),
generatedHeadertext: null,
@computed
content() {
const items = [
{
id: "create",
text: I18n.t("category.create"),
description: I18n.t("category.create_long"),
icon: "plus"
}
];
const includeReorder = this.get("siteSettings.fixed_category_positions");
if (includeReorder) {
items.push({
id: "reorder",
text: I18n.t("categories.reorder.title"),
description: I18n.t("categories.reorder.title_long"),
icon: "random"
});
}
return items;
},
actionNames: {
create: "createCategory",
reorder: "reorderCategories"
},
actions: {
onSelectRow(content) {
this._super(content);
this.sendAction(`actionNames.${this.get("value")}`);
this.set("value", null);
}
}
});

View File

@ -1,88 +0,0 @@
import Combobox from 'discourse-common/components/combo-box';
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
import computed from 'ember-addons/ember-computed-decorators';
import { observes, on } from 'ember-addons/ember-computed-decorators';
import PermissionType from 'discourse/models/permission-type';
import Category from 'discourse/models/category';
export default Combobox.extend({
classNames: ['combobox category-combobox'],
dataAttributes: ['id', 'description_text'],
overrideWidths: true,
castInteger: true,
@computed("scopedCategoryId", "categories")
content(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 = c.get('id');
if (scopedCategoryId && categoryId !== scopedCategoryId && c.get('parent_category_id') !== scopedCategoryId) { return false; }
if (c.get('isUncategorizedCategory') || excludeCategoryId === categoryId) { return false; }
return c.get('permission') === PermissionType.FULL;
});
},
@on("init")
@observes("site.sortedCategories")
_updateCategories() {
if (!this.get('categories')) {
const categories = Discourse.SiteSettings.fixed_category_positions_on_create ?
Category.list() :
Category.listByActivity();
this.set('categories', categories);
}
},
@computed("rootNone", "rootNoneLabel")
none(rootNone, rootNoneLabel) {
if (this.siteSettings.allow_uncategorized_topics || this.get('allowUncategorized')) {
if (rootNone) {
return rootNoneLabel || "category.none";
} else {
return Category.findUncategorized();
}
} else {
return 'category.choose';
}
},
comboTemplate(item) {
let category;
// If we have no id, but text with the uncategorized name, we can use that badge.
if (Ember.isEmpty(item.id)) {
const uncat = Category.findUncategorized();
if (uncat && uncat.get('name') === item.text) {
category = uncat;
}
} else {
category = Category.findById(parseInt(item.id,10));
}
if (!category) return item.text;
let result = categoryBadgeHTML(category, {link: false, allowUncategorized: true, hideParent: true});
const parentCategoryId = category.get('parent_category_id');
if (parentCategoryId) {
result = categoryBadgeHTML(Category.findById(parentCategoryId), {link: false}) + "&nbsp;" + result;
}
result += ` <span class='topic-count'>&times; ${category.get('topic_count')}</span>`;
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,28 +0,0 @@
import NotificationOptionsComponent from "discourse/components/notifications-button";
import computed from "ember-addons/ember-computed-decorators";
import { iconHTML } from "discourse-common/lib/icon-library";
export default NotificationOptionsComponent.extend({
classNames: ["category-notifications-button"],
hidden: Ember.computed.or("category.deleted", "site.isMobileDevice"),
i18nPrefix: "category.notifications",
value: Em.computed.alias("category.notification_level"),
@computed("value")
icon() {
return `${this._super()}${iconHTML("caret-down")}`.htmlSafe();
},
generatedHeadertext: null,
actions: {
onSelectRow(content) {
this._super(content);
this.get("category").setNotification(this.get("value"));
}
}
});

View File

@ -1,162 +0,0 @@
import SelectBoxComponent from "discourse/components/select-box";
import { categoryBadgeHTML } from "discourse/helpers/category-link";
import { observes, on } from "ember-addons/ember-computed-decorators";
import PermissionType from "discourse/models/permission-type";
import Category from "discourse/models/category";
export default SelectBoxComponent.extend({
classNames: ["category-select-box"],
selectBoxRowComponent: "category-select-box/category-select-box-row",
textKey: "name",
filterable: true,
castInteger: true,
clearable: true,
allowUncategorized: null,
init() {
this._super();
if (!Ember.isNone(this.get("categories"))) {
this.set("content", this.get("categories"));
this._scopeCategories();
}
if (Ember.isNone(this.get("value"))) {
if (this.siteSettings.allow_uncategorized_topics && this.get("allowUncategorized") !== false) {
this.set("value", Category.findUncategorized().id);
}
}
},
filterFunction: function(content) {
const _matchFunction = (filter, text) => {
return text.toLowerCase().indexOf(filter) > -1;
};
return (selectBox) => {
const filter = selectBox.get("filter").toLowerCase();
return _.filter(content, (c) => {
const category = Category.findById(c[selectBox.get("idKey")]);
const text = c[selectBox.get("textKey")];
if (category && category.get("parentCategory")) {
const categoryName = category.get("parentCategory.name");
return _matchFunction(filter, text) || _matchFunction(filter, categoryName);
} else {
return _matchFunction(filter, text);
}
});
};
},
@on("init")
@observes("selectedContent")
_setHeaderText: function() {
let headerText;
if (Ember.isNone(this.get("selectedContent"))) {
if (this.siteSettings.allow_uncategorized_topics) {
headerText = Ember.get(Category.findUncategorized(), this.get("textKey"));
} else {
headerText = I18n.t("category.choose").htmlSafe();
}
} else {
headerText = this.get("selectedContent.text");
}
this.set("headerText", headerText);
},
templateForRow: function() {
return (rowComponent) => this.rowContentTemplate(rowComponent.get("content"));
}.property(),
@observes("scopedCategoryId", "categories")
_scopeCategories() {
let scopedCategoryId = this.get("scopedCategoryId");
const categories = this.get("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");
const filteredCategories = categories.filter(c => {
const categoryId = c.get("id");
if (scopedCategoryId && categoryId !== scopedCategoryId && c.get("parent_category_id") !== scopedCategoryId) { return false; }
if (excludeCategoryId === categoryId) { return false; }
if (this.get("allowUncategorized") === false && c.get("isUncategorizedCategory")) { return false; }
if (this.get("allowUncategorized") !== true) {
if (!this.siteSettings.allow_uncategorized_topics && c.get("isUncategorizedCategory")) {
return false;
}
}
return c.get("permission") === PermissionType.FULL;
});
this.set("content", filteredCategories);
},
@on("didRender")
_bindComposerResizing() {
this.appEvents.on("composer:resized", this, this.applyDirection);
},
@on("willDestroyElement")
_unbindComposerResizing() {
this.appEvents.off("composer:resized");
},
@on("init")
@observes("site.sortedCategories")
_updateCategories() {
if (!this.get("categories")) {
const categories = Discourse.SiteSettings.fixed_category_positions_on_create ?
Category.list() :
Category.listByActivity();
this.set("categories", categories);
}
},
rowContentTemplate(item) {
let category;
// If we have no id, but text with the uncategorized name, we can use that badge.
if (Ember.isEmpty(item.id)) {
const uncat = Category.findUncategorized();
if (uncat && uncat.get("name") === item.text) {
category = uncat;
}
} else {
category = Category.findById(parseInt(item.id,10));
}
if (!category) return item.text;
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 computed from 'ember-addons/ember-computed-decorators';
import SelectBoxRowComponent from "discourse/components/select-box/select-box-row";
import Category from "discourse/models/category";
export default SelectBoxRowComponent.extend({
classNameBindings: ["isUncategorized"],
@computed("content")
isUncategorized(content) {
const category = Category.findById(content.id);
return category.get("isUncategorizedCategory");
}
});

View File

@ -1,33 +0,0 @@
import computed from "ember-addons/ember-computed-decorators";
import SelectBoxComponent from "discourse/components/select-box";
export default SelectBoxComponent.extend({
classNames: ["dropdown-select-box"],
wrapper: false,
verticalOffset: 3,
collectionHeight: "auto",
fullWidthOnMobile: true,
selectBoxHeaderComponent: "dropdown-select-box/dropdown-header",
@computed
templateForRow: function() {
return (rowComponent) => {
let template = "";
const content = rowComponent.get("content");
const icon = rowComponent.icon();
if (icon) {
template += `<div class="icons">${icon}</div>`;
}
template += `
<div class="texts">
<span class="title">${Handlebars.escapeExpression(Ember.get(content, this.get("textKey")))}</span>
<span class="desc">${Handlebars.escapeExpression(content.description)}</span>
</div>
`;
return template;
};
}
});

View File

@ -1,7 +0,0 @@
import SelectBoxHeaderComponent from "discourse/components/select-box/select-box-header";
export default SelectBoxHeaderComponent.extend({
layoutName: "components/dropdown-select-box/dropdown-header",
classNames: ["dropdown-header"]
});

View File

@ -1,207 +0,0 @@
import { iconHTML } from 'discourse-common/lib/icon-library';
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
import Combobox from 'discourse-common/components/combo-box';
import { CLOSE_STATUS_TYPE } from 'discourse/controllers/edit-topic-timer';
const LATER_TODAY = 'later_today';
const TOMORROW = 'tomorrow';
const LATER_THIS_WEEK = 'later_this_week';
const THIS_WEEKEND = 'this_weekend';
const NEXT_WEEK = 'next_week';
const TWO_WEEKS = 'two_weeks';
const NEXT_MONTH = 'next_month';
const FOREVER = 'forever';
export const PICK_DATE_AND_TIME = 'pick_date_and_time';
export const SET_BASED_ON_LAST_POST = 'set_based_on_last_post';
export const FORMAT = 'YYYY-MM-DD HH:mm';
export default Combobox.extend({
classNames: ['future-date-input-selector'],
isCustom: Ember.computed.equal("value", PICK_DATE_AND_TIME),
@computed()
content() {
const selections = [];
const now = moment();
const canScheduleToday = (24 - now.hour()) > 6;
const day = now.day();
if (canScheduleToday) {
selections.push({
id: LATER_TODAY,
name: I18n.t('topic.auto_update_input.later_today')
});
}
selections.push({
id: TOMORROW,
name: I18n.t('topic.auto_update_input.tomorrow')
});
if (!canScheduleToday && day < 4) {
selections.push({
id: LATER_THIS_WEEK,
name: I18n.t('topic.auto_update_input.later_this_week')
});
}
if (day < 5 && this.get('includeWeekend')) {
selections.push({
id: THIS_WEEKEND,
name: I18n.t('topic.auto_update_input.this_weekend')
});
}
if (day !== 7) {
selections.push({
id: NEXT_WEEK,
name: I18n.t('topic.auto_update_input.next_week')
});
}
selections.push({
id: TWO_WEEKS,
name: I18n.t('topic.auto_update_input.two_weeks')
});
if (moment().endOf('month').date() !== now.date()) {
selections.push({
id: NEXT_MONTH,
name: I18n.t('topic.auto_update_input.next_month')
});
}
if (this.get('includeForever')) {
selections.push({
id: FOREVER,
name: I18n.t('topic.auto_update_input.forever')
});
}
selections.push({
id: PICK_DATE_AND_TIME,
name: I18n.t('topic.auto_update_input.pick_date_and_time')
});
if (this.get('statusType') === CLOSE_STATUS_TYPE) {
selections.push({
id: SET_BASED_ON_LAST_POST,
name: I18n.t('topic.auto_update_input.set_based_on_last_post')
});
}
return selections;
},
@observes('value')
_updateInput() {
if (this.get('isCustom')) return;
let input = null;
const { time } = this.get('updateAt');
if (time && !Ember.isEmpty(this.get('value'))) {
input = time.format(FORMAT);
}
this.set('input', input);
},
@computed('value')
updateAt(value) {
return this._updateAt(value);
},
comboTemplate(state) {
return this._format(state);
},
selectionTemplate(state) {
return this._format(state);
},
_format(state) {
let { time, icon } = this._updateAt(state.id);
let icons;
if (icon) {
icons = icon.split(',').map(i => iconHTML(i)).join(" ");
}
if (time) {
if (state.id === LATER_TODAY) {
time = time.format('h a');
} else if (state.id === NEXT_MONTH || state.id === TWO_WEEKS) {
time = time.format('MMM D');
} else {
time = time.format('ddd, h a');
}
}
let output = "";
if (!Ember.isEmpty(icons)) {
output += `<span class='future-date-input-selector-icons'>${icons}</span>`;
}
output += `<span>${state.text}</span>`;
if (time && state.id !== FOREVER) {
output += `<span class='future-date-input-selector-datetime'>${time}</span>`;
}
return output;
},
_updateAt(selection) {
let time = moment();
let icon;
const timeOfDay = this.get('statusType') !== CLOSE_STATUS_TYPE ? 8 : 18;
switch(selection) {
case LATER_TODAY:
time = time.hour(18).minute(0);
icon = 'moon-o';
break;
case TOMORROW:
time = time.add(1, 'day').hour(timeOfDay).minute(0);
icon = 'sun-o';
break;
case LATER_THIS_WEEK:
time = time.add(2, 'day').hour(timeOfDay).minute(0);
icon = 'briefcase';
break;
case THIS_WEEKEND:
time = time.day(6).hour(timeOfDay).minute(0);
icon = 'bed';
break;
case NEXT_WEEK:
time = time.add(1, 'week').day(1).hour(timeOfDay).minute(0);
icon = 'briefcase';
break;
case TWO_WEEKS:
time = time.add(2, 'week').hour(timeOfDay).minute(0);
icon = 'briefcase';
break;
case NEXT_MONTH:
time = time.add(1, 'month').startOf('month').hour(timeOfDay).minute(0);
icon = 'briefcase';
break;
case FOREVER:
time = time.add(1000, 'year').hour(timeOfDay).minute(0);
icon = 'gavel';
break;
case PICK_DATE_AND_TIME:
time = null;
icon = 'calendar-plus-o';
break;
case SET_BASED_ON_LAST_POST:
time = null;
icon = 'clock-o';
break;
}
return { time, icon };
},
});

View File

@ -3,7 +3,7 @@ import {
FORMAT,
PICK_DATE_AND_TIME,
SET_BASED_ON_LAST_POST
} from "discourse/components/future-date-input-selector";
} from "select-box-kit/components/future-date-input-selector";
import { PUBLISH_TO_CATEGORY_STATUS_TYPE } from 'discourse/controllers/edit-topic-timer';

View File

@ -8,7 +8,7 @@ export default NotificationOptionsComponent.extend({
i18nPrefix: "groups.notifications",
actions: {
onSelectRow(content) {
onSelect(content) {
this._super(content);
this.get("group").setNotification(this.get("value"), this.get("user.id"));

View File

@ -1,68 +0,0 @@
import DropdownSelectBoxComponent from "discourse/components/dropdown-select-box";
import { iconHTML } from "discourse-common/lib/icon-library";
import computed from "ember-addons/ember-computed-decorators";
import { buttonDetails } from "discourse/lib/notification-levels";
import { allLevels } from "discourse/lib/notification-levels";
export default DropdownSelectBoxComponent.extend({
classNames: ["notifications-button"],
i18nPrefix: "",
i18nPostfix: "",
textKey: "key",
showFullTitle: true,
fullWidthOnMobile: true,
content: allLevels,
value: Em.computed.alias("notificationLevel"),
@computed("selectedDetails")
icon(details) {
return iconHTML(details.icon, {class: details.key}).htmlSafe();
},
@computed("selectedDetails.key", "i18nPrefix")
selectedTitle(key, prefix) {
return I18n.t(`${prefix}.${key}.title`);
},
@computed("value")
selectedDetails(value) {
return buttonDetails(value);
},
@computed("selectedTitle", "showFullTitle")
generatedHeadertext(selectedTitle, showFullTitle) {
return showFullTitle ? selectedTitle : null;
},
@computed
titleForRow: function() {
return (rowComponent) => {
const notificationLevel = rowComponent.get(`content.${this.get("idKey")}`);
const details = buttonDetails(notificationLevel);
return I18n.t(`${this.get("i18nPrefix")}.${details.key}.title`);
};
},
@computed
templateForRow: function() {
return (rowComponent) => {
const content = rowComponent.get("content");
const start = `${this.get("i18nPrefix")}.${content.key}${this.get("i18nPostfix")}`;
const title = I18n.t(`${start}.title`);
const description = I18n.t(`${start}.description`);
return `
<div class="icons">
<span class="selection-indicator"></span>
${iconHTML(content.icon, { class: content.key.dasherize() })}
</div>
<div class="texts">
<span class="title">${Handlebars.escapeExpression(title)}</span>
<span class="desc">${Handlebars.escapeExpression(description)}</span>
</div>
`;
};
}
});

View File

@ -1,73 +0,0 @@
import DropdownSelectBoxComponent from "discourse/components/dropdown-select-box";
import computed from "ember-addons/ember-computed-decorators";
import { observes } from "ember-addons/ember-computed-decorators";
import { iconHTML } from "discourse-common/lib/icon-library";
export default DropdownSelectBoxComponent.extend({
classNames: ["pinned-options"],
@computed("topic.pinned")
value(pinned) {
return pinned ? "pinned" : "unpinned";
},
@observes("topic.pinned")
_pinnedChanged() {
this.set("value", this.get("topic.pinned") ? "pinned" : "unpinned");
},
@computed("topic.pinned_globally")
content(pinnedGlobally) {
const globally = pinnedGlobally ? "_globally" : "";
return [
{
id: "pinned",
text: I18n.t("topic_statuses.pinned" + globally + ".title"),
description: I18n.t('topic_statuses.pinned' + globally + '.help'),
icon: "thumb-tack"
},
{
id: "unpinned",
text: I18n.t("topic_statuses.unpinned.title"),
icon: "thumb-tack",
description: I18n.t('topic_statuses.unpinned.help'),
iconClass: "unpinned"
}
];
},
@computed("topic.pinned", "topic.pinned_globally")
icon(pinned, pinnedGlobally) {
const globally = pinnedGlobally ? "_globally" : "";
const state = pinned ? `pinned${globally}` : "unpinned";
return iconHTML(
"thumb-tack",
{ class: (state === "unpinned" ? "unpinned" : null) }
);
},
@computed("topic.pinned", "topic.pinned_globally")
generatedHeadertext(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();
},
actions: {
onSelectRow(content) {
this._super(content);
const topic = this.get("topic");
if (this.get("value") === "unpinned") {
topic.clearPin();
} else {
topic.rePin();
}
}
}
});

View File

@ -1,485 +0,0 @@
import { on, observes } from "ember-addons/ember-computed-decorators";
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
layoutName: "components/select-box",
classNames: "select-box",
classNameBindings: ["expanded:is-expanded", "hidden:is-hidden"],
expanded: false,
focused: false,
filterFocused: false,
renderBody: false,
wrapper: true,
hidden: false,
tabindex: 0,
scrollableParentSelector: ".modal-body",
caretUpIcon: "caret-up",
caretDownIcon: "caret-down",
headerText: I18n.t("select_box.default_header_text"),
dynamicHeaderText: true,
icon: null,
clearable: false,
value: null,
highlightedValue: null,
selectedContent: null,
noContentLabel: I18n.t("select_box.no_content"),
clearSelectionLabel: null,
idKey: "id",
textKey: "text",
iconKey: "icon",
filterable: false,
filter: "",
filterPlaceholder: I18n.t("select_box.filter_placeholder"),
filterIcon: "search",
selectBoxRowComponent: "select-box/select-box-row",
selectBoxFilterComponent: "select-box/select-box-filter",
selectBoxHeaderComponent: "select-box/select-box-header",
selectBoxCollectionComponent: "select-box/select-box-collection",
collectionHeight: 200,
verticalOffset: 0,
horizontalOffset: 0,
fullWidthOnMobile: false,
castInteger: false,
click(event) {
event.stopPropagation();
},
filterFunction: function(content) {
return (selectBox) => {
const filter = selectBox.get("filter").toLowerCase();
return _.filter(content, (c) => {
return c[selectBox.get("textKey")].toLowerCase().indexOf(filter) > -1;
});
};
},
@computed("textKey")
titleForRow(textKey) {
return (rowComponent) => {
return rowComponent.get(`content.${textKey}`);
};
},
@computed("idKey")
idForRow(idKey) {
return (rowComponent) => {
return rowComponent.get(`content.${idKey}`);
};
},
@computed
shouldHighlightRow: function() {
return (rowComponent) => {
const id = this._castInteger(rowComponent.get(`content.${this.get("idKey")}`));
return id === this.get("highlightedValue");
};
},
@computed("value", "idKey")
shouldSelectRow(value, idKey) {
return (rowComponent) => {
const id = this._castInteger(rowComponent.get(`content.${idKey}`));
return id === value;
};
},
@computed
templateForRow: function() {
return (rowComponent) => {
let template = "";
const icon = rowComponent.icon();
if (icon) {
template += icon;
}
const text = rowComponent.get(`content.${this.get("textKey")}`);
template += `<p class="text">${Handlebars.escapeExpression(text)}</p>`;
return template;
};
},
applyDirection() {
this.$().removeClass("is-above is-below is-left-aligned is-right-aligned");
let options = { left: "auto", bottom: "auto", left: "auto", top: "auto" };
const headerHeight = this.$(".select-box-header").outerHeight(false);
const filterHeight = this.$(".select-box-filter").outerHeight(false);
const bodyHeight = this.$(".select-box-body").outerHeight(false);
const windowWidth = $(window).width();
const windowHeight = $(window).height();
const boundingRect = this.$()[0].getBoundingClientRect();
const offsetTop = boundingRect.top;
if (this.get("fullWidthOnMobile") && this.site.isMobileDevice) {
const margin = 10;
const relativeLeft = this.$().offset().left - $(window).scrollLeft();
options.left = margin - relativeLeft;
options.width = windowWidth - margin * 2;
options.maxWidth = options.minWidth = "unset";
} else {
const offsetLeft = boundingRect.left;
const bodyWidth = this.$(".select-box-body").outerWidth(false);
const hasRightSpace = (windowWidth - (this.get("horizontalOffset") + offsetLeft + filterHeight + bodyWidth) > 0);
if (hasRightSpace) {
this.$().addClass("is-left-aligned");
options.left = this.get("horizontalOffset");
} else {
this.$().addClass("is-right-aligned");
options.right = this.get("horizontalOffset");
}
}
const componentHeight = this.get("verticalOffset") + bodyHeight + headerHeight;
const hasBelowSpace = windowHeight - offsetTop - componentHeight > 0;
if (hasBelowSpace) {
this.$().addClass("is-below");
options.top = headerHeight + this.get("verticalOffset");
} else {
this.$().addClass("is-above");
options.bottom = headerHeight + this.get("verticalOffset");
}
this.$(".select-box-body").css(options);
},
init() {
this._super();
const content = this.getWithDefault("content", []);
this.set("content", content);
if (this.site.isMobileDevice) {
this.set("filterable", false);
}
this.setProperties({
value: this._castInteger(this.get("value")),
componentId: this.elementId
});
},
@on("willDestroyElement")
_removeDocumentListeners: function() {
$(document).off("click.select-box");
$(window).off("resize.select-box");
},
@on("willDestroyElement")
_unbindEvents: function() {
this.$(".select-box-offscreen").off(
"focusin.select-box",
"focusout.select-box",
"keydown.select-box"
);
this.$(".filter-query").off("focusin.select-box", "focusout.select-box");
},
@on("didRender")
_configureSelectBoxDOM: function() {
if (this.get("scrollableParent").length === 1) {
this._removeFixedPosition();
}
const computedWidth = this.$().outerWidth(false);
const computedHeight = this.$().outerHeight(false);
this.$(".select-box-filter").css("height", computedHeight);
if (this.get("expanded")) {
if (this.get("scrollableParent").length === 1) {
this._applyFixedPosition(computedWidth, computedHeight);
}
this.$(".select-box-collection").css("max-height", this.get("collectionHeight"));
Ember.run.schedule("afterRender", () => {
this.applyDirection();
if (this.get("wrapper")) {
this._positionSelectBoxWrapper();
}
});
} else {
if (this.get("wrapper")) {
this.$(".select-box-wrapper").hide();
}
}
},
keyDown(event) {
const keyCode = event.keyCode || event.which;
if (this.get("expanded")) {
if ((keyCode === 13 || keyCode === 9) && Ember.isPresent(this.get("highlightedValue"))) {
event.preventDefault();
this.send("onSelectRow", this.get("highlightedContent"));
}
if (keyCode === 9) {
this.set("expanded", false);
}
if (keyCode === 27) {
this.set("expanded", false);
event.stopPropagation();
}
if (keyCode === 38) {
event.preventDefault();
const self = this;
Ember.run.throttle(self, this._handleUpArrow, 50);
}
if (keyCode === 40) {
event.preventDefault();
const self = this;
Ember.run.throttle(self, this._handleDownArrow, 50);
}
}
},
@on("didRender")
_setupDocumentListeners: function() {
$(document).off("click.select-box");
$(document)
.on("click.select-box", (event) => {
if (this.isDestroying || this.isDestroyed) { return; }
const $element = this.$();
const $target = $(event.target);
if (!$target.closest($element).length) {
this.set("expanded", false);
}
});
$(window).on("resize.select-box", () => this.set("expanded", false) );
},
@on("didInsertElement")
_bindEvents: function() {
this.$(".select-box-offscreen")
.on("focusin.select-box", () => this.set("focused", true) )
.on("focusout.select-box", () => this.set("focused", false) );
this.$(".filter-query")
.on("focusin.select-box", () => this.set("filterFocused", true) )
.on("focusout.select-box", () => this.set("filterFocused", false) );
this.$(".select-box-offscreen").on("keydown.select-box", (event) => {
const keyCode = event.keyCode || event.which;
if (keyCode === 13 || keyCode === 40) {
this.setProperties({ expanded: true, focused: false });
event.stopPropagation();
}
if (keyCode >= 65 && keyCode <= 90) {
this.setProperties({ expanded: true, focused: false });
Ember.run.schedule("afterRender", () => {
this.$(".filter-query").focus().val(String.fromCharCode(keyCode));
});
}
});
},
@observes("expanded")
_expandedChanged: function() {
if (this.get("expanded")) {
this.setProperties({ highlightedValue: null, renderBody: true, focused: false });
if (this.get("filterable")) {
Ember.run.schedule("afterRender", () => this.$(".filter-query").focus());
}
};
},
@computed("value", "content.[]", "idKey")
selectedContent(value, content, idKey) {
if (Ember.isNone(value)) {
return null;
}
return content.find((c) => {
return this._castInteger(Ember.get(c, idKey)) === value;
});
},
@computed("highlightedValue", "content.[]", "idKey")
highlightedContent(highlightedValue, content, idKey) {
if (Ember.isNone(highlightedValue)) {
return null;
}
return content.find((c) => {
return this._castInteger(Ember.get(c, idKey)) === highlightedValue;
});
},
@computed("headerText", "selectedContent", "textKey")
selectedTitle(headerText, selectedContent, textKey) {
if (Ember.isNone(selectedContent)) {
return headerText;
}
return selectedContent[textKey];
},
@computed("headerText", "dynamicHeaderText", "selectedContent", "textKey", "clearSelectionLabel")
generatedHeadertext(headerText, dynamic, selectedContent, textKey, clearSelectionLabel) {
if (dynamic && !Ember.isNone(selectedContent)) {
return selectedContent[textKey];
}
if (dynamic && Ember.isNone(selectedContent) && !Ember.isNone(clearSelectionLabel)) {
return I18n.t(clearSelectionLabel);
}
return headerText;
},
@computed("content.[]", "filter", "idKey")
filteredContent(content, filter, idKey) {
let filteredContent;
if (Ember.isEmpty(filter)) {
filteredContent = content;
} else {
filteredContent = this.filterFunction(content)(this);
if (!Ember.isEmpty(filteredContent)) {
this.set("highlightedValue", filteredContent[0][idKey]);
}
}
return filteredContent;
},
@computed("scrollableParentSelector")
scrollableParent(scrollableParentSelector) {
return this.$().parents(scrollableParentSelector).first();
},
actions: {
onToggle() {
this.toggleProperty("expanded");
},
onFilterChange(filter) {
this.set("filter", filter);
},
onHoverRow(content) {
const id = this._castInteger(Ember.get(content, this.get("idKey")));
this.set("highlightedValue", id);
},
onSelectRow(content) {
this.setProperties({
value: this._castInteger(Ember.get(content, this.get("idKey"))),
expanded: false
});
},
onClearSelection() {
this.setProperties({ value: null, expanded: false });
}
},
_positionSelectBoxWrapper() {
const headerHeight = this.$(".select-box-header").outerHeight(false);
this.$(".select-box-wrapper").css({
width: this.$().width(),
display: "block",
height: headerHeight + this.$(".select-box-body").outerHeight(false)
});
},
_castInteger(id) {
if (this.get("castInteger") === true && Ember.isPresent(id)) {
return parseInt(id, 10);
}
return id;
},
_applyFixedPosition(width, height) {
const $placeholder = $(`<div class='select-box-fixed-placeholder-${this.get("componentId")}' style='vertical-align: middle; height: ${height}px; width: ${width}px; line-height: ${height}px;display:inline-block'></div>`);
this.$()
.before($placeholder)
.css({
width,
position: "fixed",
"margin-top": -this.get("scrollableParent").scrollTop(),
"margin-left": -width
});
this.get("scrollableParent").on("scroll.select-box", () => this.set("expanded", false) );
},
_removeFixedPosition() {
$(`.select-box-fixed-placeholder-${this.get("componentId")}`).remove();
this.$().css({
top: "auto",
left: "auto",
"margin-left": "auto",
"margin-top": "auto",
position: "relative"
});
this.get("scrollableParent").off("scroll.select-box");
},
_handleDownArrow() {
this._handleArrow("down");
},
_handleUpArrow() {
this._handleArrow("up");
},
_handleArrow(direction) {
const content = this.get("filteredContent");
const idKey = this.get("idKey");
const selectedContent = content.findBy(idKey, this.get("highlightedValue"));
const currentIndex = content.indexOf(selectedContent);
if (direction === "down") {
if (currentIndex < 0) {
this.set("highlightedValue", this._castInteger(Ember.get(content[0], idKey)));
} else if(currentIndex + 1 < content.length) {
this.set("highlightedValue", this._castInteger(Ember.get(content[currentIndex + 1], idKey)));
}
} else {
if (currentIndex <= 0) {
this.set("highlightedValue", this._castInteger(Ember.get(content[0], idKey)));
} else if(currentIndex - 1 < content.length) {
this.set("highlightedValue", this._castInteger(Ember.get(content[currentIndex - 1], idKey)));
}
}
Ember.run.schedule("afterRender", () => {
const $highlightedRow = this.$(".select-box-row.is-highlighted");
if ($highlightedRow.length === 0) { return; }
const $collection = this.$(".select-box-collection");
const rowOffset = $highlightedRow.offset();
const bodyOffset = $collection.offset();
$collection.scrollTop(rowOffset.top - bodyOffset.top);
});
}
});

View File

@ -1,9 +0,0 @@
export default Ember.Component.extend({
classNames: "select-box-collection",
actions: {
onClearSelection() {
this.sendAction("onClearSelection");
}
}
});

View File

@ -1,5 +0,0 @@
export default Ember.Component.extend({
classNames: "select-box-filter",
classNameBindings: ["focused:is-focused"]
});

View File

@ -1,24 +0,0 @@
export default Ember.Component.extend({
classNames: "select-box-header",
classNameBindings: ["focused:is-focused"],
didReceiveAttrs() {
this._super();
this._setCaretIcon();
},
click(event) {
this.sendAction("onToggle");
event.stopPropagation();
},
_setCaretIcon() {
if(this.get("expanded")) {
this.set("caretIcon", this.get("caretUpIcon"));
} else {
this.set("caretIcon", this.get("caretDownIcon"));
}
}
});

View File

@ -1,47 +0,0 @@
import computed from 'ember-addons/ember-computed-decorators';
import { iconHTML } from "discourse-common/lib/icon-library";
export default Ember.Component.extend({
layoutName: "components/select-box/select-box-row",
classNames: "select-box-row",
tagName: "li",
attributeBindings: ["title", "id:data-id"],
classNameBindings: ["isHighlighted:is-highlighted", "isSelected:is-selected"],
@computed("titleForRow")
title(titleForRow) { return titleForRow(this); },
@computed("idForRow")
id(idForRow) { return idForRow(this); },
@computed("templateForRow")
template(templateForRow) { return templateForRow(this); },
@computed("shouldHighlightRow", "highlightedValue")
isHighlighted(shouldHighlightRow) { return shouldHighlightRow(this); },
@computed("shouldSelectRow", "value")
isSelected(shouldSelectRow) { return shouldSelectRow(this); },
icon() {
if (this.get("content.icon")) {
const iconName = this.get("content.icon");
const iconClass = this.get("content.iconClass");
return iconHTML(iconName, { class: iconClass });
}
return null;
},
mouseEnter() {
this.sendAction("onHover", this.get("content"));
},
click() {
this.sendAction("onSelect", this.get("content"));
}
});

View File

@ -1,24 +0,0 @@
import NotificationOptionsComponent from "discourse/components/notifications-button";
import computed from "ember-addons/ember-computed-decorators";
import { iconHTML } from "discourse-common/lib/icon-library";
export default NotificationOptionsComponent.extend({
classNames: ["tag-notifications-button"],
i18nPrefix: "tagging.notifications",
@computed("value")
icon() {
return `${this._super()}${iconHTML("caret-down")}`.htmlSafe();
},
generatedHeadertext: null,
actions: {
onSelectRow(content) {
this._super(content);
this.sendAction("action", this.get("value"));
}
}
});

View File

@ -1,78 +0,0 @@
import { observes } from 'ember-addons/ember-computed-decorators';
import SelectBoxComponent from "discourse/components/select-box";
export default SelectBoxComponent.extend({
textKey: "name",
headerText: I18n.t("topic.controls"),
dynamicHeaderText: false,
collectionHeight: 300,
init() {
this._super();
this._createContent();
},
_createContent() {
const content = [];
const topic = this.get('topic');
const details = topic.get('details');
if (details.get('can_invite_to')) {
content.push({ id: 'invite', icon: 'users', name: I18n.t('topic.invite_reply.title') });
}
if (topic.get('bookmarked')) {
content.push({ id: 'bookmark', icon: 'bookmark', name: I18n.t('bookmarked.clear_bookmarks') });
} else {
content.push({ id: 'bookmark', icon: 'bookmark', name: I18n.t('bookmarked.title') });
}
content.push({ id: 'share', icon: 'link', name: I18n.t('topic.share.title') });
if (details.get('can_flag_topic')) {
content.push({ id: 'flag', icon: 'flag', name: I18n.t('topic.flag_topic.title') });
}
this.set('content', content);
},
@observes('value')
_valueChanged() {
this._super();
const value = this.get('value');
const topic = this.get('topic');
// In case it's not a valid topic
if (!topic.get('id')) {
return;
}
const refresh = () => {
this._createContent();
this.set('value', null);
};
switch(value) {
case 'invite':
this.attrs.showInvite();
refresh();
break;
case 'bookmark':
topic.toggleBookmark().then(() => refresh());
break;
case 'share':
this.appEvents.trigger('share:url', topic.get('shareUrl'), $('#topic-footer-buttons'));
refresh();
break;
case 'flag':
this.attrs.showFlagTopic();
refresh();
break;
}
}
});

View File

@ -1,9 +0,0 @@
export default Ember.Component.extend({
layoutName: "components/topic-notifications-button",
classNames: ["topic-notifications-button"],
showFullTitle: true,
appendReason: true,
});

View File

@ -1,52 +0,0 @@
import NotificationOptionsComponent from "discourse/components/notifications-button";
import { on } from "ember-addons/ember-computed-decorators";
import computed from "ember-addons/ember-computed-decorators";
import { topicLevels, buttonDetails } from "discourse/lib/notification-levels";
export default NotificationOptionsComponent.extend({
classNames: ["topic-notifications-options"],
content: topicLevels,
i18nPrefix: "topic.notifications",
value: Ember.computed.alias("topic.details.notification_level"),
@on("didInsertElement")
_bindGlobalLevelChanged() {
this.appEvents.on("topic-notifications-button:changed", (msg) => {
if (msg.type === "notification") {
if (this.get("value") !== msg.id) {
this.get("topic.details").updateNotifications(msg.id);
}
}
});
},
@on("willDestroyElement")
_unbindGlobalLevelChanged() {
this.appEvents.off("topic-notifications-button:changed");
},
@computed("value", "showFullTitle")
generatedHeadertext(value, showFullTitle) {
if (showFullTitle) {
const details = buttonDetails(value);
return I18n.t(`topic.notifications.${details.key}.title`);
} else {
return null;
}
},
actions: {
onSelectRow(content) {
const notificationLevelId = Ember.get(content, this.get("idKey"));
if (notificationLevelId !== this.get("value")) {
this.get("topic.details").updateNotifications(notificationLevelId);
}
this._super(content);
}
}
});

View File

@ -147,7 +147,7 @@ function positioningWorkaround($fixedElement) {
fixedElement.style.top = '0px';
composingTopic = $('#reply-control .category-select-box').length > 0;
composingTopic = $('#reply-control .category-chooser').length > 0;
const height = calcHeight(composingTopic);
fixedElement.style.height = height + "px";

View File

@ -10,7 +10,7 @@
<div class="control-group">
<label class="control-label"></label>
<div class="controls">
{{combo-box valueAttribute="id" value=selectedUserBadgeId nameProperty="badge.name" content=selectableUserBadges}}
{{combo-box value=selectedUserBadgeId nameProperty="badge.name" content=selectableUserBadges}}
</div>
</div>

View File

@ -1,12 +0,0 @@
<button
class="btn {{if text 'btn-icon-text' 'no-text btn-icon'}}"
aria-label="{{selectedTitle}}"
type="button"
title="{{selectedTitle}}">
{{{icon}}}
{{#if text}}
<span class="d-button-label">{{{text}}}</span>
{{/if}}
</button>

View File

@ -19,8 +19,8 @@
{{/each}}
{{else}}
<label>{{i18n 'category.parent'}}</label>
{{category-select-box
clearSelectionLabel="category.none"
{{category-chooser
none="category.none"
value=category.parent_category_id
categories=parentCategories
allowUncategorized=false}}

View File

@ -61,11 +61,13 @@
<section class="field">
<label>
{{i18n "category.sort_order"}}
{{combo-box valueAttribute="value" content=availableSorts value=category.sort_order none="category.sort_options.default"}}
{{#unless isDefaultSortOrder}}
{{combo-box valueAttribute="value" content=sortAscendingOptions value=category.sort_ascending none="category.sort_options.default"}}
{{/unless}}
</label>
<div class="controls">
{{combo-box valueAttribute="value" content=availableSorts value=category.sort_order none="category.sort_options.default"}}
{{#unless isDefaultSortOrder}}
{{combo-box valueAttribute="value" content=sortAscendingOptions value=category.sort_ascending none="category.sort_options.default"}}
{{/unless}}
</div>
</section>
<section class="field num-featured-topics-fields">

View File

@ -13,7 +13,7 @@
{{else if publishToCategory}}
<div class="control-group">
<label>{{i18n 'topic.topic_status_update.publish_to'}}</label>
{{category-select-box
{{category-chooser
value=topicTimer.category_id
excludeCategoryId=excludeCategoryId}}
</div>

View File

@ -1,16 +1,13 @@
<div class="future-date-input">
<div class="control-group">
<label>{{displayLabel}}</label>
{{future-date-input-selector
valueAttribute="id"
minimumResultsForSearch=-1
statusType=statusType
value=selection
input=input
includeWeekend=includeWeekend
includeForever=includeForever
width="50%"
none="topic.auto_update_input.none"}}
</div>

View File

@ -24,7 +24,7 @@
<span class="edit-title">
{{text-field value=buffered.title maxlength=siteSettings.max_topic_title_length}}
</span>
{{category-select-box value=buffered.category_id}}
{{category-chooser value=buffered.category_id}}
{{else}}
<span class='post-title'>
{{i18n "queue.topic"}}

View File

@ -1,55 +0,0 @@
<input
class="select-box-offscreen"
type="text"
aria-haspopup="true"
role="button"
aria-labelledby="select-box-input-{{componentId}}"
tabindex={{tabindex}}
/>
{{component selectBoxHeaderComponent
text=generatedHeadertext
selectedTitle=selectedTitle
focused=focused
caretUpIcon=caretUpIcon
caretDownIcon=caretDownIcon
onToggle=(action "onToggle")
icon=icon
expanded=expanded
value=value
}}
<div class="select-box-body">
{{#if renderBody}}
{{#if filterable}}
{{component selectBoxFilterComponent
onFilterChange=(action "onFilterChange")
icon=filterIcon
focused=filterFocused
placeholder=filterPlaceholder
tabindex=tabindex
}}
{{/if}}
{{component selectBoxCollectionComponent
clearSelectionLabel=clearSelectionLabel
filteredContent=filteredContent
selectBoxRowComponent=selectBoxRowComponent
templateForRow=templateForRow
shouldHighlightRow=shouldHighlightRow
shouldSelectRow=shouldSelectRow
titleForRow=titleForRow
idForRow=idForRow
onSelectRow=(action "onSelectRow")
onHoverRow=(action "onHoverRow")
onClearSelection=(action "onClearSelection")
noContentLabel=noContentLabel
highlightedValue=highlightedValue
value=value
}}
{{/if}}
</div>
{{#if wrapper}}
<div class="select-box-wrapper"></div>
{{/if}}

View File

@ -1,28 +0,0 @@
<ul class="collection">
{{#if clearSelectionLabel}}
<li {{action "onClearSelection" on="click"}} class="select-box-row clear-selection">
{{i18n clearSelectionLabel}}
</li>
{{/if}}
{{#each filteredContent as |content|}}
{{component selectBoxRowComponent
content=content
templateForRow=templateForRow
idForRow=idForRow
titleForRow=titleForRow
shouldHighlightRow=shouldHighlightRow
shouldSelectRow=shouldSelectRow
highlightedValue=highlightedValue
onSelect=onSelectRow
onHover=onHoverRow
value=value
}}
{{else}}
{{#if noContentLabel}}
<li class="select-box-row no-content">
{{noContentLabel}}
</li>
{{/if}}
{{/each}}
</ul>

View File

@ -1,9 +0,0 @@
{{#if icon}}
{{d-icon icon class="icon"}}
{{/if}}
<span class="current-selection" title={{selectedTitle}}>
{{text}}
</span>
{{d-icon caretIcon class="caret-icon"}}

View File

@ -72,7 +72,7 @@
{{#if model.showCategoryChooser}}
<div class="category-input">
{{category-select-box value=model.categoryId scopedCategoryId=scopedCategoryId tabindex="3"}}
{{category-chooser value=model.categoryId scopedCategoryId=scopedCategoryId tabindex="3"}}
{{popup-input-tip validation=categoryValidation}}
</div>
{{#if model.archetype.hasOptions}}

View File

@ -57,7 +57,7 @@
<span class='desc'>
{{i18n "search.sort_by"}}
</span>
{{combo-box value=sortOrder content=sortOrders castInteger="true"}}
{{combo-box value=sortOrder content=sortOrders castInteger=true}}
</div>
</div>
{{/if}}

View File

@ -1,6 +1,6 @@
<p>{{i18n "topics.bulk.choose_new_category"}}</p>
<p>{{category-select-box value=newCategoryId}}</p>
<p>{{category-chooser value=newCategoryId}}</p>
{{#conditional-loading-spinner condition=loading}}
{{d-button action="changeCategory" label="topics.bulk.change_category"}}

View File

@ -6,7 +6,7 @@
{{text-field value=topicName placeholderKey="composer.title_placeholder" elementId='split-topic-name'}}
<label>{{i18n 'categories.category'}}</label>
{{category-select-box value=categoryId class="small"}}
{{category-chooser value=categoryId class="small"}}
</form>
{{/d-modal-body}}

View File

@ -10,7 +10,7 @@
<div class="control-group">
<label class="control-label"></label>
<div class="controls">
{{combo-box valueAttribute="id" value=selectedUserBadgeId nameProperty="badge.name" content=selectableUserBadges}}
{{combo-box value=selectedUserBadgeId nameProperty="badge.name" content=selectableUserBadges}}
</div>
</div>

View File

@ -2,7 +2,7 @@
<label class="control-label">{{i18n 'user.email_settings'}}</label>
<div class='controls controls-dropdown'>
<label>{{i18n 'user.email_previous_replies.title'}}</label>
{{select-box idKey="value" textKey="name" content=previousRepliesOptions value=model.user_option.email_previous_replies}}
{{combo-box valueAttribute="value" content=previousRepliesOptions value=model.user_option.email_previous_replies}}
</div>
{{preference-checkbox labelKey="user.email_in_reply_to" checked=model.user_option.email_in_reply_to}}
{{preference-checkbox labelKey="user.email_private_messages" checked=model.user_option.email_private_messages}}
@ -25,7 +25,7 @@
{{preference-checkbox labelKey="user.email_digests.title" disabled=model.user_option.mailing_list_mode checked=model.user_option.email_digests}}
{{#if model.user_option.email_digests}}
<div class='controls controls-dropdown'>
{{select-box idKey="value" filterable=true textKey="name" content=digestFrequencies value=model.user_option.digest_after_minutes}}
{{combo-box valueAttribute="value" filterable=true content=digestFrequencies value=model.user_option.digest_after_minutes}}
</div>
{{preference-checkbox labelKey="user.include_tl0_in_digests" disabled=model.user_option.mailing_list_mode checked=model.user_option.include_tl0_in_digests}}
{{/if}}

View File

@ -2,7 +2,7 @@
<div class="control-group theme">
<label class="control-label">{{i18n 'user.theme'}}</label>
<div class="controls">
{{select-box textKey="name" content=userSelectableThemes value=themeKey}}
{{combo-box content=userSelectableThemes value=themeKey}}
</div>
<div class="controls">
{{preference-checkbox labelKey="user.theme_default_on_all_devices" checked=makeThemeDefault}}
@ -24,7 +24,7 @@
<div class="control-group pref-locale">
<label class="control-label">{{i18n 'user.locale.title'}}</label>
<div class="controls">
{{combo-box valueAttribute="value" content=availableLocales value=model.locale none="user.locale.default"}}
{{combo-box filterable=true valueAttribute="value" content=availableLocales value=model.locale none="user.locale.default"}}
</div>
<div class='instructions'>
{{i18n 'user.locale.instructions'}}

View File

@ -19,7 +19,7 @@
{{text-field id="edit-title" value=buffered.title maxlength=siteSettings.max_topic_title_length autofocus="true"}}
{{#if showCategoryChooser}}
<br>
{{category-select-box class="small" value=buffered.category_id}}
{{category-chooser class="small" value=buffered.category_id}}
{{/if}}
{{#if canEditTags}}

View File

@ -0,0 +1,50 @@
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";
export default DropdownSelectBoxComponent.extend({
classNames: "categories-admin-dropdown",
actionNames: { create: "createCategory", reorder: "reorderCategories" },
@on("didReceiveAttrs")
_setComponentOptions() {
this.set("headerComponentOptions", Ember.Object.create({
shouldDisplaySelectedName: false,
icon: `${iconHTML('bars')}${iconHTML('caret-down')}`.htmlSafe(),
}));
},
@computed
content() {
const items = [
{
id: "create",
name: I18n.t("category.create"),
description: I18n.t("category.create_long"),
icon: "plus"
}
];
const includeReorder = this.get("siteSettings.fixed_category_positions");
if (includeReorder) {
items.push({
id: "reorder",
name: I18n.t("categories.reorder.title"),
description: I18n.t("categories.reorder.title_long"),
icon: "random"
});
}
return items;
},
actions: {
onSelect(value) {
value = this.defaultOnSelect(value);
this.sendAction(`actionNames.${value}`);
this.set("value", null);
}
}
});

View File

@ -0,0 +1,131 @@
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,
filterFunction(computedContent) {
const _matchFunction = (filter, text) => {
return text.toLowerCase().indexOf(filter) > -1;
};
return (selectBox) => {
const filter = selectBox.get("filter").toLowerCase();
return _.filter(computedContent, c => {
const category = Category.findById(get(c, "value"));
const text = get(c, "name");
if (category && category.get("parentCategory")) {
const categoryName = category.get("parentCategory.name");
return _matchFunction(filter, text) || _matchFunction(filter, categoryName);
} else {
return _matchFunction(filter, text);
}
});
};
},
@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

@ -0,0 +1,17 @@
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",
actions: {
onSelect(value) {
value = this.defaultOnSelect(value);
this.get("category").setNotification(value);
}
}
});

View File

@ -0,0 +1,13 @@
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

@ -0,0 +1,21 @@
import SelectBoxKitComponent from "select-box-kit/components/select-box-kit";
import { on } from "ember-addons/ember-computed-decorators";
export default SelectBoxKitComponent.extend({
classNames: "combobox combo-box",
autoFilterable: true,
headerComponent: "combo-box/combo-box-header",
caretUpIcon: "caret-up",
caretDownIcon: "caret-down",
clearable: false,
@on("didReceiveAttrs")
_setComponentOptions() {
this.set("headerComponentOptions", Ember.Object.create({
caretUpIcon: this.get("caretUpIcon"),
caretDownIcon: this.get("caretDownIcon"),
clearable: this.get("clearable"),
}));
}
});

View File

@ -0,0 +1,39 @@
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

@ -0,0 +1,24 @@
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() {
this.close();
},
actions: {
onSelect(value) {
value = this.defaultOnSelect(value);
this.set("value", value);
this.blur();
}
}
});

View File

@ -0,0 +1,6 @@
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

@ -0,0 +1,9 @@
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

@ -0,0 +1,118 @@
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
import ComboBoxComponent from "select-box-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";
export const LATER_TODAY = "later_today";
export const TOMORROW = "tomorrow";
export const LATER_THIS_WEEK = "later_this_week";
export const THIS_WEEKEND = "this_weekend";
export const NEXT_WEEK = "next_week";
export const TWO_WEEKS = "two_weeks";
export const NEXT_MONTH = "next_month";
export const FOREVER = "forever";
export const PICK_DATE_AND_TIME = "pick_date_and_time";
export const SET_BASED_ON_LAST_POST = "set_based_on_last_post";
export const FORMAT = "YYYY-MM-DD HH:mm";
export default ComboBoxComponent.extend(DatetimeMixin, {
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() {
const selections = [];
const now = moment();
const canScheduleToday = (24 - now.hour()) > 6;
const day = now.day();
if (canScheduleToday) {
selections.push({
id: LATER_TODAY,
name: I18n.t("topic.auto_update_input.later_today")
});
}
selections.push({
id: TOMORROW,
name: I18n.t("topic.auto_update_input.tomorrow")
});
if (!canScheduleToday && day < 4) {
selections.push({
id: LATER_THIS_WEEK,
name: I18n.t("topic.auto_update_input.later_this_week")
});
}
if (day < 5 && this.get("includeWeekend")) {
selections.push({
id: THIS_WEEKEND,
name: I18n.t("topic.auto_update_input.this_weekend")
});
}
if (day !== 7) {
selections.push({
id: NEXT_WEEK,
name: I18n.t("topic.auto_update_input.next_week")
});
}
selections.push({
id: TWO_WEEKS,
name: I18n.t("topic.auto_update_input.two_weeks")
});
if (moment().endOf("month").date() !== now.date()) {
selections.push({
id: NEXT_MONTH,
name: I18n.t("topic.auto_update_input.next_month")
});
}
if (this.get("includeForever")) {
selections.push({
id: FOREVER,
name: I18n.t("topic.auto_update_input.forever")
});
}
selections.push({
id: PICK_DATE_AND_TIME,
name: I18n.t("topic.auto_update_input.pick_date_and_time")
});
if (this.get("statusType") === CLOSE_STATUS_TYPE) {
selections.push({
id: SET_BASED_ON_LAST_POST,
name: I18n.t("topic.auto_update_input.set_based_on_last_post")
});
}
return selections;
},
@observes("value")
_updateInput() {
if (this.get("isCustom")) return;
let input = null;
const { time } = this.get("updateAt");
if (time && !Ember.isEmpty(this.get("value"))) {
input = time.format(FORMAT);
}
this.set("input", input);
},
@computed("value")
updateAt(value) {
return this._updateAt(value);
}
});

View File

@ -0,0 +1,14 @@
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

@ -0,0 +1,14 @@
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

@ -0,0 +1,101 @@
import { iconHTML } from 'discourse-common/lib/icon-library';
import { CLOSE_STATUS_TYPE } from 'discourse/controllers/edit-topic-timer';
import {
LATER_TODAY,
TOMORROW,
LATER_THIS_WEEK,
THIS_WEEKEND,
NEXT_WEEK,
TWO_WEEKS,
NEXT_MONTH,
FOREVER,
PICK_DATE_AND_TIME,
SET_BASED_ON_LAST_POST,
} from "select-box-kit/components/future-date-input-selector";
export default Ember.Mixin.create({
_computeIconForValue(value) {
let {icon} = this._updateAt(value);
if (icon) {
return icon.split(",").map(i => iconHTML(i)).join(" ");
}
return null;
},
_computeDatetimeForValue(value) {
if (Ember.isNone(value)) {
return null;
}
let {time} = this._updateAt(value);
if (time) {
if (value === LATER_TODAY) {
time = time.format("h a");
} else if (value === NEXT_MONTH || value === TWO_WEEKS) {
time = time.format("MMM D");
} else {
time = time.format("ddd, h a");
}
}
if (time && value !== FOREVER) {
return time;
}
return null;
},
_updateAt(selection) {
let time = moment();
let icon;
const timeOfDay = this.get('statusType') !== CLOSE_STATUS_TYPE ? 8 : 18;
switch(selection) {
case LATER_TODAY:
time = time.hour(18).minute(0);
icon = 'moon-o';
break;
case TOMORROW:
time = time.add(1, 'day').hour(timeOfDay).minute(0);
icon = 'sun-o';
break;
case LATER_THIS_WEEK:
time = time.add(2, 'day').hour(timeOfDay).minute(0);
icon = 'briefcase';
break;
case THIS_WEEKEND:
time = time.day(6).hour(timeOfDay).minute(0);
icon = 'bed';
break;
case NEXT_WEEK:
time = time.add(1, 'week').day(1).hour(timeOfDay).minute(0);
icon = 'briefcase';
break;
case TWO_WEEKS:
time = time.add(2, 'week').hour(timeOfDay).minute(0);
icon = 'briefcase';
break;
case NEXT_MONTH:
time = time.add(1, 'month').startOf('month').hour(timeOfDay).minute(0);
icon = 'briefcase';
break;
case FOREVER:
time = time.add(1000, 'year').hour(timeOfDay).minute(0);
icon = 'gavel';
break;
case PICK_DATE_AND_TIME:
time = null;
icon = 'calendar-plus-o';
break;
case SET_BASED_ON_LAST_POST:
time = null;
icon = 'clock-o';
break;
}
return { time, icon };
},
});

View File

@ -0,0 +1,99 @@
// Experimental
import SelectBoxKitComponent from "select-box-kit/components/select-box-kit";
import computed from "ember-addons/ember-computed-decorators";
const { get, isNone } = Ember;
export default SelectBoxKitComponent.extend({
classNames: "multi-combobox",
headerComponent: "multi-combo-box/multi-combo-box-header",
filterComponent: null,
headerText: "select_box.default_header_text",
value: [],
allowAny: true,
@computed("filter")
templateForCreateRow() {
return (rowComponent) => {
return `Create: ${rowComponent.get("content.name")}`;
};
},
keyDown(event) {
const keyCode = event.keyCode || event.which;
const $filterInput = this.$filterInput();
if (keyCode === 8) {
let $lastSelectedValue = $(this.$(".choices .selected-name").last());
if ($lastSelectedValue.is(":focus") || $(document.activeElement).is($lastSelectedValue)) {
this.send("onDeselect", $lastSelectedValue.data("value"));
$filterInput.focus();
return;
}
if ($filterInput.val() === "") {
if ($filterInput.is(":focus")) {
if ($lastSelectedValue.length > 0) {
$lastSelectedValue.focus();
}
} else {
if ($lastSelectedValue.length > 0) {
$lastSelectedValue.focus();
} else {
$filterInput.focus();
}
}
}
} else {
$filterInput.focus();
this._super(event);
}
},
@computed("none")
computedNone(none) {
if (!isNone(none)) {
this.set("none", { name: I18n.t(none), value: "" });
}
},
@computed("value.[]")
computedValue(value) {
return value.map(v => this._castInteger(v));
},
@computed("computedValue.[]", "computedContent.[]")
selectedContent(computedValue, computedContent) {
const contents = [];
computedValue.forEach(cv => {
contents.push(computedContent.findBy("value", cv));
});
return contents;
},
filterFunction(content) {
return (selectBox, computedValue) => {
const filter = selectBox.get("filter").toLowerCase();
return _.filter(content, c => {
return !computedValue.includes(get(c, "value")) &&
get(c, "name").toLowerCase().indexOf(filter) > -1;
});
};
},
actions: {
onClearSelection() {
this.send("onSelect", []);
},
onSelect(value) {
this.setProperties({ filter: "", highlightedValue: null });
this.get("value").pushObject(value);
},
onDeselect(value) {
this.defaultOnDeselect(value);
this.get("value").removeObject(value);
}
}
});

View File

@ -0,0 +1,40 @@
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";
export default SelectBoxKitHeaderComponent.extend({
attributeBindings: ["names:data-name"],
classNames: "multi-combobox-header",
layoutName: "select-box-kit/templates/components/multi-combo-box/multi-combo-box-header",
@computed("filter", "selectedContent.[]", "isFocused", "selectBoxIsExpanded")
shouldDisplayFilterPlaceholder(filter, selectedContent, isFocused) {
if (Ember.isEmpty(selectedContent)) {
if (filter.length > 0) { return false; }
if (isFocused === true) { return false; }
return true;
}
return false;
},
@on("didRender")
_positionFilter() {
this.$(".filter").width(0);
const leftHeaderOffset = this.$().offset().left;
const leftFilterOffset = this.$(".filter").offset().left;
const offset = leftFilterOffset - leftHeaderOffset;
const width = this.$().outerWidth(false);
const availableSpace = width - offset;
// TODO: avoid magic number 8
// TODO: make sure the filter doesnt end up being very small
this.$(".filter").width(availableSpace - 8);
},
@computed("selectedContent.[]")
names(selectedContent) {
return selectedContent.map(sc => sc.name).join(",");
}
});

View File

@ -0,0 +1,40 @@
import DropdownSelectBoxComponent from "select-box-kit/components/dropdown-select-box";
import { default as computed, on } from "ember-addons/ember-computed-decorators";
import { buttonDetails } from "discourse/lib/notification-levels";
import { allLevels } from "discourse/lib/notification-levels";
export default DropdownSelectBoxComponent.extend({
classNames: "notifications-button",
nameProperty: "key",
fullWidthOnMobile: true,
content: allLevels,
collectionHeight: "auto",
value: Ember.computed.alias("notificationLevel"),
castInteger: true,
autofilterable: false,
filterable: false,
rowComponent: "notifications-button/notifications-button-row",
headerComponent: "notifications-button/notifications-button-header",
i18nPrefix: "",
i18nPostfix: "",
showFullTitle: true,
@on("didReceiveAttrs", "didUpdateAttrs")
_setComponentOptions() {
this.set("headerComponentOptions", Ember.Object.create({
i18nPrefix: this.get("i18nPrefix"),
showFullTitle: this.get("showFullTitle"),
}));
this.set("rowComponentOptions", Ember.Object.create({
i18nPrefix: this.get("i18nPrefix"),
i18nPostfix: this.get("i18nPostfix")
}));
},
@computed("computedValue")
selectedDetails(computedValue) {
return buttonDetails(computedValue);
}
});

View File

@ -0,0 +1,24 @@
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

@ -0,0 +1,37 @@
import DropdownSelectBoxRoxComponent from "select-box-kit/components/dropdown-select-box/dropdown-select-box-row";
import { buttonDetails } from "discourse/lib/notification-levels";
import computed from "ember-addons/ember-computed-decorators";
import { iconHTML } from 'discourse-common/lib/icon-library';
export default DropdownSelectBoxRoxComponent.extend({
classNames: "notifications-button-row",
i18nPrefix: Ember.computed.alias("options.i18nPrefix"),
i18nPostfix: Ember.computed.alias("options.i18nPostfix"),
@computed("content.value", "i18nPrefix")
title(value, prefix) {
const key = buttonDetails(value).key;
return I18n.t(`${prefix}.${key}.title`);
},
@computed("content.name", "content.originalContent.icon")
icon(contentName, icon) {
return iconHTML(icon, { class: contentName.dasherize() });
},
@computed("_start")
description(_start) {
return Handlebars.escapeExpression(I18n.t(`${_start}.description`));
},
@computed("_start")
name(_start) {
return Handlebars.escapeExpression(I18n.t(`${_start}.title`));
},
@computed("i18nPrefix", "i18nPostfix", "content.name")
_start(prefix, postfix, contentName) {
return `${prefix}.${contentName}${postfix}`;
},
});

View File

@ -2,10 +2,9 @@ import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
descriptionKey: "help",
classNames: ["pinned-button"],
classNameBindings: ["hidden:is-hidden"],
classNames: "pinned-button",
classNameBindings: ["isHidden"],
layoutName: "select-box-kit/templates/components/pinned-button",
@computed("topic.pinned_globally", "topic.pinned")
reasonText(pinnedGlobally, pinned) {
@ -16,7 +15,7 @@ export default Ember.Component.extend({
},
@computed("topic.pinned", "topic.deleted", "topic.unpinned")
hidden(pinned, deleted, unpinned) {
isHidden(pinned, deleted, unpinned) {
return deleted || (!pinned && !unpinned);
}
});

View File

@ -0,0 +1,64 @@
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"
}
];
},
actions: {
onSelect(value) {
value = this.defaultOnSelect(value);
const topic = this.get("topic");
if (value === "unpinned") {
topic.clearPin();
} else {
topic.rePin();
}
}
}
});

View File

@ -0,0 +1,30 @@
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

@ -0,0 +1,440 @@
const { get, isNone, isEmpty, isPresent } = Ember;
import { on, observes } 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,
renderBody: 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",
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",
collectionComponent: "select-box-kit/select-box-kit-collection",
collectionHeight: 200,
verticalOffset: 0,
horizontalOffset: 0,
fullWidthOnMobile: false,
castInteger: false,
allowAny: false,
init() {
this._super();
if ($(window).outerWidth(false) <= 420) {
this.setProperties({ filterable: false, autoFilterable: false });
}
this._previousScrollParentOverflow = "auto";
},
close() {
this.setProperties({ isExpanded: false, isFocused: false });
},
focus() {
Ember.run.schedule("afterRender", () => this.$offscreenInput().select() );
},
blur() {
Ember.run.schedule("afterRender", () => this.$offscreenInput().blur() );
},
clickOutside(event) {
if ($(event.target).parents(".select-box-kit").length === 1) {
this.close();
return;
}
if (this.get("isExpanded") === true) {
this.set("isExpanded", false);
this.focus();
} else {
this.close();
}
},
createFunction(input) {
return (selectedBox) => {
const formatedContent = selectedBox.formatContent(input);
formatedContent.meta.generated = true;
return formatedContent;
};
},
filterFunction(content) {
return selectBox => {
const filter = selectBox.get("filter").toLowerCase();
return _.filter(content, c => {
return get(c, "name").toLowerCase().indexOf(filter) > -1;
});
};
},
nameForContent(content) {
if (isNone(content)) {
return null;
}
if (typeof content === "object") {
return get(content, this.get("nameProperty"));
}
return content;
},
valueForContent(content) {
switch (typeof content) {
case "string":
return this._castInteger(content);
default:
return this._castInteger(get(content, this.get("valueAttribute")));
}
},
formatContent(content) {
return {
value: this.valueForContent(content),
name: this.nameForContent(content),
originalContent: content,
meta: { generated: false }
};
},
formatContents(contents) {
return contents.map(content => this.formatContent(content));
},
@computed("filter", "filterable", "autoFilterable")
computedFilterable(filter, filterable, autoFilterable) {
if (filterable === true) {
return true;
}
if (filter.length > 0 && autoFilterable === true) {
return true;
}
return false;
},
@computed("computedFilterable", "filter", "allowAny")
shouldDisplayCreateRow(computedFilterable, filter, allow) {
return computedFilterable === true && filter.length > 0 && allow === true;
},
@computed("filter", "allowAny")
createRowContent(filter, allow) {
if (allow === true) {
return Ember.Object.create({ value: filter, name: filter });
}
},
@computed("content.[]")
computedContent(content) {
return this.formatContents(content || []);
},
@computed("value", "none", "computedContent.firstObject.value")
computedValue(value, none, firstContentValue) {
if (isNone(value) && isNone(none)) {
return this._castInteger(firstContentValue);
}
return this._castInteger(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: "" });
default:
return this.formatContent(none);
}
},
@computed("computedValue", "computedContent.[]")
selectedContent(computedValue, computedContent) {
if (isNone(computedValue)) {
return [];
}
return [ computedContent.findBy("value", this._castInteger(computedValue)) ];
},
@on("didRender")
_configureSelectBoxDOM() {
if (this.get("isExpanded") === true) {
Ember.run.schedule("afterRender", () => {
this.$collection().css("max-height", this.get("collectionHeight"));
this._applyDirection();
this._positionWrapper();
});
}
},
@on("willDestroyElement")
_cleanHandlers() {
$(window).off(`resize.${this.elementId}`);
this._removeFixedPosition();
},
@on("didInsertElement")
_setupResizeListener() {
$(window).on(`resize.${this.elementId}`, () => this.set("isExpanded", false) );
},
@observes("filter", "filteredContent.[]", "shouldDisplayCreateRow")
_setHighlightedValue() {
const filteredContent = this.get("filteredContent");
const display = this.get("shouldDisplayCreateRow");
const none = this.get("computedNone");
if (isNone(this.get("highlightedValue")) && !isEmpty(filteredContent)) {
this.set("highlightedValue", get(filteredContent, "firstObject.value"));
return;
}
if (display === true && isEmpty(filteredContent)) {
this.set("highlightedValue", this.get("filter"));
}
else if (!isEmpty(filteredContent)) {
this.set("highlightedValue", get(filteredContent, "firstObject.value"));
}
else if (isEmpty(filteredContent) && isPresent(none) && display === false) {
this.set("highlightedValue", get(none, "value"));
}
},
@observes("isExpanded")
_isExpandedChanged() {
if (this.get("isExpanded") === true) {
this._applyFixedPosition();
this.setProperties({
highlightedValue: this.get("computedValue"),
renderBody: true,
isFocused: true
});
} else {
this._removeFixedPosition();
}
},
@computed("filter", "computedFilterable", "computedContent.[]", "computedValue.[]")
filteredContent(filter, computedFilterable, computedContent, computedValue) {
if (computedFilterable === false) {
return computedContent;
}
return this.filterFunction(computedContent)(this, computedValue);
},
@computed("scrollableParentSelector")
scrollableParent(scrollableParentSelector) {
return this.$().parents(scrollableParentSelector).first();
},
actions: {
onToggle() {
this.toggleProperty("isExpanded");
if (this.get("isExpanded") === true) { this.focus(); }
},
onCreateContent(input) {
const content = this.createFunction(input)(this);
this.get("computedContent").pushObject(content);
this.send("onSelect", content.value);
},
onFilterChange(filter) {
this.set("filter", filter);
},
onHighlight(value) {
this.set("highlightedValue", value);
},
onClearSelection() {
this.send("onSelect", null);
},
onSelect(value) {
value = this.defaultOnSelect(value);
this.set("value", value);
},
onDeselect() {
this.defaultOnDeselect();
this.set("value", null);
}
},
defaultOnSelect(value) {
if (value === "") { value = null; }
this.setProperties({
highlightedValue: null,
isExpanded: false,
filter: ""
});
this.focus();
return value;
},
defaultOnDeselect(value) {
const content = this.get("computedContent").findBy("value", value);
if (!isNone(content) && get(content, "meta.generated") === true) {
this.get("computedContent").removeObject(content);
}
},
_applyDirection() {
let options = { left: "auto", bottom: "auto", top: "auto" };
const headerHeight = this.$header().outerHeight(false);
const filterHeight = this.$(".select-box-kit-filter").outerHeight(false);
const bodyHeight = this.$body().outerHeight(false);
const windowWidth = $(window).width();
const windowHeight = $(window).height();
const boundingRect = this.$()[0].getBoundingClientRect();
const offsetTop = boundingRect.top;
if (this.get("fullWidthOnMobile") && windowWidth <= 420) {
const margin = 10;
const relativeLeft = this.$().offset().left - $(window).scrollLeft();
options.left = margin - relativeLeft;
options.width = windowWidth - margin * 2;
options.maxWidth = options.minWidth = "unset";
} else {
const offsetLeft = boundingRect.left;
const bodyWidth = this.$body().outerWidth(false);
const hasRightSpace = (windowWidth - (this.get("horizontalOffset") + offsetLeft + filterHeight + bodyWidth) > 0);
if (hasRightSpace) {
this.setProperties({ isLeftAligned: true, isRightAligned: false });
options.left = this.get("horizontalOffset");
} else {
this.setProperties({ isLeftAligned: false, isRightAligned: true });
options.right = this.get("horizontalOffset");
}
}
const componentHeight = this.get("verticalOffset") + bodyHeight + headerHeight;
const hasBelowSpace = windowHeight - offsetTop - componentHeight > 0;
if (hasBelowSpace) {
this.setProperties({ isBelow: true, isAbove: false });
options.top = headerHeight + this.get("verticalOffset");
} else {
this.setProperties({ isBelow: false, isAbove: true });
options.bottom = headerHeight + this.get("verticalOffset");
}
this.$body().css(options);
},
_applyFixedPosition() {
const width = this.$().outerWidth(false);
const height = this.$header().outerHeight(false);
if (this.get("scrollableParent").length === 0) {
return;
}
const $placeholder = $(`<div class='select-box-kit-fixed-placeholder-${this.elementId}'></div>`);
this._previousScrollParentOverflow = this.get("scrollableParent").css("overflow");
this.get("scrollableParent").css({ overflow: "hidden" });
this.$()
.before($placeholder.css({
display: "inline-block",
width,
height,
"vertical-align": "middle"
}))
.css({
width,
direction: $("html").css("direction"),
position: "fixed",
"margin-top": -this.get("scrollableParent").scrollTop(),
"margin-left": -width
});
},
_removeFixedPosition() {
if (this.get("scrollableParent").length === 0) {
return;
}
$(`.select-box-kit-fixed-placeholder-${this.elementId}`).remove();
this.$().css({
top: "auto",
left: "auto",
"margin-left": "auto",
"margin-top": "auto",
position: "relative"
});
this.get("scrollableParent").css({
overflow: this._previousScrollParentOverflow
});
},
_positionWrapper() {
const headerHeight = this.$header().outerHeight(false);
this.$(".select-box-kit-wrapper").css({
width: this.$().width(),
height: headerHeight + this.$body().outerHeight(false)
});
}
});

View File

@ -0,0 +1,5 @@
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

@ -0,0 +1,10 @@
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

@ -0,0 +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",
classNameBindings: ["isFocused", "isHidden"],
isHidden: Ember.computed.not("filterable"),
});

View File

@ -0,0 +1,28 @@
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

@ -0,0 +1,10 @@
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

@ -0,0 +1,59 @@
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",
attributeBindings: [
"title",
"content.value:data-value",
"content.name:data-name"
],
classNameBindings: ["isHighlighted", "isSelected"],
title: Ember.computed.alias("content.name"),
@computed("templateForRow")
template(templateForRow) { return templateForRow(this); },
@on("didReceiveAttrs", "didUpdateAttrs")
_setSelectionState() {
const contentValue = this.get("content.value");
this.set("isSelected", this.get("value") === contentValue);
this.set("isHighlighted", this._castInteger(this.get("highlightedValue")) === this._castInteger(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.sendAction("onSelect", this.get("content.value"));
},
_sendOnHighlightAction() {
this.sendAction("onHighlight", this.get("content.value"));
}
});

View File

@ -0,0 +1,15 @@
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",
actions: {
onSelect(value) {
value = this.defaultOnSelect(value);
this.sendAction("action", value);
}
}
});

View File

@ -0,0 +1,13 @@
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

@ -0,0 +1,75 @@
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";
export default ComboBoxComponent.extend({
headerText: "topic.controls",
classNames: "topic-footer-mobile-dropdown",
filterable: false,
autoFilterable: false,
@on("didReceiveAttrs")
_setComponentOptions() {
this.set("headerComponentOptions", Ember.Object.create({
selectedName: I18n.t(this.get("headerText"))
}));
},
@computed("topic", "topic.details", "value")
content(topic, details) {
const content = [];
if (details.get("can_invite_to")) {
content.push({ id: "invite", icon: "users", name: I18n.t("topic.invite_reply.title") });
}
if (topic.get("bookmarked")) {
content.push({ id: "bookmark", icon: "bookmark", name: I18n.t("bookmarked.clear_bookmarks") });
} else {
content.push({ id: "bookmark", icon: "bookmark", name: I18n.t("bookmarked.title") });
}
content.push({ id: "share", icon: "link", name: I18n.t("topic.share.title") });
if (details.get("can_flag_topic")) {
content.push({ id: "flag", icon: "flag", name: I18n.t("topic.flag_topic.title") });
}
return content;
},
actions: {
onSelect(value) {
value = this.defaultOnSelect(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.set("value", null);
switch(value) {
case "invite":
this.attrs.showInvite();
refresh();
break;
case "bookmark":
topic.toggleBookmark().then(() => refresh() );
break;
case "share":
this.appEvents.trigger("share:url", topic.get("shareUrl"), $("#topic-footer-buttons"));
refresh();
break;
case "flag":
this.attrs.showFlagTopic();
refresh();
break;
}
}
}
});

View File

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

View File

@ -0,0 +1,40 @@
import NotificationOptionsComponent from "select-box-kit/components/notifications-button";
import { on } from "ember-addons/ember-computed-decorators";
import { topicLevels } from "discourse/lib/notification-levels";
export default NotificationOptionsComponent.extend({
classNames: "topic-notifications-options",
content: topicLevels,
i18nPrefix: "topic.notifications",
value: Ember.computed.alias("topic.details.notification_level"),
@on("didInsertElement")
_bindGlobalLevelChanged() {
this.appEvents.on("topic-notifications-button:changed", (msg) => {
if (msg.type === "notification") {
if (this.get("computedValue") !== msg.id) {
this.get("topic.details").updateNotifications(msg.id);
}
}
});
},
@on("willDestroyElement")
_unbindGlobalLevelChanged() {
this.appEvents.off("topic-notifications-button:changed");
},
actions: {
onSelect(value) {
if (value !== this.get("computedValue")) {
this.get("topic.details").updateNotifications(value);
}
this.set("value", value);
this.defaultOnSelect(value);
this.blur();
}
}
});

View File

@ -0,0 +1,53 @@
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";
},
$findRowByValue(value) {
return this.$(`${this.rowSelector}[data-value='${value}']`);
},
$header() {
return this.$(this.headerSelector);
},
$body() {
return this.$(this.bodySelector);
},
$collection() {
return this.$(this.collectionSelector);
},
$rows() {
return this.$(this.rowSelector);
},
$highlightedRow() {
return this.$rows().filter(".is-highlighted");
},
$selectedRow() {
return this.$rows().filter(".is-selected");
},
$offscreenInput() {
return this.$(this.offscreenInputSelector);
},
$filterInput() {
return this.$(this.filterInputSelector);
},
_killEvent(event) {
event.preventDefault();
event.stopPropagation();
}
});

View File

@ -0,0 +1,196 @@
const { isEmpty } = Ember;
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(
"click.select-box-kit mousedown.select-box-kit touchstart.select-box-kit"
);
this.$offscreenInput()
.off("focus.select-box-kit focusin.select-box-kit blur.select-box-kit keydown.select-box-kit");
this.$filterInput().off(`keydown.select-box-kit`);
},
didInsertElement() {
this._super();
$(document).on(
"click.select-box-kit mousedown.select-box-kit touchstart.select-box-kit", event => {
if (this.$()[0].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;
switch (keyCode) {
case this.keys.UP:
case this.keys.DOWN:
if (this.get("isExpanded") === false) {
this.set("isExpanded", true);
}
Ember.run.schedule("actions", () => {
this._handleArrowKey(keyCode);
});
this._killEvent(event);
return;
case this.keys.ENTER:
if (this.get("isExpanded") === false) {
this.set("isExpanded", true);
} else {
this.send("onSelect", this.$highlightedRow().data("value"));
}
this._killEvent(event);
return;
case this.keys.TAB:
if (this.get("isExpanded") === false) {
return true;
} else {
this.send("onSelect", this.$highlightedRow().data("value"));
return;
}
case this.keys.ESC:
this.close();
this._killEvent(event);
return;
case this.keys.BACKSPACE:
this._killEvent(event);
return;
}
if (this._isSpecialKey(keyCode) === false && event.metaKey === false) {
this.setProperties({
isExpanded: true,
filter: String.fromCharCode(keyCode)
});
Ember.run.schedule("afterRender", () => this.$filterInput().focus() );
}
});
this.$filterInput()
.on(`keydown.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 (this._isSpecialKey(keyCode) === true) {
this.$offscreenInput().focus().trigger(event);
}
return true;
});
},
_handleArrowKey(keyCode) {
if (isEmpty(this.get("filteredContent"))) {
return;
}
Ember.run.schedule("afterRender", () => {
switch (keyCode) {
case 38:
Ember.run.throttle(this, this._handleUpArrow, 32);
break;
default:
Ember.run.throttle(this, this._handleDownArrow, 32);
}
});
},
_moveHighlight(direction) {
const $rows = this.$rows();
const currentIndex = $rows.index(this.$highlightedRow());
let nextIndex = 0;
if (currentIndex < 0) {
nextIndex = 0;
} else if (currentIndex + direction < $rows.length) {
nextIndex = currentIndex + direction;
}
this._rowSelection($rows, nextIndex);
},
_handleDownArrow() { this._moveHighlight(1); },
_handleUpArrow() { this._moveHighlight(-1); },
_rowSelection($rows, nextIndex) {
const highlightableValue = $rows.eq(nextIndex).data("value");
const $highlightableRow = this.$findRowByValue(highlightableValue);
this.send("onHighlight", highlightableValue);
Ember.run.schedule("afterRender", () => {
const $collection = this.$collection();
const currentOffset = $collection.offset().top +
$collection.outerHeight(false);
const nextBottom = $highlightableRow.offset().top +
$highlightableRow.outerHeight(false);
const nextOffset = $collection.scrollTop() + nextBottom - currentOffset;
if (nextIndex === 0) {
$collection.scrollTop(0);
} else if (nextBottom > currentOffset) {
$collection.scrollTop(nextOffset);
}
});
},
_isSpecialKey(keyCode) {
return Object.values(this.keys).includes(keyCode);
},
});

View File

@ -0,0 +1,9 @@
export default Ember.Mixin.create({
_castInteger(value) {
if (this.get("castInteger") === true && Ember.isPresent(value)) {
return parseInt(value, 10);
}
return Ember.isNone(value) ? value : value.toString();
}
});

View File

@ -0,0 +1,15 @@
{{#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

@ -0,0 +1,17 @@
<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

@ -0,0 +1,15 @@
{{#if template}}
{{{template}}}
{{else}}
{{#if icon}}
<div class="icons">
<span class="selection-indicator"></span>
{{{icon}}}
</div>
{{/if}}
<div class="texts">
<span class="name">{{{name}}}</span>
<span class="desc">{{{description}}}</span>
</div>
{{/if}}

View File

@ -0,0 +1,23 @@
{{#if icon}}
<div class="future-date-input-selector-icons">
{{{icon}}}
</div>
{{/if}}
<span class="selected-name" title={{selectedName}}>
{{{selectedName}}}
</span>
{{#if datetime}}
<span class="future-date-input-selector-datetime">
{{datetime}}
</span>
{{/if}}
{{#if shouldDisplayClearableButton}}
<button class="btn-clear" {{action onClearSelection bubbles=false}}>
{{d-icon 'times'}}
</button>
{{/if}}
{{d-icon caretIcon class="caret-icon"}}

View File

@ -0,0 +1,13 @@
{{#if icon}}
<div class="future-date-input-selector-icons">
{{{icon}}}
</div>
{{/if}}
<span class="name">{{content.name}}</span>
{{#if datetime}}
<span class="future-date-input-selector-datetime">
{{datetime}}
</span>
{{/if}}

View File

@ -0,0 +1,30 @@
<ul class="choices">
{{#each selectedContent as |selectedContent|}}
<li tabindex="-1" data-value={{selectedContent.value}} data-name={{selectedContent.name}} class="selected-name">
<span class="delete-icon" {{action onDeselect selectedContent.value bubbles=false}}>
{{d-icon "times"}}
</span>
<span class="name">
{{selectedContent.name}}
</span>
</li>
{{else}}
{{#if shouldDisplayFilterPlaceholder}}
<li class="choice-placeholder">
{{text}}
</li>
{{/if}}
{{/each}}
<li class="filter">
{{input
class="select-box-kit-filter-input"
key-up=onFilterChange
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck=false
value=filter
}}
</li>
</ul>

View File

@ -0,0 +1,65 @@
<input
class="select-box-kit-offscreen"
type="text"
aria-haspopup="true"
role="button"
aria-labelledby="select-box-kit-offscreen-{{elementId}}"
tabindex={{tabindex}}
name={{name}}
value={{computedValue}}
readonly=true
/>
{{component headerComponent
none=computedNone
isFocused=isFocused
isExpanded=isExpanded
selectedContent=selectedContent
onDeselect=(action "onDeselect")
onToggle=(action "onToggle")
onFilterChange=(action "onFilterChange")
onClearSelection=(action "onClearSelection")
options=headerComponentOptions
}}
<div class="select-box-kit-body">
{{component filterComponent
onFilterChange=(action "onFilterChange")
icon=filterIcon
filter=filter
filterable=computedFilterable
isFocused=isFocused
placeholder=(i18n filterPlaceholder)
tabindex=tabindex
}}
{{#if renderBody}}
{{component collectionComponent
shouldDisplayCreateRow=shouldDisplayCreateRow
none=computedNone
createRowContent=createRowContent
selectedContent=selectedContent
filteredContent=filteredContent
rowComponent=rowComponent
noneRowComponent=noneRowComponent
createRowComponent=createRowComponent
iconForRow=iconForRow
templateForRow=templateForRow
templateForNoneRow=templateForNoneRow
templateForCreateRow=templateForCreateRow
shouldHighlightRow=shouldHighlightRow
shouldSelectRow=shouldSelectRow
titleForRow=titleForRow
onClearSelection=(action "onClearSelection")
onSelect=(action "onSelect")
onHighlight=(action "onHighlight")
onCreateContent=(action "onCreateContent")
noContentLabel=noContentLabel
highlightedValue=highlightedValue
computedValue=computedValue
rowComponentOptions=rowComponentOptions
}}
{{/if}}
</div>
<div class="select-box-kit-wrapper"></div>

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