DEV: select-kit third major update with focus on accessibility (#13303)
Major changes included: - better support for screen readers - trapping focus in modals - better tabbing order in composer - alerts on no content found/number of items found - better autofocus in modals - mini-tag-chooser is now a multi-select component - each multi-select-component will now display selection on one row
This commit is contained in:
parent
f1701764a6
commit
cb59681d86
|
@ -3,7 +3,9 @@
|
|||
valueProperty="value"
|
||||
content=groupOptions
|
||||
value=groupId
|
||||
allowAny=filter.allow_any
|
||||
none="admin.dashboard.reports.groups"
|
||||
onChange=(action "onChange")
|
||||
options=(hash
|
||||
allowAny=filter.allow_any
|
||||
)
|
||||
}}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
{{list-setting
|
||||
value=settingValue
|
||||
settingName=setting.setting
|
||||
allowAny=allowAny
|
||||
choices=settingChoices
|
||||
onChange=(action "onChangeListSetting")
|
||||
onChangeChoices=(action "onChangeChoices")
|
||||
options=(hash
|
||||
allowAny=allowAny
|
||||
)
|
||||
}}
|
||||
|
||||
{{setting-validation-message message=validationMessage}}
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
{{/if}}
|
||||
|
||||
{{combo-box
|
||||
allowAny=true
|
||||
options=(hash
|
||||
allowAny=true
|
||||
)
|
||||
none=noneKey
|
||||
valueProperty=null
|
||||
nameProperty=null
|
||||
|
|
|
@ -682,6 +682,7 @@ export default Component.extend(ComposerUpload, {
|
|||
|
||||
extraButtons(toolbar) {
|
||||
toolbar.addButton({
|
||||
tabindex: "0",
|
||||
id: "quote",
|
||||
group: "fontStyles",
|
||||
icon: "far-comment",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import Button from "discourse/components/d-button";
|
||||
|
||||
export default Button.extend({
|
||||
tabindex: 5,
|
||||
classNameBindings: [":btn-primary", ":create", "disableSubmit:disabled"],
|
||||
title: "composer.title",
|
||||
});
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
|
||||
|
||||
export default Component.extend({
|
||||
init() {
|
||||
|
@ -12,7 +11,7 @@ export default Component.extend({
|
|||
this._super(...arguments);
|
||||
|
||||
if (this.focusTarget === "usernames") {
|
||||
putCursorAtEnd(this.element.querySelector("input"));
|
||||
this.element.querySelector(".select-kit .select-kit-header").focus();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ export default Component.extend({
|
|||
translatedAriaLabel: null,
|
||||
forwardEvent: false,
|
||||
preventFocus: false,
|
||||
onKeyDown: null,
|
||||
|
||||
isLoading: computed({
|
||||
set(key, value) {
|
||||
|
@ -105,6 +106,13 @@ export default Component.extend({
|
|||
}
|
||||
},
|
||||
|
||||
keyDown(e) {
|
||||
if (this.onKeyDown) {
|
||||
e.stopPropagation();
|
||||
this.onKeyDown(e);
|
||||
}
|
||||
},
|
||||
|
||||
click(event) {
|
||||
let { action } = this;
|
||||
|
||||
|
@ -132,6 +140,7 @@ export default Component.extend({
|
|||
DiscourseURL.routeTo(this.href);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
return false;
|
||||
|
|
|
@ -37,6 +37,7 @@ import { siteDir } from "discourse/lib/text-direction";
|
|||
import toMarkdown from "discourse/lib/to-markdown";
|
||||
import { translations } from "pretty-text/emoji/data";
|
||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
// Our head can be a static string or a function that returns a string
|
||||
// based on input (like for numbered lists).
|
||||
|
@ -182,6 +183,7 @@ class Toolbar {
|
|||
|
||||
const createdButton = {
|
||||
id: button.id,
|
||||
tabindex: button.tabindex || "-1",
|
||||
className: button.className || button.id,
|
||||
label: button.label,
|
||||
icon: button.label ? null : button.icon || button.id,
|
||||
|
@ -442,13 +444,19 @@ export default Component.extend({
|
|||
if (this._state !== "inDOM" || !this.element) {
|
||||
return;
|
||||
}
|
||||
const $preview = $(this.element.querySelector(".d-editor-preview"));
|
||||
if ($preview.length === 0) {
|
||||
|
||||
const preview = this.element.querySelector(".d-editor-preview");
|
||||
if (!preview) {
|
||||
return;
|
||||
}
|
||||
|
||||
// prevents any tab focus in preview
|
||||
preview.querySelectorAll("a").forEach((anchor) => {
|
||||
anchor.setAttribute("tabindex", "-1");
|
||||
});
|
||||
|
||||
if (this.previewUpdated) {
|
||||
this.previewUpdated($preview);
|
||||
this.previewUpdated($(preview));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1027,6 +1035,45 @@ export default Component.extend({
|
|||
});
|
||||
},
|
||||
|
||||
@action
|
||||
rovingButtonBar(event) {
|
||||
let target = event.target;
|
||||
let siblingFinder;
|
||||
if (event.code === "ArrowRight") {
|
||||
siblingFinder = "nextElementSibling";
|
||||
} else if (event.code === "ArrowLeft") {
|
||||
siblingFinder = "previousElementSibling";
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (
|
||||
target.parentNode &&
|
||||
!target.parentNode.classList.contains("d-editor-button-bar")
|
||||
) {
|
||||
target = target.parentNode;
|
||||
}
|
||||
|
||||
let focusable = target[siblingFinder];
|
||||
if (focusable) {
|
||||
while (
|
||||
(focusable.tagName !== "BUTTON" &&
|
||||
!focusable.classList.contains("select-kit")) ||
|
||||
focusable.classList.contains("hidden")
|
||||
) {
|
||||
focusable = focusable[siblingFinder];
|
||||
}
|
||||
|
||||
if (focusable?.tagName === "DETAILS") {
|
||||
focusable = focusable.querySelector("summary");
|
||||
}
|
||||
|
||||
focusable?.focus();
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
actions: {
|
||||
emoji() {
|
||||
if (this.disabled) {
|
||||
|
|
|
@ -5,7 +5,6 @@ export default Component.extend({
|
|||
fixed: false,
|
||||
submitOnEnter: true,
|
||||
dismissable: true,
|
||||
autoFocus: true,
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
@ -35,10 +34,8 @@ export default Component.extend({
|
|||
const maxHeightFloat = parseFloat(maxHeight) / 100.0;
|
||||
if (maxHeightFloat > 0) {
|
||||
const viewPortHeight = $(window).height();
|
||||
$(this.element).css(
|
||||
"max-height",
|
||||
Math.floor(maxHeightFloat * viewPortHeight) + "px"
|
||||
);
|
||||
this.element.style.maxHeight =
|
||||
Math.floor(maxHeightFloat * viewPortHeight) + "px";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,8 +49,7 @@ export default Component.extend({
|
|||
"rawSubtitle",
|
||||
"submitOnEnter",
|
||||
"dismissable",
|
||||
"headerClass",
|
||||
"autoFocus"
|
||||
"headerClass"
|
||||
)
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { computed } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import afterTransition from "discourse/lib/after-transition";
|
||||
import { next } from "@ember/runloop";
|
||||
import { on } from "discourse-common/utils/decorators";
|
||||
import { next, schedule } from "@ember/runloop";
|
||||
import { bind, on } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [
|
||||
|
@ -48,26 +47,20 @@ export default Component.extend({
|
|||
|
||||
@on("didInsertElement")
|
||||
setUp() {
|
||||
$("html").on("keyup.discourse-modal", (e) => {
|
||||
// only respond to events when the modal is visible
|
||||
if (!this.element.classList.contains("hidden")) {
|
||||
if (e.which === 27 && this.dismissable) {
|
||||
next(() => this.attrs.closeModal("initiatedByESC"));
|
||||
}
|
||||
|
||||
if (e.which === 13 && this.triggerClickOnEnter(e)) {
|
||||
next(() => $(".modal-footer .btn-primary").click());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.appEvents.on("modal:body-shown", this, "_modalBodyShown");
|
||||
document.documentElement.addEventListener(
|
||||
"keydown",
|
||||
this._handleModalEvents
|
||||
);
|
||||
},
|
||||
|
||||
@on("willDestroyElement")
|
||||
cleanUp() {
|
||||
$("html").off("keyup.discourse-modal");
|
||||
this.appEvents.off("modal:body-shown", this, "_modalBodyShown");
|
||||
document.documentElement.removeEventListener(
|
||||
"keydown",
|
||||
this._handleModalEvents
|
||||
);
|
||||
},
|
||||
|
||||
triggerClickOnEnter(e) {
|
||||
|
@ -141,22 +134,75 @@ export default Component.extend({
|
|||
|
||||
this.set("headerClass", data.headerClass || null);
|
||||
|
||||
if (this.element && data.autoFocus) {
|
||||
let focusTarget = this.element.querySelector(
|
||||
".modal-body input[autofocus]"
|
||||
);
|
||||
schedule("afterRender", () => {
|
||||
this._trapTab();
|
||||
});
|
||||
},
|
||||
|
||||
if (!focusTarget && !this.site.mobileView) {
|
||||
focusTarget = this.element.querySelector(
|
||||
".modal-body input, .modal-body button, .modal-footer input, .modal-footer button"
|
||||
);
|
||||
@bind
|
||||
_handleModalEvents(event) {
|
||||
if (this.element.classList.contains("hidden")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!focusTarget) {
|
||||
focusTarget = this.element.querySelector(".modal-header button");
|
||||
}
|
||||
if (event.key === "Escape" && this.dismissable) {
|
||||
next(() => this.attrs.closeModal("initiatedByESC"));
|
||||
}
|
||||
if (event.key === "Enter" && this.triggerClickOnEnter(event)) {
|
||||
this.element?.querySelector(".modal-footer .btn-primary")?.click();
|
||||
}
|
||||
if (event.key === "Tab") {
|
||||
this._trapTab(event);
|
||||
}
|
||||
},
|
||||
|
||||
_trapTab(event) {
|
||||
if (this.element.classList.contains("hidden")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const innerContainer = this.element.querySelector(".modal-inner-container");
|
||||
if (!innerContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
let focusableElements =
|
||||
'[autofocus], a, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])';
|
||||
|
||||
if (!event) {
|
||||
// on first trap we don't allow to focus modal-close
|
||||
// and apply manual focus only if we don't have any autofocus element
|
||||
const autofocusedElement = innerContainer.querySelector("[autofocus]");
|
||||
if (
|
||||
!autofocusedElement ||
|
||||
document.activeElement !== autofocusedElement
|
||||
) {
|
||||
innerContainer
|
||||
.querySelectorAll(focusableElements + ", button:not(.modal-close)")[0]
|
||||
?.focus();
|
||||
}
|
||||
if (focusTarget) {
|
||||
afterTransition(() => focusTarget.focus());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
focusableElements = focusableElements + ", button:enabled";
|
||||
const firstFocusableElement = innerContainer.querySelectorAll(
|
||||
focusableElements
|
||||
)?.[0];
|
||||
const focusableContent = innerContainer.querySelectorAll(focusableElements);
|
||||
const lastFocusableElement = focusableContent[focusableContent.length - 1];
|
||||
|
||||
if (event.shiftKey) {
|
||||
if (document.activeElement === firstFocusableElement) {
|
||||
lastFocusableElement?.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
} else {
|
||||
if (document.activeElement === lastFocusableElement) {
|
||||
(
|
||||
innerContainer.querySelector(".modal-close") || firstFocusableElement
|
||||
)?.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,50 +1,34 @@
|
|||
import { and, empty, equal } from "@ember/object/computed";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
import { action } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
import { FORMAT } from "select-kit/components/future-date-input-selector";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default Component.extend({
|
||||
selection: null,
|
||||
date: null,
|
||||
time: null,
|
||||
includeDateTime: true,
|
||||
isCustom: equal("selection", "pick_date_and_time"),
|
||||
displayDateAndTimePicker: and("includeDateTime", "isCustom"),
|
||||
displayLabel: null,
|
||||
labelClasses: null,
|
||||
timeInputDisabled: empty("_date"),
|
||||
|
||||
timeInputDisabled: empty("date"),
|
||||
_date: null,
|
||||
_time: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
if (this.input) {
|
||||
const datetime = moment(this.input);
|
||||
this.setProperties({
|
||||
selection: "pick_date_and_time",
|
||||
date: datetime.format("YYYY-MM-DD"),
|
||||
time: datetime.format("HH:mm"),
|
||||
_date: datetime.format("YYYY-MM-DD"),
|
||||
_time: datetime.format("HH:mm"),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@observes("date", "time")
|
||||
_updateInput() {
|
||||
if (!this.date) {
|
||||
this.set("time", null);
|
||||
}
|
||||
|
||||
const time = this.time ? ` ${this.time}` : "";
|
||||
const dateTime = moment(`${this.date}${time}`);
|
||||
|
||||
if (dateTime.isValid()) {
|
||||
this.attrs.onChangeInput &&
|
||||
this.attrs.onChangeInput(dateTime.format(FORMAT));
|
||||
} else {
|
||||
this.attrs.onChangeInput && this.attrs.onChangeInput(null);
|
||||
}
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
|
@ -52,4 +36,32 @@ export default Component.extend({
|
|||
this.set("displayLabel", I18n.t(this.label));
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
onChangeDate(date) {
|
||||
if (!date) {
|
||||
this.set("time", null);
|
||||
}
|
||||
|
||||
this._dateTimeChanged(date, this.time);
|
||||
},
|
||||
|
||||
@action
|
||||
onChangeTime(time) {
|
||||
if (this._date) {
|
||||
this._dateTimeChanged(this._date, time);
|
||||
}
|
||||
},
|
||||
|
||||
_dateTimeChanged(date, time) {
|
||||
time = time ? ` ${time}` : "";
|
||||
const dateTime = moment(`${date}${time}`);
|
||||
|
||||
if (dateTime.isValid()) {
|
||||
this.attrs.onChangeInput &&
|
||||
this.attrs.onChangeInput(dateTime.format(FORMAT));
|
||||
} else {
|
||||
this.attrs.onChangeInput && this.attrs.onChangeInput(null);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
const TIMEFRAME_BASE = {
|
||||
enabled: () => true,
|
||||
when: () => null,
|
||||
icon: "briefcase",
|
||||
displayWhen: true,
|
||||
};
|
||||
|
||||
function buildTimeframe(opts) {
|
||||
return jQuery.extend({}, TIMEFRAME_BASE, opts);
|
||||
}
|
||||
|
||||
const TIMEFRAMES = [
|
||||
buildTimeframe({
|
||||
id: "now",
|
||||
format: "h:mm a",
|
||||
enabled: (opts) => opts.canScheduleNow,
|
||||
when: (time) => time.add(1, "minute"),
|
||||
icon: "magic",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "later_today",
|
||||
format: "h a",
|
||||
enabled: (opts) => opts.canScheduleToday,
|
||||
when: (time) => time.hour(18).minute(0),
|
||||
icon: "far-moon",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "tomorrow",
|
||||
format: "ddd, h a",
|
||||
when: (time, timeOfDay) => time.add(1, "day").hour(timeOfDay).minute(0),
|
||||
icon: "far-sun",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "later_this_week",
|
||||
format: "ddd, h a",
|
||||
enabled: (opts) => !opts.canScheduleToday && opts.day > 0 && opts.day < 4,
|
||||
when: (time, timeOfDay) => time.add(2, "day").hour(timeOfDay).minute(0),
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "this_weekend",
|
||||
format: "ddd, h a",
|
||||
enabled: (opts) => opts.day > 0 && opts.day < 5 && opts.includeWeekend,
|
||||
when: (time, timeOfDay) => time.day(6).hour(timeOfDay).minute(0),
|
||||
icon: "bed",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "next_week",
|
||||
format: "ddd, h a",
|
||||
enabled: (opts) => opts.day !== 0,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(1, "week").day(1).hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "two_weeks",
|
||||
format: "MMM D",
|
||||
when: (time, timeOfDay) => time.add(2, "week").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "next_month",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.now.date() !== moment().endOf("month").date(),
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(1, "month").startOf("month").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "two_months",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.includeMidFuture,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(2, "month").startOf("month").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "three_months",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.includeMidFuture,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(3, "month").startOf("month").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "four_months",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.includeMidFuture,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(4, "month").startOf("month").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "six_months",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.includeMidFuture,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(6, "month").startOf("month").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "one_year",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.includeFarFuture,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(1, "year").startOf("day").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "forever",
|
||||
enabled: (opts) => opts.includeFarFuture,
|
||||
when: (time, timeOfDay) => time.add(1000, "year").hour(timeOfDay).minute(0),
|
||||
icon: "gavel",
|
||||
displayWhen: false,
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "pick_date_and_time",
|
||||
enabled: (opts) => opts.includeDateTime,
|
||||
icon: "far-calendar-plus",
|
||||
}),
|
||||
];
|
||||
|
||||
let _timeframeById = null;
|
||||
export function timeframeDetails(id) {
|
||||
if (!_timeframeById) {
|
||||
_timeframeById = {};
|
||||
TIMEFRAMES.forEach((t) => (_timeframeById[t.id] = t));
|
||||
}
|
||||
return _timeframeById[id];
|
||||
}
|
||||
|
||||
export default function buildTimeframes(options = {}) {
|
||||
return TIMEFRAMES.filter((tf) => tf.enabled(options));
|
||||
}
|
|
@ -1,37 +1,43 @@
|
|||
{{#each categoryBreadcrumbs as |breadcrumb|}}
|
||||
{{#if breadcrumb.hasOptions}}
|
||||
{{category-drop
|
||||
category=breadcrumb.category
|
||||
categories=breadcrumb.options
|
||||
tagId=tag.id
|
||||
editingCategory=editingCategory
|
||||
editingCategoryTab=editingCategoryTab
|
||||
options=(hash
|
||||
parentCategory=breadcrumb.parentCategory
|
||||
subCategory=breadcrumb.isSubcategory
|
||||
noSubcategories=breadcrumb.noSubcategories
|
||||
autoFilterable=true
|
||||
)
|
||||
}}
|
||||
<li>
|
||||
{{category-drop
|
||||
category=breadcrumb.category
|
||||
categories=breadcrumb.options
|
||||
tagId=tag.id
|
||||
editingCategory=editingCategory
|
||||
editingCategoryTab=editingCategoryTab
|
||||
options=(hash
|
||||
parentCategory=breadcrumb.parentCategory
|
||||
subCategory=breadcrumb.isSubcategory
|
||||
noSubcategories=breadcrumb.noSubcategories
|
||||
autoFilterable=true
|
||||
)
|
||||
}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
{{#if showTagsSection}}
|
||||
{{#if additionalTags}}
|
||||
{{tags-intersection-chooser
|
||||
currentCategory=category
|
||||
mainTag=tag.id
|
||||
additionalTags=additionalTags
|
||||
options=(hash
|
||||
categoryId=category.id
|
||||
)
|
||||
}}
|
||||
<li>
|
||||
{{tags-intersection-chooser
|
||||
currentCategory=category
|
||||
mainTag=tag.id
|
||||
additionalTags=additionalTags
|
||||
options=(hash
|
||||
categoryId=category.id
|
||||
)
|
||||
}}
|
||||
</li>
|
||||
{{else}}
|
||||
{{tag-drop
|
||||
currentCategory=category
|
||||
noSubcategories=noSubcategories
|
||||
tagId=tag.id
|
||||
}}
|
||||
<li>
|
||||
{{tag-drop
|
||||
currentCategory=category
|
||||
noSubcategories=noSubcategories
|
||||
tagId=tag.id
|
||||
}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{{d-editor
|
||||
tabindex="4"
|
||||
value=composer.reply
|
||||
placeholder=replyPlaceholder
|
||||
previewUpdated=(action "previewUpdated")
|
||||
|
@ -18,7 +17,8 @@
|
|||
onPopupMenuAction=onPopupMenuAction
|
||||
popupMenuOptions=popupMenuOptions
|
||||
disabled=disableTextarea
|
||||
outletArgs=(hash composer=composer editorType="composer")}}
|
||||
outletArgs=(hash composer=composer editorType="composer")
|
||||
}}
|
||||
|
||||
{{#if allowUpload}}
|
||||
{{#if acceptsAllFormats}}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
{{text-field value=composer.title
|
||||
tabindex="2"
|
||||
id="reply-title"
|
||||
maxLength=titleMaxLength
|
||||
placeholderKey=composer.titlePlaceholder
|
||||
aria-label=(I18n composer.titlePlaceholder)
|
||||
disabled=disabled
|
||||
autocomplete="discourse"}}
|
||||
{{text-field
|
||||
value=composer.title
|
||||
id="reply-title"
|
||||
maxLength=titleMaxLength
|
||||
placeholderKey=composer.titlePlaceholder
|
||||
aria-label=(I18n composer.titlePlaceholder)
|
||||
disabled=disabled
|
||||
autocomplete="discourse"
|
||||
}}
|
||||
|
||||
{{popup-input-tip validation=validation}}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
title=toggleToolbarTitle
|
||||
ariaLabel=toggleToolbarTitle
|
||||
preventFocus=true
|
||||
tabindex=-1
|
||||
}}
|
||||
{{/if}}
|
||||
|
||||
|
@ -18,6 +19,7 @@
|
|||
action=toggleComposer
|
||||
title=toggleTitle
|
||||
ariaLabel=toggleTitle
|
||||
tabindex=-1
|
||||
}}
|
||||
|
||||
{{#unless site.mobileView}}
|
||||
|
@ -27,6 +29,7 @@
|
|||
action=toggleFullscreen
|
||||
title=fullscreenTitle
|
||||
ariaLabel=fullscreenTitle
|
||||
tabindex=-1
|
||||
}}
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
{{email-group-user-chooser
|
||||
id="private-message-users"
|
||||
tabindex="1"
|
||||
value=splitRecipients
|
||||
onChange=(action "updateRecipients")
|
||||
options=(hash
|
||||
topicId=topicId
|
||||
filterPlaceholder="composer.users_placeholder"
|
||||
none="composer.users_placeholder"
|
||||
includeMessageableGroups=true
|
||||
allowEmails=currentUser.can_send_private_email_messages
|
||||
autoWrap=true
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="d-editor-container">
|
||||
<div class="d-editor-textarea-wrapper {{if disabled "disabled"}} {{if isEditorFocused "in-focus"}}">
|
||||
<div class="d-editor-button-bar">
|
||||
<div class="d-editor-button-bar" role="toolbar">
|
||||
{{#each toolbar.groups as |group|}}
|
||||
{{#each group.buttons as |b|}}
|
||||
{{#if b.popupMenu}}
|
||||
|
@ -9,6 +9,8 @@
|
|||
onChange=onPopupMenuAction
|
||||
onOpen=(action b.action b)
|
||||
class=b.className
|
||||
tabindex=-1
|
||||
onKeydown=rovingButtonBar
|
||||
options=(hash
|
||||
icon=b.icon
|
||||
focusAfterOnChange=false
|
||||
|
@ -24,6 +26,8 @@
|
|||
icon=b.icon
|
||||
class=b.className
|
||||
preventFocus=b.preventFocus
|
||||
tabindex=b.tabindex
|
||||
onKeyDown=rovingButtonBar
|
||||
}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
@ -45,7 +49,8 @@
|
|||
disabled=disabled
|
||||
input=change
|
||||
focusIn=(action "focusIn")
|
||||
focusOut=(action "focusOut")}}
|
||||
focusOut=(action "focusOut")
|
||||
}}
|
||||
{{popup-input-tip validation=validation}}
|
||||
{{plugin-outlet name="after-d-editor" tagName="" args=outletArgs}}
|
||||
</div>
|
||||
|
|
|
@ -4,10 +4,8 @@
|
|||
{{#if displayLabelIcon}}{{d-icon displayLabelIcon}}{{/if}}{{displayLabel}}
|
||||
</label>
|
||||
{{future-date-input-selector
|
||||
minimumResultsForSearch=-1
|
||||
statusType=statusType
|
||||
value=(readonly selection)
|
||||
input=(readonly input)
|
||||
includeDateTime=includeDateTime
|
||||
includeWeekend=includeWeekend
|
||||
includeFarFuture=includeFarFuture
|
||||
|
@ -26,15 +24,22 @@
|
|||
<div class="control-group">
|
||||
{{d-icon "calendar-alt"}}
|
||||
{{date-picker-future
|
||||
value=date
|
||||
defaultDate=date
|
||||
onSelect=(action (mut date))
|
||||
value=_date
|
||||
defaultDate=_date
|
||||
onSelect=(action "onChangeDate")
|
||||
}}
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
{{d-icon "far-clock"}}
|
||||
{{input placeholder="--:--" type="time" class="time-input" value=time disabled=timeInputDisabled}}
|
||||
{{input
|
||||
placeholder="--:--"
|
||||
type="time"
|
||||
class="time-input"
|
||||
value=_time
|
||||
disabled=timeInputDisabled
|
||||
input=(action "onChangeTime" value="target.value")
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
{{/link-to}}
|
||||
</li>
|
||||
{{else}}
|
||||
{{group-dropdown
|
||||
groups=group.extras.visible_group_names
|
||||
value=group.name
|
||||
}}
|
||||
<li>
|
||||
{{group-dropdown
|
||||
groups=group.extras.visible_group_names
|
||||
value=group.name
|
||||
}}
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#each tabs as |tab|}}
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
save=(action "save")}}
|
||||
<div class="grippie"></div>
|
||||
{{#if visible}}
|
||||
{{composer-messages composer=model
|
||||
messageCount=messageCount
|
||||
addLinkLookup=(action "addLinkLookup")}}
|
||||
{{composer-messages
|
||||
composer=model
|
||||
messageCount=messageCount
|
||||
addLinkLookup=(action "addLinkLookup")
|
||||
}}
|
||||
{{#if model.viewOpenOrFullscreen}}
|
||||
<div role="form" aria-label={{I18n saveLabel}} class="reply-area {{if canEditTags "with-tags"}}">
|
||||
<div class="composer-fields">
|
||||
|
@ -21,7 +23,7 @@
|
|||
openComposer=(action "openComposer")
|
||||
closeComposer=(action "closeComposer")
|
||||
canWhisper=canWhisper
|
||||
tabindex=8}}
|
||||
}}
|
||||
{{plugin-outlet name="composer-action-after" noTags=true args=(hash model=model)}}
|
||||
|
||||
{{#unless site.mobileView}}
|
||||
|
@ -37,15 +39,17 @@
|
|||
|
||||
{{#if canEdit}}
|
||||
{{#link-to-input onClick=(action "displayEditReason") showInput=showEditReason icon="info-circle" class="display-edit-reason"}}
|
||||
{{text-field value=editReason tabindex="7" id="edit-reason" maxlength="255" placeholderKey="composer.edit_reason_placeholder"}}
|
||||
{{text-field value=editReason id="edit-reason" maxlength="255" placeholderKey="composer.edit_reason_placeholder"}}
|
||||
{{/link-to-input}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{composer-toggles composeState=model.composeState showToolbar=showToolbar
|
||||
toggleComposer=(action "toggle")
|
||||
toggleToolbar=(action "toggleToolbar")
|
||||
toggleFullscreen=(action "fullscreenComposer")}}
|
||||
{{composer-toggles
|
||||
composeState=model.composeState showToolbar=showToolbar
|
||||
toggleComposer=(action "toggle")
|
||||
toggleToolbar=(action "toggleToolbar")
|
||||
toggleFullscreen=(action "fullscreenComposer")
|
||||
}}
|
||||
</div>
|
||||
{{#unless model.viewFullscreen}}
|
||||
{{#if model.canEditTitle}}
|
||||
|
@ -60,7 +64,7 @@
|
|||
}}
|
||||
{{#if showWarning}}
|
||||
<label class="add-warning">
|
||||
{{input type="checkbox" checked=model.isWarning tabindex="3"}}
|
||||
{{input type="checkbox" checked=model.isWarning}}
|
||||
{{i18n "composer.add_warning"}}
|
||||
</label>
|
||||
{{/if}}
|
||||
|
@ -75,7 +79,6 @@
|
|||
<div class="category-input">
|
||||
{{category-chooser
|
||||
value=model.categoryId
|
||||
tabindex="3"
|
||||
onChange=(action (mut model.categoryId))
|
||||
isDisabled=disableCategoryChooser
|
||||
options=(hash
|
||||
|
@ -89,7 +92,6 @@
|
|||
{{#if canEditTags}}
|
||||
{{mini-tag-chooser
|
||||
value=model.tags
|
||||
tabindex=4
|
||||
isDisabled=disableTagsChooser
|
||||
onChange=(action (mut model.tags))
|
||||
options=(hash
|
||||
|
@ -139,14 +141,16 @@
|
|||
|
||||
<div class="save-or-cancel">
|
||||
{{#unless model.viewFullscreen}}
|
||||
{{composer-save-button action=(action "save")
|
||||
icon=saveIcon
|
||||
label=saveLabel
|
||||
forwardEvent=true
|
||||
disableSubmit=disableSubmit}}
|
||||
{{composer-save-button
|
||||
action=(action "save")
|
||||
icon=saveIcon
|
||||
label=saveLabel
|
||||
forwardEvent=true
|
||||
disableSubmit=disableSubmit
|
||||
}}
|
||||
|
||||
{{#if site.mobileView}}
|
||||
<a href {{action "cancel"}} tabindex="6" title={{i18n "cancel"}} class="cancel">
|
||||
<a href {{action "cancel"}} title={{i18n "cancel"}} class="cancel">
|
||||
{{#if canEdit}}
|
||||
{{d-icon "times"}}
|
||||
{{else}}
|
||||
|
@ -154,7 +158,7 @@
|
|||
{{/if}}
|
||||
</a>
|
||||
{{else}}
|
||||
<a href {{action "cancel"}} tabindex="6" class="cancel" >{{i18n "cancel"}}</a>
|
||||
<a href {{action "cancel"}} class="cancel" >{{i18n "cancel"}}</a>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
|
@ -247,8 +251,8 @@
|
|||
{{composer-toggles composeState=model.composeState
|
||||
toggleFullscreen=(action "openIfDraft")
|
||||
toggleComposer=(action "toggle")
|
||||
toggleToolbar=(action "toggleToolbar")}}
|
||||
|
||||
toggleToolbar=(action "toggleToolbar")
|
||||
}}
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
|
|
@ -8,7 +8,7 @@ acceptance("Admin - Search Log Term", function (needs) {
|
|||
test("show search log term details", async function (assert) {
|
||||
await visit("/admin/logs/search_logs/term?term=ruby");
|
||||
|
||||
assert.ok(exists("div.search-logs-filter"), "has the search type filter");
|
||||
assert.ok(exists(".search-logs-filter"), "has the search type filter");
|
||||
assert.ok(exists("canvas.chartjs-render-monitor"), "has graph canvas");
|
||||
assert.ok(exists("div.header-search-results"), "has header search results");
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ acceptance("Admin - Search Logs", function (needs) {
|
|||
await click(".term a");
|
||||
|
||||
assert.ok(
|
||||
exists("div.search-logs-filter"),
|
||||
exists(".search-logs-filter"),
|
||||
"it should show the search log term page"
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
acceptance,
|
||||
fakeTime,
|
||||
query,
|
||||
queryAll,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { click, visit } from "@ember/test-helpers";
|
||||
|
@ -26,12 +25,6 @@ acceptance("Admin - Silence User", function (needs) {
|
|||
await click(".silence-user");
|
||||
await click(".future-date-input-selector-header");
|
||||
|
||||
assert.equal(
|
||||
query(".future-date-input-selector-header").getAttribute("aria-expanded"),
|
||||
"true",
|
||||
"selector is expanded"
|
||||
);
|
||||
|
||||
const options = Array.from(
|
||||
queryAll(`ul.select-kit-collection li span.name`).map((_, x) =>
|
||||
x.innerText.trim()
|
||||
|
|
|
@ -120,12 +120,6 @@ acceptance("Admin - Suspend User - timeframe choosing", function (needs) {
|
|||
await click(".suspend-user");
|
||||
await click(".future-date-input-selector-header");
|
||||
|
||||
assert.equal(
|
||||
query(".future-date-input-selector-header").getAttribute("aria-expanded"),
|
||||
"true",
|
||||
"selector is expanded"
|
||||
);
|
||||
|
||||
const options = Array.from(
|
||||
queryAll(`ul.select-kit-collection li span.name`).map((_, x) =>
|
||||
x.innerText.trim()
|
||||
|
|
|
@ -23,7 +23,6 @@ acceptance("Composer Actions", function (needs) {
|
|||
test("creating new topic and then reply_as_private_message keeps attributes", async function (assert) {
|
||||
await visit("/");
|
||||
await click("button#create-topic");
|
||||
|
||||
await fillIn("#reply-title", "this is the title");
|
||||
await fillIn(".d-editor-input", "this is the reply");
|
||||
|
||||
|
@ -62,12 +61,8 @@ acceptance("Composer Actions", function (needs) {
|
|||
await composerActions.expand();
|
||||
await composerActions.selectRowByValue("reply_as_private_message");
|
||||
|
||||
assert.equal(
|
||||
queryAll("#private-message-users .selected-name:nth-of-type(1)")
|
||||
.text()
|
||||
.trim(),
|
||||
"codinghorror"
|
||||
);
|
||||
const privateMessageUsers = selectKit("#private-message-users");
|
||||
assert.equal(privateMessageUsers.header().value(), "codinghorror");
|
||||
assert.ok(
|
||||
queryAll(".d-editor-input").val().indexOf("Continuing the discussion") >=
|
||||
0
|
||||
|
@ -182,12 +177,8 @@ acceptance("Composer Actions", function (needs) {
|
|||
await composerActions.expand();
|
||||
await composerActions.selectRowByValue("reply_as_new_group_message");
|
||||
|
||||
const items = [];
|
||||
queryAll("#private-message-users .selected-name").each((_, item) =>
|
||||
items.push(item.textContent.trim())
|
||||
);
|
||||
|
||||
assert.deepEqual(items, ["foo", "foo_group"]);
|
||||
const privateMessageUsers = selectKit("#private-message-users");
|
||||
assert.deepEqual(privateMessageUsers.header().value(), "foo,foo_group");
|
||||
});
|
||||
|
||||
test("hide component if no content", async function (assert) {
|
||||
|
@ -422,12 +413,8 @@ acceptance("Composer Actions", function (needs) {
|
|||
await composerActions.expand();
|
||||
await composerActions.selectRowByValue("reply_as_private_message");
|
||||
|
||||
assert.equal(
|
||||
queryAll("#private-message-users .selected-name:nth-of-type(1)")
|
||||
.text()
|
||||
.trim(),
|
||||
"uwe_keim"
|
||||
);
|
||||
const privateMessageUsers = selectKit("#private-message-users");
|
||||
assert.equal(privateMessageUsers.header().value(), "uwe_keim");
|
||||
assert.ok(
|
||||
queryAll(".d-editor-input").val().indexOf("Continuing the discussion") >=
|
||||
0
|
||||
|
|
|
@ -27,7 +27,7 @@ async function writeInComposer(assert) {
|
|||
|
||||
assert.equal(
|
||||
queryAll(".d-editor-preview:visible").html().trim(),
|
||||
'<p><a href="/404">test</a></p>'
|
||||
'<p><a href="/404" tabindex="-1">test</a></p>'
|
||||
);
|
||||
|
||||
await fillIn(".d-editor-input", "[test|attachment](upload://asdsad.png)");
|
||||
|
@ -41,7 +41,7 @@ acceptance("Composer Attachment - Cooking", function (needs) {
|
|||
await writeInComposer(assert);
|
||||
assert.equal(
|
||||
queryAll(".d-editor-preview:visible").html().trim(),
|
||||
'<p><a class="attachment" href="/uploads/short-url/asdsad.png">test</a></p>'
|
||||
'<p><a class="attachment" href="/uploads/short-url/asdsad.png" tabindex="-1">test</a></p>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -55,7 +55,7 @@ acceptance("Composer Attachment - Secure Media Enabled", function (needs) {
|
|||
await writeInComposer(assert);
|
||||
assert.equal(
|
||||
queryAll(".d-editor-preview:visible").html().trim(),
|
||||
'<p><a class="attachment" href="/secure-media-uploads/default/3X/1/asjdiasjdiasida.png">test</a></p>'
|
||||
'<p><a class="attachment" href="/secure-media-uploads/default/3X/1/asjdiasjdiasida.png" tabindex="-1">test</a></p>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,10 +32,10 @@ http://www.example.com/has-title.html
|
|||
queryAll(".d-editor-preview:visible").html().trim(),
|
||||
`
|
||||
<p><aside class=\"onebox\"><article class=\"onebox-body\"><h3><a href=\"http://www.example.com/article.html\">An interesting article</a></h3></article></aside><br>
|
||||
This is another test <a href=\"http://www.example.com/has-title.html\" class=\"inline-onebox\">This is a great title</a></p>
|
||||
<p><a href=\"http://www.example.com/no-title.html\" class=\"onebox\" target=\"_blank\">http://www.example.com/no-title.html</a></p>
|
||||
<p>This is another test <a href=\"http://www.example.com/no-title.html\" class=\"\">http://www.example.com/no-title.html</a><br>
|
||||
This is another test <a href=\"http://www.example.com/has-title.html\" class=\"inline-onebox\">This is a great title</a></p>
|
||||
This is another test <a href=\"http://www.example.com/has-title.html\" class=\"inline-onebox\" tabindex=\"-1\">This is a great title</a></p>
|
||||
<p><a href=\"http://www.example.com/no-title.html\" class=\"onebox\" target=\"_blank\" tabindex=\"-1\">http://www.example.com/no-title.html</a></p>
|
||||
<p>This is another test <a href=\"http://www.example.com/no-title.html\" class=\"\" tabindex=\"-1\">http://www.example.com/no-title.html</a><br>
|
||||
This is another test <a href=\"http://www.example.com/has-title.html\" class=\"inline-onebox\" tabindex=\"-1\">This is a great title</a></p>
|
||||
<p><aside class=\"onebox\"><article class=\"onebox-body\"><h3><a href=\"http://www.example.com/article.html\">An interesting article</a></h3></article></aside></p>
|
||||
`.trim()
|
||||
);
|
||||
|
@ -69,14 +69,14 @@ acceptance("Composer - Inline Onebox", function (needs) {
|
|||
assert.equal(requestsCount, 1);
|
||||
assert.equal(
|
||||
queryAll(".d-editor-preview").html().trim(),
|
||||
'<p>Test <a href="http://www.example.com/page" class="inline-onebox-loading">www.example.com/page</a></p>'
|
||||
'<p>Test <a href="http://www.example.com/page" class="inline-onebox-loading" tabindex="-1">www.example.com/page</a></p>'
|
||||
);
|
||||
|
||||
await fillIn(".d-editor-input", `Test www.example.com/page Test`);
|
||||
assert.equal(requestsCount, 1);
|
||||
assert.equal(
|
||||
queryAll(".d-editor-preview").html().trim(),
|
||||
'<p>Test <a href="http://www.example.com/page">www.example.com/page</a> Test</p>'
|
||||
'<p>Test <a href="http://www.example.com/page" tabindex="-1">www.example.com/page</a> Test</p>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -750,12 +750,8 @@ acceptance("Composer", function (needs) {
|
|||
await click("button.compose-pm");
|
||||
await click(".modal .btn-default");
|
||||
|
||||
assert.equal(
|
||||
queryAll("#private-message-users .selected-name:nth-of-type(1)")
|
||||
.text()
|
||||
.trim(),
|
||||
"codinghorror"
|
||||
);
|
||||
const privateMessageUsers = selectKit("#private-message-users");
|
||||
assert.equal(privateMessageUsers.header().value(), "codinghorror");
|
||||
} finally {
|
||||
toggleCheckDraftPopup(false);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
count,
|
||||
exists,
|
||||
fakeTime,
|
||||
query,
|
||||
queryAll,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
|
@ -231,14 +230,6 @@ acceptance(
|
|||
await click(".modal-footer .show-advanced");
|
||||
await click(".future-date-input-selector-header");
|
||||
|
||||
assert.equal(
|
||||
query(".future-date-input-selector-header").getAttribute(
|
||||
"aria-expanded"
|
||||
),
|
||||
"true",
|
||||
"selector is expanded"
|
||||
);
|
||||
|
||||
const options = Array.from(
|
||||
queryAll(`ul.select-kit-collection li span.name`).map((_, x) =>
|
||||
x.innerText.trim()
|
||||
|
|
|
@ -89,7 +89,7 @@ acceptance("Managing Group Membership", function (needs) {
|
|||
);
|
||||
await emailDomains.expand();
|
||||
await emailDomains.fillInFilter("foo.com");
|
||||
await emailDomains.keyboard("Enter");
|
||||
await emailDomains.selectRowByValue("foo.com");
|
||||
|
||||
assert.equal(emailDomains.header().value(), "foo.com");
|
||||
});
|
||||
|
|
|
@ -212,8 +212,9 @@ acceptance("Group - Authenticated", function (needs) {
|
|||
await click(".group-message-button");
|
||||
|
||||
assert.equal(count("#reply-control"), 1, "it opens the composer");
|
||||
const privateMessageUsers = selectKit("#private-message-users");
|
||||
assert.equal(
|
||||
queryAll("#private-message-users .selected-name").text().trim(),
|
||||
privateMessageUsers.header().value(),
|
||||
"discourse",
|
||||
"it prefills the group name"
|
||||
);
|
||||
|
|
|
@ -132,10 +132,9 @@ acceptance("Modal Keyboard Events", function (needs) {
|
|||
|
||||
test("modal-keyboard-events", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
|
||||
await click(".toggle-admin-menu");
|
||||
await click(".admin-topic-timer-update button");
|
||||
await triggerKeyEvent(".d-modal", "keyup", 13);
|
||||
await triggerKeyEvent(".d-modal", "keydown", 13);
|
||||
|
||||
assert.equal(
|
||||
count("#modal-alert:visible"),
|
||||
|
@ -148,14 +147,16 @@ acceptance("Modal Keyboard Events", function (needs) {
|
|||
"hitting Enter does not dismiss modal due to alert error"
|
||||
);
|
||||
|
||||
await triggerKeyEvent("#main-outlet", "keyup", 27);
|
||||
assert.ok(exists(".d-modal:visible"), "modal should be visible");
|
||||
|
||||
await triggerKeyEvent("#main-outlet", "keydown", 27);
|
||||
|
||||
assert.ok(!exists(".d-modal:visible"), "ESC should close the modal");
|
||||
|
||||
await click(".topic-body button.reply");
|
||||
|
||||
await click(".d-editor-button-bar .btn.link");
|
||||
await triggerKeyEvent(".d-modal", "keydown", 13);
|
||||
|
||||
await triggerKeyEvent(".d-modal", "keyup", 13);
|
||||
assert.ok(
|
||||
!exists(".d-modal:visible"),
|
||||
"modal should disappear on hitting Enter"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import {
|
||||
acceptance,
|
||||
exists,
|
||||
|
@ -35,10 +36,10 @@ acceptance("New Message - Authenticated", function (needs) {
|
|||
"message body",
|
||||
"it pre-fills message body"
|
||||
);
|
||||
|
||||
const privateMessageUsers = selectKit("#private-message-users");
|
||||
assert.equal(
|
||||
queryAll("#private-message-users .selected-name:nth-of-type(1)")
|
||||
.text()
|
||||
.trim(),
|
||||
privateMessageUsers.header().value(),
|
||||
"charlie",
|
||||
"it selects correct username"
|
||||
);
|
||||
|
|
|
@ -117,7 +117,11 @@ acceptance("User Preferences", function (needs) {
|
|||
await savePreferences();
|
||||
|
||||
await click(".preferences-nav .nav-categories a");
|
||||
await fillIn(".tracking-controls .category-selector input", "faq");
|
||||
const categorySelector = selectKit(
|
||||
".tracking-controls .category-selector "
|
||||
);
|
||||
await categorySelector.expand();
|
||||
await categorySelector.fillInFilter("faq");
|
||||
await savePreferences();
|
||||
|
||||
assert.ok(
|
||||
|
@ -510,8 +514,9 @@ acceptance("Security", function (needs) {
|
|||
"it should display three tokens"
|
||||
);
|
||||
|
||||
await click(".auth-token-dropdown button:nth-of-type(1)");
|
||||
await click("li[data-value='notYou']");
|
||||
const authTokenDropdown = selectKit(".auth-token-dropdown");
|
||||
await authTokenDropdown.expand();
|
||||
await authTokenDropdown.selectRowByValue("notYou");
|
||||
|
||||
assert.equal(count(".d-modal:visible"), 1, "modal should appear");
|
||||
|
||||
|
|
|
@ -44,11 +44,14 @@ acceptance("Review", function (needs) {
|
|||
});
|
||||
|
||||
test("Reject user", async function (assert) {
|
||||
await visit("/review");
|
||||
await click(
|
||||
`${user} .reviewable-actions button[data-name="Delete User..."]`
|
||||
let reviewableActionDropdown = selectKit(
|
||||
`${user} .reviewable-action-dropdown`
|
||||
);
|
||||
await click(`${user} li[data-value="reject_user_delete"]`);
|
||||
|
||||
await visit("/review");
|
||||
await reviewableActionDropdown.expand();
|
||||
await reviewableActionDropdown.selectRowByValue("reject_user_delete");
|
||||
|
||||
assert.ok(
|
||||
queryAll(".reject-reason-reviewable-modal:visible .title")
|
||||
.html()
|
||||
|
@ -57,11 +60,9 @@ acceptance("Review", function (needs) {
|
|||
);
|
||||
|
||||
await click(".modal-footer button[aria-label='cancel']");
|
||||
await reviewableActionDropdown.expand();
|
||||
await reviewableActionDropdown.selectRowByValue("reject_user_block");
|
||||
|
||||
await click(
|
||||
`${user} .reviewable-actions button[data-name="Delete User..."]`
|
||||
);
|
||||
await click(`${user} li[data-value="reject_user_block"]`);
|
||||
assert.ok(
|
||||
queryAll(".reject-reason-reviewable-modal:visible .title")
|
||||
.html()
|
||||
|
|
|
@ -50,9 +50,9 @@ acceptance("Tag Groups", function (needs) {
|
|||
await tags.selectRowByValue("monkey");
|
||||
await click(".tag-group-content .btn.btn-primary");
|
||||
await click(".tag-groups-sidebar li:first-child a");
|
||||
await tags.expand();
|
||||
|
||||
await click(".group-tags-list .tag-chooser .choice:nth-of-type(1)");
|
||||
await tags.expand();
|
||||
await tags.deselectItemByValue("monkey");
|
||||
assert.ok(!query(".tag-group-content .btn.btn-danger").disabled);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import {
|
||||
acceptance,
|
||||
count,
|
||||
|
@ -412,11 +413,14 @@ acceptance("Tag info", function (needs) {
|
|||
assert.ok(exists(".tag-info .tag-name"), "show tag");
|
||||
|
||||
await click("#edit-synonyms");
|
||||
await click("#add-synonyms .filter-input");
|
||||
|
||||
assert.equal(count(".tag-chooser-row"), 2);
|
||||
const addSynonymsDropdown = selectKit("#add-synonyms");
|
||||
await addSynonymsDropdown.expand();
|
||||
|
||||
assert.deepEqual(
|
||||
Array.from(find(".tag-chooser-row")).map((x) => x.dataset["value"]),
|
||||
Array.from(addSynonymsDropdown.rows()).map((r) => {
|
||||
return r.dataset.value;
|
||||
}),
|
||||
["monkey", "not-monkey"]
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
acceptance,
|
||||
fakeTime,
|
||||
query,
|
||||
queryAll,
|
||||
updateCurrentUser,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
|
@ -45,12 +44,6 @@ acceptance("Topic - Set Slow Mode", function (needs) {
|
|||
|
||||
await click(".future-date-input-selector-header");
|
||||
|
||||
assert.equal(
|
||||
query(".future-date-input-selector-header").getAttribute("aria-expanded"),
|
||||
"true",
|
||||
"selector is expanded"
|
||||
);
|
||||
|
||||
const options = Array.from(
|
||||
queryAll(`ul.select-kit-collection li span.name`).map((_, x) =>
|
||||
x.innerText.trim()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import {
|
||||
acceptance,
|
||||
exists,
|
||||
|
@ -38,16 +39,9 @@ acceptance("Topic - Slow Mode - enabled", function (needs) {
|
|||
await click(".toggle-admin-menu");
|
||||
await click(".topic-admin-slow-mode button");
|
||||
|
||||
await click(".future-date-input-selector-header");
|
||||
|
||||
const slowModeType = selectKit(".slow-mode-type");
|
||||
assert.equal(
|
||||
query(".future-date-input-selector-header").getAttribute("aria-expanded"),
|
||||
"true",
|
||||
"selector is expanded"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
query("div.slow-mode-type span.name").innerText,
|
||||
slowModeType.header().name(),
|
||||
I18n.t("topic.slow_mode_update.durations.10_minutes"),
|
||||
"slow mode interval is rendered"
|
||||
);
|
||||
|
|
|
@ -69,27 +69,11 @@ acceptance("Topic", function (needs) {
|
|||
"it fills composer with the ring string"
|
||||
);
|
||||
|
||||
const targets = queryAll(
|
||||
"#private-message-users .selected-name",
|
||||
".composer-fields"
|
||||
);
|
||||
|
||||
const privateMessageUsers = selectKit("#private-message-users");
|
||||
assert.equal(
|
||||
$(targets[0]).text().trim(),
|
||||
"someguy",
|
||||
"it fills up the composer with the right user to start the PM to"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
$(targets[1]).text().trim(),
|
||||
"test",
|
||||
"it fills up the composer with the right user to start the PM to"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
$(targets[2]).text().trim(),
|
||||
"Group",
|
||||
"it fills up the composer with the right group to start the PM to"
|
||||
privateMessageUsers.header().value(),
|
||||
"someguy,test,Group",
|
||||
"it fills up the composer correctly"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -28,12 +28,13 @@ acceptance("User Preferences - Interface", function (needs) {
|
|||
await visit("/u/eviltrout/preferences/interface");
|
||||
|
||||
// Live changes without reload
|
||||
await selectKit(".text-size .combobox").expand();
|
||||
await selectKit(".text-size .combobox").selectRowByValue("larger");
|
||||
const textSize = selectKit(".text-size .combo-box");
|
||||
await textSize.expand();
|
||||
await textSize.selectRowByValue("larger");
|
||||
assert.ok(document.documentElement.classList.contains("text-size-larger"));
|
||||
|
||||
await selectKit(".text-size .combobox").expand();
|
||||
await selectKit(".text-size .combobox").selectRowByValue("largest");
|
||||
await textSize.expand();
|
||||
await textSize.selectRowByValue("largest");
|
||||
assert.ok(document.documentElement.classList.contains("text-size-largest"));
|
||||
|
||||
assert.equal(cookie("text_size"), null, "cookie is not set");
|
||||
|
@ -43,16 +44,16 @@ acceptance("User Preferences - Interface", function (needs) {
|
|||
|
||||
assert.equal(cookie("text_size"), null, "cookie is not set");
|
||||
|
||||
await selectKit(".text-size .combobox").expand();
|
||||
await selectKit(".text-size .combobox").selectRowByValue("larger");
|
||||
await textSize.expand();
|
||||
await textSize.selectRowByValue("larger");
|
||||
await click(".text-size input[type=checkbox]");
|
||||
|
||||
await savePreferences();
|
||||
|
||||
assert.equal(cookie("text_size"), "larger|1", "cookie is set");
|
||||
await click(".text-size input[type=checkbox]");
|
||||
await selectKit(".text-size .combobox").expand();
|
||||
await selectKit(".text-size .combobox").selectRowByValue("largest");
|
||||
await textSize.expand();
|
||||
await textSize.selectRowByValue("largest");
|
||||
|
||||
await savePreferences();
|
||||
assert.equal(cookie("text_size"), null, "cookie is removed");
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
count,
|
||||
exists,
|
||||
fakeTime,
|
||||
query,
|
||||
queryAll,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { click, visit } from "@ember/test-helpers";
|
||||
|
@ -130,12 +129,6 @@ acceptance("User Notifications - Users - Ignore User", function (needs) {
|
|||
await click("div.user-notifications div div button");
|
||||
await click(".future-date-input-selector-header");
|
||||
|
||||
assert.equal(
|
||||
query(".future-date-input-selector-header").getAttribute("aria-expanded"),
|
||||
"true",
|
||||
"selector is expanded"
|
||||
);
|
||||
|
||||
const options = Array.from(
|
||||
queryAll(`ul.select-kit-collection li span.name`).map((_, x) =>
|
||||
x.innerText.trim()
|
||||
|
|
|
@ -290,12 +290,8 @@ export default function selectKit(selector) {
|
|||
);
|
||||
},
|
||||
|
||||
async deselectItem(value) {
|
||||
await click(
|
||||
queryAll(selector)
|
||||
.find(".select-kit-header")
|
||||
.find(`[data-value="${value}"]`)[0]
|
||||
);
|
||||
async deselectItemByValue(value) {
|
||||
await click(`${selector} .selected-content [data-value="${value}"]`);
|
||||
},
|
||||
|
||||
exists() {
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import componentTest, {
|
||||
setupRenderingTest,
|
||||
} from "discourse/tests/helpers/component-test";
|
||||
import {
|
||||
discourseModule,
|
||||
exists,
|
||||
queryAll,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import hbs from "htmlbars-inline-precompile";
|
||||
import I18n from "I18n";
|
||||
|
||||
discourseModule(
|
||||
"Unit | Lib | select-kit/future-date-input-selector",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.set("subject", selectKit());
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
if (this.clock) {
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
componentTest("rendering and expanding", {
|
||||
template: hbs`
|
||||
{{future-date-input-selector
|
||||
options=(hash
|
||||
none="topic.auto_update_input.none"
|
||||
)
|
||||
}}
|
||||
`,
|
||||
|
||||
async test(assert) {
|
||||
assert.ok(
|
||||
exists(".future-date-input-selector"),
|
||||
"Selector is rendered"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
this.subject.header().label() ===
|
||||
I18n.t("topic.auto_update_input.none"),
|
||||
"Default text is rendered"
|
||||
);
|
||||
|
||||
await this.subject.expand();
|
||||
|
||||
assert.ok(
|
||||
exists(".select-kit-collection"),
|
||||
"List of options is rendered"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
componentTest("shows 'Custom date and time' if it's enabled", {
|
||||
template: hbs`
|
||||
{{future-date-input-selector
|
||||
includeDateTime=true
|
||||
}}
|
||||
`,
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
const options = getOptions();
|
||||
const customDateAndTime = I18n.t(
|
||||
"topic.auto_update_input.pick_date_and_time"
|
||||
);
|
||||
|
||||
assert.ok(options.includes(customDateAndTime));
|
||||
},
|
||||
});
|
||||
|
||||
function getOptions() {
|
||||
return Array.from(
|
||||
queryAll(`.select-kit-collection .select-kit-row`).map(
|
||||
(_, span) => span.dataset.name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
|
@ -1,5 +1,5 @@
|
|||
import { set } from "@ember/object";
|
||||
import { click, fillIn } from "@ember/test-helpers";
|
||||
import { click } from "@ember/test-helpers";
|
||||
import User from "discourse/models/user";
|
||||
import componentTest, {
|
||||
setupRenderingTest,
|
||||
|
@ -40,7 +40,7 @@ discourseModule("Integration | Component | invite-panel", function (hooks) {
|
|||
async test(assert) {
|
||||
const input = selectKit(".invite-user-input");
|
||||
await input.expand();
|
||||
await fillIn(".invite-user-input .filter-input", "eviltrout@example.com");
|
||||
await input.fillInFilter("eviltrout@example.com");
|
||||
await input.selectRowByValue("eviltrout@example.com");
|
||||
assert.ok(!exists(".send-invite:disabled"));
|
||||
await click(".generate-invite-link");
|
||||
|
|
|
@ -63,15 +63,9 @@ discourseModule(
|
|||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
|
||||
assert.equal(
|
||||
this.subject.rowByIndex(0).title(),
|
||||
"Discussion about features or potential features of Discourse: how they work, why they work, etc."
|
||||
);
|
||||
assert.equal(this.subject.rowByIndex(0).title(), "feature");
|
||||
assert.equal(this.subject.rowByIndex(0).value(), 2);
|
||||
assert.equal(
|
||||
this.subject.rowByIndex(1).title(),
|
||||
"My idea here is to have mini specs for features we would like built but have no bandwidth to build"
|
||||
);
|
||||
assert.equal(this.subject.rowByIndex(1).title(), "spec");
|
||||
assert.equal(this.subject.rowByIndex(1).value(), 26);
|
||||
assert.equal(
|
||||
this.subject.rows().length,
|
||||
|
@ -320,7 +314,8 @@ discourseModule(
|
|||
await this.subject.expand();
|
||||
|
||||
assert.equal(
|
||||
this.subject.rowByIndex(0).el()[0].title,
|
||||
this.subject.rowByIndex(0).el()[0].querySelector(".category-desc")
|
||||
.innerText,
|
||||
'baz "bar ‘foo’'
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import componentTest, {
|
||||
setupRenderingTest,
|
||||
} from "discourse/tests/helpers/component-test";
|
||||
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
|
||||
import hbs from "htmlbars-inline-precompile";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
|
||||
discourseModule(
|
||||
"Integration | Component | select-kit/email-group-user-chooser",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.set("subject", selectKit());
|
||||
this.setProperties({
|
||||
value: [],
|
||||
onChange() {},
|
||||
});
|
||||
});
|
||||
|
||||
componentTest("autofocus option set to true", {
|
||||
template: hbs`{{email-group-user-chooser
|
||||
value=value
|
||||
onChange=onChange
|
||||
options=(hash
|
||||
autofocus=true
|
||||
)
|
||||
}}`,
|
||||
|
||||
async test(assert) {
|
||||
this.subject;
|
||||
assert.ok(
|
||||
this.subject.header().el()[0].classList.contains("is-focused"),
|
||||
"select-kit header has is-focused class"
|
||||
);
|
||||
assert.ok(
|
||||
this.subject.filter().el()[0].querySelector(".filter-input")
|
||||
.autofocus,
|
||||
"filter input has autofocus attribute"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
componentTest("without autofocus", {
|
||||
template: hbs`{{email-group-user-chooser
|
||||
value=value
|
||||
onChange=onChange
|
||||
}}`,
|
||||
|
||||
async test(assert) {
|
||||
this.subject;
|
||||
assert.ok(
|
||||
!this.subject.header().el()[0].classList.contains("is-focused"),
|
||||
"select-kit header doesn't have is-focused class"
|
||||
);
|
||||
assert.ok(
|
||||
!this.subject.filter().el()[0].querySelector(".filter-input")
|
||||
.autofocus,
|
||||
"filter input doesn't have autofocus attribute"
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
|
@ -1,300 +0,0 @@
|
|||
import componentTest, {
|
||||
setupRenderingTest,
|
||||
} from "discourse/tests/helpers/component-test";
|
||||
import {
|
||||
discourseModule,
|
||||
exists,
|
||||
fakeTime,
|
||||
query,
|
||||
queryAll,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import hbs from "htmlbars-inline-precompile";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import I18n from "I18n";
|
||||
|
||||
discourseModule(
|
||||
"Integration | Component | select-kit/future-date-input-selector",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.set("subject", selectKit());
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
if (this.clock) {
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
componentTest("rendering and expanding", {
|
||||
template: hbs`
|
||||
{{future-date-input-selector
|
||||
options=(hash
|
||||
none="topic.auto_update_input.none"
|
||||
)
|
||||
}}
|
||||
`,
|
||||
|
||||
async test(assert) {
|
||||
assert.ok(
|
||||
exists("div.future-date-input-selector"),
|
||||
"Selector is rendered"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query("span").innerText === I18n.t("topic.auto_update_input.none"),
|
||||
"Default text is rendered"
|
||||
);
|
||||
|
||||
await this.subject.expand();
|
||||
|
||||
assert.equal(
|
||||
query(".future-date-input-selector-header").getAttribute(
|
||||
"aria-expanded"
|
||||
),
|
||||
"true",
|
||||
"selector is expanded"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists("ul.select-kit-collection"),
|
||||
"List of options is rendered"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
componentTest("shows default options", {
|
||||
template: hbs`{{future-date-input-selector}}`,
|
||||
|
||||
beforeEach() {
|
||||
const timezone = moment.tz.guess();
|
||||
this.clock = fakeTime("2100-06-07T08:00:00", timezone, true); // Monday
|
||||
},
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
|
||||
const options = getOptions();
|
||||
const expected = [
|
||||
I18n.t("topic.auto_update_input.later_today"),
|
||||
I18n.t("topic.auto_update_input.tomorrow"),
|
||||
I18n.t("topic.auto_update_input.next_week"),
|
||||
I18n.t("topic.auto_update_input.two_weeks"),
|
||||
I18n.t("topic.auto_update_input.next_month"),
|
||||
I18n.t("topic.auto_update_input.two_months"),
|
||||
I18n.t("topic.auto_update_input.three_months"),
|
||||
I18n.t("topic.auto_update_input.four_months"),
|
||||
I18n.t("topic.auto_update_input.six_months"),
|
||||
];
|
||||
assert.deepEqual(options, expected);
|
||||
},
|
||||
});
|
||||
|
||||
componentTest("doesn't show 'Next Week' on Sundays", {
|
||||
template: hbs`{{future-date-input-selector}}`,
|
||||
|
||||
beforeEach() {
|
||||
const timezone = moment.tz.guess();
|
||||
this.clock = fakeTime("2100-06-13T08:00:00", timezone, true); // Sunday
|
||||
},
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
|
||||
const options = getOptions();
|
||||
const nextWeek = I18n.t("topic.auto_update_input.next_week");
|
||||
assert.not(options.includes(nextWeek));
|
||||
},
|
||||
});
|
||||
|
||||
componentTest("shows 'Custom date and time' if it's enabled", {
|
||||
template: hbs`
|
||||
{{future-date-input-selector
|
||||
includeDateTime=true
|
||||
}}
|
||||
`,
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
const options = getOptions();
|
||||
const customDateAndTime = I18n.t(
|
||||
"topic.auto_update_input.pick_date_and_time"
|
||||
);
|
||||
|
||||
assert.ok(options.includes(customDateAndTime));
|
||||
},
|
||||
});
|
||||
|
||||
componentTest("shows 'This Weekend' if it's enabled", {
|
||||
template: hbs`
|
||||
{{future-date-input-selector
|
||||
includeWeekend=true
|
||||
}}
|
||||
`,
|
||||
|
||||
beforeEach() {
|
||||
const timezone = moment.tz.guess();
|
||||
this.clock = fakeTime("2100-06-07T08:00:00", timezone, true); // Monday
|
||||
},
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
const options = getOptions();
|
||||
const thisWeekend = I18n.t("topic.auto_update_input.this_weekend");
|
||||
|
||||
assert.ok(options.includes(thisWeekend));
|
||||
},
|
||||
});
|
||||
|
||||
componentTest("doesn't show 'This Weekend' on Fridays", {
|
||||
template: hbs`
|
||||
{{future-date-input-selector
|
||||
includeWeekend=true
|
||||
}}
|
||||
`,
|
||||
|
||||
beforeEach() {
|
||||
const timezone = moment.tz.guess();
|
||||
this.clock = fakeTime("2100-04-23 18:00:00", timezone, true); // Friday
|
||||
},
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
const options = getOptions();
|
||||
const thisWeekend = I18n.t("topic.auto_update_input.this_weekend");
|
||||
|
||||
assert.not(options.includes(thisWeekend));
|
||||
},
|
||||
});
|
||||
|
||||
componentTest("doesn't show 'This Weekend' on Sundays", {
|
||||
/*
|
||||
We need this test to avoid regressions.
|
||||
We tend to write such conditions and think that
|
||||
they mean the beginning of work week
|
||||
(Monday, Tuesday and Wednesday in this specific case):
|
||||
|
||||
if (date.day <= 3) {
|
||||
...
|
||||
}
|
||||
|
||||
In fact, Sunday will pass this check too, because
|
||||
in moment.js 0 stands for Sunday.
|
||||
*/
|
||||
|
||||
template: hbs`
|
||||
{{future-date-input-selector
|
||||
includeWeekend=true
|
||||
}}
|
||||
`,
|
||||
|
||||
beforeEach() {
|
||||
const timezone = moment.tz.guess();
|
||||
this.clock = fakeTime("2100-04-25 18:00:00", timezone, true); // Sunday
|
||||
},
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
const options = getOptions();
|
||||
const thisWeekend = I18n.t("topic.auto_update_input.this_weekend");
|
||||
|
||||
assert.not(options.includes(thisWeekend));
|
||||
},
|
||||
});
|
||||
|
||||
componentTest(
|
||||
"shows 'Later This Week' instead of 'Later Today' at the end of the day",
|
||||
{
|
||||
template: hbs`{{future-date-input-selector}}`,
|
||||
|
||||
beforeEach() {
|
||||
const timezone = moment.tz.guess();
|
||||
this.clock = fakeTime("2100-04-19 18:00:00", timezone, true); // Monday evening
|
||||
},
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
|
||||
const options = getOptions();
|
||||
const laterToday = I18n.t("topic.auto_update_input.later_today");
|
||||
const laterThisWeek = I18n.t(
|
||||
"topic.auto_update_input.later_this_week"
|
||||
);
|
||||
|
||||
assert.not(options.includes(laterToday));
|
||||
assert.ok(options.includes(laterThisWeek));
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
componentTest("doesn't show 'Later This Week' on Tuesdays", {
|
||||
template: hbs`{{future-date-input-selector}}`,
|
||||
|
||||
beforeEach() {
|
||||
const timezone = moment.tz.guess();
|
||||
this.clock = fakeTime("2100-04-22 18:00:00", timezone, true); // Tuesday evening
|
||||
},
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
const options = getOptions();
|
||||
const laterThisWeek = I18n.t("topic.auto_update_input.later_this_week");
|
||||
assert.not(options.includes(laterThisWeek));
|
||||
},
|
||||
});
|
||||
|
||||
componentTest("doesn't show 'Later This Week' on Sundays", {
|
||||
/* We need this test to avoid regressions.
|
||||
We tend to write such conditions and think that
|
||||
they mean the beginning of business week
|
||||
(Monday, Tuesday and Wednesday in this specific case):
|
||||
|
||||
if (date.day < 3) {
|
||||
...
|
||||
}
|
||||
|
||||
In fact, Sunday will pass this check too, because
|
||||
in moment.js 0 stands for Sunday. */
|
||||
|
||||
template: hbs`{{future-date-input-selector}}`,
|
||||
|
||||
beforeEach() {
|
||||
const timezone = moment.tz.guess();
|
||||
this.clock = fakeTime("2100-04-25 18:00:00", timezone, true); // Sunday evening
|
||||
},
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
const options = getOptions();
|
||||
const laterThisWeek = I18n.t("topic.auto_update_input.later_this_week");
|
||||
assert.not(options.includes(laterThisWeek));
|
||||
},
|
||||
});
|
||||
|
||||
componentTest("doesn't show 'Next Month' on the last day of the month", {
|
||||
template: hbs`{{future-date-input-selector}}`,
|
||||
|
||||
beforeEach() {
|
||||
const timezone = moment.tz.guess();
|
||||
this.clock = fakeTime("2100-04-30 18:00:00", timezone, true); // The last day of April
|
||||
},
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.expand();
|
||||
const options = getOptions();
|
||||
const nextMonth = I18n.t("topic.auto_update_input.next_month");
|
||||
|
||||
assert.not(options.includes(nextMonth));
|
||||
},
|
||||
});
|
||||
|
||||
function getOptions() {
|
||||
return Array.from(
|
||||
queryAll(`ul.select-kit-collection li span.name`).map((_, span) =>
|
||||
span.innerText.trim()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
|
@ -45,7 +45,7 @@ discourseModule(
|
|||
assert.equal(queryAll(".select-kit-row").text().trim(), "monkey x1");
|
||||
await this.subject.fillInFilter("key");
|
||||
assert.equal(queryAll(".select-kit-row").text().trim(), "monkey x1");
|
||||
await this.subject.keyboard("Enter");
|
||||
await this.subject.selectRowByValue("monkey");
|
||||
|
||||
assert.equal(this.subject.header().value(), "foo,bar,monkey");
|
||||
},
|
||||
|
@ -64,7 +64,7 @@ discourseModule(
|
|||
|
||||
await this.subject.expand();
|
||||
await this.subject.fillInFilter("baz");
|
||||
await this.subject.keyboard("Enter");
|
||||
await this.subject.selectRowByValue("monkey");
|
||||
|
||||
const error = queryAll(".select-kit-error").text();
|
||||
assert.equal(
|
||||
|
|
|
@ -34,7 +34,8 @@ discourseModule(
|
|||
},
|
||||
|
||||
async test(assert) {
|
||||
await this.subject.deselectItem("bob");
|
||||
await this.subject.expand();
|
||||
await this.subject.deselectItemByValue("bob");
|
||||
assert.equal(this.subject.header().name(), "martin");
|
||||
},
|
||||
});
|
||||
|
|
|
@ -109,7 +109,7 @@ discourseModule("Integration | Component | value-list", function (hooks) {
|
|||
|
||||
await selectKit().expand();
|
||||
await selectKit().fillInFilter("eviltrout");
|
||||
await selectKit().keyboard("Enter");
|
||||
await selectKit().selectRowByValue("eviltrout");
|
||||
|
||||
assert.equal(
|
||||
count(".values .value"),
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
import { module, test } from "qunit";
|
||||
import { fakeTime } from "discourse/tests/helpers/qunit-helpers";
|
||||
import buildTimeframes from "discourse/lib/timeframes-builder";
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
includeWeekend: null,
|
||||
includeMidFuture: true,
|
||||
includeFarFuture: null,
|
||||
includeDateTime: null,
|
||||
canScheduleNow: false,
|
||||
};
|
||||
|
||||
function buildOptions(now, opts) {
|
||||
return Object.assign(
|
||||
{},
|
||||
DEFAULT_OPTIONS,
|
||||
{ now, day: now.day(), canScheduleToday: 24 - now.hour() > 6 },
|
||||
opts
|
||||
);
|
||||
}
|
||||
|
||||
module("Unit | Lib | timeframes-builder", function () {
|
||||
test("default options", function (assert) {
|
||||
const timezone = moment.tz.guess();
|
||||
const clock = fakeTime("2100-06-07T08:00:00", timezone, true); // Monday
|
||||
|
||||
const expected = [
|
||||
"later_today",
|
||||
"tomorrow",
|
||||
"next_week",
|
||||
"two_weeks",
|
||||
"next_month",
|
||||
"two_months",
|
||||
"three_months",
|
||||
"four_months",
|
||||
"six_months",
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
buildTimeframes(buildOptions(moment())).mapBy("id"),
|
||||
expected
|
||||
);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
test("doesn't output 'Next Week' on Sundays", function (assert) {
|
||||
const timezone = moment.tz.guess();
|
||||
const clock = fakeTime("2100-06-13T08:00:00", timezone, true); // Sunday
|
||||
|
||||
assert.ok(
|
||||
!buildTimeframes(buildOptions(moment())).mapBy("id").includes("next_week")
|
||||
);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
test("outputs 'This Weekend' if it's enabled", function (assert) {
|
||||
const timezone = moment.tz.guess();
|
||||
const clock = fakeTime("2100-06-07T08:00:00", timezone, true); // Monday
|
||||
|
||||
assert.ok(
|
||||
buildTimeframes(buildOptions(moment(), { includeWeekend: true }))
|
||||
.mapBy("id")
|
||||
.includes("this_weekend")
|
||||
);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
test("doesn't output 'This Weekend' on Fridays", function (assert) {
|
||||
const timezone = moment.tz.guess();
|
||||
const clock = fakeTime("2100-04-23 18:00:00", timezone, true); // Friday
|
||||
|
||||
assert.ok(
|
||||
!buildTimeframes(buildOptions(moment(), { includeWeekend: true }))
|
||||
.mapBy("id")
|
||||
.includes("this_weekend")
|
||||
);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
test("doesn't show 'This Weekend' on Sundays", function (assert) {
|
||||
/*
|
||||
We need this test to avoid regressions.
|
||||
We tend to write such conditions and think that
|
||||
they mean the beginning of work week
|
||||
(Monday, Tuesday and Wednesday in this specific case):
|
||||
|
||||
if (date.day <= 3) {
|
||||
...
|
||||
}
|
||||
|
||||
In fact, Sunday will pass this check too, because
|
||||
in moment.js 0 stands for Sunday.
|
||||
*/
|
||||
|
||||
const timezone = moment.tz.guess();
|
||||
const clock = fakeTime("2100-04-25 18:00:00", timezone, true); // Sunday
|
||||
|
||||
assert.ok(
|
||||
!buildTimeframes(buildOptions(moment(), { includeWeekend: true }))
|
||||
.mapBy("id")
|
||||
.includes("this_weekend")
|
||||
);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
test("outputs 'Later This Week' instead of 'Later Today' at the end of the day", function (assert) {
|
||||
const timezone = moment.tz.guess();
|
||||
const clock = fakeTime("2100-04-19 18:00:00", timezone, true); // Monday evening
|
||||
const timeframes = buildTimeframes(buildOptions(moment())).mapBy("id");
|
||||
|
||||
assert.not(timeframes.includes("later_today"));
|
||||
assert.ok(timeframes.includes("later_this_week"));
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
test("doesn't output 'Later This Week' on Tuesdays", function (assert) {
|
||||
const timezone = moment.tz.guess();
|
||||
const clock = fakeTime("2100-04-22 18:00:00", timezone, true); // Tuesday evening
|
||||
const timeframes = buildTimeframes(buildOptions(moment())).mapBy("id");
|
||||
|
||||
assert.not(timeframes.includes("later_this_week"));
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
test("doesn't output 'Later This Week' on Sundays", function (assert) {
|
||||
/*
|
||||
We need this test to avoid regressions.
|
||||
We tend to write such conditions and think that
|
||||
they mean the beginning of business week
|
||||
(Monday, Tuesday and Wednesday in this specific case):
|
||||
|
||||
if (date.day < 3) {
|
||||
...
|
||||
}
|
||||
|
||||
In fact, Sunday will pass this check too, because
|
||||
in moment.js 0 stands for Sunday.
|
||||
*/
|
||||
const timezone = moment.tz.guess();
|
||||
const clock = fakeTime("2100-04-25 18:00:00", timezone, true); // Sunday evening
|
||||
const timeframes = buildTimeframes(buildOptions(moment())).mapBy("id");
|
||||
|
||||
assert.not(timeframes.includes("later_this_week"));
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
test("doesn't output 'Next Month' on the last day of the month", function (assert) {
|
||||
const timezone = moment.tz.guess();
|
||||
const clock = fakeTime("2100-04-30 18:00:00", timezone, true); // The last day of April
|
||||
const timeframes = buildTimeframes(buildOptions(moment())).mapBy("id");
|
||||
|
||||
assert.not(timeframes.includes("next_month"));
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
});
|
|
@ -18,7 +18,6 @@ export default ComboBoxComponent.extend({
|
|||
classNames: ["category-drop"],
|
||||
value: readOnly("category.id"),
|
||||
content: readOnly("categoriesWithShortcuts.[]"),
|
||||
tagName: "li",
|
||||
categoryStyle: readOnly("siteSettings.category_style"),
|
||||
noCategoriesLabel: I18n.t("categories.no_subcategory"),
|
||||
navigateToEdit: false,
|
||||
|
|
|
@ -7,12 +7,6 @@ import { computed } from "@ember/object";
|
|||
import layout from "select-kit/templates/components/category-row";
|
||||
import { setting } from "discourse/lib/computed";
|
||||
|
||||
function htmlToText(encodedString) {
|
||||
const elem = document.createElement("textarea");
|
||||
elem.innerHTML = encodedString;
|
||||
return elem.value;
|
||||
}
|
||||
|
||||
export default SelectKitRowComponent.extend({
|
||||
layout,
|
||||
classNames: ["category-row"],
|
||||
|
@ -34,19 +28,11 @@ export default SelectKitRowComponent.extend({
|
|||
}
|
||||
),
|
||||
|
||||
title: computed(
|
||||
"descriptionText",
|
||||
"description",
|
||||
"categoryName",
|
||||
function () {
|
||||
if (this.category) {
|
||||
return htmlToText(
|
||||
this.descriptionText || this.description || this.categoryName
|
||||
);
|
||||
}
|
||||
title: computed("categoryName", function () {
|
||||
if (this.category) {
|
||||
return this.categoryName;
|
||||
}
|
||||
),
|
||||
|
||||
}),
|
||||
categoryName: reads("category.name"),
|
||||
|
||||
categoryDescription: reads("category.description"),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import EmberObject, { computed, get } from "@ember/object";
|
||||
import EmberObject, { computed } from "@ember/object";
|
||||
import Category from "discourse/models/category";
|
||||
import I18n from "I18n";
|
||||
import MultiSelectComponent from "select-kit/components/multi-select";
|
||||
|
@ -17,7 +17,7 @@ export default MultiSelectComponent.extend({
|
|||
allowAny: false,
|
||||
allowUncategorized: "allowUncategorized",
|
||||
displayCategoryDescription: false,
|
||||
selectedNameComponent: "multi-select/selected-category",
|
||||
selectedChoiceComponent: "selected-choice-category",
|
||||
},
|
||||
|
||||
init() {
|
||||
|
@ -43,13 +43,6 @@ export default MultiSelectComponent.extend({
|
|||
|
||||
value: mapBy("categories", "id"),
|
||||
|
||||
filterComputedContent(computedContent, filter) {
|
||||
const regex = new RegExp(filter, "i");
|
||||
return computedContent.filter((category) =>
|
||||
this._normalize(get(category, "name")).match(regex)
|
||||
);
|
||||
},
|
||||
|
||||
modifyComponentForRow() {
|
||||
return "category-row";
|
||||
},
|
||||
|
|
|
@ -12,7 +12,9 @@ export default SelectKitRowComponent.extend({
|
|||
|
||||
schedule("afterRender", () => {
|
||||
const color = escapeExpression(this.rowValue);
|
||||
this.element.style.borderLeftColor = `#${color}`;
|
||||
this.element.style.borderLeftColor = color.startsWith("#")
|
||||
? color
|
||||
: `#${color}`;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,11 +6,8 @@ import { readOnly } from "@ember/object/computed";
|
|||
export default SingleSelectHeaderComponent.extend({
|
||||
layout,
|
||||
classNames: ["dropdown-select-box-header"],
|
||||
tagName: "button",
|
||||
classNameBindings: ["btnClassName", "btnStyleClass"],
|
||||
showFullTitle: readOnly("selectKit.options.showFullTitle"),
|
||||
attributeBindings: ["buttonType:type"],
|
||||
buttonType: "button",
|
||||
customStyle: readOnly("selectKit.options.customStyle"),
|
||||
|
||||
btnClassName: computed("showFullTitle", function () {
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
import MultiSelectHeaderComponent from "select-kit/components/multi-select/multi-select-header";
|
||||
import { computed } from "@ember/object";
|
||||
import { gt } from "@ember/object/computed";
|
||||
import { isTesting } from "discourse-common/config/environment";
|
||||
import layout from "select-kit/templates/components/email-group-user-chooser-header";
|
||||
|
||||
export default MultiSelectHeaderComponent.extend({
|
||||
layout,
|
||||
classNames: ["email-group-user-chooser-header"],
|
||||
hasHiddenItems: gt("hiddenItemsCount", 0),
|
||||
|
||||
shownItems: computed("hiddenItemsCount", function () {
|
||||
if (
|
||||
this.selectKit.noneItem === this.selectedContent ||
|
||||
this.hiddenItemsCount === 0
|
||||
) {
|
||||
return this.selectedContent;
|
||||
}
|
||||
return this.selectedContent.slice(
|
||||
0,
|
||||
this.selectedContent.length - this.hiddenItemsCount
|
||||
);
|
||||
}),
|
||||
|
||||
hiddenItemsCount: computed(
|
||||
"selectedContent.[]",
|
||||
"selectKit.options.autoWrap",
|
||||
"selectKit.isExpanded",
|
||||
function () {
|
||||
if (
|
||||
!this.selectKit.options.autoWrap ||
|
||||
this.selectKit.isExpanded ||
|
||||
this.selectedContent === this.selectKit.noneItem ||
|
||||
this.selectedContent.length <= 1 ||
|
||||
isTesting()
|
||||
) {
|
||||
return 0;
|
||||
} else {
|
||||
const selectKitHeaderWidth = this.element.offsetWidth;
|
||||
const choices = this.element.querySelectorAll(".selected-name.choice");
|
||||
const input = this.element.querySelector(".filter-input");
|
||||
const alreadyHidden = this.element.querySelector(".x-more-item");
|
||||
if (alreadyHidden) {
|
||||
const hiddenCount = parseInt(
|
||||
alreadyHidden.getAttribute("data-hidden-count"),
|
||||
10
|
||||
);
|
||||
return (
|
||||
hiddenCount +
|
||||
(this.selectedContent.length - (choices.length + hiddenCount))
|
||||
);
|
||||
}
|
||||
if (choices.length === 0 && this.selectedContent.length > 0) {
|
||||
return 0;
|
||||
}
|
||||
let total = choices[0].offsetWidth + input.offsetWidth;
|
||||
let shownItemsCount = 1;
|
||||
let shouldHide = false;
|
||||
for (let i = 1; i < choices.length - 1; i++) {
|
||||
const currentWidth = choices[i].offsetWidth;
|
||||
const nextWidth = choices[i + 1].offsetWidth;
|
||||
const ratio =
|
||||
(total + currentWidth + nextWidth) / selectKitHeaderWidth;
|
||||
if (ratio >= 0.95) {
|
||||
shouldHide = true;
|
||||
break;
|
||||
} else {
|
||||
shownItemsCount++;
|
||||
total += currentWidth;
|
||||
}
|
||||
}
|
||||
return shouldHide ? choices.length - shownItemsCount : 0;
|
||||
}
|
||||
}
|
||||
),
|
||||
});
|
|
@ -12,7 +12,6 @@ export default UserChooserComponent.extend({
|
|||
},
|
||||
|
||||
selectKitOptions: {
|
||||
headerComponent: "email-group-user-chooser-header",
|
||||
filterComponent: "email-group-user-chooser-filter",
|
||||
fullWidthWrap: false,
|
||||
autoWrap: false,
|
||||
|
|
|
@ -1,139 +1,10 @@
|
|||
import ComboBoxComponent from "select-kit/components/combo-box";
|
||||
import DatetimeMixin from "select-kit/components/future-date-input-selector/mixin";
|
||||
import I18n from "I18n";
|
||||
import { computed } from "@ember/object";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
|
||||
const TIMEFRAME_BASE = {
|
||||
enabled: () => true,
|
||||
when: () => null,
|
||||
icon: "briefcase",
|
||||
displayWhen: true,
|
||||
};
|
||||
|
||||
function buildTimeframe(opts) {
|
||||
return jQuery.extend({}, TIMEFRAME_BASE, opts);
|
||||
}
|
||||
|
||||
export const TIMEFRAMES = [
|
||||
buildTimeframe({
|
||||
id: "now",
|
||||
format: "h:mm a",
|
||||
enabled: (opts) => opts.canScheduleNow,
|
||||
when: (time) => time.add(1, "minute"),
|
||||
icon: "magic",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "later_today",
|
||||
format: "h a",
|
||||
enabled: (opts) => opts.canScheduleToday,
|
||||
when: (time) => time.hour(18).minute(0),
|
||||
icon: "far-moon",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "tomorrow",
|
||||
format: "ddd, h a",
|
||||
when: (time, timeOfDay) => time.add(1, "day").hour(timeOfDay).minute(0),
|
||||
icon: "far-sun",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "later_this_week",
|
||||
format: "ddd, h a",
|
||||
enabled: (opts) => !opts.canScheduleToday && opts.day > 0 && opts.day < 4,
|
||||
when: (time, timeOfDay) => time.add(2, "day").hour(timeOfDay).minute(0),
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "this_weekend",
|
||||
format: "ddd, h a",
|
||||
enabled: (opts) => opts.day > 0 && opts.day < 5 && opts.includeWeekend,
|
||||
when: (time, timeOfDay) => time.day(6).hour(timeOfDay).minute(0),
|
||||
icon: "bed",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "next_week",
|
||||
format: "ddd, h a",
|
||||
enabled: (opts) => opts.day !== 0,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(1, "week").day(1).hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "two_weeks",
|
||||
format: "MMM D",
|
||||
when: (time, timeOfDay) => time.add(2, "week").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "next_month",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.now.date() !== moment().endOf("month").date(),
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(1, "month").startOf("month").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "two_months",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.includeMidFuture,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(2, "month").startOf("month").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "three_months",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.includeMidFuture,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(3, "month").startOf("month").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "four_months",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.includeMidFuture,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(4, "month").startOf("month").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "six_months",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.includeMidFuture,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(6, "month").startOf("month").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "one_year",
|
||||
format: "MMM D",
|
||||
enabled: (opts) => opts.includeFarFuture,
|
||||
when: (time, timeOfDay) =>
|
||||
time.add(1, "year").startOf("day").hour(timeOfDay).minute(0),
|
||||
icon: "briefcase",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "forever",
|
||||
enabled: (opts) => opts.includeFarFuture,
|
||||
when: (time, timeOfDay) => time.add(1000, "year").hour(timeOfDay).minute(0),
|
||||
icon: "gavel",
|
||||
displayWhen: false,
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "pick_date_and_time",
|
||||
enabled: (opts) => opts.includeDateTime,
|
||||
icon: "far-calendar-plus",
|
||||
}),
|
||||
];
|
||||
|
||||
let _timeframeById = null;
|
||||
export function timeframeDetails(id) {
|
||||
if (!_timeframeById) {
|
||||
_timeframeById = {};
|
||||
TIMEFRAMES.forEach((t) => (_timeframeById[t.id] = t));
|
||||
}
|
||||
return _timeframeById[id];
|
||||
}
|
||||
import buildTimeframes from "discourse/lib/timeframes-builder";
|
||||
import I18n from "I18n";
|
||||
|
||||
export const FORMAT = "YYYY-MM-DD HH:mmZ";
|
||||
|
||||
|
@ -165,7 +36,7 @@ export default ComboBoxComponent.extend(DatetimeMixin, {
|
|||
canScheduleToday: 24 - now.hour() > 6,
|
||||
};
|
||||
|
||||
return TIMEFRAMES.filter((tf) => tf.enabled(opts)).map((tf) => {
|
||||
return buildTimeframes(opts).map((tf) => {
|
||||
return {
|
||||
id: tf.id,
|
||||
name: I18n.t(`topic.auto_update_input.${tf.id}`),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { CLOSE_STATUS_TYPE } from "discourse/controllers/edit-topic-timer";
|
||||
import Mixin from "@ember/object/mixin";
|
||||
import { isNone } from "@ember/utils";
|
||||
import { timeframeDetails } from "select-kit/components/future-date-input-selector";
|
||||
import { timeframeDetails } from "discourse/lib/timeframes-builder";
|
||||
|
||||
export default Mixin.create({
|
||||
_computeIconsForValue(value) {
|
||||
|
|
|
@ -9,7 +9,6 @@ export default ComboBoxComponent.extend({
|
|||
pluginApiIdentifiers: ["group-dropdown"],
|
||||
classNames: ["group-dropdown"],
|
||||
content: reads("groupsWithShortcut"),
|
||||
tagName: "li",
|
||||
valueProperty: null,
|
||||
nameProperty: null,
|
||||
hasManyGroups: gte("content.length", 10),
|
||||
|
|
|
@ -14,7 +14,7 @@ export default MultiSelectComponent.extend({
|
|||
|
||||
selectKitOptions: {
|
||||
filterable: true,
|
||||
selectedNameComponent: "selectedNameComponent",
|
||||
selectedChoiceComponent: "selectedChoiceComponent",
|
||||
},
|
||||
|
||||
modifyComponentForRow(collection) {
|
||||
|
@ -27,11 +27,11 @@ export default MultiSelectComponent.extend({
|
|||
}
|
||||
},
|
||||
|
||||
selectedNameComponent: computed("settingName", function () {
|
||||
selectedChoiceComponent: computed("settingName", function () {
|
||||
if (this.settingName && this.settingName.indexOf("color") > -1) {
|
||||
return "selected-color";
|
||||
return "selected-choice-color";
|
||||
} else {
|
||||
return "selected-name";
|
||||
return "selected-choice";
|
||||
}
|
||||
}),
|
||||
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
import { empty, or } from "@ember/object/computed";
|
||||
import ComboBox from "select-kit/components/combo-box";
|
||||
import { ERRORS_COLLECTION } from "select-kit/components/select-kit";
|
||||
import MultiSelectComponent from "select-kit/components/multi-select";
|
||||
import I18n from "I18n";
|
||||
import TagsMixin from "select-kit/mixins/tags";
|
||||
import { computed } from "@ember/object";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
|
||||
const SELECTED_TAGS_COLLECTION = "MINI_TAG_CHOOSER_SELECTED_TAGS";
|
||||
import { setting } from "discourse/lib/computed";
|
||||
|
||||
export default ComboBox.extend(TagsMixin, {
|
||||
export default MultiSelectComponent.extend(TagsMixin, {
|
||||
pluginApiIdentifiers: ["mini-tag-chooser"],
|
||||
attributeBindings: ["selectKit.options.categoryId:category-id"],
|
||||
classNames: ["mini-tag-chooser"],
|
||||
|
@ -17,20 +14,8 @@ export default ComboBox.extend(TagsMixin, {
|
|||
noTags: empty("value"),
|
||||
maxTagSearchResults: setting("max_tag_search_results"),
|
||||
maxTagsPerTopic: setting("max_tags_per_topic"),
|
||||
highlightedTag: null,
|
||||
singleSelect: false,
|
||||
|
||||
collections: computed(
|
||||
"mainCollection.[]",
|
||||
"errorsCollection.[]",
|
||||
"highlightedTag",
|
||||
function () {
|
||||
return this._super(...arguments);
|
||||
}
|
||||
),
|
||||
|
||||
selectKitOptions: {
|
||||
headerComponent: "mini-tag-chooser/mini-tag-chooser-header",
|
||||
fullWidthOnMobile: true,
|
||||
filterable: true,
|
||||
caretDownIcon: "caretIcon",
|
||||
|
@ -41,6 +26,7 @@ export default ComboBox.extend(TagsMixin, {
|
|||
none: "tagging.choose_for_topic",
|
||||
closeOnChange: false,
|
||||
maximum: "maximumSelectedTags",
|
||||
minimum: "minimumSelectedTags",
|
||||
autoInsertNoneItem: false,
|
||||
},
|
||||
|
||||
|
@ -52,21 +38,6 @@ export default ComboBox.extend(TagsMixin, {
|
|||
return "tag-row";
|
||||
},
|
||||
|
||||
modifyComponentForCollection(collection) {
|
||||
if (collection === SELECTED_TAGS_COLLECTION) {
|
||||
return "mini-tag-chooser/selected-collection";
|
||||
}
|
||||
},
|
||||
|
||||
modifyContentForCollection(collection) {
|
||||
if (collection === SELECTED_TAGS_COLLECTION) {
|
||||
return {
|
||||
selectedTags: this.value,
|
||||
highlightedTag: this.highlightedTag,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
allowAnyTag: or("allowCreate", "site.can_create_tag"),
|
||||
|
||||
maximumSelectedTags: computed(function () {
|
||||
|
@ -78,7 +49,7 @@ export default ComboBox.extend(TagsMixin, {
|
|||
);
|
||||
}),
|
||||
|
||||
modifyNoSelection() {
|
||||
minimumSelectedTags: computed(function () {
|
||||
if (
|
||||
this.selectKit.options.minimum ||
|
||||
this.selectKit.options.requiredTagGroups
|
||||
|
@ -91,42 +62,18 @@ export default ComboBox.extend(TagsMixin, {
|
|||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.insertAfterCollection(ERRORS_COLLECTION, SELECTED_TAGS_COLLECTION);
|
||||
},
|
||||
|
||||
caretIcon: computed("value.[]", function () {
|
||||
caretIcon: computed("value.[]", "content.[]", function () {
|
||||
const maximum = this.selectKit.options.maximum;
|
||||
return maximum && makeArray(this.value).length >= parseInt(maximum, 10)
|
||||
? null
|
||||
: "plus";
|
||||
}),
|
||||
|
||||
modifySelection(content) {
|
||||
const minimum = this.selectKit.options.minimum;
|
||||
if (minimum && makeArray(this.value).length < parseInt(minimum, 10)) {
|
||||
const key =
|
||||
this.selectKit.options.minimumLabel ||
|
||||
"select_kit.min_content_not_reached";
|
||||
const label = I18n.t(key, { count: this.selectKit.options.minimum });
|
||||
content.title = content.name = content.label = label;
|
||||
} else {
|
||||
content.name = content.value = makeArray(this.value).join(",");
|
||||
content.title = content.label = makeArray(this.value).join(", ");
|
||||
|
||||
if (content.label.length > 32) {
|
||||
content.label = `${content.label.slice(0, 32)}...`;
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
},
|
||||
content: computed("value.[]", function () {
|
||||
return makeArray(this.value).map((x) => this.defaultItem(x, x));
|
||||
}),
|
||||
|
||||
search(filter) {
|
||||
const data = {
|
||||
|
@ -147,6 +94,10 @@ export default ComboBox.extend(TagsMixin, {
|
|||
},
|
||||
|
||||
_transformJson(context, json) {
|
||||
if (context.isDestroyed || context.isDestroying) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let results = json.results;
|
||||
|
||||
context.setProperties({
|
||||
|
@ -158,79 +109,10 @@ export default ComboBox.extend(TagsMixin, {
|
|||
results = results.sort((a, b) => a.text.localeCompare(b.text));
|
||||
}
|
||||
|
||||
results = results
|
||||
return results
|
||||
.filter((r) => !makeArray(context.tags).includes(r.id))
|
||||
.map((result) => {
|
||||
return { id: result.text, name: result.text, count: result.count };
|
||||
});
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
select(value) {
|
||||
this._reset();
|
||||
|
||||
if (!this.validateSelect(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tags = [...new Set(makeArray(this.value).concat(value))];
|
||||
this.selectKit.change(tags, tags);
|
||||
},
|
||||
|
||||
deselect(value) {
|
||||
this._reset();
|
||||
|
||||
const tags = [...new Set(makeArray(this.value).removeObject(value))];
|
||||
this.selectKit.change(tags, tags);
|
||||
},
|
||||
|
||||
_reset() {
|
||||
this.clearErrors();
|
||||
this.set("highlightedTag", null);
|
||||
},
|
||||
|
||||
_onKeydown(event) {
|
||||
const value = makeArray(this.value);
|
||||
|
||||
if (event.key === "Backspace") {
|
||||
if (!this.selectKit.filter) {
|
||||
this._onBackspace(this.value, this.highlightedTag);
|
||||
}
|
||||
} else if (event.key === "ArrowLeft") {
|
||||
if (this.highlightedTag) {
|
||||
const index = value.indexOf(this.highlightedTag);
|
||||
const highlightedTag = value[index - 1]
|
||||
? value[index - 1]
|
||||
: value.lastObject;
|
||||
this.set("highlightedTag", highlightedTag);
|
||||
} else {
|
||||
this.set("highlightedTag", value.lastObject);
|
||||
}
|
||||
} else if (event.key === "ArrowRight") {
|
||||
if (this.highlightedTag) {
|
||||
const index = value.indexOf(this.highlightedTag);
|
||||
const highlightedTag = value[index + 1]
|
||||
? value[index + 1]
|
||||
: value.firstObject;
|
||||
this.set("highlightedTag", highlightedTag);
|
||||
} else {
|
||||
this.set("highlightedTag", value.firstObject);
|
||||
}
|
||||
} else {
|
||||
this.set("highlightedTag", null);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_onBackspace(value, highlightedTag) {
|
||||
if (value && value.length) {
|
||||
if (!highlightedTag) {
|
||||
this.set("highlightedTag", value.lastObject);
|
||||
} else {
|
||||
this.deselect(highlightedTag);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,54 +1,32 @@
|
|||
import { empty, reads } from "@ember/object/computed";
|
||||
import { reads } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import { computed } from "@ember/object";
|
||||
import layout from "select-kit/templates/components/mini-tag-chooser/selected-collection";
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
|
||||
layout,
|
||||
classNames: [
|
||||
"mini-tag-chooser-selected-collection",
|
||||
"selected-tags",
|
||||
"shouldHide:hidden",
|
||||
],
|
||||
shouldHide: empty("selectedTags.[]"),
|
||||
|
||||
selectedTags: reads("collection.content.selectedTags.[]"),
|
||||
highlightedTag: reads("collection.content.highlightedTag"),
|
||||
|
||||
tags: computed(
|
||||
"selectedTags.[]",
|
||||
"highlightedTag",
|
||||
"selectKit.filter",
|
||||
function () {
|
||||
if (!this.selectedTags) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let tags = this.selectedTags;
|
||||
if (tags.length >= 20 && this.selectKit.filter) {
|
||||
tags = tags.filter((t) => t.indexOf(this.selectKit.filter) >= 0);
|
||||
} else if (tags.length >= 20) {
|
||||
tags = tags.slice(0, 20);
|
||||
}
|
||||
|
||||
tags = tags.map((selectedTag) => {
|
||||
const classNames = ["selected-tag"];
|
||||
if (selectedTag === this.highlightedTag) {
|
||||
classNames.push("is-highlighted");
|
||||
}
|
||||
|
||||
return {
|
||||
value: selectedTag,
|
||||
classNames: classNames.join(" "),
|
||||
};
|
||||
});
|
||||
|
||||
return tags;
|
||||
tags: computed("selectedTags.[]", "selectKit.filter", function () {
|
||||
if (!this.selectedTags) {
|
||||
return [];
|
||||
}
|
||||
),
|
||||
|
||||
actions: {
|
||||
deselectTag(tag) {
|
||||
return this.selectKit.deselect(tag);
|
||||
},
|
||||
},
|
||||
let tags = this.selectedTags;
|
||||
if (tags.length >= 20 && this.selectKit.filter) {
|
||||
tags = tags.filter((t) => t.indexOf(this.selectKit.filter) >= 0);
|
||||
} else if (tags.length >= 20) {
|
||||
tags = tags.slice(0, 20);
|
||||
}
|
||||
|
||||
return tags.map((selectedTag) => {
|
||||
return {
|
||||
value: selectedTag,
|
||||
classNames: "selected-tag",
|
||||
};
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import SelectKitComponent from "select-kit/components/select-kit";
|
||||
import { computed } from "@ember/object";
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
import { isPresent } from "@ember/utils";
|
||||
import { next } from "@ember/runloop";
|
||||
import layout from "select-kit/templates/components/multi-select";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
|
||||
|
@ -16,13 +16,21 @@ export default SelectKitComponent.extend({
|
|||
clearable: true,
|
||||
filterable: true,
|
||||
filterIcon: null,
|
||||
clearOnClick: true,
|
||||
closeOnChange: false,
|
||||
autoInsertNoneItem: false,
|
||||
headerComponent: "multi-select/multi-select-header",
|
||||
filterComponent: "multi-select/multi-select-filter",
|
||||
autoFilterable: true,
|
||||
caretDownIcon: "caretIcon",
|
||||
caretUpIcon: "caretIcon",
|
||||
},
|
||||
|
||||
caretIcon: computed("value.[]", function () {
|
||||
const maximum = this.selectKit.options.maximum;
|
||||
return maximum && makeArray(this.value).length >= parseInt(maximum, 10)
|
||||
? null
|
||||
: "plus";
|
||||
}),
|
||||
|
||||
search(filter) {
|
||||
return this._super(filter).filter(
|
||||
(content) => !makeArray(this.selectedContent).includes(content)
|
||||
|
@ -68,6 +76,16 @@ export default SelectKitComponent.extend({
|
|||
},
|
||||
|
||||
select(value, item) {
|
||||
if (this.selectKit.hasSelection && this.selectKit.options.maximum === 1) {
|
||||
this.selectKit.deselectByValue(
|
||||
this.getValue(this.selectedContent.firstObject)
|
||||
);
|
||||
next(() => {
|
||||
this.selectKit.select(value, item);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPresent(value)) {
|
||||
if (!this.validateSelect(this.selectKit.highlighted)) {
|
||||
return;
|
||||
|
@ -98,7 +116,7 @@ export default SelectKitComponent.extend({
|
|||
);
|
||||
|
||||
this.selectKit.change(
|
||||
newValues,
|
||||
[...new Set(newValues)],
|
||||
newContent.length
|
||||
? newContent
|
||||
: makeArray(this.defaultItem(value, value))
|
||||
|
@ -131,9 +149,9 @@ export default SelectKitComponent.extend({
|
|||
});
|
||||
|
||||
return this.selectKit.modifySelection(content);
|
||||
} else {
|
||||
return this.selectKit.noneItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}),
|
||||
|
||||
_onKeydown(event) {
|
||||
|
@ -142,7 +160,6 @@ export default SelectKitComponent.extend({
|
|||
event.target.classList.contains("selected-name")
|
||||
) {
|
||||
event.stopPropagation();
|
||||
|
||||
this.selectKit.deselectByValue(event.target.dataset.value);
|
||||
return false;
|
||||
}
|
||||
|
@ -171,23 +188,4 @@ export default SelectKitComponent.extend({
|
|||
|
||||
return true;
|
||||
},
|
||||
|
||||
handleDeprecations() {
|
||||
this._super(...arguments);
|
||||
|
||||
this._deprecateValues();
|
||||
},
|
||||
|
||||
_deprecateValues() {
|
||||
if (this.values && !this.value) {
|
||||
deprecated(
|
||||
"The `values` property is deprecated for multi-select. Use `value` instead",
|
||||
{
|
||||
since: "v2.4.0",
|
||||
}
|
||||
);
|
||||
|
||||
this.set("value", this.values);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import Component from "@ember/component";
|
||||
import { computed } from "@ember/object";
|
||||
import layout from "select-kit/templates/components/multi-select/format-selected-content";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
import UtilsMixin from "select-kit/mixins/utils";
|
||||
|
||||
export default Component.extend(UtilsMixin, {
|
||||
tagName: "",
|
||||
layout,
|
||||
content: null,
|
||||
selectKit: null,
|
||||
|
||||
formatedContent: computed("content", function () {
|
||||
if (this.content) {
|
||||
return makeArray(this.content)
|
||||
.map((c) => this.getName(c))
|
||||
.join(", ");
|
||||
} else {
|
||||
return this.getName(this.selectKit.noneItem);
|
||||
}
|
||||
}),
|
||||
});
|
|
@ -1,33 +1,21 @@
|
|||
import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header";
|
||||
import { computed } from "@ember/object";
|
||||
import layout from "select-kit/templates/components/multi-select/multi-select-header";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
import { computed } from "@ember/object";
|
||||
import { reads } from "@ember/object/computed";
|
||||
|
||||
export default SelectKitHeaderComponent.extend({
|
||||
tagName: "summary",
|
||||
classNames: ["multi-select-header"],
|
||||
layout,
|
||||
|
||||
selectedNames: computed("selectedContent", function () {
|
||||
return makeArray(this.selectedContent).map((c) => this.getName(c));
|
||||
}),
|
||||
|
||||
hasReachedMaximumSelection: computed("selectedValue", function () {
|
||||
if (!this.selectKit.options.maximum) {
|
||||
return false;
|
||||
caretUpIcon: reads("selectKit.options.caretUpIcon"),
|
||||
caretDownIcon: reads("selectKit.options.caretDownIcon"),
|
||||
caretIcon: computed(
|
||||
"selectKit.isExpanded",
|
||||
"caretUpIcon",
|
||||
"caretDownIcon",
|
||||
function () {
|
||||
return this.selectKit.isExpanded ? this.caretUpIcon : this.caretDownIcon;
|
||||
}
|
||||
|
||||
return this.selectedValue.length >= this.selectKit.options.maximum;
|
||||
}),
|
||||
|
||||
selectedValue: computed("selectedContent", function () {
|
||||
return makeArray(this.selectedContent)
|
||||
.map((c) => {
|
||||
if (this.getName(c) !== this.getName(this.selectKit.noneItem)) {
|
||||
return this.getValue(c);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { INPUT_DELAY } from "discourse-common/config/environment";
|
||||
import EmberObject, { computed, get } from "@ember/object";
|
||||
import PluginApiMixin, {
|
||||
applyContentPluginApiCallbacks,
|
||||
|
@ -36,6 +37,7 @@ export default Component.extend(
|
|||
PluginApiMixin,
|
||||
UtilsMixin,
|
||||
{
|
||||
tagName: "details",
|
||||
pluginApiIdentifiers: ["select-kit"],
|
||||
classNames: ["select-kit"],
|
||||
classNameBindings: [
|
||||
|
@ -75,7 +77,7 @@ export default Component.extend(
|
|||
this.set(
|
||||
"selectKit",
|
||||
EmberObject.create({
|
||||
uniqueID: guidFor(this),
|
||||
uniqueID: this.attrs?.id || guidFor(this),
|
||||
valueProperty: this.valueProperty,
|
||||
nameProperty: this.nameProperty,
|
||||
labelProperty: this.labelProperty,
|
||||
|
@ -112,11 +114,16 @@ export default Component.extend(
|
|||
open: bind(this, this._open),
|
||||
highlightNext: bind(this, this._highlightNext),
|
||||
highlightPrevious: bind(this, this._highlightPrevious),
|
||||
highlightLast: bind(this, this._highlightLast),
|
||||
highlightFirst: bind(this, this._highlightFirst),
|
||||
change: bind(this, this._onChangeWrapper),
|
||||
select: bind(this, this.select),
|
||||
deselect: bind(this, this.deselect),
|
||||
deselectByValue: bind(this, this.deselectByValue),
|
||||
append: bind(this, this.append),
|
||||
cancelSearch: bind(this, this._cancelSearch),
|
||||
triggerSearch: bind(this, this.triggerSearch),
|
||||
focusFilter: bind(this, this._focusFilter),
|
||||
|
||||
onOpen: bind(this, this._onOpenWrapper),
|
||||
onClose: bind(this, this._onCloseWrapper),
|
||||
|
@ -124,6 +131,10 @@ export default Component.extend(
|
|||
onClearSelection: bind(this, this._onClearSelection),
|
||||
onHover: bind(this, this._onHover),
|
||||
onKeydown: bind(this, this._onKeydownWrapper),
|
||||
|
||||
mainElement: bind(this, this._mainElement),
|
||||
headerElement: bind(this, this._headerElement),
|
||||
bodyElement: bind(this, this._bodyElement),
|
||||
})
|
||||
);
|
||||
},
|
||||
|
@ -185,15 +196,23 @@ export default Component.extend(
|
|||
this.handleDeprecations();
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.element.addEventListener("toggle", this.selectKit.toggle);
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
this._searchPromise && cancel(this._searchPromise);
|
||||
this._cancelSearch();
|
||||
|
||||
if (this.popper) {
|
||||
this.popper.destroy();
|
||||
this.popper = null;
|
||||
}
|
||||
|
||||
this.element.removeEventListener("toggle", this.selectKit.toggle);
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
|
@ -260,7 +279,7 @@ export default Component.extend(
|
|||
filterable: false,
|
||||
autoFilterable: "autoFilterable",
|
||||
filterIcon: "search",
|
||||
filterPlaceholder: "filterPlaceholder",
|
||||
filterPlaceholder: null,
|
||||
translatedFilterPlaceholder: null,
|
||||
icon: null,
|
||||
icons: null,
|
||||
|
@ -269,13 +288,12 @@ export default Component.extend(
|
|||
minimum: null,
|
||||
minimumLabel: null,
|
||||
autoInsertNoneItem: true,
|
||||
clearOnClick: false,
|
||||
closeOnChange: true,
|
||||
limitMatches: null,
|
||||
placement: isDocumentRTL() ? "bottom-end" : "bottom-start",
|
||||
placementStrategy: null,
|
||||
filterComponent: "select-kit/select-kit-filter",
|
||||
selectedNameComponent: "selected-name",
|
||||
selectedChoiceComponent: "selected-choice",
|
||||
castInteger: false,
|
||||
preventsClickPropagation: false,
|
||||
focusAfterOnChange: true,
|
||||
|
@ -291,12 +309,6 @@ export default Component.extend(
|
|||
);
|
||||
}),
|
||||
|
||||
filterPlaceholder: computed("options.allowAny", function () {
|
||||
return this.options.allowAny
|
||||
? "select_kit.filter_placeholder_with_any"
|
||||
: "select_kit.filter_placeholder";
|
||||
}),
|
||||
|
||||
collections: computed(
|
||||
"selectedContent.[]",
|
||||
"mainCollection.[]",
|
||||
|
@ -386,11 +398,18 @@ export default Component.extend(
|
|||
cancel(this._searchPromise);
|
||||
}
|
||||
|
||||
discourseDebounce(this, this._debouncedInput, event.target.value, 200);
|
||||
this.selectKit.set("isLoading", true);
|
||||
|
||||
discourseDebounce(
|
||||
this,
|
||||
this._debouncedInput,
|
||||
event.target.value,
|
||||
INPUT_DELAY
|
||||
);
|
||||
},
|
||||
|
||||
_debouncedInput(filter) {
|
||||
this.selectKit.setProperties({ filter, isLoading: true });
|
||||
this.selectKit.set("filter", filter);
|
||||
this.triggerSearch(filter);
|
||||
},
|
||||
|
||||
|
@ -435,8 +454,11 @@ export default Component.extend(
|
|||
resolve(items);
|
||||
}).finally(() => {
|
||||
if (!this.isDestroying && !this.isDestroyed) {
|
||||
if (this.selectKit.options.closeOnChange) {
|
||||
this.selectKit.close();
|
||||
if (
|
||||
this.selectKit.options.closeOnChange &&
|
||||
this.selectKit.mainElement()
|
||||
) {
|
||||
this.selectKit.mainElement().open = false;
|
||||
}
|
||||
|
||||
if (this.selectKit.options.focusAfterOnChange) {
|
||||
|
@ -505,6 +527,18 @@ export default Component.extend(
|
|||
return this._boundaryActionHandler("onKeydown", event);
|
||||
},
|
||||
|
||||
_mainElement() {
|
||||
return document.querySelector(`#${this.selectKit.uniqueID}`);
|
||||
},
|
||||
|
||||
_headerElement() {
|
||||
return this.selectKit.mainElement().querySelector("summary");
|
||||
},
|
||||
|
||||
_bodyElement() {
|
||||
return this.selectKit.mainElement().querySelector(".select-kit-body");
|
||||
},
|
||||
|
||||
_onHover(value, item) {
|
||||
throttle(this, this._highlight, item, 25, true);
|
||||
},
|
||||
|
@ -572,15 +606,18 @@ export default Component.extend(
|
|||
},
|
||||
|
||||
triggerSearch(filter) {
|
||||
if (this._searchPromise) {
|
||||
cancel(this._searchPromise);
|
||||
}
|
||||
this._searchPromise && cancel(this._searchPromise);
|
||||
|
||||
this._searchPromise = this._searchWrapper(
|
||||
filter || this.selectKit.filter
|
||||
);
|
||||
},
|
||||
|
||||
_searchWrapper(filter) {
|
||||
if (this.isDestroyed || this.isDestroying) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
this.clearErrors();
|
||||
this.setProperties({
|
||||
mainCollection: [],
|
||||
|
@ -593,6 +630,10 @@ export default Component.extend(
|
|||
|
||||
return Promise.resolve(this.search(filter))
|
||||
.then((result) => {
|
||||
if (this.isDestroyed || this.isDestroying) {
|
||||
return [];
|
||||
}
|
||||
|
||||
content = content.concat(makeArray(result));
|
||||
content = this.selectKit.modifyContent(content).filter(Boolean);
|
||||
|
||||
|
@ -620,7 +661,6 @@ export default Component.extend(
|
|||
}
|
||||
|
||||
const hasNoContent = isEmpty(content);
|
||||
|
||||
if (
|
||||
this.selectKit.hasSelection &&
|
||||
noneItem &&
|
||||
|
@ -635,6 +675,8 @@ export default Component.extend(
|
|||
highlighted:
|
||||
this.singleSelect && this.value
|
||||
? this.itemForValue(this.value, this.mainCollection)
|
||||
: isEmpty(this.selectKit.filter)
|
||||
? null
|
||||
: this.mainCollection.firstObject,
|
||||
isLoading: false,
|
||||
hasNoContent,
|
||||
|
@ -665,17 +707,29 @@ export default Component.extend(
|
|||
});
|
||||
},
|
||||
|
||||
_scrollToRow(rowItem) {
|
||||
_scrollToRow(rowItem, preventScroll = true) {
|
||||
const value = this.getValue(rowItem);
|
||||
const rowContainer = this.element.querySelector(
|
||||
`.select-kit-row[data-value="${value}"]`
|
||||
);
|
||||
rowContainer && rowContainer.focus({ preventScroll });
|
||||
},
|
||||
|
||||
if (rowContainer) {
|
||||
const collectionContainer = rowContainer.parentNode;
|
||||
_highlightLast() {
|
||||
const highlighted = this.mainCollection.objectAt(
|
||||
this.mainCollection.length - 1
|
||||
);
|
||||
if (highlighted) {
|
||||
this._scrollToRow(highlighted, false);
|
||||
this.set("selectKit.highlighted", highlighted);
|
||||
}
|
||||
},
|
||||
|
||||
collectionContainer.scrollTop =
|
||||
rowContainer.offsetTop - collectionContainer.offsetTop;
|
||||
_highlightFirst() {
|
||||
const highlighted = this.mainCollection.objectAt(0);
|
||||
if (highlighted) {
|
||||
this._scrollToRow(highlighted, false);
|
||||
this.set("selectKit.highlighted", highlighted);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -688,12 +742,16 @@ export default Component.extend(
|
|||
if (highlightedIndex < count - 1) {
|
||||
highlightedIndex = highlightedIndex + 1;
|
||||
} else {
|
||||
highlightedIndex = 0;
|
||||
if (this.selectKit.isFilterExpanded) {
|
||||
this._focusFilter();
|
||||
} else {
|
||||
highlightedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const highlighted = this.mainCollection.objectAt(highlightedIndex);
|
||||
if (highlighted) {
|
||||
this._scrollToRow(highlighted);
|
||||
this._scrollToRow(highlighted, false);
|
||||
this.set("selectKit.highlighted", highlighted);
|
||||
}
|
||||
},
|
||||
|
@ -707,12 +765,16 @@ export default Component.extend(
|
|||
if (highlightedIndex > 0) {
|
||||
highlightedIndex = highlightedIndex - 1;
|
||||
} else {
|
||||
highlightedIndex = count - 1;
|
||||
if (this.selectKit.isFilterExpanded) {
|
||||
this._focusFilter();
|
||||
} else {
|
||||
highlightedIndex = count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
const highlighted = this.mainCollection.objectAt(highlightedIndex);
|
||||
if (highlighted) {
|
||||
this._scrollToRow(highlighted);
|
||||
this._scrollToRow(highlighted, false);
|
||||
this.set("selectKit.highlighted", highlighted);
|
||||
}
|
||||
},
|
||||
|
@ -747,7 +809,12 @@ export default Component.extend(
|
|||
return this._boundaryActionHandler("onOpen");
|
||||
},
|
||||
|
||||
_cancelSearch() {
|
||||
this._searchPromise && cancel(this._searchPromise);
|
||||
},
|
||||
|
||||
_onCloseWrapper() {
|
||||
this._cancelSearch();
|
||||
this.set("selectKit.highlighted", null);
|
||||
|
||||
return this._boundaryActionHandler("onClose");
|
||||
|
@ -768,6 +835,12 @@ export default Component.extend(
|
|||
|
||||
this.clearErrors();
|
||||
|
||||
const inModal = this.element.closest("#discourse-modal");
|
||||
if (inModal && this.site.mobileView) {
|
||||
const modalBody = inModal.querySelector(".modal-body");
|
||||
modalBody.style = "";
|
||||
}
|
||||
|
||||
this.selectKit.onClose(event);
|
||||
|
||||
this.selectKit.setProperties({
|
||||
|
@ -783,6 +856,8 @@ export default Component.extend(
|
|||
|
||||
this.clearErrors();
|
||||
|
||||
const inModal = this.element.closest("#discourse-modal");
|
||||
|
||||
this.selectKit.onOpen(event);
|
||||
|
||||
if (!this.popper) {
|
||||
|
@ -793,13 +868,7 @@ export default Component.extend(
|
|||
`#${this.selectKit.uniqueID}-body`
|
||||
);
|
||||
|
||||
const inModal = $(this.element).parents("#discourse-modal").length;
|
||||
|
||||
let placementStrategy = this.selectKit.options.placementStrategy;
|
||||
if (!placementStrategy) {
|
||||
placementStrategy = inModal ? "fixed" : "absolute";
|
||||
}
|
||||
|
||||
const placementStrategy = this.site.mobileView ? "absolute" : "fixed";
|
||||
const verticalOffset = 3;
|
||||
|
||||
this.popper = createPopper(anchor, popper, {
|
||||
|
@ -855,62 +924,32 @@ export default Component.extend(
|
|||
requires: ["computeStyles"],
|
||||
fn: ({ state }) => {
|
||||
state.styles.popper.minWidth = `${state.rects.reference.width}px`;
|
||||
|
||||
if (state.rects.reference.width >= 300) {
|
||||
state.styles.popper.maxWidth = `${state.rects.reference.width}px`;
|
||||
} else {
|
||||
state.styles.popper.maxWidth = "300px";
|
||||
}
|
||||
},
|
||||
effect: ({ state }) => {
|
||||
state.elements.popper.style.minWidth = `${state.elements.reference.offsetWidth}px`;
|
||||
|
||||
if (state.elements.reference.offsetWidth >= 300) {
|
||||
state.elements.popper.style.maxWidth = `${state.elements.reference.offsetWidth}px`;
|
||||
} else {
|
||||
state.elements.popper.style.maxWidth = "300px";
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positionWrapper",
|
||||
name: "modalHeight",
|
||||
enabled: !!(inModal && this.site.mobileView),
|
||||
phase: "afterWrite",
|
||||
enabled: true,
|
||||
fn: (data) => {
|
||||
const wrapper = this.element.querySelector(
|
||||
".select-kit-wrapper"
|
||||
);
|
||||
if (wrapper) {
|
||||
let height = this.element.offsetHeight + verticalOffset;
|
||||
|
||||
const body = this.element.querySelector(".select-kit-body");
|
||||
if (body) {
|
||||
height += body.offsetHeight;
|
||||
}
|
||||
|
||||
const popperElement = data.state.elements.popper;
|
||||
const topPlacement =
|
||||
popperElement &&
|
||||
popperElement
|
||||
.getAttribute("data-popper-placement")
|
||||
.startsWith("top-");
|
||||
if (topPlacement) {
|
||||
this.element.classList.remove("is-under");
|
||||
this.element.classList.add("is-above");
|
||||
} else {
|
||||
this.element.classList.remove("is-above");
|
||||
this.element.classList.add("is-under");
|
||||
}
|
||||
|
||||
wrapper.style.width = `${this.element.offsetWidth}px`;
|
||||
wrapper.style.height = `${height}px`;
|
||||
if (placementStrategy === "fixed") {
|
||||
const rects = this.element.getClientRects()[0];
|
||||
|
||||
if (rects) {
|
||||
const bodyRects = body && body.getClientRects()[0];
|
||||
|
||||
wrapper.style.position = "fixed";
|
||||
wrapper.style.left = `${rects.left}px`;
|
||||
if (topPlacement && bodyRects) {
|
||||
wrapper.style.top = `${rects.top - bodyRects.height}px`;
|
||||
} else {
|
||||
wrapper.style.top = `${rects.top}px`;
|
||||
}
|
||||
if (isDocumentRTL()) {
|
||||
wrapper.style.right = "unset";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn: ({ state }) => {
|
||||
const modalBody = inModal.querySelector(".modal-body");
|
||||
modalBody.style = "";
|
||||
modalBody.style.height =
|
||||
modalBody.clientHeight + state.rects.popper.height + "px";
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -953,6 +992,16 @@ export default Component.extend(
|
|||
},
|
||||
|
||||
_focusFilter(forceHeader = false) {
|
||||
if (!this.selectKit.mainElement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.selectKit.mainElement().open) {
|
||||
const headerContainer = this.getHeader();
|
||||
headerContainer && headerContainer.focus({ preventScroll: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this._safeAfterRender(() => {
|
||||
const input = this.getFilterInput();
|
||||
if (!forceHeader && input) {
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import Component from "@ember/component";
|
||||
import { empty } from "@ember/object/computed";
|
||||
import layout from "select-kit/templates/components/select-kit/errors-collection";
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
classNames: ["select-kit-errors-collection"],
|
||||
classNameBindings: ["shouldHide:hidden"],
|
||||
tagName: "ul",
|
||||
shouldHide: empty("collection.content"),
|
||||
tagName: "",
|
||||
});
|
||||
|
|
|
@ -6,14 +6,13 @@ import layout from "select-kit/templates/components/select-kit/select-kit-body";
|
|||
export default Component.extend({
|
||||
layout,
|
||||
classNames: ["select-kit-body"],
|
||||
attributeBindings: ["role"],
|
||||
classNameBindings: ["emptyBody:empty-body"],
|
||||
emptyBody: computed("selectKit.{filter,hasNoContent}", function () {
|
||||
return !this.selectKit.filter && this.selectKit.hasNoContent;
|
||||
}),
|
||||
rootEventType: "click",
|
||||
|
||||
role: "listbox",
|
||||
emptyBody: computed("selectKit.{filter,hasNoContent}", function () {
|
||||
return false;
|
||||
}),
|
||||
|
||||
rootEventType: "click",
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
@ -24,6 +23,8 @@ export default Component.extend({
|
|||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.element.style.position = "relative";
|
||||
|
||||
document.addEventListener(
|
||||
this.rootEventType,
|
||||
this.handleRootMouseDownHandler,
|
||||
|
@ -58,6 +59,8 @@ export default Component.extend({
|
|||
return;
|
||||
}
|
||||
|
||||
this.selectKit.close(event);
|
||||
if (this.selectKit.mainElement()) {
|
||||
this.selectKit.mainElement().open = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import Component from "@ember/component";
|
||||
import { empty } from "@ember/object/computed";
|
||||
import layout from "select-kit/templates/components/select-kit/select-kit-collection";
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
classNames: ["select-kit-collection"],
|
||||
classNameBindings: ["shouldHide:hidden"],
|
||||
tagName: "ul",
|
||||
shouldHide: empty("collection"),
|
||||
tagName: "",
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import UtilsMixin from "select-kit/mixins/utils";
|
||||
import { computed } from "@ember/object";
|
||||
import { action, computed } from "@ember/object";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { isPresent } from "@ember/utils";
|
||||
import layout from "select-kit/templates/components/select-kit/select-kit-filter";
|
||||
|
@ -12,8 +12,7 @@ export default Component.extend(UtilsMixin, {
|
|||
classNames: ["select-kit-filter"],
|
||||
classNameBindings: ["isExpanded:is-expanded"],
|
||||
attributeBindings: ["role"],
|
||||
|
||||
role: "searchbox",
|
||||
tabIndex: -1,
|
||||
|
||||
isHidden: computed(
|
||||
"selectKit.options.{filterable,allowAny,autoFilterable}",
|
||||
|
@ -31,7 +30,8 @@ export default Component.extend(UtilsMixin, {
|
|||
|
||||
@discourseComputed(
|
||||
"selectKit.options.filterPlaceholder",
|
||||
"selectKit.options.translatedFilterPlaceholder"
|
||||
"selectKit.options.translatedFilterPlaceholder",
|
||||
"selectKit.options.allowAny"
|
||||
)
|
||||
placeholder(placeholder, translatedPlaceholder) {
|
||||
if (isPresent(translatedPlaceholder)) {
|
||||
|
@ -42,87 +42,83 @@ export default Component.extend(UtilsMixin, {
|
|||
return I18n.t(placeholder);
|
||||
}
|
||||
|
||||
return "";
|
||||
return I18n.t(
|
||||
this.selectKit.options.allowAny
|
||||
? "select_kit.filter_placeholder_with_any"
|
||||
: "select_kit.filter_placeholder"
|
||||
);
|
||||
},
|
||||
|
||||
actions: {
|
||||
onPaste() {},
|
||||
@action
|
||||
onPaste() {},
|
||||
|
||||
onInput(event) {
|
||||
this.selectKit.onInput(event);
|
||||
@action
|
||||
onInput(event) {
|
||||
this.selectKit.onInput(event);
|
||||
return true;
|
||||
},
|
||||
|
||||
@action
|
||||
onKeyup(event) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
return true;
|
||||
},
|
||||
|
||||
@action
|
||||
onKeydown(event) {
|
||||
if (!this.selectKit.onKeydown(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "Tab" && this.selectKit.isLoading) {
|
||||
this.selectKit.cancelSearch();
|
||||
this.selectKit.mainElement().open = false;
|
||||
return true;
|
||||
},
|
||||
}
|
||||
|
||||
onKeyup(event) {
|
||||
if (event.key === "Enter" && this.selectKit.enterDisabled) {
|
||||
this.element.querySelector("input").focus();
|
||||
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowUp") {
|
||||
this.selectKit.highlightLast();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowDown") {
|
||||
this.selectKit.highlightFirst();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "Escape") {
|
||||
this.selectKit.mainElement().open = false;
|
||||
this.selectKit.headerElement().focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "Enter" && this.selectKit.highlighted) {
|
||||
this.selectKit.select(
|
||||
this.getValue(this.selectKit.highlighted),
|
||||
this.selectKit.highlighted
|
||||
);
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
event.key === "Enter" &&
|
||||
(!this.selectKit.highlighted || this.selectKit.enterDisabled)
|
||||
) {
|
||||
this.element.querySelector("input").focus();
|
||||
if (this.selectKit.enterDisabled) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
return false;
|
||||
}
|
||||
|
||||
onKeydown(event) {
|
||||
if (!this.selectKit.onKeydown(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do nothing for left/right arrow
|
||||
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowUp") {
|
||||
this.selectKit.highlightPrevious();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowDown") {
|
||||
this.selectKit.highlightNext();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Escape
|
||||
if (event.key === "Escape") {
|
||||
this.selectKit.close(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enter
|
||||
if (event.key === "Enter" && this.selectKit.highlighted) {
|
||||
this.selectKit.select(
|
||||
this.getValue(this.selectKit.highlighted),
|
||||
this.selectKit.highlighted
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
event.key === "Enter" &&
|
||||
(!this.selectKit.highlighted || this.selectKit.enterDisabled)
|
||||
) {
|
||||
this.element.querySelector("input").focus();
|
||||
if (this.selectKit.enterDisabled) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tab
|
||||
if (event.key === "Tab") {
|
||||
if (this.selectKit.highlighted && this.selectKit.isExpanded) {
|
||||
this.selectKit.select(
|
||||
this.getValue(this.selectKit.highlighted),
|
||||
this.selectKit.highlighted
|
||||
);
|
||||
}
|
||||
this.selectKit.close(event);
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectKit.set("highlighted", null);
|
||||
},
|
||||
this.selectKit.set("highlighted", null);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -2,38 +2,28 @@ import Component from "@ember/component";
|
|||
import UtilsMixin from "select-kit/mixins/utils";
|
||||
import { computed } from "@ember/object";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
import { schedule } from "@ember/runloop";
|
||||
|
||||
export default Component.extend(UtilsMixin, {
|
||||
eventType: "click",
|
||||
|
||||
click(event) {
|
||||
if (typeof document === "undefined") {
|
||||
return;
|
||||
}
|
||||
if (this.isDestroyed || !this.selectKit || this.selectKit.isDisabled) {
|
||||
return;
|
||||
}
|
||||
if (this.eventType !== "click" || event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
this.selectKit.toggle(event);
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
classNames: ["select-kit-header"],
|
||||
classNameBindings: ["isFocused"],
|
||||
attributeBindings: [
|
||||
"role",
|
||||
"tabindex",
|
||||
"ariaOwns:aria-owns",
|
||||
"ariaHasPopup:aria-haspopup",
|
||||
"ariaIsExpanded:aria-expanded",
|
||||
"headerRole:role",
|
||||
"ariaLevel:aria-level",
|
||||
"selectedValue:data-value",
|
||||
"selectedNames:data-name",
|
||||
"buttonTitle:title",
|
||||
"selectKit.options.autofocus:autofocus",
|
||||
],
|
||||
|
||||
selectKit: null,
|
||||
|
||||
role: "application",
|
||||
|
||||
ariaLevel: 1,
|
||||
|
||||
tabindex: 0,
|
||||
|
||||
selectedValue: computed("value", function () {
|
||||
return this.value === this.getValue(this.selectKit.noneItem)
|
||||
? null
|
||||
|
@ -62,20 +52,6 @@ export default Component.extend(UtilsMixin, {
|
|||
return icon.concat(icons).filter(Boolean);
|
||||
}),
|
||||
|
||||
ariaIsExpanded: computed("selectKit.isExpanded", function () {
|
||||
return this.selectKit.isExpanded ? "true" : "false";
|
||||
}),
|
||||
|
||||
ariaHasPopup: "menu",
|
||||
|
||||
ariaOwns: computed("selectKit.uniqueID", function () {
|
||||
return `${this.selectKit.uniqueID}-body`;
|
||||
}),
|
||||
|
||||
headerRole: "listbox",
|
||||
|
||||
tabindex: 0,
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
if (this.selectKit.options.autofocus) {
|
||||
|
@ -83,6 +59,10 @@ export default Component.extend(UtilsMixin, {
|
|||
}
|
||||
},
|
||||
|
||||
click(event) {
|
||||
event.stopImmediatePropagation();
|
||||
},
|
||||
|
||||
keyUp(event) {
|
||||
if (event.key === " ") {
|
||||
event.preventDefault();
|
||||
|
@ -104,6 +84,8 @@ export default Component.extend(UtilsMixin, {
|
|||
}
|
||||
|
||||
if (event.key === "Enter") {
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.selectKit.isExpanded) {
|
||||
if (this.selectKit.highlighted) {
|
||||
this.selectKit.select(
|
||||
|
@ -113,44 +95,40 @@ export default Component.extend(UtilsMixin, {
|
|||
return false;
|
||||
}
|
||||
} else {
|
||||
this.selectKit.close(event);
|
||||
this.selectKit.mainElement().open = false;
|
||||
}
|
||||
} else if (event.key === "ArrowUp") {
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.selectKit.isExpanded) {
|
||||
this.selectKit.highlightPrevious();
|
||||
} else {
|
||||
this.selectKit.open(event);
|
||||
this.selectKit.mainElement().open = true;
|
||||
}
|
||||
return false;
|
||||
} else if (event.key === "ArrowDown") {
|
||||
event.stopPropagation();
|
||||
if (this.selectKit.isExpanded) {
|
||||
this.selectKit.highlightNext();
|
||||
} else {
|
||||
this.selectKit.open(event);
|
||||
this.selectKit.mainElement().open = true;
|
||||
}
|
||||
return false;
|
||||
} else if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
||||
// Do nothing for left/right arrow
|
||||
return true;
|
||||
} else if (event.key === " ") {
|
||||
event.stopPropagation();
|
||||
event.preventDefault(); // prevents the space to trigger a scroll page-next
|
||||
this.selectKit.toggle(event);
|
||||
this.selectKit.mainElement().open = true;
|
||||
} else if (event.key === "Escape") {
|
||||
this.selectKit.close(event);
|
||||
event.stopPropagation();
|
||||
if (this.selectKit.isExpanded) {
|
||||
this.selectKit.mainElement().open = false;
|
||||
} else {
|
||||
this.element.blur();
|
||||
}
|
||||
} else if (event.key === "Tab") {
|
||||
return true;
|
||||
} else if (event.key === "Backspace") {
|
||||
this._focusFilterInput();
|
||||
} else if (event.key === "Tab") {
|
||||
if (
|
||||
this.selectKit.highlighted &&
|
||||
this.selectKit.isExpanded &&
|
||||
this.selectKit.options.triggerOnChangeOnTab
|
||||
) {
|
||||
this.selectKit.select(
|
||||
this.getValue(this.selectKit.highlighted),
|
||||
this.selectKit.highlighted
|
||||
);
|
||||
}
|
||||
this.selectKit.close(event);
|
||||
} else if (
|
||||
this.selectKit.options.filterable ||
|
||||
this.selectKit.options.autoFilterable ||
|
||||
|
@ -159,8 +137,12 @@ export default Component.extend(UtilsMixin, {
|
|||
if (this.selectKit.isExpanded) {
|
||||
this._focusFilterInput();
|
||||
} else {
|
||||
this.selectKit.open(event);
|
||||
schedule("afterRender", () => this._focusFilterInput());
|
||||
if (this.isValidInput(event.key)) {
|
||||
this.selectKit.set("filter", event.key);
|
||||
this.selectKit.mainElement().open = true;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.selectKit.isExpanded) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { propertyEqual } from "discourse/lib/computed";
|
||||
import { action, computed } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
|
@ -11,17 +12,16 @@ export default Component.extend(UtilsMixin, {
|
|||
layout,
|
||||
classNames: ["select-kit-row"],
|
||||
tagName: "li",
|
||||
tabIndex: -1,
|
||||
tabIndex: 0,
|
||||
attributeBindings: [
|
||||
"tabIndex",
|
||||
"title",
|
||||
"rowValue:data-value",
|
||||
"rowName:data-name",
|
||||
"ariaLabel:aria-label",
|
||||
"ariaSelected:aria-selected",
|
||||
"role",
|
||||
"ariaChecked:aria-checked",
|
||||
"guid:data-guid",
|
||||
"rowLang:lang",
|
||||
"role",
|
||||
],
|
||||
classNameBindings: [
|
||||
"isHighlighted",
|
||||
|
@ -31,15 +31,24 @@ export default Component.extend(UtilsMixin, {
|
|||
"item.classNames",
|
||||
],
|
||||
|
||||
role: "menuitemradio",
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
this.element.addEventListener("mouseenter", this.handleMouseEnter);
|
||||
|
||||
if (!this.site.mobileView) {
|
||||
this.element.addEventListener("mouseenter", this.handleMouseEnter);
|
||||
this.element.addEventListener("focus", this.handleMouseEnter);
|
||||
this.element.addEventListener("blur", this.handleBlur);
|
||||
}
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
if (this.element) {
|
||||
this.element.removeEventListener("mouseenter", this.handleMouseEnter);
|
||||
if (!this.site.mobileView && this.element) {
|
||||
this.element.removeEventListener("mouseenter", this.handleBlur);
|
||||
this.element.removeEventListener("focus", this.handleMouseEnter);
|
||||
this.element.removeEventListener("blur", this.handleMouseEnter);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -47,19 +56,13 @@ export default Component.extend(UtilsMixin, {
|
|||
return this.rowValue === this.getValue(this.selectKit.noneItem);
|
||||
}),
|
||||
|
||||
role: "option",
|
||||
|
||||
guid: computed("item", function () {
|
||||
return guidFor(this.item);
|
||||
}),
|
||||
|
||||
lang: reads("item.lang"),
|
||||
|
||||
ariaLabel: computed("item.ariaLabel", "title", function () {
|
||||
return this.getProperty(this.item, "ariaLabel") || this.title;
|
||||
}),
|
||||
|
||||
ariaSelected: computed("isSelected", function () {
|
||||
ariaChecked: computed("isSelected", function () {
|
||||
return this.isSelected ? "true" : "false";
|
||||
}),
|
||||
|
||||
|
@ -112,23 +115,36 @@ export default Component.extend(UtilsMixin, {
|
|||
return this.getValue(this.selectKit.highlighted);
|
||||
}),
|
||||
|
||||
isHighlighted: computed("rowValue", "highlightedValue", function () {
|
||||
return this.rowValue === this.highlightedValue;
|
||||
}),
|
||||
isHighlighted: propertyEqual("rowValue", "highlightedValue"),
|
||||
|
||||
isSelected: computed("rowValue", "value", function () {
|
||||
return this.rowValue === this.value;
|
||||
}),
|
||||
isSelected: propertyEqual("rowValue", "value"),
|
||||
|
||||
@action
|
||||
handleMouseEnter() {
|
||||
if (!this.isDestroying || !this.isDestroyed) {
|
||||
this.element.focus({ preventScroll: true });
|
||||
this.selectKit.onHover(this.rowValue, this.item);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
click() {
|
||||
@action
|
||||
handleBlur(event) {
|
||||
if (
|
||||
(!this.isDestroying || !this.isDestroyed) &&
|
||||
event.relatedTarget &&
|
||||
this.selectKit.mainElement()
|
||||
) {
|
||||
if (!this.selectKit.mainElement().contains(event.relatedTarget)) {
|
||||
this.selectKit.mainElement().open = false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
click(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.selectKit.select(this.rowValue, this.item);
|
||||
return false;
|
||||
},
|
||||
|
@ -138,4 +154,44 @@ export default Component.extend(UtilsMixin, {
|
|||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
keyDown(event) {
|
||||
if (this.selectKit.isExpanded) {
|
||||
if (event.key === "Backspace") {
|
||||
if (this.selectKit.isFilterExpanded) {
|
||||
this.selectKit.set("filter", this.selectKit.filter.slice(0, -1));
|
||||
this.selectKit.triggerSearch();
|
||||
this.selectKit.focusFilter();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
} else if (event.key === "ArrowUp") {
|
||||
this.selectKit.highlightPrevious();
|
||||
return false;
|
||||
} else if (event.key === "ArrowDown") {
|
||||
this.selectKit.highlightNext();
|
||||
return false;
|
||||
} else if (event.key === "Enter") {
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
this.selectKit.select(
|
||||
this.getValue(this.selectKit.highlighted),
|
||||
this.selectKit.highlighted
|
||||
);
|
||||
return false;
|
||||
} else if (event.key === "Escape") {
|
||||
this.selectKit.mainElement().open = false;
|
||||
this.selectKit.headerElement().focus();
|
||||
} else {
|
||||
if (this.isValidInput(event.key)) {
|
||||
this.selectKit.set("filter", event.key);
|
||||
this.selectKit.triggerSearch();
|
||||
this.selectKit.focusFilter();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,11 +5,18 @@ import layout from "select-kit/templates/components/select-kit/single-select-hea
|
|||
import I18n from "I18n";
|
||||
|
||||
export default SelectKitHeaderComponent.extend(UtilsMixin, {
|
||||
tagName: "summary",
|
||||
layout,
|
||||
classNames: ["single-select-header"],
|
||||
attributeBindings: ["role", "name"],
|
||||
attributeBindings: ["name"],
|
||||
|
||||
role: "combobox",
|
||||
focusIn(event) {
|
||||
document.querySelectorAll(".select-kit-header").forEach((header) => {
|
||||
if (header !== event.target) {
|
||||
header.parentNode.open = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
name: computed("selectedContent.name", function () {
|
||||
if (this.selectedContent) {
|
||||
|
@ -20,10 +27,4 @@ export default SelectKitHeaderComponent.extend(UtilsMixin, {
|
|||
return I18n.t("select_kit.select_to_filter");
|
||||
}
|
||||
}),
|
||||
|
||||
mouseDown(event) {
|
||||
if (this.selectKit.options.preventHeaderFocus) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import layout from "select-kit/templates/components/selected-choice-category";
|
||||
import SelectedChoiceComponent from "select-kit/components/selected-choice";
|
||||
import { categoryBadgeHTML } from "discourse/helpers/category-link";
|
||||
import { computed } from "@ember/object";
|
||||
|
||||
export default SelectedChoiceComponent.extend({
|
||||
tagName: "",
|
||||
layout,
|
||||
extraClass: "selected-choice-category",
|
||||
|
||||
badge: computed("item", function () {
|
||||
return categoryBadgeHTML(this.item, {
|
||||
allowUncategorized: true,
|
||||
link: false,
|
||||
}).htmlSafe();
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
import SelectedChoiceComponent from "select-kit/components/selected-choice";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { computed } from "@ember/object";
|
||||
|
||||
export default SelectedChoiceComponent.extend({
|
||||
tagName: "",
|
||||
|
||||
extraClass: "selected-choice-color",
|
||||
|
||||
escapedColor: computed("item", function () {
|
||||
const color = `${escapeExpression(this.item?.name || this.item)}`;
|
||||
return color.startsWith("#") ? color : `#${color}`;
|
||||
}),
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
schedule("afterRender", () => {
|
||||
const element = document.querySelector(
|
||||
`#${this.selectKit.uniqueID} #${this.id}-choice`
|
||||
);
|
||||
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.style.borderBottomColor = this.escapedColor;
|
||||
});
|
||||
},
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
import { guidFor } from "@ember/object/internals";
|
||||
import Component from "@ember/component";
|
||||
import { computed } from "@ember/object";
|
||||
import layout from "select-kit/templates/components/selected-choice";
|
||||
import UtilsMixin from "select-kit/mixins/utils";
|
||||
|
||||
export default Component.extend(UtilsMixin, {
|
||||
tagName: "",
|
||||
layout,
|
||||
item: null,
|
||||
selectKit: null,
|
||||
extraClass: null,
|
||||
id: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.set("id", guidFor(this));
|
||||
},
|
||||
|
||||
itemValue: computed("item", function () {
|
||||
return this.getValue(this.item);
|
||||
}),
|
||||
|
||||
itemName: computed("item", function () {
|
||||
return this.getName(this.item);
|
||||
}),
|
||||
});
|
|
@ -9,13 +9,17 @@ export default SelectedNameComponent.extend({
|
|||
this._super(...arguments);
|
||||
|
||||
schedule("afterRender", () => {
|
||||
const color = escapeExpression(this.name),
|
||||
el = document.querySelector(`[data-value="${color}"]`);
|
||||
const element = document.querySelector(
|
||||
`#${this.selectKit.uniqueID} #${this.id}`
|
||||
);
|
||||
|
||||
if (el) {
|
||||
el.style.borderBottom = "2px solid transparent";
|
||||
el.style.borderBottomColor = `#${color}`;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.style.borderBottom = "2px solid transparent";
|
||||
const color = escapeExpression(this.name);
|
||||
element.style.borderBottomColor = `#${color}`;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { action, computed, get } from "@ember/object";
|
||||
import { guidFor } from "@ember/object/internals";
|
||||
import { computed, get } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
import UtilsMixin from "select-kit/mixins/utils";
|
||||
import layout from "select-kit/templates/components/selected-name";
|
||||
|
@ -13,13 +14,12 @@ export default Component.extend(UtilsMixin, {
|
|||
headerTitle: null,
|
||||
headerLang: null,
|
||||
headerLabel: null,
|
||||
id: null,
|
||||
|
||||
@action
|
||||
onSelectedNameClick() {
|
||||
if (this.selectKit.options.clearOnClick) {
|
||||
this.selectKit.deselect(this.item);
|
||||
return false;
|
||||
}
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.set("id", guidFor(this));
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
|
|
|
@ -105,6 +105,10 @@ export default MultiSelectComponent.extend(TagsMixin, {
|
|||
},
|
||||
|
||||
_transformJson(context, json) {
|
||||
if (context.isDestroyed || context.isDestroying) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let results = json.results;
|
||||
|
||||
context.setProperties({
|
||||
|
|
|
@ -18,7 +18,6 @@ export default ComboBoxComponent.extend(TagsMixin, {
|
|||
classNameBindings: ["categoryStyle", "tagClass"],
|
||||
classNames: ["tag-drop"],
|
||||
value: readOnly("tagId"),
|
||||
tagName: "li",
|
||||
categoryStyle: setting("category_style"),
|
||||
maxTagSearchResults: setting("max_tag_search_results"),
|
||||
sortTagsAlphabetically: setting("tags_sort_alphabetically"),
|
||||
|
|
|
@ -2,6 +2,14 @@ import Mixin from "@ember/object/mixin";
|
|||
import { get } from "@ember/object";
|
||||
|
||||
export default Mixin.create({
|
||||
isValidInput(eventKey) {
|
||||
// relying on passing the event to the input is risky as it could not work
|
||||
// dispatching the event won't work as the event won't be trusted
|
||||
// safest solution is to filter event and prefill filter with it
|
||||
const nonInputKeysRegex = /F\d+|Arrow.+|Meta|Alt|Control|Shift|Delete|Enter|Escape|Tab|Space|Insert|Backspace/;
|
||||
return !nonInputKeysRegex.test(eventKey);
|
||||
},
|
||||
|
||||
defaultItem(value, name) {
|
||||
if (this.selectKit.valueProperty) {
|
||||
const item = {};
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
{{component selectKit.options.selectedNameComponent
|
||||
tabindex=tabindex
|
||||
item=selectedContent
|
||||
selectKit=selectKit
|
||||
shouldDisplayClearableButton=shouldDisplayClearableButton
|
||||
}}
|
||||
<div class="select-kit-header-wrapper">
|
||||
{{component selectKit.options.selectedNameComponent
|
||||
tabindex=tabindex
|
||||
item=selectedContent
|
||||
selectKit=selectKit
|
||||
shouldDisplayClearableButton=shouldDisplayClearableButton
|
||||
}}
|
||||
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{#if category}}
|
||||
<div class="category-status">
|
||||
<div class="category-status" aria-hidden="true">
|
||||
{{#if hasParentCategory}}
|
||||
{{#unless hideParentCategory}}
|
||||
{{badgeForParentCategory}}
|
||||
|
@ -9,7 +9,7 @@
|
|||
</div>
|
||||
|
||||
{{#if shouldDisplayDescription}}
|
||||
<div class="category-desc">{{dir-span description}}</div>
|
||||
<div class="category-desc" aria-hidden="true">{{dir-span description}}</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{html-safe label}}
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
{{#each icons as |icon|}} {{d-icon icon}} {{/each}}
|
||||
<div class="select-kit-header-wrapper">
|
||||
{{#each icons as |icon|}} {{d-icon icon}} {{/each}}
|
||||
|
||||
{{component selectKit.options.selectedNameComponent
|
||||
tabindex=tabindex
|
||||
item=selectedContent
|
||||
selectKit=selectKit
|
||||
}}
|
||||
|
||||
{{#if shouldDisplayClearableButton}}
|
||||
{{d-button
|
||||
class="btn-clear"
|
||||
icon="times"
|
||||
action=selectKit.onClearSelection
|
||||
ariaLabel="clear_input"
|
||||
{{component selectKit.options.selectedNameComponent
|
||||
tabindex=tabindex
|
||||
item=selectedContent
|
||||
selectKit=selectKit
|
||||
}}
|
||||
{{/if}}
|
||||
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
{{#if shouldDisplayClearableButton}}
|
||||
{{d-button
|
||||
class="btn-clear"
|
||||
icon="times"
|
||||
action=selectKit.onClearSelection
|
||||
ariaLabel="clear_input"
|
||||
}}
|
||||
{{/if}}
|
||||
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
{{#each icons as |icon|}} {{d-icon icon}} {{/each}}
|
||||
<div class="select-kit-header-wrapper">
|
||||
{{#each icons as |icon|}} {{d-icon icon}} {{/each}}
|
||||
|
||||
{{component selectKit.options.selectedNameComponent
|
||||
tabindex=tabindex
|
||||
item=selectedContent
|
||||
selectKit=selectKit
|
||||
shouldDisplayClearableButton=shouldDisplayClearableButton
|
||||
}}
|
||||
{{component selectKit.options.selectedNameComponent
|
||||
tabindex=tabindex
|
||||
item=selectedContent
|
||||
selectKit=selectKit
|
||||
shouldDisplayClearableButton=shouldDisplayClearableButton
|
||||
}}
|
||||
|
||||
{{#if selectKit.options.showCaret}}
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
{{/if}}
|
||||
{{#if selectKit.options.showCaret}}
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
<div class="choices">
|
||||
{{#each shownItems as |item|}}
|
||||
{{component selectKit.options.selectedNameComponent
|
||||
tabindex=tabindex
|
||||
item=item
|
||||
selectKit=selectKit
|
||||
}}
|
||||
{{/each}}
|
||||
{{#if hasHiddenItems}}
|
||||
<div class="x-more-item" data-hidden-count={{hiddenItemsCount}}>
|
||||
{{i18n "x_more" count=hiddenItemsCount}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#unless hasReachedMaximumSelection}}
|
||||
<div class="choice input-wrapper">
|
||||
{{component selectKit.options.filterComponent
|
||||
selectKit=selectKit
|
||||
id=(concat selectKit.uniqueID "-filter")
|
||||
}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
|
@ -1,19 +1,21 @@
|
|||
{{#if icons}}
|
||||
<div class="future-date-input-selector-icons">
|
||||
{{#each icons as |icon|}} {{d-icon icon}} {{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="select-kit-header-wrapper">
|
||||
{{#if icons}}
|
||||
<div class="future-date-input-selector-icons">
|
||||
{{#each icons as |icon|}} {{d-icon icon}} {{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{component selectKit.options.selectedNameComponent
|
||||
tabindex=tabindex
|
||||
item=selectedContent
|
||||
selectKit=selectKit
|
||||
}}
|
||||
{{component selectKit.options.selectedNameComponent
|
||||
tabindex=tabindex
|
||||
item=selectedContent
|
||||
selectKit=selectKit
|
||||
}}
|
||||
|
||||
{{#if selectedContent.datetime}}
|
||||
<span class="future-date-input-selector-datetime">
|
||||
{{selectedContent.datetime}}
|
||||
</span>
|
||||
{{/if}}
|
||||
{{#if selectedContent.datetime}}
|
||||
<span class="future-date-input-selector-datetime">
|
||||
{{selectedContent.datetime}}
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
</div>
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
{{component selectKit.options.selectedNameComponent
|
||||
tabindex=tabindex
|
||||
item=selectedContent
|
||||
selectKit=selectKit
|
||||
}}
|
||||
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
|
@ -1,11 +1,16 @@
|
|||
{{#each tags as |tag|}}
|
||||
{{#d-button
|
||||
translatedTitle=tag.value
|
||||
icon="times"
|
||||
action=(action "deselectTag")
|
||||
actionParam=tag.value
|
||||
class=tag.classNames
|
||||
}}
|
||||
{{discourse-tag tag.value noHref=true}}
|
||||
{{/d-button}}
|
||||
{{/each}}
|
||||
{{#if tags}}
|
||||
<div class="mini-tag-chooser-selected-collection selected-tags">
|
||||
{{#each tags as |tag|}}
|
||||
{{#d-button
|
||||
translatedTitle=tag.value
|
||||
icon="times"
|
||||
action=(action selectKit.deselect)
|
||||
actionParam=tag.value
|
||||
class=tag.classNames
|
||||
tabindex=0
|
||||
}}
|
||||
{{discourse-tag tag.value noHref=true}}
|
||||
{{/d-button}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -8,6 +8,22 @@
|
|||
}}
|
||||
|
||||
{{#select-kit/select-kit-body selectKit=selectKit id=(concat selectKit.uniqueID "-body")}}
|
||||
{{component selectKit.options.filterComponent
|
||||
selectKit=selectKit
|
||||
id=(concat selectKit.uniqueID "-filter")
|
||||
}}
|
||||
|
||||
{{#if selectedContent}}
|
||||
<div class="selected-content">
|
||||
{{#each selectedContent as |item|}}
|
||||
{{component selectKit.options.selectedChoiceComponent
|
||||
item=item
|
||||
selectKit=selectKit
|
||||
}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if selectKit.isLoading}}
|
||||
<span class="is-loading">
|
||||
{{#if site}}
|
||||
|
@ -15,14 +31,6 @@
|
|||
{{/if}}
|
||||
</span>
|
||||
{{else}}
|
||||
{{#if selectKit.filter}}
|
||||
{{#if selectKit.hasNoContent}}
|
||||
<span class="no-content">
|
||||
{{i18n "select_kit.no_content"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#each collections as |collection|}}
|
||||
{{component (component-for-collection collection.identifier selectKit)
|
||||
collection=collection
|
||||
|
@ -30,8 +38,18 @@
|
|||
value=value
|
||||
}}
|
||||
{{/each}}
|
||||
|
||||
{{#if selectKit.filter}}
|
||||
{{#if selectKit.hasNoContent}}
|
||||
<span class="no-content" role="alert">
|
||||
{{i18n "select_kit.no_content"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<span class="results-count" role="alert">
|
||||
{{i18n "select_kit.results_count" count=mainCollection.length}}
|
||||
</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/select-kit/select-kit-body}}
|
||||
|
||||
<div class="select-kit-wrapper"></div>
|
||||
{{/unless}}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<span class="formated-selection">
|
||||
{{formatedContent}}
|
||||
</span>
|
|
@ -1,18 +1,9 @@
|
|||
<div class="choices">
|
||||
{{#each selectedContent as |item|}}
|
||||
{{component selectKit.options.selectedNameComponent
|
||||
tabindex=tabindex
|
||||
item=item
|
||||
selectKit=selectKit
|
||||
}}
|
||||
<div class="select-kit-header-wrapper">
|
||||
{{#each icons as |icon|}}
|
||||
{{d-icon icon}}
|
||||
{{/each}}
|
||||
|
||||
{{#unless hasReachedMaximumSelection}}
|
||||
<div class="choice input-wrapper">
|
||||
{{component selectKit.options.filterComponent
|
||||
selectKit=selectKit
|
||||
id=(concat selectKit.uniqueID "-filter")
|
||||
}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{multi-select/format-selected-content content=selectedContent selectKit=selectKit}}
|
||||
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<label class="filter-text">
|
||||
{{i18n "user.user_notifications.filters.filter_by"}}
|
||||
</label>
|
||||
<label class="header-text">
|
||||
{{i18n label}}
|
||||
</label>
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
<div class="select-kit-header-wrapper">
|
||||
<span class="filter-text">
|
||||
{{i18n "user.user_notifications.filters.filter_by"}}
|
||||
</span>
|
||||
<span class="header-text">
|
||||
{{i18n label}}
|
||||
</span>
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{{#if collection}}
|
||||
{{#each collection.content as |item|}}
|
||||
<li class="select-kit-error">{{item}}</li>
|
||||
{{/each}}
|
||||
{{#if collection.content}}
|
||||
<ul class="select-kit-errors-collection">
|
||||
{{#each collection.content as |item|}}
|
||||
<li class="select-kit-error">{{item}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
{{#each collection.content as |item|}}
|
||||
{{component (component-for-row collection.identifier item selectKit)
|
||||
item=item
|
||||
value=value
|
||||
selectKit=selectKit
|
||||
}}
|
||||
{{/each}}
|
||||
{{#if collection.content.length}}
|
||||
<ul class="select-kit-collection" aria-live="polite" role="menu">
|
||||
{{#each collection.content as |item|}}
|
||||
{{component (component-for-row collection.identifier item selectKit)
|
||||
item=item
|
||||
value=value
|
||||
selectKit=selectKit
|
||||
}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
{{#unless isHidden}}
|
||||
{{!-- filter-input-search prevents 1password from attempting autocomplete --}}
|
||||
{{input
|
||||
tabindex=-1
|
||||
tabindex=0
|
||||
class="filter-input"
|
||||
placeholder=placeholder
|
||||
autocomplete="discourse"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
name="filter-input-search"
|
||||
autofocus=selectKit.options.autofocus
|
||||
spellcheck=false
|
||||
value=(readonly selectKit.filter)
|
||||
input=(action "onInput")
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue