mirror of
https://github.com/discourse/discourse.git
synced 2025-04-14 11:22:57 +08:00
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:
parent
77d4b3304e
commit
c4428715b5
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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(" ");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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({
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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" : "");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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) : "";
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
import CategoryListItem from "discourse/components/category-list-item";
|
||||
|
||||
export default CategoryListItem.extend({});
|
||||
export default class ParentCategoryRow extends CategoryListItem {}
|
||||
|
@ -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.
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({});
|
||||
export default class PopupMenu extends Component {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user