diff --git a/app/assets/javascripts/admin/addon/templates/components/report-filters/group.hbs b/app/assets/javascripts/admin/addon/templates/components/report-filters/group.hbs index 9e17bda2ac3..bac1facc694 100644 --- a/app/assets/javascripts/admin/addon/templates/components/report-filters/group.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/report-filters/group.hbs @@ -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 + ) }} diff --git a/app/assets/javascripts/admin/addon/templates/components/site-settings/compact-list.hbs b/app/assets/javascripts/admin/addon/templates/components/site-settings/compact-list.hbs index 4d37e15e871..c896d5ecc35 100644 --- a/app/assets/javascripts/admin/addon/templates/components/site-settings/compact-list.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/site-settings/compact-list.hbs @@ -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}} diff --git a/app/assets/javascripts/admin/addon/templates/components/value-list.hbs b/app/assets/javascripts/admin/addon/templates/components/value-list.hbs index 853daac745f..94b1d12325f 100644 --- a/app/assets/javascripts/admin/addon/templates/components/value-list.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/value-list.hbs @@ -21,7 +21,9 @@ {{/if}} {{combo-box - allowAny=true + options=(hash + allowAny=true + ) none=noneKey valueProperty=null nameProperty=null diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js index 7ffdb991eeb..c319408bbc3 100644 --- a/app/assets/javascripts/discourse/app/components/composer-editor.js +++ b/app/assets/javascripts/discourse/app/components/composer-editor.js @@ -682,6 +682,7 @@ export default Component.extend(ComposerUpload, { extraButtons(toolbar) { toolbar.addButton({ + tabindex: "0", id: "quote", group: "fontStyles", icon: "far-comment", diff --git a/app/assets/javascripts/discourse/app/components/composer-save-button.js b/app/assets/javascripts/discourse/app/components/composer-save-button.js index 4c7500d6402..efa5d2bd773 100644 --- a/app/assets/javascripts/discourse/app/components/composer-save-button.js +++ b/app/assets/javascripts/discourse/app/components/composer-save-button.js @@ -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", }); diff --git a/app/assets/javascripts/discourse/app/components/composer-user-selector.js b/app/assets/javascripts/discourse/app/components/composer-user-selector.js index fd7babb76d0..8bf2d053912 100644 --- a/app/assets/javascripts/discourse/app/components/composer-user-selector.js +++ b/app/assets/javascripts/discourse/app/components/composer-user-selector.js @@ -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(); } }, diff --git a/app/assets/javascripts/discourse/app/components/d-button.js b/app/assets/javascripts/discourse/app/components/d-button.js index 36987bebb3d..73d635009e9 100644 --- a/app/assets/javascripts/discourse/app/components/d-button.js +++ b/app/assets/javascripts/discourse/app/components/d-button.js @@ -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; diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js index e2cc964cdbe..c6ebb29af00 100644 --- a/app/assets/javascripts/discourse/app/components/d-editor.js +++ b/app/assets/javascripts/discourse/app/components/d-editor.js @@ -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) { diff --git a/app/assets/javascripts/discourse/app/components/d-modal-body.js b/app/assets/javascripts/discourse/app/components/d-modal-body.js index 4f2f38292fc..2ea7d11324d 100644 --- a/app/assets/javascripts/discourse/app/components/d-modal-body.js +++ b/app/assets/javascripts/discourse/app/components/d-modal-body.js @@ -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" ) ); }, diff --git a/app/assets/javascripts/discourse/app/components/d-modal.js b/app/assets/javascripts/discourse/app/components/d-modal.js index 652035b4675..a08ec543e95 100644 --- a/app/assets/javascripts/discourse/app/components/d-modal.js +++ b/app/assets/javascripts/discourse/app/components/d-modal.js @@ -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(); } } }, diff --git a/app/assets/javascripts/discourse/app/components/future-date-input.js b/app/assets/javascripts/discourse/app/components/future-date-input.js index 75a33161b4f..f673f73ae5c 100644 --- a/app/assets/javascripts/discourse/app/components/future-date-input.js +++ b/app/assets/javascripts/discourse/app/components/future-date-input.js @@ -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); + } + }, }); diff --git a/app/assets/javascripts/discourse/app/lib/timeframes-builder.js b/app/assets/javascripts/discourse/app/lib/timeframes-builder.js new file mode 100644 index 00000000000..6fd51382092 --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/timeframes-builder.js @@ -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)); +} diff --git a/app/assets/javascripts/discourse/app/templates/components/bread-crumbs.hbs b/app/assets/javascripts/discourse/app/templates/components/bread-crumbs.hbs index df1de24a368..f59fa8155f2 100644 --- a/app/assets/javascripts/discourse/app/templates/components/bread-crumbs.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/bread-crumbs.hbs @@ -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 - ) - }} +
  • + {{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 + ) + }} +
  • {{/if}} {{/each}} {{#if showTagsSection}} {{#if additionalTags}} - {{tags-intersection-chooser - currentCategory=category - mainTag=tag.id - additionalTags=additionalTags - options=(hash - categoryId=category.id - ) - }} +
  • + {{tags-intersection-chooser + currentCategory=category + mainTag=tag.id + additionalTags=additionalTags + options=(hash + categoryId=category.id + ) + }} +
  • {{else}} - {{tag-drop - currentCategory=category - noSubcategories=noSubcategories - tagId=tag.id - }} +
  • + {{tag-drop + currentCategory=category + noSubcategories=noSubcategories + tagId=tag.id + }} +
  • {{/if}} {{/if}} diff --git a/app/assets/javascripts/discourse/app/templates/components/composer-editor.hbs b/app/assets/javascripts/discourse/app/templates/components/composer-editor.hbs index 84a88a87146..4f723b40e39 100644 --- a/app/assets/javascripts/discourse/app/templates/components/composer-editor.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/composer-editor.hbs @@ -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}} diff --git a/app/assets/javascripts/discourse/app/templates/components/composer-title.hbs b/app/assets/javascripts/discourse/app/templates/components/composer-title.hbs index c29ed22187d..56674525f17 100644 --- a/app/assets/javascripts/discourse/app/templates/components/composer-title.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/composer-title.hbs @@ -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}} diff --git a/app/assets/javascripts/discourse/app/templates/components/composer-toggles.hbs b/app/assets/javascripts/discourse/app/templates/components/composer-toggles.hbs index ac9ac22d340..80be3f6f265 100644 --- a/app/assets/javascripts/discourse/app/templates/components/composer-toggles.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/composer-toggles.hbs @@ -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}} diff --git a/app/assets/javascripts/discourse/app/templates/components/composer-user-selector.hbs b/app/assets/javascripts/discourse/app/templates/components/composer-user-selector.hbs index b18046a5d77..fda26a748aa 100644 --- a/app/assets/javascripts/discourse/app/templates/components/composer-user-selector.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/composer-user-selector.hbs @@ -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 diff --git a/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs index e9df77f00e9..d5e1810f607 100644 --- a/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs @@ -1,6 +1,6 @@
    -
    + diff --git a/app/assets/javascripts/discourse/app/templates/components/future-date-input.hbs b/app/assets/javascripts/discourse/app/templates/components/future-date-input.hbs index 2ff6e7de08c..68fdc11df9f 100644 --- a/app/assets/javascripts/discourse/app/templates/components/future-date-input.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/future-date-input.hbs @@ -4,10 +4,8 @@ {{#if displayLabelIcon}}{{d-icon displayLabelIcon}}{{/if}}{{displayLabel}} {{future-date-input-selector - minimumResultsForSearch=-1 statusType=statusType value=(readonly selection) - input=(readonly input) includeDateTime=includeDateTime includeWeekend=includeWeekend includeFarFuture=includeFarFuture @@ -26,15 +24,22 @@
    {{d-icon "calendar-alt"}} {{date-picker-future - value=date - defaultDate=date - onSelect=(action (mut date)) + value=_date + defaultDate=_date + onSelect=(action "onChangeDate") }}
    {{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") + }}
    {{/if}}
    diff --git a/app/assets/javascripts/discourse/app/templates/components/group-navigation.hbs b/app/assets/javascripts/discourse/app/templates/components/group-navigation.hbs index 8b37b67c408..5c5969fd1ee 100644 --- a/app/assets/javascripts/discourse/app/templates/components/group-navigation.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/group-navigation.hbs @@ -6,10 +6,12 @@ {{/link-to}} {{else}} - {{group-dropdown - groups=group.extras.visible_group_names - value=group.name - }} +
  • + {{group-dropdown + groups=group.extras.visible_group_names + value=group.name + }} +
  • {{/if}} {{#each tabs as |tab|}} diff --git a/app/assets/javascripts/discourse/app/templates/composer.hbs b/app/assets/javascripts/discourse/app/templates/composer.hbs index 7dbd377c5c5..4ca58d34431 100644 --- a/app/assets/javascripts/discourse/app/templates/composer.hbs +++ b/app/assets/javascripts/discourse/app/templates/composer.hbs @@ -6,9 +6,11 @@ save=(action "save")}}
    {{#if visible}} - {{composer-messages composer=model - messageCount=messageCount - addLinkLookup=(action "addLinkLookup")}} + {{composer-messages + composer=model + messageCount=messageCount + addLinkLookup=(action "addLinkLookup") + }} {{#if model.viewOpenOrFullscreen}}
    @@ -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}}
    {{/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") + }}
    {{#unless model.viewFullscreen}} {{#if model.canEditTitle}} @@ -60,7 +64,7 @@ }} {{#if showWarning}} {{/if}} @@ -75,7 +79,6 @@
    {{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 @@
    {{#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}} - + {{#if canEdit}} {{d-icon "times"}} {{else}} @@ -154,7 +158,7 @@ {{/if}} {{else}} - {{i18n "cancel"}} + {{i18n "cancel"}} {{/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}} diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-search-log-term-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-search-log-term-test.js index 6b4d080b683..a1d472ae881 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/admin-search-log-term-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-search-log-term-test.js @@ -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"); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-search-logs-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-search-logs-test.js index 8d6335451c0..f9814bfa0c6 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/admin-search-logs-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-search-logs-test.js @@ -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" ); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-silence-user-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-silence-user-test.js index 3c87d0a3ff7..5c60ea6d19a 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/admin-silence-user-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-silence-user-test.js @@ -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() diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-suspend-user-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-suspend-user-test.js index 771118fbef9..2c66f4dbb65 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/admin-suspend-user-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-suspend-user-test.js @@ -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() diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js index f2252193f9c..1771a57f326 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js @@ -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 diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-attachment-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-attachment-test.js index 4bd4eec0b87..c4c21dfc9d6 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-attachment-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-attachment-test.js @@ -27,7 +27,7 @@ async function writeInComposer(assert) { assert.equal( queryAll(".d-editor-preview:visible").html().trim(), - '

    test

    ' + '

    test

    ' ); 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(), - '

    test

    ' + '

    test

    ' ); }); }); @@ -55,7 +55,7 @@ acceptance("Composer Attachment - Secure Media Enabled", function (needs) { await writeInComposer(assert); assert.equal( queryAll(".d-editor-preview:visible").html().trim(), - '

    test

    ' + '

    test

    ' ); }); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-onebox-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-onebox-test.js index cc8ea8eecef..221f61083c5 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-onebox-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-onebox-test.js @@ -32,10 +32,10 @@ http://www.example.com/has-title.html queryAll(".d-editor-preview:visible").html().trim(), `


    -This is another test This is a great title

    -

    http://www.example.com/no-title.html

    -

    This is another test http://www.example.com/no-title.html
    -This is another test This is a great title

    +This is another test This is a great title

    +

    http://www.example.com/no-title.html

    +

    This is another test http://www.example.com/no-title.html
    +This is another test This is a great title

    `.trim() ); @@ -69,14 +69,14 @@ acceptance("Composer - Inline Onebox", function (needs) { assert.equal(requestsCount, 1); assert.equal( queryAll(".d-editor-preview").html().trim(), - '

    Test www.example.com/page

    ' + '

    Test www.example.com/page

    ' ); await fillIn(".d-editor-input", `Test www.example.com/page Test`); assert.equal(requestsCount, 1); assert.equal( queryAll(".d-editor-preview").html().trim(), - '

    Test www.example.com/page Test

    ' + '

    Test www.example.com/page Test

    ' ); }); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js index 190017f2da6..5f612ef6c5d 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js @@ -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); } diff --git a/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js b/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js index 55105ba1630..d12ec51292e 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js @@ -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() diff --git a/app/assets/javascripts/discourse/tests/acceptance/group-manage-membership-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-manage-membership-test.js index ef2e6a0d0c0..0637892b675 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/group-manage-membership-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-manage-membership-test.js @@ -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"); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/group-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-test.js index 96432613ef5..d8197e6688d 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/group-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-test.js @@ -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" ); diff --git a/app/assets/javascripts/discourse/tests/acceptance/modal-test.js b/app/assets/javascripts/discourse/tests/acceptance/modal-test.js index e1afdc715f3..72ce0b2101f 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/modal-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/modal-test.js @@ -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" diff --git a/app/assets/javascripts/discourse/tests/acceptance/new-message-test.js b/app/assets/javascripts/discourse/tests/acceptance/new-message-test.js index b5ef162d4fc..96ffed16843 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/new-message-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/new-message-test.js @@ -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" ); diff --git a/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js b/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js index 0d5f64bcda7..d0b2d9029e6 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js @@ -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"); diff --git a/app/assets/javascripts/discourse/tests/acceptance/review-test.js b/app/assets/javascripts/discourse/tests/acceptance/review-test.js index 23c7b64c139..b5a04482412 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/review-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/review-test.js @@ -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() diff --git a/app/assets/javascripts/discourse/tests/acceptance/tag-groups-test.js b/app/assets/javascripts/discourse/tests/acceptance/tag-groups-test.js index 3de7f851df1..79a6f9777a1 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/tag-groups-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/tag-groups-test.js @@ -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); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/tags-test.js b/app/assets/javascripts/discourse/tests/acceptance/tags-test.js index 02b253cceb4..adc5277edb8 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/tags-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/tags-test.js @@ -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"] ); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-set-slow-mode-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-set-slow-mode-test.js index 51f63142537..f2c445a907f 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/topic-set-slow-mode-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-set-slow-mode-test.js @@ -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() diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-slow-mode-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-slow-mode-test.js index b752e38fc4f..8b1449cfbac 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/topic-slow-mode-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-slow-mode-test.js @@ -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" ); diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-test.js index 421c5fdc348..c380d9ba9d9 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/topic-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-test.js @@ -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" ); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js index 8a79e7de8b7..46252a0a9a8 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js @@ -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"); diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-notifications-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-notifications-test.js index 5173934d2fa..d827790294b 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-notifications-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-notifications-test.js @@ -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() diff --git a/app/assets/javascripts/discourse/tests/helpers/select-kit-helper.js b/app/assets/javascripts/discourse/tests/helpers/select-kit-helper.js index 8ac77253c38..6785b6e1c85 100644 --- a/app/assets/javascripts/discourse/tests/helpers/select-kit-helper.js +++ b/app/assets/javascripts/discourse/tests/helpers/select-kit-helper.js @@ -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() { diff --git a/app/assets/javascripts/discourse/tests/integration/components/future-date-input-selector-test.js b/app/assets/javascripts/discourse/tests/integration/components/future-date-input-selector-test.js new file mode 100644 index 00000000000..d3b396b6f9c --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/future-date-input-selector-test.js @@ -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 + ) + ); + } + } +); diff --git a/app/assets/javascripts/discourse/tests/integration/components/invite-panel-test.js b/app/assets/javascripts/discourse/tests/integration/components/invite-panel-test.js index 965b4800236..f35ea125fae 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/invite-panel-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/invite-panel-test.js @@ -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"); diff --git a/app/assets/javascripts/discourse/tests/integration/components/select-kit/category-chooser-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/category-chooser-test.js index e0446a7a156..a49c4d1dae2 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/select-kit/category-chooser-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/category-chooser-test.js @@ -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’' ); }, diff --git a/app/assets/javascripts/discourse/tests/integration/components/select-kit/email-group-user-chooser-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/email-group-user-chooser-test.js deleted file mode 100644 index 78b122d60dd..00000000000 --- a/app/assets/javascripts/discourse/tests/integration/components/select-kit/email-group-user-chooser-test.js +++ /dev/null @@ -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" - ); - }, - }); - } -); diff --git a/app/assets/javascripts/discourse/tests/integration/components/select-kit/future-date-input-selector-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/future-date-input-selector-test.js deleted file mode 100644 index ad368c5ef5c..00000000000 --- a/app/assets/javascripts/discourse/tests/integration/components/select-kit/future-date-input-selector-test.js +++ /dev/null @@ -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() - ) - ); - } - } -); diff --git a/app/assets/javascripts/discourse/tests/integration/components/select-kit/mini-tag-chooser-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/mini-tag-chooser-test.js index c9a5bbcd71e..f6515e86149 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/select-kit/mini-tag-chooser-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/mini-tag-chooser-test.js @@ -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( diff --git a/app/assets/javascripts/discourse/tests/integration/components/select-kit/user-chooser-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/user-chooser-test.js index 54dee262543..22cd0c624b7 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/select-kit/user-chooser-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/user-chooser-test.js @@ -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"); }, }); diff --git a/app/assets/javascripts/discourse/tests/integration/components/value-list-test.js b/app/assets/javascripts/discourse/tests/integration/components/value-list-test.js index 45945469839..3e86f3294ad 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/value-list-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/value-list-test.js @@ -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"), diff --git a/app/assets/javascripts/discourse/tests/unit/lib/timeframes-builder-test.js b/app/assets/javascripts/discourse/tests/unit/lib/timeframes-builder-test.js new file mode 100644 index 00000000000..cb72b3a3090 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/unit/lib/timeframes-builder-test.js @@ -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(); + }); +}); diff --git a/app/assets/javascripts/select-kit/addon/components/category-drop.js b/app/assets/javascripts/select-kit/addon/components/category-drop.js index 93083db0b37..7723bdc2c17 100644 --- a/app/assets/javascripts/select-kit/addon/components/category-drop.js +++ b/app/assets/javascripts/select-kit/addon/components/category-drop.js @@ -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, diff --git a/app/assets/javascripts/select-kit/addon/components/category-row.js b/app/assets/javascripts/select-kit/addon/components/category-row.js index dedd8a5c48d..7794a926cc7 100644 --- a/app/assets/javascripts/select-kit/addon/components/category-row.js +++ b/app/assets/javascripts/select-kit/addon/components/category-row.js @@ -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"), diff --git a/app/assets/javascripts/select-kit/addon/components/category-selector.js b/app/assets/javascripts/select-kit/addon/components/category-selector.js index 1a081f9e9c2..785139da23c 100644 --- a/app/assets/javascripts/select-kit/addon/components/category-selector.js +++ b/app/assets/javascripts/select-kit/addon/components/category-selector.js @@ -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"; }, diff --git a/app/assets/javascripts/select-kit/addon/components/create-color-row.js b/app/assets/javascripts/select-kit/addon/components/create-color-row.js index e19630287f0..4f978fe0cb1 100644 --- a/app/assets/javascripts/select-kit/addon/components/create-color-row.js +++ b/app/assets/javascripts/select-kit/addon/components/create-color-row.js @@ -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}`; }); }, }); diff --git a/app/assets/javascripts/select-kit/addon/components/dropdown-select-box/dropdown-select-box-header.js b/app/assets/javascripts/select-kit/addon/components/dropdown-select-box/dropdown-select-box-header.js index 1cb0ca1bace..76f6d03d4b2 100644 --- a/app/assets/javascripts/select-kit/addon/components/dropdown-select-box/dropdown-select-box-header.js +++ b/app/assets/javascripts/select-kit/addon/components/dropdown-select-box/dropdown-select-box-header.js @@ -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 () { diff --git a/app/assets/javascripts/select-kit/addon/components/email-group-user-chooser-header.js b/app/assets/javascripts/select-kit/addon/components/email-group-user-chooser-header.js deleted file mode 100644 index b25bb6edef6..00000000000 --- a/app/assets/javascripts/select-kit/addon/components/email-group-user-chooser-header.js +++ /dev/null @@ -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; - } - } - ), -}); diff --git a/app/assets/javascripts/select-kit/addon/components/email-group-user-chooser.js b/app/assets/javascripts/select-kit/addon/components/email-group-user-chooser.js index 7923147906e..6a5463ab3b2 100644 --- a/app/assets/javascripts/select-kit/addon/components/email-group-user-chooser.js +++ b/app/assets/javascripts/select-kit/addon/components/email-group-user-chooser.js @@ -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, diff --git a/app/assets/javascripts/select-kit/addon/components/future-date-input-selector.js b/app/assets/javascripts/select-kit/addon/components/future-date-input-selector.js index 495d87c7dd5..7263a27ca0b 100644 --- a/app/assets/javascripts/select-kit/addon/components/future-date-input-selector.js +++ b/app/assets/javascripts/select-kit/addon/components/future-date-input-selector.js @@ -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}`), diff --git a/app/assets/javascripts/select-kit/addon/components/future-date-input-selector/mixin.js b/app/assets/javascripts/select-kit/addon/components/future-date-input-selector/mixin.js index c9b64fa5318..83b9d8b41da 100644 --- a/app/assets/javascripts/select-kit/addon/components/future-date-input-selector/mixin.js +++ b/app/assets/javascripts/select-kit/addon/components/future-date-input-selector/mixin.js @@ -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) { diff --git a/app/assets/javascripts/select-kit/addon/components/group-dropdown.js b/app/assets/javascripts/select-kit/addon/components/group-dropdown.js index a0a12c1a70a..c27dda050ee 100644 --- a/app/assets/javascripts/select-kit/addon/components/group-dropdown.js +++ b/app/assets/javascripts/select-kit/addon/components/group-dropdown.js @@ -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), diff --git a/app/assets/javascripts/select-kit/addon/components/list-setting.js b/app/assets/javascripts/select-kit/addon/components/list-setting.js index da9d9f7d4bd..bd76a84b30c 100644 --- a/app/assets/javascripts/select-kit/addon/components/list-setting.js +++ b/app/assets/javascripts/select-kit/addon/components/list-setting.js @@ -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"; } }), diff --git a/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser.js b/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser.js index d1c4cfce065..59bc2a959bf 100644 --- a/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser.js +++ b/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser.js @@ -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); - } - } }, }); diff --git a/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser/selected-collection.js b/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser/selected-collection.js index e8187c687a6..9ccbe516280 100644 --- a/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser/selected-collection.js +++ b/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser/selected-collection.js @@ -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", + }; + }); + }), }); diff --git a/app/assets/javascripts/select-kit/addon/components/multi-select.js b/app/assets/javascripts/select-kit/addon/components/multi-select.js index 534f6994dd2..ec0e6986cd6 100644 --- a/app/assets/javascripts/select-kit/addon/components/multi-select.js +++ b/app/assets/javascripts/select-kit/addon/components/multi-select.js @@ -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); - } - }, }); diff --git a/app/assets/javascripts/select-kit/addon/components/multi-select/format-selected-content.js b/app/assets/javascripts/select-kit/addon/components/multi-select/format-selected-content.js new file mode 100644 index 00000000000..c2469d737c3 --- /dev/null +++ b/app/assets/javascripts/select-kit/addon/components/multi-select/format-selected-content.js @@ -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); + } + }), +}); diff --git a/app/assets/javascripts/select-kit/addon/components/multi-select/multi-select-header.js b/app/assets/javascripts/select-kit/addon/components/multi-select/multi-select-header.js index ed3b298da8b..72f200b4ba1 100644 --- a/app/assets/javascripts/select-kit/addon/components/multi-select/multi-select-header.js +++ b/app/assets/javascripts/select-kit/addon/components/multi-select/multi-select-header.js @@ -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); - }), + ), }); diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit.js b/app/assets/javascripts/select-kit/addon/components/select-kit.js index 3551ce7bb35..d74d48380b1 100644 --- a/app/assets/javascripts/select-kit/addon/components/select-kit.js +++ b/app/assets/javascripts/select-kit/addon/components/select-kit.js @@ -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) { diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit/errors-collection.js b/app/assets/javascripts/select-kit/addon/components/select-kit/errors-collection.js index f299f38e772..099bd7aeba1 100644 --- a/app/assets/javascripts/select-kit/addon/components/select-kit/errors-collection.js +++ b/app/assets/javascripts/select-kit/addon/components/select-kit/errors-collection.js @@ -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: "", }); diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-body.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-body.js index 5ef8061fd8e..736de1d6f05 100644 --- a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-body.js +++ b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-body.js @@ -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; + } }, }); diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-collection.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-collection.js index 1ba82c202ce..8c4b3e1e2a8 100644 --- a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-collection.js +++ b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-collection.js @@ -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: "", }); diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-filter.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-filter.js index 4e064f5d64d..71eaf00ef5e 100644 --- a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-filter.js +++ b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-filter.js @@ -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); }, }); diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-header.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-header.js index 7e9c361e6c2..624f86e2946 100644 --- a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-header.js +++ b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-header.js @@ -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) { diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js index 9e8b734e558..cf7d5ac90aa 100644 --- a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js +++ b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js @@ -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(); + } + } + } + }, }); diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit/single-select-header.js b/app/assets/javascripts/select-kit/addon/components/select-kit/single-select-header.js index 337f25de5ec..168e5b9e867 100644 --- a/app/assets/javascripts/select-kit/addon/components/select-kit/single-select-header.js +++ b/app/assets/javascripts/select-kit/addon/components/select-kit/single-select-header.js @@ -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(); - } - }, }); diff --git a/app/assets/javascripts/select-kit/addon/components/selected-choice-category.js b/app/assets/javascripts/select-kit/addon/components/selected-choice-category.js new file mode 100644 index 00000000000..9fdb94831a2 --- /dev/null +++ b/app/assets/javascripts/select-kit/addon/components/selected-choice-category.js @@ -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(); + }), +}); diff --git a/app/assets/javascripts/select-kit/addon/components/selected-choice-color.js b/app/assets/javascripts/select-kit/addon/components/selected-choice-color.js new file mode 100644 index 00000000000..7d9cc14e3ce --- /dev/null +++ b/app/assets/javascripts/select-kit/addon/components/selected-choice-color.js @@ -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; + }); + }, +}); diff --git a/app/assets/javascripts/select-kit/addon/components/selected-choice.js b/app/assets/javascripts/select-kit/addon/components/selected-choice.js new file mode 100644 index 00000000000..1768c202fd7 --- /dev/null +++ b/app/assets/javascripts/select-kit/addon/components/selected-choice.js @@ -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); + }), +}); diff --git a/app/assets/javascripts/select-kit/addon/components/selected-color.js b/app/assets/javascripts/select-kit/addon/components/selected-color.js index 9b4cbe27b24..12b52eecac6 100644 --- a/app/assets/javascripts/select-kit/addon/components/selected-color.js +++ b/app/assets/javascripts/select-kit/addon/components/selected-color.js @@ -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}`; }); }, }); diff --git a/app/assets/javascripts/select-kit/addon/components/selected-name.js b/app/assets/javascripts/select-kit/addon/components/selected-name.js index 0ec2da4077f..aad9ebb5646 100644 --- a/app/assets/javascripts/select-kit/addon/components/selected-name.js +++ b/app/assets/javascripts/select-kit/addon/components/selected-name.js @@ -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() { diff --git a/app/assets/javascripts/select-kit/addon/components/tag-chooser.js b/app/assets/javascripts/select-kit/addon/components/tag-chooser.js index 64693c62fea..e7518f48626 100644 --- a/app/assets/javascripts/select-kit/addon/components/tag-chooser.js +++ b/app/assets/javascripts/select-kit/addon/components/tag-chooser.js @@ -105,6 +105,10 @@ export default MultiSelectComponent.extend(TagsMixin, { }, _transformJson(context, json) { + if (context.isDestroyed || context.isDestroying) { + return []; + } + let results = json.results; context.setProperties({ diff --git a/app/assets/javascripts/select-kit/addon/components/tag-drop.js b/app/assets/javascripts/select-kit/addon/components/tag-drop.js index 7ef59011c44..9bd4bc5a019 100644 --- a/app/assets/javascripts/select-kit/addon/components/tag-drop.js +++ b/app/assets/javascripts/select-kit/addon/components/tag-drop.js @@ -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"), diff --git a/app/assets/javascripts/select-kit/addon/mixins/utils.js b/app/assets/javascripts/select-kit/addon/mixins/utils.js index cb1c4244bc3..12b92b743d0 100644 --- a/app/assets/javascripts/select-kit/addon/mixins/utils.js +++ b/app/assets/javascripts/select-kit/addon/mixins/utils.js @@ -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 = {}; diff --git a/app/assets/javascripts/select-kit/addon/templates/components/category-drop/category-drop-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/category-drop/category-drop-header.hbs index a800565e6d1..55c109ac10f 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/category-drop/category-drop-header.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/category-drop/category-drop-header.hbs @@ -1,8 +1,10 @@ -{{component selectKit.options.selectedNameComponent - tabindex=tabindex - item=selectedContent - selectKit=selectKit - shouldDisplayClearableButton=shouldDisplayClearableButton -}} +
    + {{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit + shouldDisplayClearableButton=shouldDisplayClearableButton + }} -{{d-icon caretIcon class="caret-icon"}} + {{d-icon caretIcon class="caret-icon"}} +
    diff --git a/app/assets/javascripts/select-kit/addon/templates/components/category-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/category-row.hbs index cff579c047a..dfd630dffcb 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/category-row.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/category-row.hbs @@ -1,5 +1,5 @@ {{#if category}} -
    + {{#if shouldDisplayDescription}} -
    {{dir-span description}}
    + {{/if}} {{else}} {{html-safe label}} diff --git a/app/assets/javascripts/select-kit/addon/templates/components/combo-box/combo-box-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/combo-box/combo-box-header.hbs index 59504133b9a..4517a8a0d58 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/combo-box/combo-box-header.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/combo-box/combo-box-header.hbs @@ -1,18 +1,20 @@ -{{#each icons as |icon|}} {{d-icon icon}} {{/each}} +
    + {{#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"}} +
    diff --git a/app/assets/javascripts/select-kit/addon/templates/components/dropdown-select-box/dropdown-select-box-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/dropdown-select-box/dropdown-select-box-header.hbs index 297c4fde9db..d27bd9a0fda 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/dropdown-select-box/dropdown-select-box-header.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/dropdown-select-box/dropdown-select-box-header.hbs @@ -1,12 +1,14 @@ -{{#each icons as |icon|}} {{d-icon icon}} {{/each}} +
    + {{#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}} +
    diff --git a/app/assets/javascripts/select-kit/addon/templates/components/email-group-user-chooser-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/email-group-user-chooser-header.hbs deleted file mode 100644 index 6225633023e..00000000000 --- a/app/assets/javascripts/select-kit/addon/templates/components/email-group-user-chooser-header.hbs +++ /dev/null @@ -1,23 +0,0 @@ -
    - {{#each shownItems as |item|}} - {{component selectKit.options.selectedNameComponent - tabindex=tabindex - item=item - selectKit=selectKit - }} - {{/each}} - {{#if hasHiddenItems}} -
    - {{i18n "x_more" count=hiddenItemsCount}} -
    - {{/if}} - - {{#unless hasReachedMaximumSelection}} -
    - {{component selectKit.options.filterComponent - selectKit=selectKit - id=(concat selectKit.uniqueID "-filter") - }} -
    - {{/unless}} -
    diff --git a/app/assets/javascripts/select-kit/addon/templates/components/future-date-input-selector/future-date-input-selector-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/future-date-input-selector/future-date-input-selector-header.hbs index 171ef3f3a63..d1aeaec9685 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/future-date-input-selector/future-date-input-selector-header.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/future-date-input-selector/future-date-input-selector-header.hbs @@ -1,19 +1,21 @@ -{{#if icons}} -
    - {{#each icons as |icon|}} {{d-icon icon}} {{/each}} -
    -{{/if}} +
    + {{#if icons}} +
    + {{#each icons as |icon|}} {{d-icon icon}} {{/each}} +
    + {{/if}} -{{component selectKit.options.selectedNameComponent - tabindex=tabindex - item=selectedContent - selectKit=selectKit -}} + {{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit + }} -{{#if selectedContent.datetime}} - - {{selectedContent.datetime}} - -{{/if}} + {{#if selectedContent.datetime}} + + {{selectedContent.datetime}} + + {{/if}} -{{d-icon caretIcon class="caret-icon"}} + {{d-icon caretIcon class="caret-icon"}} +
    diff --git a/app/assets/javascripts/select-kit/addon/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs deleted file mode 100644 index a2826cffb83..00000000000 --- a/app/assets/javascripts/select-kit/addon/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs +++ /dev/null @@ -1,7 +0,0 @@ -{{component selectKit.options.selectedNameComponent - tabindex=tabindex - item=selectedContent - selectKit=selectKit -}} - -{{d-icon caretIcon class="caret-icon"}} diff --git a/app/assets/javascripts/select-kit/addon/templates/components/mini-tag-chooser/selected-collection.hbs b/app/assets/javascripts/select-kit/addon/templates/components/mini-tag-chooser/selected-collection.hbs index a5d613354d0..8b280a41512 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/mini-tag-chooser/selected-collection.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/mini-tag-chooser/selected-collection.hbs @@ -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}} +
    + {{#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}} +
    +{{/if}} diff --git a/app/assets/javascripts/select-kit/addon/templates/components/multi-select.hbs b/app/assets/javascripts/select-kit/addon/templates/components/multi-select.hbs index 8f68a91b5ed..445584c32e1 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/multi-select.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/multi-select.hbs @@ -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}} +
    + {{#each selectedContent as |item|}} + {{component selectKit.options.selectedChoiceComponent + item=item + selectKit=selectKit + }} + {{/each}} +
    + {{/if}} + {{#if selectKit.isLoading}} {{#if site}} @@ -15,14 +31,6 @@ {{/if}} {{else}} - {{#if selectKit.filter}} - {{#if selectKit.hasNoContent}} - - {{i18n "select_kit.no_content"}} - - {{/if}} - {{/if}} - {{#each collections as |collection|}} {{component (component-for-collection collection.identifier selectKit) collection=collection @@ -30,8 +38,18 @@ value=value }} {{/each}} + + {{#if selectKit.filter}} + {{#if selectKit.hasNoContent}} + + {{i18n "select_kit.no_content"}} + + {{else}} + + {{i18n "select_kit.results_count" count=mainCollection.length}} + + {{/if}} + {{/if}} {{/if}} {{/select-kit/select-kit-body}} - -
    {{/unless}} diff --git a/app/assets/javascripts/select-kit/addon/templates/components/multi-select/format-selected-content.hbs b/app/assets/javascripts/select-kit/addon/templates/components/multi-select/format-selected-content.hbs new file mode 100644 index 00000000000..da31085fdf9 --- /dev/null +++ b/app/assets/javascripts/select-kit/addon/templates/components/multi-select/format-selected-content.hbs @@ -0,0 +1,3 @@ + + {{formatedContent}} + diff --git a/app/assets/javascripts/select-kit/addon/templates/components/multi-select/multi-select-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/multi-select/multi-select-header.hbs index 0b3792d56b9..b45cfd1c7a0 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/multi-select/multi-select-header.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/multi-select/multi-select-header.hbs @@ -1,18 +1,9 @@ -
    - {{#each selectedContent as |item|}} - {{component selectKit.options.selectedNameComponent - tabindex=tabindex - item=item - selectKit=selectKit - }} +
    + {{#each icons as |icon|}} + {{d-icon icon}} {{/each}} - {{#unless hasReachedMaximumSelection}} -
    - {{component selectKit.options.filterComponent - selectKit=selectKit - id=(concat selectKit.uniqueID "-filter") - }} -
    - {{/unless}} + {{multi-select/format-selected-content content=selectedContent selectKit=selectKit}} + + {{d-icon caretIcon class="caret-icon"}}
    diff --git a/app/assets/javascripts/select-kit/addon/templates/components/notifications-filter/notifications-filter-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/notifications-filter/notifications-filter-header.hbs index 21cee39d91e..e980933ffc9 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/notifications-filter/notifications-filter-header.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/notifications-filter/notifications-filter-header.hbs @@ -1,7 +1,9 @@ - - -{{d-icon caretIcon class="caret-icon"}} +
    + + {{i18n "user.user_notifications.filters.filter_by"}} + + + {{i18n label}} + + {{d-icon caretIcon class="caret-icon"}} +
    diff --git a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/errors-collection.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/errors-collection.hbs index 71befd8decb..1c6072737a4 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/errors-collection.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/errors-collection.hbs @@ -1,5 +1,7 @@ -{{#if collection}} - {{#each collection.content as |item|}} -
  • {{item}}
  • - {{/each}} +{{#if collection.content}} +
      + {{#each collection.content as |item|}} +
    • {{item}}
    • + {{/each}} +
    {{/if}} diff --git a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-collection.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-collection.hbs index 3a3cae0cecf..a9159ba7894 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-collection.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-collection.hbs @@ -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}} + +{{/if}} diff --git a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-filter.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-filter.hbs index 2882367dcae..9dc5578a85b 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-filter.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-filter.hbs @@ -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") diff --git a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/single-select-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/single-select-header.hbs index 37bb407decc..f31aecab0f9 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/single-select-header.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/single-select-header.hbs @@ -1,7 +1,8 @@ -{{#each icons as |icon|}} {{d-icon icon}} {{/each}} +
    + {{#each icons as |icon|}} {{d-icon icon}} {{/each}} -{{component selectKit.options.selectedNameComponent - tabindex=tabindex - item=selectedContent - selectKit=selectKit -}} + {{component selectKit.options.selectedNameComponent + item=selectedContent + selectKit=selectKit + }} +
    diff --git a/app/assets/javascripts/select-kit/addon/templates/components/selected-choice-category.hbs b/app/assets/javascripts/select-kit/addon/templates/components/selected-choice-category.hbs new file mode 100644 index 00000000000..3014c035585 --- /dev/null +++ b/app/assets/javascripts/select-kit/addon/templates/components/selected-choice-category.hbs @@ -0,0 +1,3 @@ +{{#selected-choice item=item selectKit=selectKit extraClass=extraClass}} + {{badge}} +{{/selected-choice}} diff --git a/app/assets/javascripts/select-kit/addon/templates/components/selected-choice.hbs b/app/assets/javascripts/select-kit/addon/templates/components/selected-choice.hbs new file mode 100644 index 00000000000..1e215546500 --- /dev/null +++ b/app/assets/javascripts/select-kit/addon/templates/components/selected-choice.hbs @@ -0,0 +1,10 @@ + diff --git a/app/assets/javascripts/select-kit/addon/templates/components/selected-name.hbs b/app/assets/javascripts/select-kit/addon/templates/components/selected-name.hbs index 5a6ffd0466b..ec8683ff608 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/selected-name.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/selected-name.hbs @@ -1,5 +1,5 @@ {{#if selectKit.options.showFullTitle}} -
    +
    {{#if item.icon}} {{d-icon item.icon}} {{/if}} @@ -8,18 +8,14 @@ {{label}} - {{#if selectKit.options.clearOnClick}} - {{d-icon "times"}} - {{else}} - {{#if shouldDisplayClearableButton}} - {{d-button - class="btn-clear" - icon="times" - action=selectKit.deselect - actionParam=item - ariaLabel="clear_input" - }} - {{/if}} + {{#if shouldDisplayClearableButton}} + {{d-button + class="btn-clear" + icon="times" + action=selectKit.deselect + actionParam=item + ariaLabel="clear_input" + }} {{/if}}
    {{else}} diff --git a/app/assets/javascripts/select-kit/addon/templates/components/single-select.hbs b/app/assets/javascripts/select-kit/addon/templates/components/single-select.hbs index 78f88ef5dae..d10c76954f1 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/single-select.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/single-select.hbs @@ -8,7 +8,10 @@ }} {{#select-kit/select-kit-body selectKit=selectKit id=(concat selectKit.uniqueID "-body")}} - {{component selectKit.options.filterComponent selectKit=selectKit id=(concat selectKit.uniqueID "-filter")}} + {{component selectKit.options.filterComponent + selectKit=selectKit + id=(concat selectKit.uniqueID "-filter") + }} {{#if selectKit.isLoading}} @@ -17,14 +20,6 @@ {{/if}} {{else}} - {{#if selectKit.filter}} - {{#if selectKit.hasNoContent}} - - {{i18n "select_kit.no_content"}} - - {{/if}} - {{/if}} - {{#each collections as |collection|}} {{component (component-for-collection collection.identifier selectKit) collection=collection @@ -32,8 +27,18 @@ value=value }} {{/each}} + + {{#if selectKit.filter}} + {{#if selectKit.hasNoContent}} + + {{i18n "select_kit.no_content"}} + + {{else}} + + {{i18n "select_kit.results_count" count=mainCollection.length}} + + {{/if}} + {{/if}} {{/if}} {{/select-kit/select-kit-body}} - -
    {{/unless}} diff --git a/app/assets/javascripts/select-kit/addon/templates/components/tag-drop/tag-drop-header.hbs b/app/assets/javascripts/select-kit/addon/templates/components/tag-drop/tag-drop-header.hbs index a800565e6d1..55c109ac10f 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/tag-drop/tag-drop-header.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/tag-drop/tag-drop-header.hbs @@ -1,8 +1,10 @@ -{{component selectKit.options.selectedNameComponent - tabindex=tabindex - item=selectedContent - selectKit=selectKit - shouldDisplayClearableButton=shouldDisplayClearableButton -}} +
    + {{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit + shouldDisplayClearableButton=shouldDisplayClearableButton + }} -{{d-icon caretIcon class="caret-icon"}} + {{d-icon caretIcon class="caret-icon"}} +
    diff --git a/app/assets/javascripts/select-kit/addon/templates/components/user-chooser/user-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/user-chooser/user-row.hbs index 2fc47dda495..1684862836f 100644 --- a/app/assets/javascripts/select-kit/addon/templates/components/user-chooser/user-row.hbs +++ b/app/assets/javascripts/select-kit/addon/templates/components/user-chooser/user-row.hbs @@ -1,3 +1,7 @@ {{avatar item imageSize="tiny"}} + {{format-username item.username}} -{{item.name}} + +{{#if item.name}} + {{item.name}} +{{/if}} diff --git a/app/assets/stylesheets/common/base/compose.scss b/app/assets/stylesheets/common/base/compose.scss index 4ff99259e3e..e216dc45e30 100644 --- a/app/assets/stylesheets/common/base/compose.scss +++ b/app/assets/stylesheets/common/base/compose.scss @@ -223,7 +223,7 @@ display: flex; flex: 1 0 40%; max-width: 40%; - margin: 0 0 5px 10px; + margin: 0 0 0 10px; @media screen and (max-width: 955px) { flex: 1 0 100%; margin-left: 0; @@ -294,7 +294,7 @@ .mini-tag-chooser { width: 49%; - margin: 0 0 5px auto; + margin: 0 0 0 auto; background: var(--secondary); z-index: z("composer", "dropdown"); } diff --git a/app/assets/stylesheets/common/d-editor.scss b/app/assets/stylesheets/common/d-editor.scss index 651cfdaac2e..4d7ddc2b559 100644 --- a/app/assets/stylesheets/common/d-editor.scss +++ b/app/assets/stylesheets/common/d-editor.scss @@ -52,14 +52,22 @@ .d-editor-button-bar { display: flex; align-items: center; - min-height: 30px; border-bottom: 1px solid var(--primary-low); + padding: 2px; + + .btn:focus { + border-radius: 0; + @include default-focus; + } .btn, .btn-default { background-color: transparent; display: inline-block; color: var(--primary-medium); + height: 34px; + box-sizing: border-box; + .d-icon { color: currentColor; } @@ -264,13 +272,25 @@ // d-editor bar button sizing for all editors - this is kept separate to keep // everything in one place .d-editor-button-bar { - margin: 0.25em; + box-sizing: border-box; // shared styles for all font sizes .btn, .btn-default { padding: 0.5em; + margin: 0; } + + button { + background: pink; + } + + .select-kit { + .select-kit-header { + margin: 0; + } + } + .d-editor-spacer { margin: 0 0.25em; } diff --git a/app/assets/stylesheets/common/select-kit/category-drop.scss b/app/assets/stylesheets/common/select-kit/category-drop.scss index 4d2fc6f2305..920277c159e 100644 --- a/app/assets/stylesheets/common/select-kit/category-drop.scss +++ b/app/assets/stylesheets/common/select-kit/category-drop.scss @@ -74,11 +74,6 @@ } } } - - &.is-expanded .select-kit-wrapper, - .select-kit-wrapper { - display: none; - } } } } diff --git a/app/assets/stylesheets/common/select-kit/combo-box.scss b/app/assets/stylesheets/common/select-kit/combo-box.scss index 6e8c3db0e99..edebb874c79 100644 --- a/app/assets/stylesheets/common/select-kit/combo-box.scss +++ b/app/assets/stylesheets/common/select-kit/combo-box.scss @@ -2,7 +2,6 @@ &.combo-box { .select-kit-body { border-radius: 0; - box-shadow: shadow("dropdown"); } .select-kit-row { @@ -78,13 +77,6 @@ } } - &.is-expanded { - .select-kit-wrapper { - display: block; - @include default-focus; - } - } - &.tag-drop, &.group-dropdown { min-width: auto; @@ -99,10 +91,5 @@ font-weight: bold; } } - - &.is-expanded .select-kit-wrapper, - .select-kit-wrapper { - display: none; - } } } diff --git a/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss b/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss index ce5d6144db9..506e5520c59 100644 --- a/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss +++ b/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss @@ -1,87 +1,96 @@ .select-kit { - &.dropdown-select-box { - display: inline-flex; - min-width: auto; - border: none; - - &.is-expanded { - .select-kit-collection, - .select-kit-body { - border-radius: 0; - } - } - - .select-kit-body { - border: 1px solid var(--primary-low); - background-clip: padding-box; - box-shadow: shadow("dropdown"); - } - - .select-kit-row { - margin: 0; - - .icons { - display: flex; - align-self: flex-start; - margin-right: 0.5em; - - .d-icon { - flex: 0 0 100%; - overflow: hidden; - font-size: $font-up-2; - margin-right: 0; - } - } - - .texts { - line-height: $line-height-medium; - flex: 1 1 0%; - align-items: flex-start; - display: flex; - flex-wrap: wrap; - flex-direction: column; - - .name { - flex: 1 1 auto; - font-weight: bold; - font-size: $font-0; - color: var(--primary); - padding: 0; - @include ellipsis; - max-width: 100%; - } - - .desc { - flex: 1 1 auto; - font-size: $font-down-1; - font-weight: normal; - color: var(--primary-medium); - white-space: normal; - min-width: 350px; - } - } - } - - .select-kit-collection { - padding: 0; - max-height: 100%; - } - - .dropdown-select-box-header { - box-sizing: border-box; - border: 0; - align-items: center; - justify-content: space-between; - flex-direction: row; + &.single-select { + &.dropdown-select-box { display: inline-flex; + min-width: auto; + border: none; - .d-icon + .d-icon { - margin-left: 0.25em; + &.is-expanded { + .select-kit-collection, + .select-kit-body { + border-radius: 0; + } } - &.is-focused { - outline-style: auto; - outline-color: var(--tertiary); + .select-kit-body { + border: 1px solid var(--primary-low); + background-clip: padding-box; + box-shadow: shadow("dropdown"); + } + + .select-kit-row { + margin: 0; + + .icons { + display: flex; + align-self: flex-start; + margin-right: 0.5em; + + .d-icon { + flex: 0 0 100%; + overflow: hidden; + font-size: $font-up-2; + margin-right: 0; + } + } + + .texts { + line-height: $line-height-medium; + flex: 1 1 0%; + align-items: flex-start; + display: flex; + flex-wrap: wrap; + flex-direction: column; + + .name { + flex: 1 1 auto; + font-weight: bold; + font-size: $font-0; + color: var(--primary); + padding: 0; + @include ellipsis; + max-width: 100%; + } + + .desc { + flex: 1 1 auto; + font-size: $font-down-1; + font-weight: normal; + color: var(--primary-medium); + white-space: normal; + } + } + } + + .select-kit-collection { + padding: 0; + max-height: 100%; + } + + .select-kit-header { + box-sizing: border-box; + border: 1px solid transparent; + align-items: center; + justify-content: space-between; + flex-direction: row; + display: inline-flex; + + .select-kit-header-wrapper { + min-height: unset; + } + + &:focus { + border: 1px solid transparent; + } + + .d-icon + .d-icon { + margin-left: 0.25em; + } + + &.is-focused { + outline-style: auto; + outline-color: var(--tertiary); + } } } } diff --git a/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss index 14423d923d0..8f9b37d357f 100644 --- a/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss +++ b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss @@ -10,34 +10,6 @@ } } - .select-kit-header { - height: 30px; - - .selected-name { - margin: 0; - border: 0; - padding: 0; - outline: none; - box-shadow: none; - cursor: pointer; - max-width: 250px; - overflow: hidden; - - .name { - @include ellipsis; - } - } - } - - .select-kit-filter { - border-top: 0; - } - - &.is-expanded .select-kit-wrapper, - .select-kit-wrapper { - display: none; - } - .select-kit-row { &.is-selected { background: none; @@ -47,42 +19,6 @@ background: var(--tertiary-low); } } - - .selected-tags { - max-height: 125px; - overflow-y: auto; - display: flex; - border-bottom: 1px solid var(--primary-low); - padding: 0.25em 0 0 0.25em; - box-sizing: border-box; - flex-wrap: wrap; - width: 100%; - - .selected-tag { - background: var(--primary-low); - box-sizing: border-box; - margin: 0; - padding: 0.5em; - border: 0; - font-size: $font-down-1; - margin-right: 0.25em; - margin-bottom: 0.25em; - - &.is-highlighted { - box-shadow: 0 0 2px var(--danger), 0 1px 0 rgba(0, 0, 0, 0.05); - } - - .d-icon { - color: var(--primary-low-mid); - vertical-align: middle; - margin-right: 0.25em; - } - - &:hover .d-icon.d-icon-times { - color: var(--danger); - } - } - } } } } diff --git a/app/assets/stylesheets/common/select-kit/multi-select.scss b/app/assets/stylesheets/common/select-kit/multi-select.scss index 0c57f7da214..8662ddb33ad 100644 --- a/app/assets/stylesheets/common/select-kit/multi-select.scss +++ b/app/assets/stylesheets/common/select-kit/multi-select.scss @@ -4,27 +4,16 @@ background: var(--secondary); border-radius: 0; - .select-kit-row { - margin: 5px; - min-height: 1px; - padding: 5px; - border-radius: 0; - } - .select-kit-filter { - border: 0; - flex: 1 0 0px; - margin: 1px; + & + .selected-content, + & + .select-kit-collection { + border-top: 1px solid var(--primary-low); + } } - .multi-select-header { - background: var(--secondary); - border: 1px solid var(--primary-medium); - - &.is-focused, - &:focus { - @include default-focus; - } + .select-kit-row { + min-height: 1px; + border-radius: 0; } &.is-disabled { @@ -38,109 +27,56 @@ } } - &.is-expanded { - .select-kit-wrapper { - display: block; - } + .selected-content { + display: flex; + width: 100%; + flex-wrap: wrap; + padding: 0; + justify-content: flex-start; + box-sizing: border-box; + border-bottom: 1px solid var(--primary-low); + padding: 0.25em 0.25em 0 0.25em; + .selected-choice { + margin: 0 0.25em 0.25em 0; + font-size: var(--font-down-1); + + &.selected-choice-color { + border-bottom: 2px solid transparent; + } + } + } + + .multi-select-header { + background: var(--secondary); + border: 1px solid var(--primary-medium); + padding: 0 0.25em 0 0.5em; + font-weight: 500; + font-size: var(--font-0); + box-sizing: border-box; + overflow: hidden; + + .formated-selection { + margin: 0; + border: 0; + padding: 0; + outline: none; + box-shadow: none; + cursor: pointer; + @include ellipsis; + } + } + + .multi-select-header:focus { + border-radius: 0; + @include default-focus; + } + + &.is-expanded { .multi-select-header { border-radius: 0; @include default-focus; } - - .select-kit-body { - border-radius: 0; - box-shadow: shadow("dropdown"); - &.empty-body { - visibility: hidden; - } - } - } - - .choices { - margin: 0; - padding: 0 0 2px 0; - box-sizing: border-box; - display: block; - width: 100%; - - .choice { - display: inline-flex; - box-sizing: border-box; - align-items: center; - justify-content: space-between; - margin: 2px 0 0px 3px; - padding: 0; - float: left; - height: 30px; - - &.input-wrapper { - min-width: 120px; - } - } - - .multi-select-filter .filter-input { - padding-left: 5px; - } - - .selected-color { - .selected-color-wrapper { - display: flex; - flex: 1 0 0px; - flex-direction: column; - } - - .color-preview { - height: 5px; - margin: 0 2px 2px 0px; - display: flex; - width: 100%; - } - } - - .selected-category { - .body { - display: flex; - align-items: center; - } - .badge-wrapper { - &.bullet { - margin-right: 2.5px; - } - - padding: 2px 4px; - line-height: $line-height-medium; - display: flex; - flex: 1 0 0px; - align-items: center; - } - } - - .selected-name { - background: var(--primary-low); - padding: 0.25em; - flex: 1; - align-items: center; - display: flex; - justify-content: space-between; - max-width: calc(100% - 5px); - - .name { - @include ellipsis; - } - - .d-icon { - color: var(--primary-low-mid); - vertical-align: middle; - - &:last-child { - margin-left: 0.5em; - } - } - &:hover .d-icon:last-child { - color: var(--danger); - } - } } } } diff --git a/app/assets/stylesheets/common/select-kit/notifications-filter.scss b/app/assets/stylesheets/common/select-kit/notifications-filter.scss index 3592594e549..9ff4450ade2 100644 --- a/app/assets/stylesheets/common/select-kit/notifications-filter.scss +++ b/app/assets/stylesheets/common/select-kit/notifications-filter.scss @@ -11,7 +11,6 @@ .notifications-filter-header { padding: 0.5em; background: none; - border: none; outline: none; cursor: pointer; diff --git a/app/assets/stylesheets/common/select-kit/select-kit.scss b/app/assets/stylesheets/common/select-kit/select-kit.scss index 9c504d23d9a..78e4d15de63 100644 --- a/app/assets/stylesheets/common/select-kit/select-kit.scss +++ b/app/assets/stylesheets/common/select-kit/select-kit.scss @@ -7,6 +7,24 @@ position: relative; vertical-align: middle; + &.is-hidden { + display: none !important; + } + + > summary { + list-style-type: none; + display: block; + + &:before { + content: none !important; + margin: 0; + } + } + + > summary::marker { + display: none !important; + } + &.is-disabled { pointer-events: none; } @@ -19,13 +37,21 @@ flex-direction: column; align-items: center; justify-content: center; + border-radius: 0; + border: none; + border: 1px solid var(--primary-low); + box-shadow: shadow("dropdown"); + background: var(--secondary); } .select-kit-collection { - border-radius: inherit; box-sizing: border-box; width: 100%; } + + .select-kit-filter.is-expanded { + padding: 0.25em 0.5em; + } } &.is-loading { @@ -34,17 +60,6 @@ } } - &.is-above { - .select-kit-filter { - border-top: 0; - } - - .select-kit-wrapper { - bottom: 0; - top: auto; - } - } - .d-icon { color: var(--primary-high); } @@ -54,10 +69,16 @@ overflow: hidden; transition: all 0.25s; cursor: pointer; - display: flex; - align-items: center; - flex-direction: row; - min-height: 30px; + + .select-kit-header-wrapper { + box-sizing: border-box; + min-height: 28px; + display: flex; + align-items: center; + flex: 1 0 auto; + width: 100%; + height: 100%; + } .d-icon-spinner { -webkit-animation: rotate-forever 1s infinite linear; @@ -146,7 +167,11 @@ box-sizing: border-box; align-items: center; justify-content: flex-start; - padding: 0.75em; + padding: 0.5em; + + > * { + pointer-events: none; + } .name { margin: 0; @@ -185,16 +210,17 @@ } .select-kit-collection { - background: var(--secondary); overflow-x: hidden; overflow-y: auto; - border-radius: inherit; -webkit-overflow-scrolling: touch; margin: 0; - padding: 0; max-height: 250px; width: 100%; + &:hover .select-kit-row.is-highlighted:hover { + background: var(--tertiary-low); + } + .validation-message { white-space: nowrap; color: var(--danger); @@ -203,15 +229,6 @@ padding: 0 2px; } - .select-kit-collection { - padding: 0; - margin: 0; - - &:hover .select-kit-row.is-highlighted:hover { - background: var(--tertiary-low); - } - } - &::-webkit-scrollbar { -webkit-appearance: none; width: 10px; @@ -244,7 +261,7 @@ border: 0; border-radius: 0; box-shadow: none; - width: auto; + width: 100%; } &.is-hidden { @@ -261,16 +278,6 @@ } } - .select-kit-wrapper { - position: absolute; - top: 0; - left: 0; - background: none; - display: none; - box-sizing: border-box; - pointer-events: none; - } - .select-kit-errors-collection { background: var(--danger); padding: 0.25em 1em; @@ -285,7 +292,8 @@ } .no-content, - .is-loading { + .is-loading, + .results-count { display: flex; flex: 1 0 auto; padding: 0.5em; @@ -294,8 +302,22 @@ background: var(--secondary); } + .results-count { + font-size: var(--font-down-1); + color: var(--primary-med-or-secondary-med); + position: absolute; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + padding: 0; + border: 0; + height: 1px; + width: 1px; + overflow: hidden; + white-space: nowrap; + } + .is-loading { - align-items: center; + align-items: flex-start; justify-content: center; } } diff --git a/app/assets/stylesheets/common/select-kit/single-select.scss b/app/assets/stylesheets/common/select-kit/single-select.scss index caf06efb833..4e45a57a01d 100644 --- a/app/assets/stylesheets/common/select-kit/single-select.scss +++ b/app/assets/stylesheets/common/select-kit/single-select.scss @@ -3,8 +3,6 @@ display: flex; &.is-expanded { - padding: 0.25em 0.5em; - border-top: 1px solid var(--primary-low); border-bottom: 1px solid var(--primary-low); } } @@ -15,10 +13,15 @@ @include default-focus; } + .select-kit-header:focus { + @include default-focus; + } + .select-kit-header.btn:focus, .select-kit-header.btn:active { outline: none; } + &.is-disabled { .select-kit-header { opacity: 0.5; diff --git a/app/assets/stylesheets/common/select-kit/toolbar-popup-menu-options.scss b/app/assets/stylesheets/common/select-kit/toolbar-popup-menu-options.scss index 00994b6a067..4907972aa6c 100644 --- a/app/assets/stylesheets/common/select-kit/toolbar-popup-menu-options.scss +++ b/app/assets/stylesheets/common/select-kit/toolbar-popup-menu-options.scss @@ -8,7 +8,15 @@ } .select-kit-header { - height: 32px; + width: 30px; + display: flex; + align-items: center; + justify-content: center; + + &:focus { + border-color: var(--tertiary); + } + .d-icon { color: var(--primary-medium); } diff --git a/app/assets/stylesheets/common/select-kit/user-notifications-dropdown.scss b/app/assets/stylesheets/common/select-kit/user-notifications-dropdown.scss index f339b62f0a4..b04b7046258 100644 --- a/app/assets/stylesheets/common/select-kit/user-notifications-dropdown.scss +++ b/app/assets/stylesheets/common/select-kit/user-notifications-dropdown.scss @@ -12,10 +12,6 @@ max-width: 485px; } - .select-kit-header { - justify-content: center; - } - .dropdown-select-box-header { align-items: center; } diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss index 592abf7e491..9caa91b85e1 100644 --- a/app/assets/stylesheets/desktop/topic-list.scss +++ b/app/assets/stylesheets/desktop/topic-list.scss @@ -210,7 +210,7 @@ button.dismiss-read { > li { height: 100%; } - .select-kit-header { + .select-kit { align-self: stretch; height: 100%; margin-bottom: var(--nav-space); diff --git a/app/assets/stylesheets/mobile/compose.scss b/app/assets/stylesheets/mobile/compose.scss index 6211b8ea1d3..6cbdf9b3a63 100644 --- a/app/assets/stylesheets/mobile/compose.scss +++ b/app/assets/stylesheets/mobile/compose.scss @@ -182,6 +182,7 @@ .with-tags { .mini-tag-chooser { z-index: z("base"); + margin-bottom: 5px; } .selected-name { diff --git a/app/assets/stylesheets/mobile/modal.scss b/app/assets/stylesheets/mobile/modal.scss index e030c3d2bfb..9ee3e2dd06a 100644 --- a/app/assets/stylesheets/mobile/modal.scss +++ b/app/assets/stylesheets/mobile/modal.scss @@ -25,6 +25,8 @@ } .modal-body { padding: 0.667em; + overflow-y: scroll; + > * { // adding box-sizing: border-box; to .modal-body causes iOS dropdown issues box-sizing: border-box; diff --git a/app/assets/stylesheets/mobile/select-kit/select-kit.scss b/app/assets/stylesheets/mobile/select-kit/select-kit.scss index 1589036a073..38a9d9d68f1 100644 --- a/app/assets/stylesheets/mobile/select-kit/select-kit.scss +++ b/app/assets/stylesheets/mobile/select-kit/select-kit.scss @@ -8,4 +8,8 @@ font-size: 16px; } } + + .select-kit-row { + padding: 0.75em 0.5em; + } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index d87a6ec388b..685689e0aa9 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1956,10 +1956,14 @@ en: other: "+ %{count} subcategories" select_kit: + delete_item: "Delete %{name}" filter_by: "Filter by: %{name}" select_to_filter: "Select a value to filter" default_header_text: Select... no_content: No matches found + results_count: + one: "%{count} result" + other: "%{count} results" filter_placeholder: Search... filter_placeholder_with_any: Search or create... create: "Create: '%{content}'" diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/dropdowns.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/dropdowns.hbs index 0089e62841b..41dae968c48 100644 --- a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/dropdowns.hbs +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/dropdowns.hbs @@ -52,7 +52,6 @@ {{#styleguide-example title="future-date-input-selector"}} {{future-date-input-selector - minimumResultsForSearch=-1 statusType="open" input=dummy.topicTimerUpdateDate includeWeekend=true