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 { 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);
},
});
}
}

View File

@ -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");
}
},
});
}
}

View File

@ -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);
}
}
},
});
}
}

View File

@ -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);
}
}

View File

@ -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);
},
});
}
}

View File

@ -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,
});
}
},
});
}
}

View File

@ -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(" ");
},
});
}
}

View File

@ -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;
},
});
}
}

View File

@ -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));
}
},
});
}
}

View File

@ -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();
}
}

View File

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

View File

@ -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;
}

View File

@ -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({
});
}
});
},
});
}
}

View File

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

View File

@ -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" : "");
},
});
}
}

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 { 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 <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",
}
);
},
}
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) : "";
},
});
}
}

View File

@ -1,3 +1,3 @@
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.
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.
},
});
}
}

View File

@ -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);
},
});
}
}

View File

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

View File

@ -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);
}
}

View File

@ -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);
}
},
});
}
}

View File

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