diff --git a/app/assets/javascripts/discourse/app/components/table-header-toggle.js b/app/assets/javascripts/discourse/app/components/table-header-toggle.js index 6745c9639d0..be3484d04e1 100644 --- a/app/assets/javascripts/discourse/app/components/table-header-toggle.js +++ b/app/assets/javascripts/discourse/app/components/table-header-toggle.js @@ -1,23 +1,28 @@ import Component from "@ember/component"; import { schedule } from "@ember/runloop"; import { htmlSafe } from "@ember/template"; +import { + attributeBindings, + classNames, + tagName, +} from "@ember-decorators/component"; import { iconHTML } from "discourse-common/lib/icon-library"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default Component.extend({ - tagName: "div", - classNames: ["directory-table__column-header", "sortable"], - attributeBindings: ["title", "colspan", "ariaSort:aria-sort", "role"], - role: "columnheader", - labelKey: null, - chevronIcon: null, - columnIcon: null, - translated: false, - automatic: false, - onActiveRender: null, - pressedState: null, - ariaLabel: null, +@tagName("div") +@classNames("directory-table__column-header", "sortable") +@attributeBindings("title", "colspan", "ariaSort:aria-sort", "role") +export default class TableHeaderToggle extends Component { + role = "columnheader"; + labelKey = null; + chevronIcon = null; + columnIcon = null; + translated = false; + automatic = false; + onActiveRender = null; + pressedState = null; + ariaLabel = null; @discourseComputed("order", "field", "asc") ariaSort() { @@ -26,14 +31,16 @@ export default Component.extend({ } else { return "none"; } - }, + } + toggleProperties() { if (this.order === this.field) { this.set("asc", this.asc ? null : true); } else { this.setProperties({ order: this.field, asc: null }); } - }, + } + toggleChevron() { if (this.order === this.field) { let chevron = iconHTML(this.asc ? "chevron-up" : "chevron-down"); @@ -41,32 +48,35 @@ export default Component.extend({ } else { this.set("chevronIcon", null); } - }, + } + click() { this.toggleProperties(); - }, + } + keyPress(e) { if (e.which === 13) { this.toggleProperties(); } - }, + } + didReceiveAttrs() { - this._super(...arguments); + super.didReceiveAttrs(...arguments); if (!this.automatic && !this.translated) { this.set("labelKey", this.field); } this.set("id", `table-header-toggle-${this.field.replace(/\s/g, "")}`); this.toggleChevron(); this._updateA11yAttributes(); - }, + } didRender() { - this._super(...arguments); + super.didRender(...arguments); if (this.onActiveRender && this.chevronIcon) { this.onActiveRender(this.element); } - }, + } _updateA11yAttributes() { let criteria = ""; @@ -99,10 +109,11 @@ export default Component.extend({ } else { this.set("pressedState", "false"); } - }, + } + _focusHeader() { schedule("afterRender", () => { document.getElementById(this.id)?.focus(); }); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/tag-drop-link.js b/app/assets/javascripts/discourse/app/components/tag-drop-link.js index b040b55cd5f..b308d099c07 100644 --- a/app/assets/javascripts/discourse/app/components/tag-drop-link.js +++ b/app/assets/javascripts/discourse/app/components/tag-drop-link.js @@ -1,18 +1,22 @@ import Component from "@ember/component"; +import { + attributeBindings, + classNameBindings, + tagName, +} from "@ember-decorators/component"; import DiscourseURL from "discourse/lib/url"; import getURL from "discourse-common/lib/get-url"; import discourseComputed from "discourse-common/utils/decorators"; -export default Component.extend({ - tagName: "a", - classNameBindings: [ - ":tag-badge-wrapper", - ":badge-wrapper", - ":bullet", - "tagClass", - ], - attributeBindings: ["href"], - +@tagName("a") +@classNameBindings( + ":tag-badge-wrapper", + ":badge-wrapper", + ":bullet", + "tagClass" +) +@attributeBindings("href") +export default class TagDropLink extends Component { @discourseComputed("tagId", "category") href(tagId, category) { let path; @@ -24,16 +28,16 @@ export default Component.extend({ } return getURL(path); - }, + } @discourseComputed("tagId") tagClass(tagId) { return "tag-" + tagId; - }, + } click(e) { e.preventDefault(); DiscourseURL.routeTo(this.href); return true; - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/tag-groups-form.hbs b/app/assets/javascripts/discourse/app/components/tag-groups-form.hbs index d802515a69a..e5d51636a98 100644 --- a/app/assets/javascripts/discourse/app/components/tag-groups-form.hbs +++ b/app/assets/javascripts/discourse/app/components/tag-groups-form.hbs @@ -125,7 +125,7 @@ /> { this.set("allGroups", groups); }); - }, + } @discourseComputed( "buffered.name", @@ -36,7 +41,7 @@ export default Component.extend(bufferedProperty("model"), { (!this.everyoneSelected(permissions) && isEmpty(this.selectedGroupNames(permissions))) ); - }, + } @discourseComputed("buffered.permissions", "allGroups") selectedGroupIds(permissions, allGroups) { @@ -53,7 +58,7 @@ export default Component.extend(bufferedProperty("model"), { }); return groupIds; - }, + } everyoneSelected(permissions) { if (!permissions) { @@ -61,7 +66,7 @@ export default Component.extend(bufferedProperty("model"), { } return permissions.everyone === PermissionType.FULL; - }, + } selectedGroupNames(permissions) { if (!permissions) { @@ -69,87 +74,89 @@ export default Component.extend(bufferedProperty("model"), { } return Object.keys(permissions).filter((name) => name !== "everyone"); - }, + } - actions: { - setPermissionsType(permissionName) { - let updatedPermissions = Object.assign( - {}, - this.buffered.get("permissions") - ); + @action + setPermissionsType(permissionName) { + let updatedPermissions = Object.assign( + {}, + this.buffered.get("permissions") + ); - if (permissionName === "private") { - delete updatedPermissions.everyone; - } else if (permissionName === "visible") { - updatedPermissions.everyone = PermissionType.READONLY; + if (permissionName === "private") { + delete updatedPermissions.everyone; + } else if (permissionName === "visible") { + updatedPermissions.everyone = PermissionType.READONLY; + } else { + updatedPermissions.everyone = PermissionType.FULL; + } + + this.buffered.set("permissions", updatedPermissions); + } + + @action + setPermissionsGroups(groupIds) { + let updatedPermissions = Object.assign( + {}, + this.buffered.get("permissions") + ); + + this.allGroups.forEach((group) => { + if (groupIds.includes(group.id)) { + updatedPermissions[group.name] = PermissionType.FULL; } else { - updatedPermissions.everyone = PermissionType.FULL; + delete updatedPermissions[group.name]; } + }); - this.buffered.set("permissions", updatedPermissions); - }, + this.buffered.set("permissions", updatedPermissions); + } - setPermissionsGroups(groupIds) { - let updatedPermissions = Object.assign( - {}, - this.buffered.get("permissions") - ); + @action + save() { + if (this.cannotSave) { + this.dialog.alert(I18n.t("tagging.groups.cannot_save")); + return false; + } - this.allGroups.forEach((group) => { - if (groupIds.includes(group.id)) { - updatedPermissions[group.name] = PermissionType.FULL; - } else { - delete updatedPermissions[group.name]; - } - }); + const attrs = this.buffered.getProperties( + "name", + "tag_names", + "parent_tag_name", + "one_per_topic", + "permissions" + ); - this.buffered.set("permissions", updatedPermissions); - }, + // If 'everyone' is set to full, we can remove any groups. + if ( + !attrs.permissions || + attrs.permissions.everyone === PermissionType.FULL + ) { + attrs.permissions = { everyone: PermissionType.FULL }; + } - save() { - if (this.cannotSave) { - this.dialog.alert(I18n.t("tagging.groups.cannot_save")); - return false; + this.model.save(attrs).then(() => { + this.commitBuffer(); + + if (this.onSave) { + this.onSave(); + } else { + this.router.transitionTo("tagGroups.index"); } + }); + } - const attrs = this.buffered.getProperties( - "name", - "tag_names", - "parent_tag_name", - "one_per_topic", - "permissions" - ); - - // If 'everyone' is set to full, we can remove any groups. - if ( - !attrs.permissions || - attrs.permissions.everyone === PermissionType.FULL - ) { - attrs.permissions = { everyone: PermissionType.FULL }; - } - - this.model.save(attrs).then(() => { - this.commitBuffer(); - - if (this.onSave) { - this.onSave(); - } else { - this.router.transitionTo("tagGroups.index"); - } - }); - }, - - destroy() { - return this.dialog.yesNoConfirm({ - message: I18n.t("tagging.groups.confirm_delete"), - didConfirm: () => { - this.model.destroyRecord().then(() => { - if (this.onDestroy) { - this.onDestroy(); - } - }); - }, - }); - }, - }, -}); + @action + destroyTagGroup() { + return this.dialog.yesNoConfirm({ + message: I18n.t("tagging.groups.confirm_delete"), + didConfirm: () => { + this.model.destroyRecord().then(() => { + if (this.onDestroy) { + this.onDestroy(); + } + }); + }, + }); + } +} diff --git a/app/assets/javascripts/discourse/app/components/tag-info.js b/app/assets/javascripts/discourse/app/components/tag-info.js index a7fe447c34a..72514aee729 100644 --- a/app/assets/javascripts/discourse/app/components/tag-info.js +++ b/app/assets/javascripts/discourse/app/components/tag-info.js @@ -4,24 +4,27 @@ import { and, reads } from "@ember/object/computed"; import { service } from "@ember/service"; import { htmlSafe } from "@ember/template"; import { isEmpty } from "@ember/utils"; +import { tagName } from "@ember-decorators/component"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default Component.extend({ - dialog: service(), - tagName: "", - loading: false, - tagInfo: null, - newSynonyms: null, - showEditControls: false, - canAdminTag: reads("currentUser.staff"), - editSynonymsMode: and("canAdminTag", "showEditControls"), - editing: false, - newTagName: null, - newTagDescription: null, - router: service(), +@tagName("") +export default class TagInfo extends Component { + @service dialog; + @service router; + + loading = false; + tagInfo = null; + newSynonyms = null; + showEditControls = false; + editing = false; + newTagName = null; + newTagDescription = null; + + @reads("currentUser.staff") canAdminTag; + @and("canAdminTag", "showEditControls") editSynonymsMode; @discourseComputed("tagInfo.tag_group_names") tagGroupsInfo(tagGroupNames) { @@ -29,14 +32,14 @@ export default Component.extend({ count: tagGroupNames.length, tag_groups: tagGroupNames.join(", "), }); - }, + } @discourseComputed("tagInfo.categories") categoriesInfo(categories) { return I18n.t("tagging.category_restrictions", { count: categories.length, }); - }, + } @discourseComputed( "tagInfo.tag_group_names", @@ -45,25 +48,25 @@ export default Component.extend({ ) nothingToShow(tagGroupNames, categories, synonyms) { return isEmpty(tagGroupNames) && isEmpty(categories) && isEmpty(synonyms); - }, + } @discourseComputed("newTagName") updateDisabled(newTagName) { const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); newTagName = newTagName ? newTagName.replace(filterRegexp, "").trim() : ""; return newTagName.length === 0; - }, + } didInsertElement() { - this._super(...arguments); + super.didInsertElement(...arguments); this.loadTagInfo(); - }, + } didUpdateAttrs() { - this._super(...arguments); + super.didUpdateAttrs(...arguments); this.set("tagInfo", null); this.loadTagInfo(); - }, + } loadTagInfo() { if (this.loading) { @@ -81,7 +84,7 @@ export default Component.extend({ }) .finally(() => this.set("loading", false)) .catch(popupAjaxError); - }, + } @action edit(event) { @@ -95,7 +98,7 @@ export default Component.extend({ newTagName: this.tag.id, newTagDescription: this.tagInfo.description, }); - }, + } @action unlinkSynonym(tag, event) { @@ -105,7 +108,7 @@ export default Component.extend({ }) .then(() => this.tagInfo.synonyms.removeObject(tag)) .catch(popupAjaxError); - }, + } @action deleteSynonym(tag, event) { @@ -122,17 +125,17 @@ export default Component.extend({ .catch(popupAjaxError); }, }); - }, + } @action toggleEditControls() { this.toggleProperty("showEditControls"); - }, + } @action cancelEditing() { this.set("editing", false); - }, + } @action finishedEditing() { @@ -151,7 +154,7 @@ export default Component.extend({ } }) .catch(popupAjaxError); - }, + } @action deleteTag() { @@ -182,7 +185,7 @@ export default Component.extend({ } }, }); - }, + } @action addSynonyms() { @@ -217,5 +220,5 @@ export default Component.extend({ .catch(popupAjaxError); }, }); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/tag-list.js b/app/assets/javascripts/discourse/app/components/tag-list.js index 4bc9728f36b..a3f2d125fd5 100644 --- a/app/assets/javascripts/discourse/app/components/tag-list.js +++ b/app/assets/javascripts/discourse/app/components/tag-list.js @@ -1,34 +1,35 @@ import Component from "@ember/component"; import { sort } from "@ember/object/computed"; +import { classNameBindings } from "@ember-decorators/component"; import Category from "discourse/models/category"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default Component.extend({ - classNameBindings: [ - ":tags-list", - ":tag-list", - "categoryClass", - "tagGroupNameClass", - ], +@classNameBindings( + ":tags-list", + ":tag-list", + "categoryClass", + "tagGroupNameClass" +) +export default class TagList extends Component { + isPrivateMessage = false; - isPrivateMessage: false, - sortedTags: sort("tags", "sortProperties"), + @sort("tags", "sortProperties") sortedTags; @discourseComputed("titleKey") title(titleKey) { return titleKey && I18n.t(titleKey); - }, + } @discourseComputed("categoryId") category(categoryId) { return categoryId && Category.findById(categoryId); - }, + } @discourseComputed("category.fullSlug") categoryClass(slug) { return slug && `tag-list-${slug}`; - }, + } @discourseComputed("tagGroupName") tagGroupNameClass(groupName) { @@ -39,5 +40,5 @@ export default Component.extend({ .toLowerCase(); return groupName && `tag-group-${groupName}`; } - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/tap-tile-grid.js b/app/assets/javascripts/discourse/app/components/tap-tile-grid.js index c2282aad142..dc643685bd3 100644 --- a/app/assets/javascripts/discourse/app/components/tap-tile-grid.js +++ b/app/assets/javascripts/discourse/app/components/tap-tile-grid.js @@ -1,6 +1,7 @@ import Component from "@ember/component"; +import { classNames } from "@ember-decorators/component"; -export default Component.extend({ - classNames: ["tap-tile-grid"], - activeTile: null, -}); +@classNames("tap-tile-grid") +export default class TapTileGrid extends Component { + activeTile = null; +} diff --git a/app/assets/javascripts/discourse/app/components/tap-tile.js b/app/assets/javascripts/discourse/app/components/tap-tile.js index e9b23155e5a..04a484d6803 100644 --- a/app/assets/javascripts/discourse/app/components/tap-tile.js +++ b/app/assets/javascripts/discourse/app/components/tap-tile.js @@ -1,30 +1,35 @@ import Component from "@ember/component"; import { reads } from "@ember/object/computed"; +import { + attributeBindings, + classNameBindings, + classNames, +} from "@ember-decorators/component"; import { propertyEqual } from "discourse/lib/computed"; -export default Component.extend({ - init() { - this._super(...arguments); - this.set("elementId", `tap_tile_${this.tileId}`); - }, +@classNames("tap-tile") +@classNameBindings("active") +@attributeBindings("role", "ariaPressed", "tabIndex") +export default class TapTile extends Component { + role = "button"; + tabIndex = 0; - classNames: ["tap-tile"], - classNameBindings: ["active"], - attributeBindings: ["role", "ariaPressed", "tabIndex"], - role: "button", - tabIndex: 0, - ariaPressed: reads("active"), + @reads("active") ariaPressed; + @propertyEqual("activeTile", "tileId") active; + + init() { + super.init(...arguments); + this.set("elementId", `tap_tile_${this.tileId}`); + } click() { this.onChange(this.tileId); - }, + } keyDown(e) { if (e.key === "Enter") { e.stopPropagation(); this.onChange(this.tileId); } - }, - - active: propertyEqual("activeTile", "tileId"), -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/text-field.js b/app/assets/javascripts/discourse/app/components/text-field.js index 548f469e20b..5f84c541890 100644 --- a/app/assets/javascripts/discourse/app/components/text-field.js +++ b/app/assets/javascripts/discourse/app/components/text-field.js @@ -1,37 +1,33 @@ import { TextField } from "@ember/legacy-built-in-components"; +import { computed } from "@ember/object"; import { cancel, next } from "@ember/runloop"; +import { attributeBindings } from "@ember-decorators/component"; import discourseDebounce from "discourse-common/lib/debounce"; -import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; const DEBOUNCE_MS = 500; -export default TextField.extend({ - attributeBindings: [ - "autocorrect", - "autocapitalize", - "autofocus", - "maxLength", - "dir", - "aria-label", - "aria-controls", - ], - - init() { - this._super(...arguments); - - this._prevValue = null; - this._timer = null; - }, +@attributeBindings( + "autocorrect", + "autocapitalize", + "autofocus", + "maxLength", + "dir", + "aria-label", + "aria-controls" +) +export default class DiscourseTextField extends TextField { + _prevValue = null; + _timer = null; didReceiveAttrs() { - this._super(...arguments); + super.didReceiveAttrs(...arguments); this._prevValue = this.value; - }, + } didUpdateAttrs() { - this._super(...arguments); + super.didUpdateAttrs(...arguments); if (this._prevValue !== this.value) { if (this.onChangeImmediate) { @@ -46,33 +42,32 @@ export default TextField.extend({ ); } } - }, + } _debouncedChange() { next(() => this.onChange(this.value)); - }, + } get dir() { if (this.siteSettings.support_mixed_text_direction) { return "auto"; } - }, + } willDestroyElement() { - this._super(...arguments); + super.willDestroyElement(...arguments); cancel(this._timer); - }, + } - @discourseComputed("placeholderKey") - placeholder: { - get() { - if (this._placeholder) { - return this._placeholder; - } - return this.placeholderKey ? I18n.t(this.placeholderKey) : ""; - }, - set(value) { - return (this._placeholder = value); - }, - }, -}); + @computed("placeholderKey", "_placeholder") + get placeholder() { + if (this._placeholder) { + return this._placeholder; + } + return this.placeholderKey ? I18n.t(this.placeholderKey) : ""; + } + + set placeholder(value) { + this.set("_placeholder", value); + } +} diff --git a/app/assets/javascripts/discourse/app/components/time-input.js b/app/assets/javascripts/discourse/app/components/time-input.js index 76c9870d591..e62bfdc1c8d 100644 --- a/app/assets/javascripts/discourse/app/components/time-input.js +++ b/app/assets/javascripts/discourse/app/components/time-input.js @@ -2,6 +2,7 @@ import Component from "@ember/component"; import { action, computed } from "@ember/object"; import { htmlSafe } from "@ember/template"; import { isPresent } from "@ember/utils"; +import { classNames } from "@ember-decorators/component"; function convertMinutes(num) { return { hours: Math.floor(num / 60), minutes: num % 60 }; @@ -32,17 +33,14 @@ function convertMinutesToDurationString(n) { return output; } -export default Component.extend({ - classNames: ["d-time-input"], - - hours: null, - - minutes: null, - - relativeDate: null, +@classNames("d-time-input") +export default class TimeInput extends Component { + hours = null; + minutes = null; + relativeDate = null; didReceiveAttrs() { - this._super(...arguments); + super.didReceiveAttrs(...arguments); if (isPresent(this.date)) { this.setProperties({ @@ -61,9 +59,10 @@ export default Component.extend({ minutes: null, }); } - }, + } - minimumTime: computed("relativeDate", "date", function () { + @computed("relativeDate", "date") + get minimumTime() { if (this.relativeDate) { if (this.date) { if (!this.date.isSame(this.relativeDate, "day")) { @@ -75,9 +74,10 @@ export default Component.extend({ return this.relativeDate.hours() * 60 + this.relativeDate.minutes(); } } - }), + } - timeOptions: computed("minimumTime", "hours", "minutes", function () { + @computed("minimumTime", "hours", "minutes") + get timeOptions() { let options = []; const start = this.minimumTime @@ -136,20 +136,21 @@ export default Component.extend({ title: name, }; }); - }), + } - time: computed("minimumTime", "hours", "minutes", function () { + @computed("minimumTime", "hours", "minutes") + get time() { if (isPresent(this.hours) && isPresent(this.minutes)) { return parseInt(this.hours, 10) * 60 + parseInt(this.minutes, 10); } - }), + } @action onFocusIn(value, event) { if (value && event.target) { event.target.select(); } - }, + } @action onChangeTime(time) { @@ -182,5 +183,5 @@ export default Component.extend({ }); } } - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js b/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js index 77ff70e3fa5..f782f47f4ef 100644 --- a/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js +++ b/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js @@ -1,6 +1,8 @@ import Component from "@ember/component"; import { action } from "@ember/object"; import { and, equal } from "@ember/object/computed"; +import { tagName } from "@ember-decorators/component"; +import { observes, on } from "@ember-decorators/object"; import { defaultTimeShortcuts, formatTime, @@ -9,10 +11,7 @@ import { TIME_SHORTCUT_TYPES, } from "discourse/lib/time-shortcut"; import { laterToday, now, parseCustomDatetime } from "discourse/lib/time-utils"; -import discourseComputed, { - observes, - on, -} from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; const BINDINGS = { @@ -40,30 +39,34 @@ const BINDINGS = { "n r": { handler: "selectShortcut", args: [TIME_SHORTCUT_TYPES.NONE] }, }; -export default Component.extend({ - tagName: "", +@tagName("") +export default class TimeShortcutPicker extends Component { + @equal("selectedShortcut", TIME_SHORTCUT_TYPES.CUSTOM) customDatetimeSelected; + @equal("selectedShortcut", TIME_SHORTCUT_TYPES.RELATIVE) + relativeTimeSelected; + @and("customDate", "customTime") customDatetimeFilled; - userTimezone: null, + userTimezone = null; - onTimeSelected: null, + onTimeSelected = null; - selectedShortcut: null, - selectedTime: null, - selectedDate: null, - selectedDatetime: null, - prefilledDatetime: null, - selectedDurationMins: null, + selectedShortcut = null; + selectedTime = null; + selectedDate = null; + selectedDatetime = null; + prefilledDatetime = null; + selectedDurationMins = null; - hiddenOptions: null, - customOptions: null, + hiddenOptions = null; + customOptions = null; - lastCustomDate: null, - lastCustomTime: null, - parsedLastCustomDatetime: null, - customDate: null, - customTime: null, + lastCustomDate = null; + lastCustomTime = null; + parsedLastCustomDatetime = null; + customDate = null; + customTime = null; - _itsatrap: null, + _itsatrap = null; @on("init") _setupPicker() { @@ -79,7 +82,7 @@ export default Component.extend({ } this._bindKeyboardShortcuts(); - }, + } @observes("prefilledDatetime") prefilledDatetimeChanged() { @@ -92,13 +95,13 @@ export default Component.extend({ selectedShortcut: null, }); } - }, + } willDestroyElement() { - this._super(...arguments); + super.willDestroyElement(...arguments); this._itsatrap.unbind(Object.keys(BINDINGS)); - }, + } parsePrefilledDatetime() { let parsedDatetime = parseCustomDatetime( @@ -116,7 +119,7 @@ export default Component.extend({ customTime: parsedDatetime.format("HH:mm"), selectedShortcut: TIME_SHORTCUT_TYPES.CUSTOM, }); - }, + } _loadLastUsedCustomDatetime() { const lastTime = this.keyValueStore.lastCustomTime; @@ -135,7 +138,7 @@ export default Component.extend({ parsedLastCustomDatetime: parsed, }); } - }, + } _bindKeyboardShortcuts() { Object.keys(BINDINGS).forEach((shortcut) => { @@ -145,11 +148,7 @@ export default Component.extend({ return false; }); }); - }, - - customDatetimeSelected: equal("selectedShortcut", TIME_SHORTCUT_TYPES.CUSTOM), - relativeTimeSelected: equal("selectedShortcut", TIME_SHORTCUT_TYPES.RELATIVE), - customDatetimeFilled: and("customDate", "customTime"), + } @observes("customDate", "customTime") customDatetimeChanged() { @@ -157,7 +156,7 @@ export default Component.extend({ return; } this.selectShortcut(TIME_SHORTCUT_TYPES.CUSTOM); - }, + } @discourseComputed( "timeShortcuts", @@ -203,7 +202,7 @@ export default Component.extend({ this._applyCustomLabels(options, customLabels); options.forEach((o) => (o.timeFormatted = formatTime(o))); return options; - }, + } @action relativeTimeChanged(relativeTimeMins) { @@ -215,7 +214,7 @@ export default Component.extend({ }); this.onTimeSelected?.(TIME_SHORTCUT_TYPES.RELATIVE, dateTime); - }, + } @action selectShortcut(type) { @@ -259,7 +258,7 @@ export default Component.extend({ if (this.onTimeSelected) { this.onTimeSelected(type, dateTime); } - }, + } _applyCustomLabels(options, customLabels) { options.forEach((option) => { @@ -267,7 +266,7 @@ export default Component.extend({ option.label = customLabels[option.id]; } }); - }, + } _formatTime(options) { options.forEach((option) => { @@ -275,9 +274,9 @@ export default Component.extend({ option.timeFormatted = option.time.format(I18n.t(option.timeFormatKey)); } }); - }, + } _defaultCustomDateTime() { return moment.tz(this.userTimezone).add(1, "hour"); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/top-period-buttons.js b/app/assets/javascripts/discourse/app/components/top-period-buttons.js index 43353085c07..28bfc68e60a 100644 --- a/app/assets/javascripts/discourse/app/components/top-period-buttons.js +++ b/app/assets/javascripts/discourse/app/components/top-period-buttons.js @@ -1,17 +1,17 @@ import Component from "@ember/component"; import { action } from "@ember/object"; +import { classNames } from "@ember-decorators/component"; import discourseComputed from "discourse-common/utils/decorators"; -export default Component.extend({ - classNames: ["top-title-buttons"], - +@classNames("top-title-buttons") +export default class TopPeriodButtons extends Component { @discourseComputed("period") periods(period) { return this.site.get("periods").filter((p) => p !== period); - }, + } @action changePeriod(p) { this.action(p); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/topic-category.js b/app/assets/javascripts/discourse/app/components/topic-category.js index 3f003c8d77e..1985ed434b3 100644 --- a/app/assets/javascripts/discourse/app/components/topic-category.js +++ b/app/assets/javascripts/discourse/app/components/topic-category.js @@ -1,3 +1,4 @@ import Component from "@ember/component"; + // Injections don't occur without a class -export default Component.extend(); +export default class TopicCategory extends Component {} diff --git a/app/assets/javascripts/discourse/app/components/topic-entrance.js b/app/assets/javascripts/discourse/app/components/topic-entrance.js index 49d71ad85f6..7d982f81387 100644 --- a/app/assets/javascripts/discourse/app/components/topic-entrance.js +++ b/app/assets/javascripts/discourse/app/components/topic-entrance.js @@ -1,6 +1,8 @@ import Component from "@ember/component"; +import { action } from "@ember/object"; import { scheduleOnce } from "@ember/runloop"; import { service } from "@ember/service"; +import { classNameBindings } from "@ember-decorators/component"; import $ from "jquery"; import DiscourseURL from "discourse/lib/url"; import CleansUp from "discourse/mixins/cleans-up"; @@ -30,41 +32,50 @@ function entranceDate(dt, showTime) { ); } -export default Component.extend(CleansUp, { - router: service(), - session: service(), - historyStore: service(), - elementId: "topic-entrance", - classNameBindings: ["visible::hidden"], - topic: null, - visible: null, - _position: null, - _originalActiveElement: null, - _activeButton: null, +@classNameBindings("visible::hidden") +export default class TopicEntrance extends Component.extend(CleansUp) { + @service router; + @service session; + @service historyStore; + + elementId = "topic-entrance"; + topic = null; + visible = null; + _position = null; + _originalActiveElement = null; + _activeButton = null; @discourseComputed("topic.created_at") - createdDate: (createdAt) => new Date(createdAt), + createdDate(createdAt) { + return new Date(createdAt); + } @discourseComputed("topic.bumped_at") - bumpedDate: (bumpedAt) => new Date(bumpedAt), + bumpedDate(bumpedAt) { + return new Date(bumpedAt); + } @discourseComputed("createdDate", "bumpedDate") showTime(createdDate, bumpedDate) { return ( bumpedDate.getTime() - createdDate.getTime() < 1000 * 60 * 60 * 24 * 2 ); - }, + } @discourseComputed("createdDate", "showTime") - topDate: (createdDate, showTime) => entranceDate(createdDate, showTime), + topDate(createdDate, showTime) { + return entranceDate(createdDate, showTime); + } @discourseComputed("bumpedDate", "showTime") - bottomDate: (bumpedDate, showTime) => entranceDate(bumpedDate, showTime), + bottomDate(bumpedDate, showTime) { + return entranceDate(bumpedDate, showTime); + } didInsertElement() { - this._super(...arguments); + super.didInsertElement(...arguments); this.appEvents.on("topic-entrance:show", this, "_show"); - }, + } _setCSS() { const pos = this._position; @@ -79,7 +90,7 @@ export default Component.extend(CleansUp, { pos.left = windowWidth - width - 15; } $self.css(pos); - }, + } @bind _escListener(e) { @@ -96,42 +107,42 @@ export default Component.extend(CleansUp, { e.preventDefault(); } } - }, + } _jumpTopButton() { return this.element.querySelector(".jump-top"); - }, + } _jumpBottomButton() { return this.element.querySelector(".jump-bottom"); - }, + } _setupEscListener() { document.body.addEventListener("keydown", this._escListener); - }, + } _removeEscListener() { document.body.removeEventListener("keydown", this._escListener); - }, + } _trapFocus() { this._originalActiveElement = document.activeElement; this._jumpTopButton().focus(); this._activeButton = "top"; - }, + } _releaseFocus() { if (this._originalActiveElement) { this._originalActiveElement.focus(); this._originalActiveElement = null; } - }, + } _applyDomChanges() { this._setCSS(); this._setupEscListener(); this._trapFocus(); - }, + } _show(data) { this._position = data.position; @@ -152,34 +163,34 @@ export default Component.extend(CleansUp, { } this.cleanUp(); }); - }, + } cleanUp() { this.setProperties({ topic: null, visible: false }); $("html").off("mousedown.topic-entrance"); this._removeEscListener(); this._releaseFocus(); - }, + } willDestroyElement() { - this._super(...arguments); + super.willDestroyElement(...arguments); this.appEvents.off("topic-entrance:show", this, "_show"); - }, + } _jumpTo(destination) { this.historyStore.set("lastTopicIdViewed", this.topic.id); this.cleanUp(); DiscourseURL.routeTo(destination); - }, + } - actions: { - enterTop() { - this._jumpTo(this.get("topic.url")); - }, + @action + enterTop() { + this._jumpTo(this.get("topic.url")); + } - enterBottom() { - this._jumpTo(this.get("topic.lastPostUrl")); - }, - }, -}); + @action + enterBottom() { + this._jumpTo(this.get("topic.lastPostUrl")); + } +} diff --git a/app/assets/javascripts/discourse/app/components/topic-footer-buttons.js b/app/assets/javascripts/discourse/app/components/topic-footer-buttons.js index d15b3e3be50..215bebf364a 100644 --- a/app/assets/javascripts/discourse/app/components/topic-footer-buttons.js +++ b/app/assets/javascripts/discourse/app/components/topic-footer-buttons.js @@ -2,79 +2,81 @@ import Component from "@ember/component"; import { computed } from "@ember/object"; import { alias, or } from "@ember/object/computed"; import { getOwner } from "@ember/owner"; +import { attributeBindings } from "@ember-decorators/component"; import { NotificationLevels } from "discourse/lib/notification-levels"; import { getTopicFooterButtons } from "discourse/lib/register-topic-footer-button"; import { getTopicFooterDropdowns } from "discourse/lib/register-topic-footer-dropdown"; import TopicBookmarkManager from "discourse/lib/topic-bookmark-manager"; import discourseComputed from "discourse-common/utils/decorators"; -export default Component.extend({ - elementId: "topic-footer-buttons", +@attributeBindings("role") +export default class TopicFooterButtons extends Component { + elementId = "topic-footer-buttons"; + role = "region"; - attributeBindings: ["role"], + @getTopicFooterButtons() inlineButtons; + @getTopicFooterDropdowns() inlineDropdowns; - role: "region", + @alias("currentUser.can_send_private_messages") canSendPms; + @alias("topic.details.can_invite_to") canInviteTo; + @alias("currentUser.user_option.enable_defer") canDefer; + @or("topic.archived", "topic.closed", "topic.deleted") inviteDisabled; @discourseComputed("canSendPms", "topic.isPrivateMessage") canArchive(canSendPms, isPM) { return canSendPms && isPM; - }, + } - inlineButtons: getTopicFooterButtons(), - inlineDropdowns: getTopicFooterDropdowns(), + @computed("inlineButtons.[]", "inlineDropdowns.[]") + get inlineActionables() { + return this.inlineButtons + .filterBy("dropdown", false) + .filterBy("anonymousOnly", false) + .concat(this.inlineDropdowns) + .sortBy("priority") + .reverse(); + } - inlineActionables: computed( - "inlineButtons.[]", - "inlineDropdowns.[]", - function () { - return this.inlineButtons - .filterBy("dropdown", false) - .filterBy("anonymousOnly", false) - .concat(this.inlineDropdowns) - .sortBy("priority") - .reverse(); - } - ), - - topicBookmarkManager: computed("topic", function () { + @computed("topic") + get topicBookmarkManager() { return new TopicBookmarkManager(getOwner(this), this.topic); - }), + } // topic.assigned_to_user is for backward plugin support @discourseComputed("inlineButtons.[]", "topic.assigned_to_user") dropdownButtons(inlineButtons) { return inlineButtons.filter((button) => button.dropdown); - }, + } @discourseComputed("topic.isPrivateMessage") showNotificationsButton(isPM) { return !isPM || this.canSendPms; - }, + } @discourseComputed("topic.details.notification_level") showNotificationUserTip(notificationLevel) { return notificationLevel >= NotificationLevels.TRACKING; - }, - - canSendPms: alias("currentUser.can_send_private_messages"), - - canInviteTo: alias("topic.details.can_invite_to"), - - canDefer: alias("currentUser.user_option.enable_defer"), - - inviteDisabled: or("topic.archived", "topic.closed", "topic.deleted"), + } @discourseComputed("topic.message_archived") - archiveIcon: (archived) => (archived ? "envelope" : "folder"), + archiveIcon(archived) { + return archived ? "envelope" : "folder"; + } @discourseComputed("topic.message_archived") - archiveTitle: (archived) => - archived ? "topic.move_to_inbox.help" : "topic.archive_message.help", + archiveTitle(archived) { + return archived ? "topic.move_to_inbox.help" : "topic.archive_message.help"; + } @discourseComputed("topic.message_archived") - archiveLabel: (archived) => - archived ? "topic.move_to_inbox.title" : "topic.archive_message.title", + archiveLabel(archived) { + return archived + ? "topic.move_to_inbox.title" + : "topic.archive_message.title"; + } @discourseComputed("topic.isPrivateMessage") - showBookmarkLabel: (isPM) => !isPM, -}); + showBookmarkLabel(isPM) { + return !isPM; + } +} diff --git a/app/assets/javascripts/discourse/app/components/topic-list-item.js b/app/assets/javascripts/discourse/app/components/topic-list-item.js index 0fbf3d6e2e8..14abf256af9 100644 --- a/app/assets/javascripts/discourse/app/components/topic-list-item.js +++ b/app/assets/javascripts/discourse/app/components/topic-list-item.js @@ -1,20 +1,22 @@ import Component from "@ember/component"; import { alias } from "@ember/object/computed"; -import { on } from "@ember/object/evented"; import { getOwner } from "@ember/owner"; import { schedule } from "@ember/runloop"; import { service } from "@ember/service"; import { htmlSafe } from "@ember/template"; +import { + attributeBindings, + classNameBindings, + tagName, +} from "@ember-decorators/component"; +import { observes, on } from "@ember-decorators/object"; import $ from "jquery"; import { topicTitleDecorators } from "discourse/components/topic-title"; import { wantsNewWindow } from "discourse/lib/intercept-click"; import DiscourseURL, { groupPath } from "discourse/lib/url"; import { RUNTIME_OPTIONS } from "discourse-common/lib/raw-handlebars-helpers"; import { findRawTemplate } from "discourse-common/lib/raw-templates"; -import discourseComputed, { - bind, - observes, -} from "discourse-common/utils/decorators"; +import discourseComputed, { bind } from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; export function showEntrance(e) { @@ -44,18 +46,19 @@ export function navigateToTopic(topic, href) { return false; } -export default Component.extend({ - router: service(), - historyStore: service(), - tagName: "tr", - classNameBindings: [":topic-list-item", "unboundClassNames", "topic.visited"], - attributeBindings: ["data-topic-id", "role", "ariaLevel:aria-level"], - "data-topic-id": alias("topic.id"), +@tagName("tr") +@classNameBindings(":topic-list-item", "unboundClassNames", "topic.visited") +@attributeBindings("dataTopicId:data-topic-id", "role", "ariaLevel:aria-level") +export default class TopicListItem extends Component { + @service router; + @service historyStore; + + @alias("topic.id") dataTopicId; didReceiveAttrs() { - this._super(...arguments); + super.didReceiveAttrs(...arguments); this.renderTopicListItem(); - }, + } // Already-rendered topic is marked as highlighted // Ideally this should be a modifier... but we can't do that @@ -65,7 +68,7 @@ export default Component.extend({ if (this.topic.highlight) { this._highlightIfNeeded(); } - }, + } @observes("topic.pinned", "expandGloballyPinned", "expandAllPinned") renderTopicListItem() { @@ -91,10 +94,10 @@ export default Component.extend({ } }); } - }, + } didInsertElement() { - this._super(...arguments); + super.didInsertElement(...arguments); if (this.includeUnreadIndicator) { this.messageBus.subscribe(this.unreadIndicatorChannel, this.onMessage); @@ -110,10 +113,10 @@ export default Component.extend({ ); } }); - }, + } willDestroyElement() { - this._super(...arguments); + super.willDestroyElement(...arguments); this.messageBus.unsubscribe(this.unreadIndicatorChannel, this.onMessage); @@ -124,7 +127,7 @@ export default Component.extend({ title.removeEventListener("blur", this._onTitleBlur); } } - }, + } @bind onMessage(data) { @@ -133,7 +136,7 @@ export default Component.extend({ ).classList; nodeClassList.toggle("read", !data.show_indicator); - }, + } @discourseComputed("topic.participant_groups") participantGroups(groupNames) { @@ -144,29 +147,29 @@ export default Component.extend({ return groupNames.map((name) => { return { name, url: groupPath(name) }; }); - }, + } @discourseComputed("topic.id") unreadIndicatorChannel(topicId) { return `/private-messages/unread-indicator/${topicId}`; - }, + } @discourseComputed("topic.unread_by_group_member") unreadClass(unreadByGroupMember) { return unreadByGroupMember ? "" : "read"; - }, + } @discourseComputed("topic.unread_by_group_member") includeUnreadIndicator(unreadByGroupMember) { return typeof unreadByGroupMember !== "undefined"; - }, + } @discourseComputed newDotText() { return this.currentUser && this.currentUser.trust_level > 0 ? "" : I18n.t("filters.new.lower_title"); - }, + } @discourseComputed("topic", "lastVisitedTopic") unboundClassNames(topic, lastVisitedTopic) { @@ -177,7 +180,7 @@ export default Component.extend({ } if (topic.get("tags")) { - topic.get("tags").forEach((tagName) => classes.push("tag-" + tagName)); + topic.get("tags").forEach((tag) => classes.push("tag-" + tag)); } if (topic.get("hasExcerpt")) { @@ -203,15 +206,15 @@ export default Component.extend({ } return classes.join(" "); - }, + } hasLikes() { return this.get("topic.like_count") > 0; - }, + } hasOpLikes() { return this.get("topic.op_like_count") > 0; - }, + } @discourseComputed expandPinned() { @@ -239,9 +242,11 @@ export default Component.extend({ } return false; - }, + } - showEntrance, + showEntrance() { + return showEntrance.call(this, ...arguments); + } click(e) { const result = this.showEntrance(e); @@ -316,18 +321,20 @@ export default Component.extend({ } return this.unhandledRowClick(e, topic); - }, + } - unhandledRowClick() {}, + unhandledRowClick() {} keyDown(e) { if (e.key === "Enter" && e.target.classList.contains("post-activity")) { e.preventDefault(); return this.navigateToTopic(this.topic, e.target.getAttribute("href")); } - }, + } - navigateToTopic, + navigateToTopic() { + return navigateToTopic.call(this, ...arguments); + } highlight(opts = { isLastViewedTopic: false }) { schedule("afterRender", () => { @@ -347,9 +354,10 @@ export default Component.extend({ this._titleElement()?.focus(); } }); - }, + } - _highlightIfNeeded: on("didInsertElement", function () { + @on("didInsertElement") + _highlightIfNeeded() { // highlight the last topic viewed const lastViewedTopicId = this.historyStore.get("lastTopicIdViewed"); const isLastViewedTopic = lastViewedTopicId === this.topic.id; @@ -362,27 +370,27 @@ export default Component.extend({ this.set("topic.highlight", false); this.highlight(); } - }), + } @bind _onTitleFocus() { if (this.element && !this.isDestroying && !this.isDestroyed) { this.element.classList.add("selected"); } - }, + } @bind _onTitleBlur() { if (this.element && !this.isDestroying && !this.isDestroyed) { this.element.classList.remove("selected"); } - }, + } _shouldFocusLastVisited() { return this.site.desktopView && this.focusLastVisitedTopic; - }, + } _titleElement() { return this.element.querySelector(".main-link .title"); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/topic-list.js b/app/assets/javascripts/discourse/app/components/topic-list.js index 09ddc2db3f2..af01cd984f9 100644 --- a/app/assets/javascripts/discourse/app/components/topic-list.js +++ b/app/assets/javascripts/discourse/app/components/topic-list.js @@ -1,68 +1,75 @@ import Component from "@ember/component"; import { dependentKeyCompat } from "@ember/object/compat"; import { alias } from "@ember/object/computed"; -import { on } from "@ember/object/evented"; import { service } from "@ember/service"; +import { + classNameBindings, + classNames, + tagName, +} from "@ember-decorators/component"; +import { observes, on } from "@ember-decorators/object"; import LoadMore from "discourse/mixins/load-more"; -import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; -export default Component.extend(LoadMore, { - modal: service(), - router: service(), - siteSettings: service(), +@tagName("table") +@classNames("topic-list") +@classNameBindings("bulkSelectEnabled:sticky-header") +export default class TopicList extends Component.extend(LoadMore) { + @service modal; + @service router; + @service siteSettings; - tagName: "table", - classNames: ["topic-list"], - classNameBindings: ["bulkSelectEnabled:sticky-header"], - showTopicPostBadges: true, - listTitle: "topic.title", - lastCheckedElementId: null, + showTopicPostBadges = true; + listTitle = "topic.title"; + lastCheckedElementId = null; + + // Overwrite this to perform client side filtering of topics, if desired + @alias("topics") filteredTopics; get canDoBulkActions() { return ( this.currentUser?.canManageTopic && this.bulkSelectHelper?.selected.length ); - }, + } - // Overwrite this to perform client side filtering of topics, if desired - filteredTopics: alias("topics"), - - _init: on("init", function () { + @on("init") + _init() { this.addObserver("hideCategory", this.rerender); this.addObserver("order", this.rerender); this.addObserver("ascending", this.rerender); this.refreshLastVisited(); - }), + } get selected() { return this.bulkSelectHelper?.selected; - }, + } - @dependentKeyCompat // for the classNameBindings + // for the classNameBindings + @dependentKeyCompat get bulkSelectEnabled() { return this.bulkSelectHelper?.bulkSelectEnabled; - }, + } get toggleInTitle() { return ( !this.bulkSelectHelper?.bulkSelectEnabled && this.get("canBulkSelect") ); - }, + } @discourseComputed sortable() { return !!this.changeSort; - }, + } @discourseComputed("order") showLikes(order) { return order === "likes"; - }, + } @discourseComputed("order") showOpLikes(order) { return order === "op_likes"; - }, + } @observes("topics.[]") topicsAdded() { @@ -70,22 +77,22 @@ export default Component.extend(LoadMore, { if (!this.lastVisitedTopic) { this.refreshLastVisited(); } - }, + } @observes("topics", "order", "ascending", "category", "top", "hot") lastVisitedTopicChanged() { this.refreshLastVisited(); - }, + } scrolled() { - this._super(...arguments); + super.scrolled(...arguments); let onScroll = this.onScroll; if (!onScroll) { return; } onScroll.call(this); - }, + } _updateLastVisitedTopic(topics, order, ascending, top, hot) { this.set("lastVisitedTopic", null); @@ -145,7 +152,7 @@ export default Component.extend(LoadMore, { } this.set("lastVisitedTopic", lastVisitedTopic); - }, + } refreshLastVisited() { this._updateLastVisitedTopic( @@ -155,7 +162,7 @@ export default Component.extend(LoadMore, { this.top, this.hot ); - }, + } click(e) { const onClick = (sel, callback) => { @@ -200,7 +207,7 @@ export default Component.extend(LoadMore, { } this.rerender(); }); - }, + } keyDown(e) { if (e.key === "Enter" || e.key === " ") { @@ -217,5 +224,5 @@ export default Component.extend(LoadMore, { this.rerender(); }); } - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/topic-navigation-popup.js b/app/assets/javascripts/discourse/app/components/topic-navigation-popup.js index 2a04dba997a..62781c75ec7 100644 --- a/app/assets/javascripts/discourse/app/components/topic-navigation-popup.js +++ b/app/assets/javascripts/discourse/app/components/topic-navigation-popup.js @@ -1,14 +1,15 @@ import Component from "@ember/component"; import { action } from "@ember/object"; +import { tagName } from "@ember-decorators/component"; import discourseComputed from "discourse-common/utils/decorators"; -export default Component.extend({ - tagName: "", - popupId: null, - hidden: false, +@tagName("") +export default class TopicNavigationPopup extends Component { + popupId = null; + hidden = false; init() { - this._super(...arguments); + super.init(...arguments); if (this.popupKey) { const value = this.keyValueStore.getItem(this.popupKey); @@ -18,14 +19,14 @@ export default Component.extend({ this.keyValueStore.removeItem(this.popupKey); } } - }, + } @discourseComputed("popupId") popupKey(popupId) { if (popupId) { return `dismiss_topic_nav_popup_${popupId}`; } - }, + } @action close() { @@ -39,5 +40,5 @@ export default Component.extend({ this.keyValueStore.setItem(this.popupKey, true); } } - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/topic-navigation.js b/app/assets/javascripts/discourse/app/components/topic-navigation.js index 16f61995630..d5f456d14a2 100644 --- a/app/assets/javascripts/discourse/app/components/topic-navigation.js +++ b/app/assets/javascripts/discourse/app/components/topic-navigation.js @@ -2,43 +2,40 @@ import Component from "@ember/component"; import EmberObject from "@ember/object"; import { next } from "@ember/runloop"; import { service } from "@ember/service"; +import { classNameBindings } from "@ember-decorators/component"; +import { observes } from "@ember-decorators/object"; import $ from "jquery"; import { headerOffset } from "discourse/lib/offset-calculator"; import SwipeEvents from "discourse/lib/swipe-events"; import discourseDebounce from "discourse-common/lib/debounce"; import discourseLater from "discourse-common/lib/later"; -import { bind, observes } from "discourse-common/utils/decorators"; +import { bind } from "discourse-common/utils/decorators"; import JumpToPost from "./modal/jump-to-post"; const MIN_WIDTH_TIMELINE = 925; const MIN_HEIGHT_TIMELINE = 325; -export default Component.extend({ - modal: service(), +@classNameBindings( + "info.topicProgressExpanded:topic-progress-expanded", + "info.renderTimeline:with-timeline:with-topic-progress" +) +export default class TopicNavigation extends Component { + @service modal; - classNameBindings: [ - "info.topicProgressExpanded:topic-progress-expanded", - "info.renderTimeline:with-timeline:with-topic-progress", - ], - composerOpen: null, - info: null, - canRender: true, - _lastTopicId: null, - _swipeEvents: null, - - init() { - this._super(...arguments); - this.set("info", EmberObject.create()); - }, + composerOpen = null; + info = EmberObject.create(); + canRender = true; + _lastTopicId = null; + _swipeEvents = null; didUpdateAttrs() { - this._super(...arguments); + super.didUpdateAttrs(...arguments); if (this._lastTopicId !== this.topic.id) { this._lastTopicId = this.topic.id; this.set("canRender", false); next(() => this.set("canRender", true)); } - }, + } _performCheckSize() { if (!this.element || this.isDestroying || this.isDestroyed) { @@ -60,17 +57,17 @@ export default Component.extend({ this.mediaQuery.matches && verticalSpace > MIN_HEIGHT_TIMELINE ); } - }, + } @bind _checkSize() { discourseDebounce(this, this._performCheckSize, 200, true); - }, + } // we need to store this so topic progress has something to init with _topicScrolled(event) { this.set("info.prevEvent", event); - }, + } @observes("info.topicProgressExpanded") _expanded() { @@ -95,17 +92,17 @@ export default Component.extend({ $(window).off("click.hide-fullscreen"); } this._checkSize(); - }, + } composerOpened() { this.set("composerOpen", true); this._checkSize(); - }, + } composerClosed() { this.set("composerOpen", false); this._checkSize(); - }, + } _collapseFullscreen(delay = 500) { if (this.get("info.topicProgressExpanded")) { @@ -119,7 +116,7 @@ export default Component.extend({ this._checkSize(); }, delay); } - }, + } keyboardTrigger(e) { if (e.type === "jump") { @@ -131,7 +128,7 @@ export default Component.extend({ }, }); } - }, + } @bind onSwipeStart(event) { @@ -153,7 +150,7 @@ export default Component.extend({ } else if (e.direction === "up" || e.direction === "down") { this.movingElement = document.querySelector(".timeline-container"); } - }, + } @bind onSwipeCancel() { @@ -164,7 +161,7 @@ export default Component.extend({ fill: "forwards", easing: "ease-out", }); - }, + } @bind onSwipeEnd(event) { @@ -195,7 +192,7 @@ export default Component.extend({ easing: "ease-out", }); } - }, + } @bind onSwipe(event) { @@ -207,10 +204,10 @@ export default Component.extend({ [{ transform: `translate3d(0, ${this.pxClosed}px, 0)` }], { fill: "forwards" } ); - }, + } didInsertElement() { - this._super(...arguments); + super.didInsertElement(...arguments); this._lastTopicId = this.topic.id; @@ -237,10 +234,10 @@ export default Component.extend({ this.element.addEventListener("swipecancel", this.onSwipeCancel); this.element.addEventListener("swipe", this.onSwipe); } - }, + } willDestroyElement() { - this._super(...arguments); + super.willDestroyElement(...arguments); this.appEvents .off("topic:current-post-scrolled", this, this._topicScrolled) @@ -263,5 +260,5 @@ export default Component.extend({ this.element.removeEventListener("swipe", this.onSwipe); this._swipeEvents.removeTouchListeners(); } - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/topic-progress.js b/app/assets/javascripts/discourse/app/components/topic-progress.js index f6007491550..e78ece26a5e 100644 --- a/app/assets/javascripts/discourse/app/components/topic-progress.js +++ b/app/assets/javascripts/discourse/app/components/topic-progress.js @@ -2,20 +2,23 @@ import Component from "@ember/component"; import { action } from "@ember/object"; import { alias } from "@ember/object/computed"; import { scheduleOnce } from "@ember/runloop"; +import { classNameBindings } from "@ember-decorators/component"; import { isTesting } from "discourse-common/config/environment"; import discourseLater from "discourse-common/lib/later"; import discourseComputed, { bind } from "discourse-common/utils/decorators"; const CSS_TRANSITION_DELAY = isTesting() ? 0 : 500; -export default Component.extend({ - elementId: "topic-progress-wrapper", - classNameBindings: ["docked", "withTransitions"], - docked: false, - withTransitions: null, - progressPosition: null, - postStream: alias("topic.postStream"), - _streamPercentage: null, +@classNameBindings("docked", "withTransitions") +export default class TopicProgress extends Component { + elementId = "topic-progress-wrapper"; + docked = false; + withTransitions = null; + progressPosition = null; + + @alias("topic.postStream") postStream; + + _streamPercentage = null; @discourseComputed( "postStream.loaded", @@ -25,14 +28,14 @@ export default Component.extend({ hideProgress(loaded, currentPost, filteredPostsCount) { const hideOnShortStream = this.site.desktopView && filteredPostsCount < 2; return !loaded || !currentPost || hideOnShortStream; - }, + } @discourseComputed("postStream.filteredPostsCount") hugeNumberOfPosts(filteredPostsCount) { return ( filteredPostsCount >= this.siteSettings.short_progress_text_threshold ); - }, + } @discourseComputed("progressPosition", "topic.last_read_post_id") showBackButton(position, lastReadId) { @@ -43,7 +46,7 @@ export default Component.extend({ const stream = this.get("postStream.stream"); const readPos = stream.indexOf(lastReadId) || 0; return readPos < stream.length - 1 && readPos > position; - }, + } _topicScrolled(event) { if (this.docked) { @@ -57,15 +60,15 @@ export default Component.extend({ _streamPercentage: (event.percent * 100).toFixed(2), }); } - }, + } @discourseComputed("_streamPercentage") progressStyle(_streamPercentage) { return `--progress-bg-width: ${_streamPercentage || 0}%`; - }, + } didInsertElement() { - this._super(...arguments); + super.didInsertElement(...arguments); this.appEvents .on("composer:resized", this, this._composerEvent) @@ -79,15 +82,15 @@ export default Component.extend({ // start CSS transitions a tiny bit later // to avoid jumpiness on initial topic load discourseLater(this._addCssTransitions, CSS_TRANSITION_DELAY); - }, + } willDestroyElement() { - this._super(...arguments); + super.willDestroyElement(...arguments); this._topicBottomObserver?.disconnect(); this.appEvents .off("composer:resized", this, this._composerEvent) .off("topic:current-post-scrolled", this, this._topicScrolled); - }, + } @bind _addCssTransitions() { @@ -95,7 +98,7 @@ export default Component.extend({ return; } this.set("withTransitions", true); - }, + } _startObserver() { if ("IntersectionObserver" in window) { @@ -104,7 +107,7 @@ export default Component.extend({ document.querySelector("#topic-bottom") ); } - }, + } _setupObserver() { // minimum 50px here ensures element is not docked when @@ -117,7 +120,7 @@ export default Component.extend({ threshold: 1, rootMargin: `0px 0px -${bottomIntersectionMargin}px 0px`, }); - }, + } _composerEvent() { // reinitializing needed to account for composer height @@ -127,7 +130,7 @@ export default Component.extend({ this._topicBottomObserver?.disconnect(); this._startObserver(); } - }, + } @bind _intersectionHandler(entries) { @@ -167,16 +170,16 @@ export default Component.extend({ } } } - }, + } click(e) { if (e.target.closest("#topic-progress")) { this.toggleProperty("expanded"); } - }, + } @action goBack() { this.jumpToPost(this.get("topic.last_read_post_number")); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/topic-timer-info.js b/app/assets/javascripts/discourse/app/components/topic-timer-info.js index 4900454dd66..b386a6130a8 100644 --- a/app/assets/javascripts/discourse/app/components/topic-timer-info.js +++ b/app/assets/javascripts/discourse/app/components/topic-timer-info.js @@ -1,55 +1,58 @@ import Component from "@ember/component"; import { cancel, next } from "@ember/runloop"; import { htmlSafe } from "@ember/template"; +import { classNames } from "@ember-decorators/component"; +import { on } from "@ember-decorators/object"; import { DELETE_REPLIES_TYPE } from "discourse/components/modal/edit-topic-timer"; import Category from "discourse/models/category"; import { isTesting } from "discourse-common/config/environment"; import { iconHTML } from "discourse-common/lib/icon-library"; import discourseLater from "discourse-common/lib/later"; -import discourseComputed, { on } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default Component.extend({ - classNames: ["topic-timer-info"], - _delayedRerender: null, - clockIcon: htmlSafe(`${iconHTML("far-clock")}`), - trashLabel: I18n.t("post.controls.remove_timer"), - title: null, - notice: null, - showTopicTimer: null, - showTopicTimerModal: null, - removeTopicTimer: null, +@classNames("topic-timer-info") +export default class TopicTimerInfo extends Component { + clockIcon = htmlSafe(`${iconHTML("far-clock")}`); + trashLabel = I18n.t("post.controls.remove_timer"); + + title = null; + notice = null; + showTopicTimer = null; + showTopicTimerModal = null; + removeTopicTimer = null; + _delayedRerender = null; @on("didReceiveAttrs") setupRenderer() { this.renderTopicTimer(); - }, + } @on("willDestroyElement") cancelDelayedRenderer() { if (this._delayedRerender) { cancel(this._delayedRerender); } - }, + } @discourseComputed canModifyTimer() { return this.currentUser && this.currentUser.get("canManageTopic"); - }, + } @discourseComputed("canModifyTimer", "removeTopicTimer") showTrashCan(canModifyTimer, removeTopicTimer) { return canModifyTimer && removeTopicTimer; - }, + } @discourseComputed("canModifyTimer", "showTopicTimerModal") showEdit(canModifyTimer, showTopicTimerModal) { return canModifyTimer && showTopicTimerModal; - }, + } additionalOpts() { return {}; - }, + } renderTopicTimer() { const isDeleteRepliesType = this.statusType === DELETE_REPLIES_TYPE; @@ -130,7 +133,7 @@ export default Component.extend({ } else { this.set("showTopicTimer", null); } - }, + } rerenderDelay(minutesLeft) { if (minutesLeft > 2160) { @@ -144,7 +147,7 @@ export default Component.extend({ } return 1000; - }, + } _noticeKey() { let statusType = this.statusType; @@ -156,5 +159,5 @@ export default Component.extend({ } return `topic.status_update_notice.auto_${statusType}`; - }, -}); + } +}