DEV: Convert core components to native class syntax (batch 6) (#28598)

Changes made using the ember-native-class-codemod, plus some manual tweaks
This commit is contained in:
David Taylor 2024-08-28 14:34:02 +01:00 committed by GitHub
parent 77d4b3304e
commit c4428715b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 305 additions and 291 deletions

View File

@ -1,14 +1,14 @@
import Component from "@ember/component"; 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 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") @on("didInsertElement")
@observes("highlight") @observes("highlight")
_highlightOnInsert() { _highlightOnInsert() {
const term = this.highlight; const term = this.highlight;
highlightSearch(this.element, term); highlightSearch(this.element, term);
}, }
}); }

View File

@ -1,7 +1,7 @@
import { on } from "@ember-decorators/object";
import TextField from "discourse/components/text-field"; 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") @on("init")
_init() { _init() {
// Chrome autocomplete is buggy per: // Chrome autocomplete is buggy per:
@ -13,5 +13,5 @@ export default TextField.extend({
} else { } else {
this.set("type", "password"); this.set("type", "password");
} }
}, }
}); }

View File

@ -4,12 +4,12 @@ import {
shouldOpenInNewTab, shouldOpenInNewTab,
} from "discourse/lib/click-track"; } from "discourse/lib/click-track";
export default Component.extend({ export default class HtmlWithLinks extends Component {
click(event) { click(event) {
if (event?.target?.tagName === "A") { if (event?.target?.tagName === "A") {
if (shouldOpenInNewTab(event.target.href)) { if (shouldOpenInNewTab(event.target.href)) {
openLinkInNewTab(event, event.target); openLinkInNewTab(event, event.target);
} }
} }
}, }
}); }

View File

@ -1,10 +1,13 @@
import Component from "@ember/component"; import Component from "@ember/component";
export default Component.extend({ import { action } from "@ember/object";
tagName: "div", import { tagName } from "@ember-decorators/component";
items: null,
actions: { @tagName("div")
removeIgnoredUser(item) { export default class IgnoredUserListItem extends Component {
this.onRemoveIgnoredUser(item); items = null;
},
}, @action
}); removeIgnoredUser(item) {
this.onRemoveIgnoredUser(item);
}
}

View File

@ -1,22 +1,23 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { tagName } from "@ember-decorators/component";
import UppyUploadMixin from "discourse/mixins/uppy-upload"; import UppyUploadMixin from "discourse/mixins/uppy-upload";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
export default Component.extend(UppyUploadMixin, { @tagName("span")
type: "avatar", export default class ImagesUploader extends Component.extend(UppyUploadMixin) {
tagName: "span", type = "avatar";
@discourseComputed("uploadingOrProcessing") @discourseComputed("uploadingOrProcessing")
uploadButtonText(uploadingOrProcessing) { uploadButtonText(uploadingOrProcessing) {
return uploadingOrProcessing ? I18n.t("uploading") : I18n.t("upload"); return uploadingOrProcessing ? I18n.t("uploading") : I18n.t("upload");
}, }
validateUploadedFilesOptions() { validateUploadedFilesOptions() {
return { imagesOnly: true }; return { imagesOnly: true };
}, }
uploadDone(upload) { uploadDone(upload) {
this.done(upload); this.done(upload);
}, }
}); }

View File

@ -9,32 +9,44 @@ import Group from "discourse/models/group";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
export default Component.extend({ export default class InvitePanel extends Component {
tagName: null, @readOnly("currentUser.staff") isStaff;
groupIds: null, @readOnly("currentUser.admin") isAdmin;
allGroups: null, @alias("inviteModel.id") topicId;
@equal("inviteModel.archetype", "private_message") isPM;
@and("isStaff", "siteSettings.must_approve_users") showApprovalMessage;
isStaff: readOnly("currentUser.staff"), // eg: visible only to specific group members
isAdmin: readOnly("currentUser.admin"), @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 is either a user, group or email
invitee: null, invitee = null;
isInviteeGroup: false,
hasCustomMessage: false, isInviteeGroup = false;
customMessage: null, hasCustomMessage = false;
inviteIcon: "envelope", customMessage = null;
invitingExistingUserToTopic: false, inviteIcon = "envelope";
invitingExistingUserToTopic = false;
init() { init() {
this._super(...arguments); super.init(...arguments);
this.setDefaultSelectedGroups(); this.setDefaultSelectedGroups();
this.setGroupOptions(); this.setGroupOptions();
}, }
willDestroyElement() { willDestroyElement() {
this._super(...arguments); super.willDestroyElement(...arguments);
this.reset(); this.reset();
}, }
@discourseComputed( @discourseComputed(
"isAdmin", "isAdmin",
@ -81,7 +93,7 @@ export default Component.extend({
} }
return false; return false;
}, }
@discourseComputed( @discourseComputed(
"isAdmin", "isAdmin",
@ -125,49 +137,36 @@ export default Component.extend({
} }
return false; return false;
}, }
@discourseComputed("inviteModel.saving") @discourseComputed("inviteModel.saving")
buttonTitle(saving) { buttonTitle(saving) {
return saving ? "topic.inviting" : "topic.invite_reply.action"; return saving ? "topic.inviting" : "topic.invite_reply.action";
}, }
// We are inviting to a topic if the topic isn't the current user. // 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. // The current user would mean we are inviting to the forum in general.
@discourseComputed("inviteModel") @discourseComputed("inviteModel")
invitingToTopic(inviteModel) { invitingToTopic(inviteModel) {
return inviteModel !== this.currentUser; return inviteModel !== this.currentUser;
}, }
@discourseComputed("inviteModel", "inviteModel.details.can_invite_via_email") @discourseComputed("inviteModel", "inviteModel.details.can_invite_via_email")
canInviteViaEmail(inviteModel, canInviteViaEmail) { canInviteViaEmail(inviteModel, canInviteViaEmail) {
return inviteModel === this.currentUser ? true : canInviteViaEmail; return inviteModel === this.currentUser ? true : canInviteViaEmail;
}, }
@discourseComputed("isPM", "canInviteViaEmail") @discourseComputed("isPM", "canInviteViaEmail")
showCopyInviteButton(isPM, canInviteViaEmail) { showCopyInviteButton(isPM, canInviteViaEmail) {
return canInviteViaEmail && !isPM; 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") @discourseComputed("isAdmin", "inviteModel.group_users")
isGroupOwnerOrAdmin(isAdmin, groupUsers) { isGroupOwnerOrAdmin(isAdmin, groupUsers) {
return ( return (
isAdmin || (groupUsers && groupUsers.some((groupUser) => groupUser.owner)) isAdmin || (groupUsers && groupUsers.some((groupUser) => groupUser.owner))
); );
}, }
// Show Groups? (add invited user to private group) // Show Groups? (add invited user to private group)
@discourseComputed( @discourseComputed(
@ -192,12 +191,12 @@ export default Component.extend({
!isPM && !isPM &&
(emailValid(invitee) || isPrivateTopic || !invitingToTopic) (emailValid(invitee) || isPrivateTopic || !invitingToTopic)
); );
}, }
@discourseComputed("invitee") @discourseComputed("invitee")
showCustomMessage(invitee) { showCustomMessage(invitee) {
return this.inviteModel === this.currentUser || emailValid(invitee); return this.inviteModel === this.currentUser || emailValid(invitee);
}, }
// Instructional text for the modal. // Instructional text for the modal.
@discourseComputed( @discourseComputed(
@ -243,12 +242,12 @@ export default Component.extend({
// inviting to forum // inviting to forum
return I18n.t("topic.invite_reply.to_forum"); return I18n.t("topic.invite_reply.to_forum");
} }
}, }
@discourseComputed("isPrivateTopic") @discourseComputed("isPrivateTopic")
showGroupsClass(isPrivateTopic) { showGroupsClass(isPrivateTopic) {
return isPrivateTopic ? "required" : "optional"; return isPrivateTopic ? "required" : "optional";
}, }
@discourseComputed("isPM", "invitee", "invitingExistingUserToTopic") @discourseComputed("isPM", "invitee", "invitingExistingUserToTopic")
successMessage(isPM, invitee, invitingExistingUserToTopic) { successMessage(isPM, invitee, invitingExistingUserToTopic) {
@ -265,7 +264,7 @@ export default Component.extend({
} else { } else {
return I18n.t("topic.invite_reply.success_username"); return I18n.t("topic.invite_reply.success_username");
} }
}, }
@discourseComputed("isPM", "ajaxError") @discourseComputed("isPM", "ajaxError")
errorMessage(isPM, ajaxError) { errorMessage(isPM, ajaxError) {
@ -275,18 +274,14 @@ export default Component.extend({
return isPM return isPM
? I18n.t("topic.invite_private.error") ? I18n.t("topic.invite_private.error")
: I18n.t("topic.invite_reply.error"); : I18n.t("topic.invite_reply.error");
}, }
@discourseComputed("canInviteViaEmail") @discourseComputed("canInviteViaEmail")
placeholderKey(canInviteViaEmail) { placeholderKey(canInviteViaEmail) {
return canInviteViaEmail return canInviteViaEmail
? "topic.invite_private.email_or_username_placeholder" ? "topic.invite_private.email_or_username_placeholder"
: "topic.invite_reply.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 the modal to allow a new user to be invited.
reset() { reset() {
@ -305,17 +300,17 @@ export default Component.extend({
finished: false, finished: false,
inviteLink: null, inviteLink: null,
}); });
}, }
setDefaultSelectedGroups() { setDefaultSelectedGroups() {
this.set("groupIds", []); this.set("groupIds", []);
}, }
setGroupOptions() { setGroupOptions() {
Group.findAll().then((groups) => { Group.findAll().then((groups) => {
this.set("allGroups", groups.filterBy("automatic", false)); this.set("allGroups", groups.filterBy("automatic", false));
}); });
}, }
@action @action
createInvite() { createInvite() {
@ -367,7 +362,7 @@ export default Component.extend({
}) })
.catch(onerror); .catch(onerror);
} }
}, }
@action @action
generateInviteLink() { generateInviteLink() {
@ -401,7 +396,7 @@ export default Component.extend({
} }
model.setProperties({ saving: false, error: true }); model.setProperties({ saving: false, error: true });
}); });
}, }
@action @action
showCustomMessageBox() { showCustomMessageBox() {
@ -421,14 +416,14 @@ export default Component.extend({
} else { } else {
this.set("customMessage", null); this.set("customMessage", null);
} }
}, }
@action @action
searchContact() { searchContact() {
getNativeContact(this.capabilities, ["email"], false).then((result) => { getNativeContact(this.capabilities, ["email"], false).then((result) => {
this.set("invitee", result[0].email[0]); this.set("invitee", result[0].email[0]);
}); });
}, }
@action @action
updateInvitee(selected, content) { updateInvitee(selected, content) {
@ -448,5 +443,5 @@ export default Component.extend({
isInviteeGroup: false, isInviteeGroup: false,
}); });
} }
}, }
}); }

View File

@ -1,16 +1,19 @@
import Component from "@ember/component"; import Component from "@ember/component";
import {
attributeBindings,
classNameBindings,
} from "@ember-decorators/component";
import { import {
navigateToTopic, navigateToTopic,
showEntrance, showEntrance,
} from "discourse/components/topic-list-item"; } from "discourse/components/topic-list-item";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({ @attributeBindings("topic.id:data-topic-id")
attributeBindings: ["topic.id:data-topic-id"], @classNameBindings(":latest-topic-list-item", "unboundClassNames")
classNameBindings: [":latest-topic-list-item", "unboundClassNames"], export default class LatestTopicListItem extends Component {
showEntrance = showEntrance;
showEntrance, navigateToTopic = navigateToTopic;
navigateToTopic,
click(e) { click(e) {
// for events undefined has a different meaning than false // for events undefined has a different meaning than false
@ -19,10 +22,10 @@ export default Component.extend({
} }
return this.unhandledRowClick(e, this.topic); return this.unhandledRowClick(e, this.topic);
}, }
// Can be overwritten by plugins to handle clicks on other parts of the row // Can be overwritten by plugins to handle clicks on other parts of the row
unhandledRowClick() {}, unhandledRowClick() {}
@discourseComputed("topic") @discourseComputed("topic")
unboundClassNames(topic) { unboundClassNames(topic) {
@ -45,5 +48,5 @@ export default Component.extend({
); );
return classes.join(" "); return classes.join(" ");
}, }
}); }

View File

@ -2,8 +2,8 @@ import Component from "@ember/component";
import { schedule } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import $ from "jquery"; import $ from "jquery";
export default Component.extend({ export default class LinkToInput extends Component {
showInput: false, showInput = false;
click() { click() {
this.onClick(); this.onClick();
@ -13,5 +13,5 @@ export default Component.extend({
}); });
return false; return false;
}, }
}); }

View File

@ -2,10 +2,10 @@ import Component from "@ember/component";
import { getOwner } from "@ember/owner"; import { getOwner } from "@ember/owner";
import ClickTrack from "discourse/lib/click-track"; import ClickTrack from "discourse/lib/click-track";
export default Component.extend({ export default class LinksRedirect extends Component {
click(event) { click(event) {
if (event?.target?.tagName === "A") { if (event?.target?.tagName === "A") {
return ClickTrack.trackClick(event, getOwner(this)); return ClickTrack.trackClick(event, getOwner(this));
} }
}, }
}); }

View File

@ -1,16 +1,16 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { action } from "@ember/object";
import LoadMore from "discourse/mixins/load-more"; import LoadMore from "discourse/mixins/load-more";
export default Component.extend(LoadMore, { export default class LoadMoreComponent extends Component.extend(LoadMore) {
init() { init() {
this._super(...arguments); super.init(...arguments);
this.set("eyelineSelector", this.selector); this.set("eyelineSelector", this.selector);
}, }
actions: { @action
loadMore() { loadMore() {
this.action(); this.action();
}, }
}, }
});

View File

@ -1,11 +1,12 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { classNameBindings } from "@ember-decorators/component";
import { isWebauthnSupported } from "discourse/lib/webauthn"; import { isWebauthnSupported } from "discourse/lib/webauthn";
import { findAll } from "discourse/models/login-method"; import { findAll } from "discourse/models/login-method";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({ @classNameBindings("hidden")
elementId: "login-buttons", export default class LoginButtons extends Component {
classNameBindings: ["hidden"], elementId = "login-buttons";
@discourseComputed( @discourseComputed(
"buttons.length", "buttons.length",
@ -14,12 +15,12 @@ export default Component.extend({
) )
hidden(buttonsCount, showLoginWithEmailLink, showPasskeysButton) { hidden(buttonsCount, showLoginWithEmailLink, showPasskeysButton) {
return buttonsCount === 0 && !showLoginWithEmailLink && !showPasskeysButton; return buttonsCount === 0 && !showLoginWithEmailLink && !showPasskeysButton;
}, }
@discourseComputed @discourseComputed
buttons() { buttons() {
return findAll(); return findAll();
}, }
@discourseComputed @discourseComputed
showPasskeysButton() { showPasskeysButton() {
@ -29,5 +30,5 @@ export default Component.extend({
this.context === "login" && this.context === "login" &&
isWebauthnSupported() isWebauthnSupported()
); );
}, }
}); }

View File

@ -1,12 +1,9 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { classNameBindings, tagName } from "@ember-decorators/component";
import { showEntrance } from "discourse/components/topic-list-item"; import { showEntrance } from "discourse/components/topic-list-item";
export default Component.extend({ @tagName("tr")
tagName: "tr", @classNameBindings(":category-topic-link", "topic.archived", "topic.visited")
classNameBindings: [ export default class MobileCategoryTopic extends Component {
":category-topic-link", click = showEntrance;
"topic.archived", }
"topic.visited",
],
click: showEntrance,
});

View File

@ -2,10 +2,16 @@ import Component from "@ember/component";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { next } from "@ember/runloop"; import { next } from "@ember/runloop";
import { service } from "@ember/service"; import { service } from "@ember/service";
import { classNames, tagName } from "@ember-decorators/component";
import { on } from "@ember-decorators/object";
import $ from "jquery"; 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") @on("init")
_init() { _init() {
if (this.site.desktopView) { if (this.site.desktopView) {
@ -15,19 +21,12 @@ export default Component.extend({
this.set("classNames", classes); this.set("classNames", classes);
} }
} }
}, }
tagName: "ul",
selectedHtml: null,
classNames: ["mobile-nav"],
router: service(),
currentRouteChanged() { currentRouteChanged() {
this.set("expanded", false); this.set("expanded", false);
next(() => this._updateSelectedHtml()); next(() => this._updateSelectedHtml());
}, }
_updateSelectedHtml() { _updateSelectedHtml() {
if (!this.element || this.isDestroying || this.isDestroyed) { if (!this.element || this.isDestroying || this.isDestroyed) {
@ -38,19 +37,19 @@ export default Component.extend({
if (active && active.innerHTML) { if (active && active.innerHTML) {
this.set("selectedHtml", active.innerHTML); this.set("selectedHtml", active.innerHTML);
} }
}, }
didInsertElement() { didInsertElement() {
this._super(...arguments); super.didInsertElement(...arguments);
this._updateSelectedHtml(); this._updateSelectedHtml();
this.router.on("routeDidChange", this, this.currentRouteChanged); this.router.on("routeDidChange", this, this.currentRouteChanged);
}, }
willDestroyElement() { willDestroyElement() {
this._super(...arguments); super.willDestroyElement(...arguments);
this.router.off("routeDidChange", this, this.currentRouteChanged); this.router.off("routeDidChange", this, this.currentRouteChanged);
}, }
@action @action
toggleExpanded(event) { toggleExpanded(event) {
@ -74,5 +73,5 @@ export default Component.extend({
}); });
} }
}); });
}, }
}); }

View File

@ -33,18 +33,18 @@ export function resetWidgetCleanCallbacks() {
_cleanCallbacks = {}; _cleanCallbacks = {};
} }
export default Component.extend({ export default class MountWidget extends Component {
_tree: null, dirtyKeys = null;
_rootNode: null, _tree = null;
_timeout: null, _rootNode = null;
_widgetClass: null, _timeout = null;
_renderCallback: null, _widgetClass = null;
_childEvents: null, _renderCallback = null;
_dispatched: null, _childEvents = null;
dirtyKeys: null, _dispatched = null;
init() { init() {
this._super(...arguments); super.init(...arguments);
const name = this.widget; const name = this.widget;
if (name === "post-cooked") { if (name === "post-cooked") {
@ -81,19 +81,19 @@ export default Component.extend({
this._childComponents = ArrayProxy.create({ content: [] }); this._childComponents = ArrayProxy.create({ content: [] });
this._dispatched = []; this._dispatched = [];
this.dirtyKeys = new DirtyKeys(name); this.dirtyKeys = new DirtyKeys(name);
}, }
didInsertElement() { didInsertElement() {
this._super(...arguments); super.didInsertElement(...arguments);
WidgetClickHook.setupDocumentCallback(); WidgetClickHook.setupDocumentCallback();
this._rootNode = document.createElement("div"); this._rootNode = document.createElement("div");
this.element.appendChild(this._rootNode); this.element.appendChild(this._rootNode);
this._timeout = scheduleOnce("render", this, this.rerenderWidget); this._timeout = scheduleOnce("render", this, this.rerenderWidget);
}, }
willClearRender() { willClearRender() {
this._super(...arguments); super.willClearRender(...arguments);
const callbacks = _cleanCallbacks[this.widget]; const callbacks = _cleanCallbacks[this.widget];
if (callbacks) { if (callbacks) {
callbacks.forEach((cb) => cb(this._tree)); callbacks.forEach((cb) => cb(this._tree));
@ -105,29 +105,27 @@ export default Component.extend({
traverseCustomWidgets(this._tree, (w) => w.destroy()); traverseCustomWidgets(this._tree, (w) => w.destroy());
this._rootNode = patch(this._rootNode, diff(this._tree, null)); this._rootNode = patch(this._rootNode, diff(this._tree, null));
this._tree = null; this._tree = null;
}, }
willDestroyElement() { willDestroyElement() {
this._super(...arguments); super.willDestroyElement(...arguments);
this._dispatched.forEach((evt) => { this._dispatched.forEach((evt) => {
const [eventName, caller] = evt; const [eventName, caller] = evt;
this.appEvents.off(eventName, this, caller); this.appEvents.off(eventName, this, caller);
}); });
cancel(this._timeout); cancel(this._timeout);
}, }
afterRender() {}, afterRender() {}
beforePatch() {}
beforePatch() {}, afterPatch() {}
afterPatch() {},
eventDispatched(eventName, key, refreshArg) { eventDispatched(eventName, key, refreshArg) {
key = typeof key === "function" ? key(refreshArg) : key; key = typeof key === "function" ? key(refreshArg) : key;
const onRefresh = camelize(eventName.replace(/:/, "-")); const onRefresh = camelize(eventName.replace(/:/, "-"));
this.dirtyKeys.keyDirty(key, { onRefresh, refreshArg }); this.dirtyKeys.keyDirty(key, { onRefresh, refreshArg });
this.queueRerender(); this.queueRerender();
}, }
dispatch(eventName, key) { dispatch(eventName, key) {
this._childEvents.push(eventName); this._childEvents.push(eventName);
@ -136,7 +134,7 @@ export default Component.extend({
this.eventDispatched(eventName, key, refreshArg); this.eventDispatched(eventName, key, refreshArg);
this._dispatched.push([eventName, caller]); this._dispatched.push([eventName, caller]);
this.appEvents.on(eventName, this, caller); this.appEvents.on(eventName, this, caller);
}, }
queueRerender(callback) { queueRerender(callback) {
if (callback && !this._renderCallback) { if (callback && !this._renderCallback) {
@ -144,9 +142,9 @@ export default Component.extend({
} }
scheduleOnce("render", this, this.rerenderWidget); scheduleOnce("render", this, this.rerenderWidget);
}, }
buildArgs() {}, buildArgs() {}
rerenderWidget() { rerenderWidget() {
cancel(this._timeout); cancel(this._timeout);
@ -190,18 +188,18 @@ export default Component.extend({
console.log(Date.now() - t0); console.log(Date.now() - t0);
} }
} }
}, }
mountChildComponent(info) { mountChildComponent(info) {
this._childComponents.pushObject(info); this._childComponents.pushObject(info);
}, }
unmountChildComponent(info) { unmountChildComponent(info) {
this._childComponents.removeObject(info); this._childComponents.removeObject(info);
}, }
didUpdateAttrs() { didUpdateAttrs() {
this._super(...arguments); super.didUpdateAttrs(...arguments);
this.queueRerender(); this.queueRerender();
}, }
}); }

View File

@ -1,28 +1,34 @@
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import Component from "@ember/component"; import Component from "@ember/component";
import { dependentKeyCompat } from "@ember/object/compat"; import { dependentKeyCompat } from "@ember/object/compat";
import {
attributeBindings,
classNameBindings,
tagName,
} from "@ember-decorators/component";
import { filterTypeForMode } from "discourse/lib/filter-mode"; import { filterTypeForMode } from "discourse/lib/filter-mode";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({ @tagName("li")
tagName: "li", @classNameBindings(
classNameBindings: [ "active",
"active", "content.hasIcon:has-icon",
"content.hasIcon:has-icon", "content.classNames",
"content.classNames", "isHidden:hidden",
"isHidden:hidden", "content.name"
"content.name", )
], @attributeBindings("content.title:title")
attributeBindings: ["content.title:title"], export default class NavigationItem extends Component {
hidden: false, @tracked filterMode;
activeClass: "",
hrefLink: null, hidden = false;
filterMode: tracked(), activeClass = "";
hrefLink = null;
@dependentKeyCompat @dependentKeyCompat
get filterType() { get filterType() {
return filterTypeForMode(this.filterMode); return filterTypeForMode(this.filterMode);
}, }
@discourseComputed("content.filterType", "filterType", "content.active") @discourseComputed("content.filterType", "filterType", "content.active")
active(contentFilterType, filterType, active) { active(contentFilterType, filterType, active) {
@ -30,7 +36,7 @@ export default Component.extend({
return active; return active;
} }
return contentFilterType === filterType; return contentFilterType === filterType;
}, }
@discourseComputed("content.count", "content.name") @discourseComputed("content.count", "content.name")
isHidden(count, name) { isHidden(count, name) {
@ -42,10 +48,10 @@ export default Component.extend({
(name === "new" || name === "unread") && (name === "new" || name === "unread") &&
count < 1 count < 1
); );
}, }
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); super.didReceiveAttrs(...arguments);
const content = this.content; const content = this.content;
let href = content.get("href"); let href = content.get("href");
@ -87,5 +93,5 @@ export default Component.extend({
this.set("hrefLink", href); this.set("hrefLink", href);
this.set("activeClass", this.active ? "active" : ""); this.set("activeClass", this.active ? "active" : "");
}, }
}); }

View File

@ -1,14 +1,15 @@
import { computed } from "@ember/object";
import { classNameBindings } from "@ember-decorators/component";
import TextField from "discourse/components/text-field"; import TextField from "discourse/components/text-field";
import { allowOnlyNumericInput } from "discourse/lib/utilities"; import { allowOnlyNumericInput } from "discourse/lib/utilities";
import deprecated from "discourse-common/lib/deprecated"; import deprecated from "discourse-common/lib/deprecated";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
export default TextField.extend({ @classNameBindings("invalid")
classNameBindings: ["invalid"], export default class NumberField extends TextField {
init() { init() {
this._super(...arguments); super.init(...arguments);
deprecated( deprecated(
`NumberField component is deprecated. Use native <input> elements instead.\ne.g. <input {{on "input" (with-event-value (fn (mut this.value)))}} type="number" value={{this.value}} />`, `NumberField component is deprecated. Use native <input> elements instead.\ne.g. <input {{on "input" (with-event-value (fn (mut this.value)))}} type="number" value={{this.value}} />`,
{ {
@ -17,46 +18,47 @@ export default TextField.extend({
dropFrom: "3.3.0", dropFrom: "3.3.0",
} }
); );
}, }
keyDown: function (event) { keyDown(event) {
allowOnlyNumericInput(event, this._minNumber && this._minNumber < 0); allowOnlyNumericInput(event, this._minNumber && this._minNumber < 0);
}, }
get _minNumber() { get _minNumber() {
if (!this.get("min")) { if (!this.get("min")) {
return; return;
} }
return parseInt(this.get("min"), 10); return parseInt(this.get("min"), 10);
}, }
get _maxNumber() { get _maxNumber() {
if (!this.get("max")) { if (!this.get("max")) {
return; return;
} }
return parseInt(this.get("max"), 10); return parseInt(this.get("max"), 10);
}, }
@discourseComputed("number") @computed("number")
value: { get value() {
get(number) { if (this.number === null) {
return parseInt(number, 10); return "";
}, }
set(value) { return parseInt(this.number, 10);
const num = parseInt(value, 10); }
if (isNaN(num)) {
this.set("invalid", true); set value(value) {
return value; const num = parseInt(value, 10);
} else { if (isNaN(num)) {
this.set("invalid", false); this.set("invalid", true);
this.set("number", num); this.set("number", null);
return num.toString(); } else {
} this.set("invalid", false);
}, this.set("number", num);
}, }
}
@discourseComputed("placeholderKey") @discourseComputed("placeholderKey")
placeholder(key) { placeholder(key) {
return key ? I18n.t(key) : ""; return key ? I18n.t(key) : "";
}, }
}); }

View File

@ -1,3 +1,3 @@
import CategoryListItem from "discourse/components/category-list-item"; import CategoryListItem from "discourse/components/category-list-item";
export default CategoryListItem.extend({}); export default class ParentCategoryRow extends CategoryListItem {}

View File

@ -4,8 +4,8 @@ import TextField from "discourse/components/text-field";
Same as text-field, but with special features for a password input. 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. Be sure to test on a variety of browsers and operating systems when changing this logic.
**/ **/
export default TextField.extend({ export default class PasswordField extends TextField {
canToggle: false, canToggle = false;
keyPress(e) { keyPress(e) {
if ( if (
@ -21,19 +21,19 @@ export default TextField.extend({
this.set("canToggle", true); this.set("canToggle", true);
this.set("capsLockOn", false); this.set("capsLockOn", false);
} }
}, }
keyUp(e) { keyUp(e) {
if (e.which === 20 && this.canToggle) { if (e.which === 20 && this.canToggle) {
this.toggleProperty("capsLockOn"); this.toggleProperty("capsLockOn");
} }
}, }
focusOut() { focusOut() {
this.set("capsLockOn", false); this.set("capsLockOn", false);
}, }
focusIn() { focusIn() {
this.set("canToggle", false); // can't know the state of caps lock yet. keyPress will figure it out. this.set("canToggle", false); // can't know the state of caps lock yet. keyPress will figure it out.
}, }
}); }

View File

@ -4,12 +4,12 @@ import { ajax } from "discourse/lib/ajax";
import { loadOneboxes } from "discourse/lib/load-oneboxes"; import { loadOneboxes } from "discourse/lib/load-oneboxes";
import { afterRender } from "discourse-common/utils/decorators"; import { afterRender } from "discourse-common/utils/decorators";
export default Component.extend({ export default class PendingPost extends Component {
didRender() { didRender() {
this._super(...arguments); super.didRender(...arguments);
this._loadOneboxes(); this._loadOneboxes();
this._resolveUrls(); this._resolveUrls();
}, }
@afterRender @afterRender
_loadOneboxes() { _loadOneboxes() {
@ -21,10 +21,10 @@ export default Component.extend({
this.siteSettings.max_oneboxes_per_post, this.siteSettings.max_oneboxes_per_post,
true true
); );
}, }
@afterRender @afterRender
_resolveUrls() { _resolveUrls() {
resolveAllShortUrls(ajax, this.siteSettings, this.element, this.opts); resolveAllShortUrls(ajax, this.siteSettings, this.element, this.opts);
}, }
}); }

View File

@ -2,6 +2,7 @@ import Component from "@ember/component";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { service } from "@ember/service"; import { service } from "@ember/service";
import { isBlank } from "@ember/utils"; import { isBlank } from "@ember/utils";
import { classNames } from "@ember-decorators/component";
import { import {
authorizedExtensions, authorizedExtensions,
authorizesAllExtensions, authorizesAllExtensions,
@ -17,39 +18,40 @@ import I18n from "discourse-i18n";
// binding will still be added, and the file type will be validated here. This // 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 // 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. // the file, such as directly using JSON or CSV data from a file in JS.
export default Component.extend({ @classNames("pick-files-button")
dialog: service(), export default class PickFilesButton extends Component {
fileInputId: null, @service dialog;
fileInputClass: null,
fileInputDisabled: false, fileInputId = null;
classNames: ["pick-files-button"], fileInputClass = null;
acceptedFormatsOverride: null, fileInputDisabled = false;
allowMultiple: false, acceptedFormatsOverride = null;
showButton: false, allowMultiple = false;
showButton = false;
didInsertElement() { didInsertElement() {
this._super(...arguments); super.didInsertElement(...arguments);
if (this.onFilesPicked) { if (this.onFilesPicked) {
const fileInput = this.element.querySelector("input"); const fileInput = this.element.querySelector("input");
this.set("fileInput", fileInput); this.set("fileInput", fileInput);
fileInput.addEventListener("change", this.onChange, false); fileInput.addEventListener("change", this.onChange, false);
} }
}, }
willDestroyElement() { willDestroyElement() {
this._super(...arguments); super.willDestroyElement(...arguments);
if (this.onFilesPicked) { if (this.onFilesPicked) {
this.fileInput.removeEventListener("change", this.onChange); this.fileInput.removeEventListener("change", this.onChange);
} }
}, }
@bind @bind
onChange() { onChange() {
const files = this.fileInput.files; const files = this.fileInput.files;
this._filesPicked(files); this._filesPicked(files);
}, }
@discourseComputed() @discourseComputed()
acceptsAllFormats() { acceptsAllFormats() {
@ -57,7 +59,7 @@ export default Component.extend({
this.capabilities.isIOS || this.capabilities.isIOS ||
authorizesAllExtensions(this.currentUser.staff, this.siteSettings) authorizesAllExtensions(this.currentUser.staff, this.siteSettings)
); );
}, }
@discourseComputed() @discourseComputed()
acceptedFormats() { acceptedFormats() {
@ -72,12 +74,12 @@ export default Component.extend({
); );
return extensions.map((ext) => `.${ext}`).join(); return extensions.map((ext) => `.${ext}`).join();
}, }
@action @action
openSystemFilePicker() { openSystemFilePicker() {
this.fileInput.click(); this.fileInput.click();
}, }
_filesPicked(files) { _filesPicked(files) {
if (!files || !files.length) { if (!files || !files.length) {
@ -95,7 +97,7 @@ export default Component.extend({
if (typeof this.onFilesPicked === "function") { if (typeof this.onFilesPicked === "function") {
this.onFilesPicked(files); this.onFilesPicked(files);
} }
}, }
_haveAcceptedTypes(files) { _haveAcceptedTypes(files) {
for (const file of files) { for (const file of files) {
@ -104,7 +106,7 @@ export default Component.extend({
} }
} }
return true; return true;
}, }
_hasAcceptedExtensionOrType(file) { _hasAcceptedExtensionOrType(file) {
const extension = this._fileExtension(file.name); const extension = this._fileExtension(file.name);
@ -112,9 +114,9 @@ export default Component.extend({
this.acceptedFormats.includes(`.${extension}`) || this.acceptedFormats.includes(`.${extension}`) ||
this.acceptedFormats.includes(file.type) this.acceptedFormats.includes(file.type)
); );
}, }
_fileExtension(fileName) { _fileExtension(fileName) {
return fileName.split(".").pop(); return fileName.split(".").pop();
}, }
}); }

View File

@ -18,9 +18,9 @@ export function resetDecorators() {
_decorators = {}; _decorators = {};
} }
export default Component.extend({ export default class PluginConnector extends Component {
init() { init() {
this._super(...arguments); super.init(...arguments);
const args = this.args || {}; const args = this.args || {};
Object.keys(args).forEach((key) => { Object.keys(args).forEach((key) => {
@ -68,31 +68,31 @@ export default Component.extend({
connectorInfo connectorInfo
); );
connectorClass?.setupComponent?.call(this, merged, this); connectorClass?.setupComponent?.call(this, merged, this);
}, }
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); super.didReceiveAttrs(...arguments);
this._decoratePluginOutlets(); this._decoratePluginOutlets();
}, }
@afterRender @afterRender
_decoratePluginOutlets() { _decoratePluginOutlets() {
(_decorators[this.connector.outletName] || []).forEach((dec) => (_decorators[this.connector.outletName] || []).forEach((dec) =>
dec(this.element, this.args) dec(this.element, this.args)
); );
}, }
willDestroyElement() { willDestroyElement() {
this._super(...arguments); super.willDestroyElement(...arguments);
const connectorClass = this.connector.connectorClass; const connectorClass = this.connector.connectorClass;
connectorClass?.teardownComponent?.call(this, this); connectorClass?.teardownComponent?.call(this, this);
}, }
send(name, ...args) { send(name, ...args) {
const connectorClass = this.connector.connectorClass; const connectorClass = this.connector.connectorClass;
const action = connectorClass?.actions?.[name]; 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);
}, }
}); }

View File

@ -2,54 +2,61 @@ import Component from "@ember/component";
import { not, or, reads } from "@ember/object/computed"; import { not, or, reads } from "@ember/object/computed";
import { service } from "@ember/service"; import { service } from "@ember/service";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import {
attributeBindings,
classNameBindings,
tagName,
} from "@ember-decorators/component";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({ @tagName("a")
composer: service(), @classNameBindings(":popup-tip", "good", "bad", "lastShownAt::hide")
tagName: "a", @attributeBindings("role", "ariaLabel", "tabindex")
classNameBindings: [":popup-tip", "good", "bad", "lastShownAt::hide"], export default class PopupInputTip extends Component {
attributeBindings: ["role", "ariaLabel", "tabindex"], @service composer;
tipReason: null,
lastShownAt: or("shownAt", "validation.lastShownAt"), tipReason = null;
bad: reads("validation.failed"), tabindex = "0";
good: not("bad"),
tabindex: "0", @or("shownAt", "validation.lastShownAt") lastShownAt;
@reads("validation.failed") bad;
@not("bad") good;
@discourseComputed("bad") @discourseComputed("bad")
role(bad) { role(bad) {
if (bad) { if (bad) {
return "alert"; return "alert";
} }
}, }
@discourseComputed("validation.reason") @discourseComputed("validation.reason")
ariaLabel(reason) { ariaLabel(reason) {
return reason?.replace(/(<([^>]+)>)/gi, ""); return reason?.replace(/(<([^>]+)>)/gi, "");
}, }
dismiss() { dismiss() {
this.set("shownAt", null); this.set("shownAt", null);
this.composer.clearLastValidatedAt(); this.composer.clearLastValidatedAt();
this.element.previousElementSibling?.focus(); this.element.previousElementSibling?.focus();
}, }
click() { click() {
this.dismiss(); this.dismiss();
}, }
keyDown(event) { keyDown(event) {
if (event.key === "Enter") { if (event.key === "Enter") {
this.dismiss(); this.dismiss();
} }
}, }
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); super.didReceiveAttrs(...arguments);
let reason = this.get("validation.reason"); let reason = this.get("validation.reason");
if (reason) { if (reason) {
this.set("tipReason", htmlSafe(`${reason}`)); this.set("tipReason", htmlSafe(`${reason}`));
} else { } else {
this.set("tipReason", null); this.set("tipReason", null);
} }
}, }
}); }

View File

@ -1,3 +1,3 @@
import Component from "@ember/component"; import Component from "@ember/component";
export default Component.extend({}); export default class PopupMenu extends Component {}