From c4428715b5d345a75bdbfd8f8a62233203b09767 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 28 Aug 2024 14:34:02 +0100 Subject: [PATCH] DEV: Convert core components to native class syntax (batch 6) (#28598) Changes made using the ember-native-class-codemod, plus some manual tweaks --- .../app/components/highlight-search.js | 12 +- .../app/components/honeypot-input.js | 8 +- .../app/components/html-with-links.js | 6 +- .../app/components/ignored-user-list-item.js | 21 ++-- .../app/components/images-uploader.js | 15 +-- .../discourse/app/components/invite-panel.js | 107 +++++++++--------- .../app/components/latest-topic-list-item.js | 23 ++-- .../discourse/app/components/link-to-input.js | 8 +- .../app/components/links-redirect.js | 6 +- .../discourse/app/components/load-more.js | 18 +-- .../discourse/app/components/login-buttons.js | 15 +-- .../app/components/mobile-category-topic.js | 15 +-- .../discourse/app/components/mobile-nav.js | 35 +++--- .../discourse/app/components/mount-widget.js | 62 +++++----- .../app/components/navigation-item.js | 46 ++++---- .../discourse/app/components/number-field.js | 58 +++++----- .../app/components/parent-category-row.js | 2 +- .../app/components/password-field.js | 14 +-- .../discourse/app/components/pending-post.js | 12 +- .../app/components/pick-files-button.js | 46 ++++---- .../app/components/plugin-connector.js | 22 ++-- .../app/components/popup-input-tip.js | 43 ++++--- .../discourse/app/components/popup-menu.js | 2 +- 23 files changed, 305 insertions(+), 291 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/highlight-search.js b/app/assets/javascripts/discourse/app/components/highlight-search.js index 375217888cd..964c774a3e9 100644 --- a/app/assets/javascripts/discourse/app/components/highlight-search.js +++ b/app/assets/javascripts/discourse/app/components/highlight-search.js @@ -1,14 +1,14 @@ import Component from "@ember/component"; +import { tagName } from "@ember-decorators/component"; +import { observes, on } from "@ember-decorators/object"; import highlightSearch from "discourse/lib/highlight-search"; -import { observes, on } from "discourse-common/utils/decorators"; - -export default Component.extend({ - tagName: "span", +@tagName("span") +export default class HighlightSearch extends Component { @on("didInsertElement") @observes("highlight") _highlightOnInsert() { const term = this.highlight; highlightSearch(this.element, term); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/honeypot-input.js b/app/assets/javascripts/discourse/app/components/honeypot-input.js index edf2f72ce8c..8a6ef38a47c 100644 --- a/app/assets/javascripts/discourse/app/components/honeypot-input.js +++ b/app/assets/javascripts/discourse/app/components/honeypot-input.js @@ -1,7 +1,7 @@ +import { on } from "@ember-decorators/object"; import TextField from "discourse/components/text-field"; -import { on } from "discourse-common/utils/decorators"; -export default TextField.extend({ +export default class HoneypotInput extends TextField { @on("init") _init() { // Chrome autocomplete is buggy per: @@ -13,5 +13,5 @@ export default TextField.extend({ } else { this.set("type", "password"); } - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/html-with-links.js b/app/assets/javascripts/discourse/app/components/html-with-links.js index 08592b74b65..c30aee6c5ae 100644 --- a/app/assets/javascripts/discourse/app/components/html-with-links.js +++ b/app/assets/javascripts/discourse/app/components/html-with-links.js @@ -4,12 +4,12 @@ import { shouldOpenInNewTab, } from "discourse/lib/click-track"; -export default Component.extend({ +export default class HtmlWithLinks extends Component { click(event) { if (event?.target?.tagName === "A") { if (shouldOpenInNewTab(event.target.href)) { openLinkInNewTab(event, event.target); } } - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/ignored-user-list-item.js b/app/assets/javascripts/discourse/app/components/ignored-user-list-item.js index 20a74e832ef..58e32098628 100644 --- a/app/assets/javascripts/discourse/app/components/ignored-user-list-item.js +++ b/app/assets/javascripts/discourse/app/components/ignored-user-list-item.js @@ -1,10 +1,13 @@ import Component from "@ember/component"; -export default Component.extend({ - tagName: "div", - items: null, - actions: { - removeIgnoredUser(item) { - this.onRemoveIgnoredUser(item); - }, - }, -}); +import { action } from "@ember/object"; +import { tagName } from "@ember-decorators/component"; + +@tagName("div") +export default class IgnoredUserListItem extends Component { + items = null; + + @action + removeIgnoredUser(item) { + this.onRemoveIgnoredUser(item); + } +} diff --git a/app/assets/javascripts/discourse/app/components/images-uploader.js b/app/assets/javascripts/discourse/app/components/images-uploader.js index 5e4ff8991ef..3d0e93201d2 100644 --- a/app/assets/javascripts/discourse/app/components/images-uploader.js +++ b/app/assets/javascripts/discourse/app/components/images-uploader.js @@ -1,22 +1,23 @@ import Component from "@ember/component"; +import { tagName } from "@ember-decorators/component"; import UppyUploadMixin from "discourse/mixins/uppy-upload"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default Component.extend(UppyUploadMixin, { - type: "avatar", - tagName: "span", +@tagName("span") +export default class ImagesUploader extends Component.extend(UppyUploadMixin) { + type = "avatar"; @discourseComputed("uploadingOrProcessing") uploadButtonText(uploadingOrProcessing) { return uploadingOrProcessing ? I18n.t("uploading") : I18n.t("upload"); - }, + } validateUploadedFilesOptions() { return { imagesOnly: true }; - }, + } uploadDone(upload) { this.done(upload); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/invite-panel.js b/app/assets/javascripts/discourse/app/components/invite-panel.js index 58e30c8ae92..2b8792a79f9 100644 --- a/app/assets/javascripts/discourse/app/components/invite-panel.js +++ b/app/assets/javascripts/discourse/app/components/invite-panel.js @@ -9,32 +9,44 @@ import Group from "discourse/models/group"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default Component.extend({ - tagName: null, - groupIds: null, - allGroups: null, +export default class InvitePanel extends Component { + @readOnly("currentUser.staff") isStaff; + @readOnly("currentUser.admin") isAdmin; + @alias("inviteModel.id") topicId; + @equal("inviteModel.archetype", "private_message") isPM; + @and("isStaff", "siteSettings.must_approve_users") showApprovalMessage; - isStaff: readOnly("currentUser.staff"), - isAdmin: readOnly("currentUser.admin"), + // eg: visible only to specific group members + @and("invitingToTopic", "inviteModel.category.read_restricted") + isPrivateTopic; + + // scope to allowed usernames + @alias("invitingToTopic") allowExistingMembers; + + @i18n("invite.custom_message_placeholder") customMessagePlaceholder; + + groupIds = null; + allGroups = null; // invitee is either a user, group or email - invitee: null, - isInviteeGroup: false, - hasCustomMessage: false, - customMessage: null, - inviteIcon: "envelope", - invitingExistingUserToTopic: false, + invitee = null; + + isInviteeGroup = false; + hasCustomMessage = false; + customMessage = null; + inviteIcon = "envelope"; + invitingExistingUserToTopic = false; init() { - this._super(...arguments); + super.init(...arguments); this.setDefaultSelectedGroups(); this.setGroupOptions(); - }, + } willDestroyElement() { - this._super(...arguments); + super.willDestroyElement(...arguments); this.reset(); - }, + } @discourseComputed( "isAdmin", @@ -81,7 +93,7 @@ export default Component.extend({ } return false; - }, + } @discourseComputed( "isAdmin", @@ -125,49 +137,36 @@ export default Component.extend({ } return false; - }, + } @discourseComputed("inviteModel.saving") buttonTitle(saving) { return saving ? "topic.inviting" : "topic.invite_reply.action"; - }, + } // We are inviting to a topic if the topic isn't the current user. // The current user would mean we are inviting to the forum in general. @discourseComputed("inviteModel") invitingToTopic(inviteModel) { return inviteModel !== this.currentUser; - }, + } @discourseComputed("inviteModel", "inviteModel.details.can_invite_via_email") canInviteViaEmail(inviteModel, canInviteViaEmail) { return inviteModel === this.currentUser ? true : canInviteViaEmail; - }, + } @discourseComputed("isPM", "canInviteViaEmail") showCopyInviteButton(isPM, canInviteViaEmail) { return canInviteViaEmail && !isPM; - }, - - topicId: alias("inviteModel.id"), - - // eg: visible only to specific group members - isPrivateTopic: and( - "invitingToTopic", - "inviteModel.category.read_restricted" - ), - - isPM: equal("inviteModel.archetype", "private_message"), - - // scope to allowed usernames - allowExistingMembers: alias("invitingToTopic"), + } @discourseComputed("isAdmin", "inviteModel.group_users") isGroupOwnerOrAdmin(isAdmin, groupUsers) { return ( isAdmin || (groupUsers && groupUsers.some((groupUser) => groupUser.owner)) ); - }, + } // Show Groups? (add invited user to private group) @discourseComputed( @@ -192,12 +191,12 @@ export default Component.extend({ !isPM && (emailValid(invitee) || isPrivateTopic || !invitingToTopic) ); - }, + } @discourseComputed("invitee") showCustomMessage(invitee) { return this.inviteModel === this.currentUser || emailValid(invitee); - }, + } // Instructional text for the modal. @discourseComputed( @@ -243,12 +242,12 @@ export default Component.extend({ // inviting to forum return I18n.t("topic.invite_reply.to_forum"); } - }, + } @discourseComputed("isPrivateTopic") showGroupsClass(isPrivateTopic) { return isPrivateTopic ? "required" : "optional"; - }, + } @discourseComputed("isPM", "invitee", "invitingExistingUserToTopic") successMessage(isPM, invitee, invitingExistingUserToTopic) { @@ -265,7 +264,7 @@ export default Component.extend({ } else { return I18n.t("topic.invite_reply.success_username"); } - }, + } @discourseComputed("isPM", "ajaxError") errorMessage(isPM, ajaxError) { @@ -275,18 +274,14 @@ export default Component.extend({ return isPM ? I18n.t("topic.invite_private.error") : I18n.t("topic.invite_reply.error"); - }, + } @discourseComputed("canInviteViaEmail") placeholderKey(canInviteViaEmail) { return canInviteViaEmail ? "topic.invite_private.email_or_username_placeholder" : "topic.invite_reply.username_placeholder"; - }, - - showApprovalMessage: and("isStaff", "siteSettings.must_approve_users"), - - customMessagePlaceholder: i18n("invite.custom_message_placeholder"), + } // Reset the modal to allow a new user to be invited. reset() { @@ -305,17 +300,17 @@ export default Component.extend({ finished: false, inviteLink: null, }); - }, + } setDefaultSelectedGroups() { this.set("groupIds", []); - }, + } setGroupOptions() { Group.findAll().then((groups) => { this.set("allGroups", groups.filterBy("automatic", false)); }); - }, + } @action createInvite() { @@ -367,7 +362,7 @@ export default Component.extend({ }) .catch(onerror); } - }, + } @action generateInviteLink() { @@ -401,7 +396,7 @@ export default Component.extend({ } model.setProperties({ saving: false, error: true }); }); - }, + } @action showCustomMessageBox() { @@ -421,14 +416,14 @@ export default Component.extend({ } else { this.set("customMessage", null); } - }, + } @action searchContact() { getNativeContact(this.capabilities, ["email"], false).then((result) => { this.set("invitee", result[0].email[0]); }); - }, + } @action updateInvitee(selected, content) { @@ -448,5 +443,5 @@ export default Component.extend({ isInviteeGroup: false, }); } - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/latest-topic-list-item.js b/app/assets/javascripts/discourse/app/components/latest-topic-list-item.js index 3b46e79c839..5f41ed92646 100644 --- a/app/assets/javascripts/discourse/app/components/latest-topic-list-item.js +++ b/app/assets/javascripts/discourse/app/components/latest-topic-list-item.js @@ -1,16 +1,19 @@ import Component from "@ember/component"; +import { + attributeBindings, + classNameBindings, +} from "@ember-decorators/component"; import { navigateToTopic, showEntrance, } from "discourse/components/topic-list-item"; import discourseComputed from "discourse-common/utils/decorators"; -export default Component.extend({ - attributeBindings: ["topic.id:data-topic-id"], - classNameBindings: [":latest-topic-list-item", "unboundClassNames"], - - showEntrance, - navigateToTopic, +@attributeBindings("topic.id:data-topic-id") +@classNameBindings(":latest-topic-list-item", "unboundClassNames") +export default class LatestTopicListItem extends Component { + showEntrance = showEntrance; + navigateToTopic = navigateToTopic; click(e) { // for events undefined has a different meaning than false @@ -19,10 +22,10 @@ export default Component.extend({ } return this.unhandledRowClick(e, this.topic); - }, + } // Can be overwritten by plugins to handle clicks on other parts of the row - unhandledRowClick() {}, + unhandledRowClick() {} @discourseComputed("topic") unboundClassNames(topic) { @@ -45,5 +48,5 @@ export default Component.extend({ ); return classes.join(" "); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/link-to-input.js b/app/assets/javascripts/discourse/app/components/link-to-input.js index a34aaec46d8..97b9b912b1f 100644 --- a/app/assets/javascripts/discourse/app/components/link-to-input.js +++ b/app/assets/javascripts/discourse/app/components/link-to-input.js @@ -2,8 +2,8 @@ import Component from "@ember/component"; import { schedule } from "@ember/runloop"; import $ from "jquery"; -export default Component.extend({ - showInput: false, +export default class LinkToInput extends Component { + showInput = false; click() { this.onClick(); @@ -13,5 +13,5 @@ export default Component.extend({ }); return false; - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/links-redirect.js b/app/assets/javascripts/discourse/app/components/links-redirect.js index 0972b937990..7cf3be71808 100644 --- a/app/assets/javascripts/discourse/app/components/links-redirect.js +++ b/app/assets/javascripts/discourse/app/components/links-redirect.js @@ -2,10 +2,10 @@ import Component from "@ember/component"; import { getOwner } from "@ember/owner"; import ClickTrack from "discourse/lib/click-track"; -export default Component.extend({ +export default class LinksRedirect extends Component { click(event) { if (event?.target?.tagName === "A") { return ClickTrack.trackClick(event, getOwner(this)); } - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/load-more.js b/app/assets/javascripts/discourse/app/components/load-more.js index 22bc1921b38..bc9c73f36ee 100644 --- a/app/assets/javascripts/discourse/app/components/load-more.js +++ b/app/assets/javascripts/discourse/app/components/load-more.js @@ -1,16 +1,16 @@ import Component from "@ember/component"; +import { action } from "@ember/object"; import LoadMore from "discourse/mixins/load-more"; -export default Component.extend(LoadMore, { +export default class LoadMoreComponent extends Component.extend(LoadMore) { init() { - this._super(...arguments); + super.init(...arguments); this.set("eyelineSelector", this.selector); - }, + } - actions: { - loadMore() { - this.action(); - }, - }, -}); + @action + loadMore() { + this.action(); + } +} diff --git a/app/assets/javascripts/discourse/app/components/login-buttons.js b/app/assets/javascripts/discourse/app/components/login-buttons.js index e5d0ba29ac3..cd4ad997cba 100644 --- a/app/assets/javascripts/discourse/app/components/login-buttons.js +++ b/app/assets/javascripts/discourse/app/components/login-buttons.js @@ -1,11 +1,12 @@ import Component from "@ember/component"; +import { classNameBindings } from "@ember-decorators/component"; import { isWebauthnSupported } from "discourse/lib/webauthn"; import { findAll } from "discourse/models/login-method"; import discourseComputed from "discourse-common/utils/decorators"; -export default Component.extend({ - elementId: "login-buttons", - classNameBindings: ["hidden"], +@classNameBindings("hidden") +export default class LoginButtons extends Component { + elementId = "login-buttons"; @discourseComputed( "buttons.length", @@ -14,12 +15,12 @@ export default Component.extend({ ) hidden(buttonsCount, showLoginWithEmailLink, showPasskeysButton) { return buttonsCount === 0 && !showLoginWithEmailLink && !showPasskeysButton; - }, + } @discourseComputed buttons() { return findAll(); - }, + } @discourseComputed showPasskeysButton() { @@ -29,5 +30,5 @@ export default Component.extend({ this.context === "login" && isWebauthnSupported() ); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/mobile-category-topic.js b/app/assets/javascripts/discourse/app/components/mobile-category-topic.js index ce83bdeb6e5..f02ad5080f3 100644 --- a/app/assets/javascripts/discourse/app/components/mobile-category-topic.js +++ b/app/assets/javascripts/discourse/app/components/mobile-category-topic.js @@ -1,12 +1,9 @@ import Component from "@ember/component"; +import { classNameBindings, tagName } from "@ember-decorators/component"; import { showEntrance } from "discourse/components/topic-list-item"; -export default Component.extend({ - tagName: "tr", - classNameBindings: [ - ":category-topic-link", - "topic.archived", - "topic.visited", - ], - click: showEntrance, -}); +@tagName("tr") +@classNameBindings(":category-topic-link", "topic.archived", "topic.visited") +export default class MobileCategoryTopic extends Component { + click = showEntrance; +} diff --git a/app/assets/javascripts/discourse/app/components/mobile-nav.js b/app/assets/javascripts/discourse/app/components/mobile-nav.js index e51aee4e230..b16e7f3f390 100644 --- a/app/assets/javascripts/discourse/app/components/mobile-nav.js +++ b/app/assets/javascripts/discourse/app/components/mobile-nav.js @@ -2,10 +2,16 @@ import Component from "@ember/component"; import { action } from "@ember/object"; import { next } from "@ember/runloop"; import { service } from "@ember/service"; +import { classNames, tagName } from "@ember-decorators/component"; +import { on } from "@ember-decorators/object"; import $ from "jquery"; -import { on } from "discourse-common/utils/decorators"; -export default Component.extend({ +@tagName("ul") +@classNames("mobile-nav") +export default class MobileNav extends Component { + @service router; + selectedHtml = null; + @on("init") _init() { if (this.site.desktopView) { @@ -15,19 +21,12 @@ export default Component.extend({ this.set("classNames", classes); } } - }, - - tagName: "ul", - selectedHtml: null, - - classNames: ["mobile-nav"], - - router: service(), + } currentRouteChanged() { this.set("expanded", false); next(() => this._updateSelectedHtml()); - }, + } _updateSelectedHtml() { if (!this.element || this.isDestroying || this.isDestroyed) { @@ -38,19 +37,19 @@ export default Component.extend({ if (active && active.innerHTML) { this.set("selectedHtml", active.innerHTML); } - }, + } didInsertElement() { - this._super(...arguments); + super.didInsertElement(...arguments); this._updateSelectedHtml(); this.router.on("routeDidChange", this, this.currentRouteChanged); - }, + } willDestroyElement() { - this._super(...arguments); + super.willDestroyElement(...arguments); this.router.off("routeDidChange", this, this.currentRouteChanged); - }, + } @action toggleExpanded(event) { @@ -74,5 +73,5 @@ export default Component.extend({ }); } }); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/mount-widget.js b/app/assets/javascripts/discourse/app/components/mount-widget.js index 5c74169c1c0..bc3629ccf04 100644 --- a/app/assets/javascripts/discourse/app/components/mount-widget.js +++ b/app/assets/javascripts/discourse/app/components/mount-widget.js @@ -33,18 +33,18 @@ export function resetWidgetCleanCallbacks() { _cleanCallbacks = {}; } -export default Component.extend({ - _tree: null, - _rootNode: null, - _timeout: null, - _widgetClass: null, - _renderCallback: null, - _childEvents: null, - _dispatched: null, - dirtyKeys: null, +export default class MountWidget extends Component { + dirtyKeys = null; + _tree = null; + _rootNode = null; + _timeout = null; + _widgetClass = null; + _renderCallback = null; + _childEvents = null; + _dispatched = null; init() { - this._super(...arguments); + super.init(...arguments); const name = this.widget; if (name === "post-cooked") { @@ -81,19 +81,19 @@ export default Component.extend({ this._childComponents = ArrayProxy.create({ content: [] }); this._dispatched = []; this.dirtyKeys = new DirtyKeys(name); - }, + } didInsertElement() { - this._super(...arguments); + super.didInsertElement(...arguments); WidgetClickHook.setupDocumentCallback(); this._rootNode = document.createElement("div"); this.element.appendChild(this._rootNode); this._timeout = scheduleOnce("render", this, this.rerenderWidget); - }, + } willClearRender() { - this._super(...arguments); + super.willClearRender(...arguments); const callbacks = _cleanCallbacks[this.widget]; if (callbacks) { callbacks.forEach((cb) => cb(this._tree)); @@ -105,29 +105,27 @@ export default Component.extend({ traverseCustomWidgets(this._tree, (w) => w.destroy()); this._rootNode = patch(this._rootNode, diff(this._tree, null)); this._tree = null; - }, + } willDestroyElement() { - this._super(...arguments); + super.willDestroyElement(...arguments); this._dispatched.forEach((evt) => { const [eventName, caller] = evt; this.appEvents.off(eventName, this, caller); }); cancel(this._timeout); - }, + } - afterRender() {}, - - beforePatch() {}, - - afterPatch() {}, + afterRender() {} + beforePatch() {} + afterPatch() {} eventDispatched(eventName, key, refreshArg) { key = typeof key === "function" ? key(refreshArg) : key; const onRefresh = camelize(eventName.replace(/:/, "-")); this.dirtyKeys.keyDirty(key, { onRefresh, refreshArg }); this.queueRerender(); - }, + } dispatch(eventName, key) { this._childEvents.push(eventName); @@ -136,7 +134,7 @@ export default Component.extend({ this.eventDispatched(eventName, key, refreshArg); this._dispatched.push([eventName, caller]); this.appEvents.on(eventName, this, caller); - }, + } queueRerender(callback) { if (callback && !this._renderCallback) { @@ -144,9 +142,9 @@ export default Component.extend({ } scheduleOnce("render", this, this.rerenderWidget); - }, + } - buildArgs() {}, + buildArgs() {} rerenderWidget() { cancel(this._timeout); @@ -190,18 +188,18 @@ export default Component.extend({ console.log(Date.now() - t0); } } - }, + } mountChildComponent(info) { this._childComponents.pushObject(info); - }, + } unmountChildComponent(info) { this._childComponents.removeObject(info); - }, + } didUpdateAttrs() { - this._super(...arguments); + super.didUpdateAttrs(...arguments); this.queueRerender(); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/navigation-item.js b/app/assets/javascripts/discourse/app/components/navigation-item.js index 6ef9379c960..8f46c8cec32 100644 --- a/app/assets/javascripts/discourse/app/components/navigation-item.js +++ b/app/assets/javascripts/discourse/app/components/navigation-item.js @@ -1,28 +1,34 @@ import { tracked } from "@glimmer/tracking"; import Component from "@ember/component"; import { dependentKeyCompat } from "@ember/object/compat"; +import { + attributeBindings, + classNameBindings, + tagName, +} from "@ember-decorators/component"; import { filterTypeForMode } from "discourse/lib/filter-mode"; import discourseComputed from "discourse-common/utils/decorators"; -export default Component.extend({ - tagName: "li", - classNameBindings: [ - "active", - "content.hasIcon:has-icon", - "content.classNames", - "isHidden:hidden", - "content.name", - ], - attributeBindings: ["content.title:title"], - hidden: false, - activeClass: "", - hrefLink: null, - filterMode: tracked(), +@tagName("li") +@classNameBindings( + "active", + "content.hasIcon:has-icon", + "content.classNames", + "isHidden:hidden", + "content.name" +) +@attributeBindings("content.title:title") +export default class NavigationItem extends Component { + @tracked filterMode; + + hidden = false; + activeClass = ""; + hrefLink = null; @dependentKeyCompat get filterType() { return filterTypeForMode(this.filterMode); - }, + } @discourseComputed("content.filterType", "filterType", "content.active") active(contentFilterType, filterType, active) { @@ -30,7 +36,7 @@ export default Component.extend({ return active; } return contentFilterType === filterType; - }, + } @discourseComputed("content.count", "content.name") isHidden(count, name) { @@ -42,10 +48,10 @@ export default Component.extend({ (name === "new" || name === "unread") && count < 1 ); - }, + } didReceiveAttrs() { - this._super(...arguments); + super.didReceiveAttrs(...arguments); const content = this.content; let href = content.get("href"); @@ -87,5 +93,5 @@ export default Component.extend({ this.set("hrefLink", href); this.set("activeClass", this.active ? "active" : ""); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/number-field.js b/app/assets/javascripts/discourse/app/components/number-field.js index d9df7fb93a3..29f263034f9 100644 --- a/app/assets/javascripts/discourse/app/components/number-field.js +++ b/app/assets/javascripts/discourse/app/components/number-field.js @@ -1,14 +1,15 @@ +import { computed } from "@ember/object"; +import { classNameBindings } from "@ember-decorators/component"; import TextField from "discourse/components/text-field"; import { allowOnlyNumericInput } from "discourse/lib/utilities"; import deprecated from "discourse-common/lib/deprecated"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default TextField.extend({ - classNameBindings: ["invalid"], - +@classNameBindings("invalid") +export default class NumberField extends TextField { init() { - this._super(...arguments); + super.init(...arguments); deprecated( `NumberField component is deprecated. Use native elements instead.\ne.g. `, { @@ -17,46 +18,47 @@ export default TextField.extend({ dropFrom: "3.3.0", } ); - }, + } - keyDown: function (event) { + keyDown(event) { allowOnlyNumericInput(event, this._minNumber && this._minNumber < 0); - }, + } get _minNumber() { if (!this.get("min")) { return; } return parseInt(this.get("min"), 10); - }, + } get _maxNumber() { if (!this.get("max")) { return; } return parseInt(this.get("max"), 10); - }, + } - @discourseComputed("number") - value: { - get(number) { - return parseInt(number, 10); - }, - set(value) { - const num = parseInt(value, 10); - if (isNaN(num)) { - this.set("invalid", true); - return value; - } else { - this.set("invalid", false); - this.set("number", num); - return num.toString(); - } - }, - }, + @computed("number") + get value() { + if (this.number === null) { + return ""; + } + return parseInt(this.number, 10); + } + + set value(value) { + const num = parseInt(value, 10); + if (isNaN(num)) { + this.set("invalid", true); + this.set("number", null); + } else { + this.set("invalid", false); + this.set("number", num); + } + } @discourseComputed("placeholderKey") placeholder(key) { return key ? I18n.t(key) : ""; - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/parent-category-row.js b/app/assets/javascripts/discourse/app/components/parent-category-row.js index 5d22a613297..6f84f8eaddd 100644 --- a/app/assets/javascripts/discourse/app/components/parent-category-row.js +++ b/app/assets/javascripts/discourse/app/components/parent-category-row.js @@ -1,3 +1,3 @@ import CategoryListItem from "discourse/components/category-list-item"; -export default CategoryListItem.extend({}); +export default class ParentCategoryRow extends CategoryListItem {} diff --git a/app/assets/javascripts/discourse/app/components/password-field.js b/app/assets/javascripts/discourse/app/components/password-field.js index 65b933d3337..46af0df6fbd 100644 --- a/app/assets/javascripts/discourse/app/components/password-field.js +++ b/app/assets/javascripts/discourse/app/components/password-field.js @@ -4,8 +4,8 @@ import TextField from "discourse/components/text-field"; Same as text-field, but with special features for a password input. Be sure to test on a variety of browsers and operating systems when changing this logic. **/ -export default TextField.extend({ - canToggle: false, +export default class PasswordField extends TextField { + canToggle = false; keyPress(e) { if ( @@ -21,19 +21,19 @@ export default TextField.extend({ this.set("canToggle", true); this.set("capsLockOn", false); } - }, + } keyUp(e) { if (e.which === 20 && this.canToggle) { this.toggleProperty("capsLockOn"); } - }, + } focusOut() { this.set("capsLockOn", false); - }, + } focusIn() { this.set("canToggle", false); // can't know the state of caps lock yet. keyPress will figure it out. - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/pending-post.js b/app/assets/javascripts/discourse/app/components/pending-post.js index fd825064f9f..b98cf239c00 100644 --- a/app/assets/javascripts/discourse/app/components/pending-post.js +++ b/app/assets/javascripts/discourse/app/components/pending-post.js @@ -4,12 +4,12 @@ import { ajax } from "discourse/lib/ajax"; import { loadOneboxes } from "discourse/lib/load-oneboxes"; import { afterRender } from "discourse-common/utils/decorators"; -export default Component.extend({ +export default class PendingPost extends Component { didRender() { - this._super(...arguments); + super.didRender(...arguments); this._loadOneboxes(); this._resolveUrls(); - }, + } @afterRender _loadOneboxes() { @@ -21,10 +21,10 @@ export default Component.extend({ this.siteSettings.max_oneboxes_per_post, true ); - }, + } @afterRender _resolveUrls() { resolveAllShortUrls(ajax, this.siteSettings, this.element, this.opts); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/pick-files-button.js b/app/assets/javascripts/discourse/app/components/pick-files-button.js index 192f7686c68..c717a9ad8e8 100644 --- a/app/assets/javascripts/discourse/app/components/pick-files-button.js +++ b/app/assets/javascripts/discourse/app/components/pick-files-button.js @@ -2,6 +2,7 @@ import Component from "@ember/component"; import { action } from "@ember/object"; import { service } from "@ember/service"; import { isBlank } from "@ember/utils"; +import { classNames } from "@ember-decorators/component"; import { authorizedExtensions, authorizesAllExtensions, @@ -17,39 +18,40 @@ import I18n from "discourse-i18n"; // binding will still be added, and the file type will be validated here. This // is sometimes useful if you need to do something outside the uppy upload with // the file, such as directly using JSON or CSV data from a file in JS. -export default Component.extend({ - dialog: service(), - fileInputId: null, - fileInputClass: null, - fileInputDisabled: false, - classNames: ["pick-files-button"], - acceptedFormatsOverride: null, - allowMultiple: false, - showButton: false, +@classNames("pick-files-button") +export default class PickFilesButton extends Component { + @service dialog; + + fileInputId = null; + fileInputClass = null; + fileInputDisabled = false; + acceptedFormatsOverride = null; + allowMultiple = false; + showButton = false; didInsertElement() { - this._super(...arguments); + super.didInsertElement(...arguments); if (this.onFilesPicked) { const fileInput = this.element.querySelector("input"); this.set("fileInput", fileInput); fileInput.addEventListener("change", this.onChange, false); } - }, + } willDestroyElement() { - this._super(...arguments); + super.willDestroyElement(...arguments); if (this.onFilesPicked) { this.fileInput.removeEventListener("change", this.onChange); } - }, + } @bind onChange() { const files = this.fileInput.files; this._filesPicked(files); - }, + } @discourseComputed() acceptsAllFormats() { @@ -57,7 +59,7 @@ export default Component.extend({ this.capabilities.isIOS || authorizesAllExtensions(this.currentUser.staff, this.siteSettings) ); - }, + } @discourseComputed() acceptedFormats() { @@ -72,12 +74,12 @@ export default Component.extend({ ); return extensions.map((ext) => `.${ext}`).join(); - }, + } @action openSystemFilePicker() { this.fileInput.click(); - }, + } _filesPicked(files) { if (!files || !files.length) { @@ -95,7 +97,7 @@ export default Component.extend({ if (typeof this.onFilesPicked === "function") { this.onFilesPicked(files); } - }, + } _haveAcceptedTypes(files) { for (const file of files) { @@ -104,7 +106,7 @@ export default Component.extend({ } } return true; - }, + } _hasAcceptedExtensionOrType(file) { const extension = this._fileExtension(file.name); @@ -112,9 +114,9 @@ export default Component.extend({ this.acceptedFormats.includes(`.${extension}`) || this.acceptedFormats.includes(file.type) ); - }, + } _fileExtension(fileName) { return fileName.split(".").pop(); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/plugin-connector.js b/app/assets/javascripts/discourse/app/components/plugin-connector.js index d95e6571788..a871a0e8d0a 100644 --- a/app/assets/javascripts/discourse/app/components/plugin-connector.js +++ b/app/assets/javascripts/discourse/app/components/plugin-connector.js @@ -18,9 +18,9 @@ export function resetDecorators() { _decorators = {}; } -export default Component.extend({ +export default class PluginConnector extends Component { init() { - this._super(...arguments); + super.init(...arguments); const args = this.args || {}; Object.keys(args).forEach((key) => { @@ -68,31 +68,31 @@ export default Component.extend({ connectorInfo ); connectorClass?.setupComponent?.call(this, merged, this); - }, + } didReceiveAttrs() { - this._super(...arguments); + super.didReceiveAttrs(...arguments); this._decoratePluginOutlets(); - }, + } @afterRender _decoratePluginOutlets() { (_decorators[this.connector.outletName] || []).forEach((dec) => dec(this.element, this.args) ); - }, + } willDestroyElement() { - this._super(...arguments); + super.willDestroyElement(...arguments); const connectorClass = this.connector.connectorClass; connectorClass?.teardownComponent?.call(this, this); - }, + } send(name, ...args) { const connectorClass = this.connector.connectorClass; const action = connectorClass?.actions?.[name]; - return action ? action.call(this, ...args) : this._super(name, ...args); - }, -}); + return action ? action.call(this, ...args) : super.send(name, ...args); + } +} diff --git a/app/assets/javascripts/discourse/app/components/popup-input-tip.js b/app/assets/javascripts/discourse/app/components/popup-input-tip.js index 25c05eb0218..1b792dcdfa9 100644 --- a/app/assets/javascripts/discourse/app/components/popup-input-tip.js +++ b/app/assets/javascripts/discourse/app/components/popup-input-tip.js @@ -2,54 +2,61 @@ import Component from "@ember/component"; import { not, or, reads } from "@ember/object/computed"; import { service } from "@ember/service"; import { htmlSafe } from "@ember/template"; +import { + attributeBindings, + classNameBindings, + tagName, +} from "@ember-decorators/component"; import discourseComputed from "discourse-common/utils/decorators"; -export default Component.extend({ - composer: service(), - tagName: "a", - classNameBindings: [":popup-tip", "good", "bad", "lastShownAt::hide"], - attributeBindings: ["role", "ariaLabel", "tabindex"], - tipReason: null, - lastShownAt: or("shownAt", "validation.lastShownAt"), - bad: reads("validation.failed"), - good: not("bad"), - tabindex: "0", +@tagName("a") +@classNameBindings(":popup-tip", "good", "bad", "lastShownAt::hide") +@attributeBindings("role", "ariaLabel", "tabindex") +export default class PopupInputTip extends Component { + @service composer; + + tipReason = null; + tabindex = "0"; + + @or("shownAt", "validation.lastShownAt") lastShownAt; + @reads("validation.failed") bad; + @not("bad") good; @discourseComputed("bad") role(bad) { if (bad) { return "alert"; } - }, + } @discourseComputed("validation.reason") ariaLabel(reason) { return reason?.replace(/(<([^>]+)>)/gi, ""); - }, + } dismiss() { this.set("shownAt", null); this.composer.clearLastValidatedAt(); this.element.previousElementSibling?.focus(); - }, + } click() { this.dismiss(); - }, + } keyDown(event) { if (event.key === "Enter") { this.dismiss(); } - }, + } didReceiveAttrs() { - this._super(...arguments); + super.didReceiveAttrs(...arguments); let reason = this.get("validation.reason"); if (reason) { this.set("tipReason", htmlSafe(`${reason}`)); } else { this.set("tipReason", null); } - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/components/popup-menu.js b/app/assets/javascripts/discourse/app/components/popup-menu.js index 87d5ddb040f..a61673a6b7e 100644 --- a/app/assets/javascripts/discourse/app/components/popup-menu.js +++ b/app/assets/javascripts/discourse/app/components/popup-menu.js @@ -1,3 +1,3 @@ import Component from "@ember/component"; -export default Component.extend({}); +export default class PopupMenu extends Component {}