From 061e79297fb986a2c2d08558416126992d3e1e39 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 13 Feb 2024 10:49:18 +0000 Subject: [PATCH] DEV: Convert User model to native class syntax (#25628) This commit was created with a combination of the ember-native-class-codemod and manual cleanup. User-status-related functionality was previously encapsulated in its own `User.reopen` call, which is essentially an 'inline mixin'. This commit refactors it into a utility class, with an instance accessible on `User#statusManager` --- .../app/components/user-card-contents.js | 4 +- .../discourse/app/components/user-info.js | 4 +- .../javascripts/discourse/app/models/user.js | 360 +++++++++--------- .../javascripts/discourse/app/routes/user.js | 4 +- .../discourse/app/widgets/post-cooked.js | 4 +- .../discourse/app/widgets/poster-name.js | 4 +- .../discourse/tests/helpers/qunit-helpers.js | 4 +- .../discourse/tests/unit/models/user-test.js | 28 +- .../discourse/components/chat-channel-row.gjs | 4 +- .../discourse/components/chat-message.gjs | 6 +- .../components/chat/message/info.gjs | 4 +- .../discourse/initializers/chat-sidebar.js | 4 +- 12 files changed, 221 insertions(+), 209 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/user-card-contents.js b/app/assets/javascripts/discourse/app/components/user-card-contents.js index 1fc23a3db39..11dfee48cb0 100644 --- a/app/assets/javascripts/discourse/app/components/user-card-contents.js +++ b/app/assets/javascripts/discourse/app/components/user-card-contents.js @@ -207,7 +207,7 @@ export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, { ); } this.setProperties({ user }); - this.user.trackStatus(); + this.user.statusManager.trackStatus(); return user; }) .catch(() => this._close()) @@ -216,7 +216,7 @@ export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, { _close() { if (this.user) { - this.user.stopTrackingStatus(); + this.user.statusManager.stopTrackingStatus(); } this.setProperties({ diff --git a/app/assets/javascripts/discourse/app/components/user-info.js b/app/assets/javascripts/discourse/app/components/user-info.js index d529f8e26d7..31a3851678b 100644 --- a/app/assets/javascripts/discourse/app/components/user-info.js +++ b/app/assets/javascripts/discourse/app/components/user-info.js @@ -14,12 +14,12 @@ export default Component.extend({ didInsertElement() { this._super(...arguments); - this.user?.trackStatus?.(); + this.user?.statusManager?.trackStatus(); }, willDestroyElement() { this._super(...arguments); - this.user?.stopTrackingStatus?.(); + this.user?.statusManager?.stopTrackingStatus(); }, @discourseComputed("user.username") diff --git a/app/assets/javascripts/discourse/app/models/user.js b/app/assets/javascripts/discourse/app/models/user.js index f80910f6391..eeecc2e6eaf 100644 --- a/app/assets/javascripts/discourse/app/models/user.js +++ b/app/assets/javascripts/discourse/app/models/user.js @@ -1,3 +1,4 @@ +import { getOwner, setOwner } from "@ember/application"; import { A } from "@ember/array"; import EmberObject, { computed, get, getProperties } from "@ember/object"; import { dependentKeyCompat } from "@ember/object/compat"; @@ -169,74 +170,83 @@ function userOption(userOptionKey) { }); } -const User = RestModel.extend({ - appEvents: service(), - userTips: service(), +export default class User extends RestModel.extend(Evented) { + @service appEvents; + @service userTips; - mailing_list_mode: userOption("mailing_list_mode"), - external_links_in_new_tab: userOption("external_links_in_new_tab"), - enable_quoting: userOption("enable_quoting"), - dynamic_favicon: userOption("dynamic_favicon"), - automatically_unpin_topics: userOption("automatically_unpin_topics"), - likes_notifications_disabled: userOption("likes_notifications_disabled"), - hide_profile_and_presence: userOption("hide_profile_and_presence"), - title_count_mode: userOption("title_count_mode"), - enable_defer: userOption("enable_defer"), - timezone: userOption("timezone"), - skip_new_user_tips: userOption("skip_new_user_tips"), - default_calendar: userOption("default_calendar"), - bookmark_auto_delete_preference: userOption( - "bookmark_auto_delete_preference" - ), - seen_popups: userOption("seen_popups"), - should_be_redirected_to_top: userOption("should_be_redirected_to_top"), - redirected_to_top: userOption("redirected_to_top"), - treat_as_new_topic_start_date: userOption("treat_as_new_topic_start_date"), + @userOption("mailing_list_mode") mailing_list_mode; + @userOption("external_links_in_new_tab") external_links_in_new_tab; + @userOption("enable_quoting") enable_quoting; + @userOption("dynamic_favicon") dynamic_favicon; + @userOption("automatically_unpin_topics") automatically_unpin_topics; + @userOption("likes_notifications_disabled") likes_notifications_disabled; + @userOption("hide_profile_and_presence") hide_profile_and_presence; + @userOption("title_count_mode") title_count_mode; + @userOption("enable_defer") enable_defer; + @userOption("timezone") timezone; + @userOption("skip_new_user_tips") skip_new_user_tips; + @userOption("default_calendar") default_calendar; + @userOption("bookmark_auto_delete_preference") + bookmark_auto_delete_preference; + @userOption("seen_popups") seen_popups; + @userOption("should_be_redirected_to_top") should_be_redirected_to_top; + @userOption("redirected_to_top") redirected_to_top; + @userOption("treat_as_new_topic_start_date") treat_as_new_topic_start_date; - hasPMs: gt("private_messages_stats.all", 0), - hasStartedPMs: gt("private_messages_stats.mine", 0), - hasUnreadPMs: gt("private_messages_stats.unread", 0), + @gt("private_messages_stats.all", 0) hasPMs; + @gt("private_messages_stats.mine", 0) hasStartedPMs; + @gt("private_messages_stats.unread", 0) hasUnreadPMs; + @url("id", "username_lower", "/admin/users/%@1/%@2") adminPath; + @equal("trust_level", 0) isBasic; + @equal("trust_level", 3) isRegular; + @equal("trust_level", 4) isLeader; + @or("staff", "isLeader") canManageTopic; + @alias("sidebar_category_ids") sidebarCategoryIds; + @alias("sidebar_sections") sidebarSections; + @mapBy("sidebarTags", "name") sidebarTagNames; + @filterBy("groups", "has_messages", true) groupsWithMessages; + + numGroupsToDisplay = 2; + + statusManager = new UserStatusManager(this); @discourseComputed("can_be_deleted", "post_count") canBeDeleted(canBeDeleted, postCount) { const maxPostCount = this.siteSettings.delete_all_posts_max; return canBeDeleted && postCount <= maxPostCount; - }, + } @discourseComputed() stream() { return UserStream.create({ user: this }); - }, + } @discourseComputed() bookmarks() { return Bookmark.create({ user: this }); - }, + } @discourseComputed() postsStream() { return UserPostsStream.create({ user: this }); - }, + } @discourseComputed() userDraftsStream() { return UserDraftsStream.create({ user: this }); - }, + } - staff: computed("admin", "moderator", { - get() { - return this.admin || this.moderator; - }, + @computed("admin", "moderator") + get staff() { + return this.admin || this.moderator; + } - // prevents staff property to be overridden - set() { - return this.admin || this.moderator; - }, - }), + // prevents staff property to be overridden + set staff(value) {} destroySession() { return ajax(`/session/${this.username}`, { type: "DELETE" }); - }, + } @discourseComputed("username_lower") searchContext(username) { @@ -245,7 +255,7 @@ const User = RestModel.extend({ id: username, user: this, }; - }, + } @discourseComputed("username", "name") displayName(username, name) { @@ -253,7 +263,7 @@ const User = RestModel.extend({ return name; } return username; - }, + } @discourseComputed("profile_background_upload_url") profileBackgroundUrl(bgUrl) { @@ -261,13 +271,13 @@ const User = RestModel.extend({ return htmlSafe(""); } return htmlSafe("background-image: url(" + getURLWithCDN(bgUrl) + ")"); - }, + } @discourseComputed() path() { // no need to observe, requires a hard refresh to update return userPath(this.username_lower); - }, + } @discourseComputed() userApiKeys() { @@ -287,7 +297,7 @@ const User = RestModel.extend({ return obj; }); } - }, + } revokeApiKey(key) { return ajax("/user-api-key/revoke", { @@ -296,7 +306,7 @@ const User = RestModel.extend({ }).then(() => { key.set("revoked", true); }); - }, + } undoRevokeApiKey(key) { return ajax("/user-api-key/undo-revoke", { @@ -305,7 +315,7 @@ const User = RestModel.extend({ }).then(() => { key.set("revoked", false); }); - }, + } pmPath(topic) { const userId = this.id; @@ -323,35 +333,33 @@ const User = RestModel.extend({ return userPath(`${username}/messages/group/${groups[0].name}`); } } - }, - - adminPath: url("id", "username_lower", "/admin/users/%@1/%@2"), + } @discourseComputed() mutedTopicsPath() { return defaultHomepage() === "latest" ? getURL("/?state=muted") : getURL("/latest?state=muted"); - }, + } @discourseComputed() watchingTopicsPath() { return defaultHomepage() === "latest" ? getURL("/?state=watching") : getURL("/latest?state=watching"); - }, + } @discourseComputed() trackingTopicsPath() { return defaultHomepage() === "latest" ? getURL("/?state=tracking") : getURL("/latest?state=tracking"); - }, + } @discourseComputed("username") username_lower(username) { return username.toLowerCase(); - }, + } @discourseComputed("trust_level") trustLevel(trustLevel) { @@ -359,36 +367,37 @@ const User = RestModel.extend({ "id", parseInt(trustLevel, 10) ); - }, - - isBasic: equal("trust_level", 0), - isRegular: equal("trust_level", 3), - isLeader: equal("trust_level", 4), - canManageTopic: or("staff", "isLeader"), + } @discourseComputed("previous_visit_at") previousVisitAt(previous_visit_at) { return new Date(previous_visit_at); - }, + } @discourseComputed("suspended_till") suspended(suspendedTill) { return suspendedTill && moment(suspendedTill).isAfter(); - }, + } @discourseComputed("suspended_till") - suspendedForever: isForever, + suspendedForever(suspendedTill) { + return isForever(suspendedTill); + } @discourseComputed("silenced_till") - silencedForever: isForever, + silencedForever(silencedTill) { + isForever(silencedTill); + } @discourseComputed("suspended_till") - suspendedTillDate: longDate, + suspendedTillDate(silencedTill) { + return longDate(silencedTill); + } @discourseComputed("silenced_till") - silencedTillDate: longDate, - - sidebarCategoryIds: alias("sidebar_category_ids"), + silencedTillDate(silencedTill) { + return longDate(silencedTill); + } @discourseComputed("sidebar_tags.[]") sidebarTags(sidebarTags) { @@ -399,18 +408,14 @@ const User = RestModel.extend({ return sidebarTags.sort((a, b) => { return a.name.localeCompare(b.name); }); - }, - - sidebarSections: alias("sidebar_sections"), - - sidebarTagNames: mapBy("sidebarTags", "name"), + } changeUsername(new_username) { return ajax(userPath(`${this.username_lower}/preferences/username`), { type: "PUT", data: { new_username }, }); - }, + } addEmail(email) { return ajax(userPath(`${this.username_lower}/preferences/email`), { @@ -423,7 +428,7 @@ const User = RestModel.extend({ this.unconfirmed_emails.pushObject(email); }); - }, + } changeEmail(email) { return ajax(userPath(`${this.username_lower}/preferences/email`), { @@ -436,7 +441,7 @@ const User = RestModel.extend({ this.unconfirmed_emails.pushObject(email); }); - }, + } save(fields) { const data = this.getProperties( @@ -506,7 +511,7 @@ const User = RestModel.extend({ .finally(() => { this.set("isSaving", false); }); - }, + } setPrimaryEmail(email) { return ajax(userPath(`${this.username}/preferences/primary-email.json`), { @@ -517,7 +522,7 @@ const User = RestModel.extend({ this.secondary_emails.pushObject(this.email); this.set("email", email); }); - }, + } destroyEmail(email) { return ajax(userPath(`${this.username}/preferences/email.json`), { @@ -530,7 +535,7 @@ const User = RestModel.extend({ this.secondary_emails.removeObject(email); } }); - }, + } changePassword() { return ajax("/session/forgot_password", { @@ -538,55 +543,55 @@ const User = RestModel.extend({ data: { login: this.email || this.username }, type: "POST", }); - }, + } loadSecondFactorCodes() { return ajax("/u/second_factors.json", { type: "POST", }); - }, + } requestSecurityKeyChallenge() { return ajax("/u/create_second_factor_security_key.json", { type: "POST", }); - }, + } registerSecurityKey(credential) { return ajax("/u/register_second_factor_security_key.json", { data: credential, type: "POST", }); - }, + } trustedSession() { return ajax("/u/trusted-session.json"); - }, + } createPasskey() { return ajax("/u/create_passkey.json", { type: "POST", }); - }, + } registerPasskey(credential) { return ajax("/u/register_passkey.json", { data: credential, type: "POST", }); - }, + } deletePasskey(id) { return ajax(`/u/delete_passkey/${id}`, { type: "DELETE", }); - }, + } createSecondFactorTotp() { return ajax("/u/create_second_factor_totp.json", { type: "POST", }); - }, + } enableSecondFactorTotp(authToken, name) { return ajax("/u/enable_second_factor_totp.json", { @@ -596,13 +601,13 @@ const User = RestModel.extend({ }, type: "POST", }); - }, + } disableAllSecondFactors() { return ajax("/u/disable_second_factor.json", { type: "PUT", }); - }, + } updateSecondFactor(id, name, disable, targetMethod) { return ajax("/u/second_factor.json", { @@ -614,7 +619,7 @@ const User = RestModel.extend({ }, type: "PUT", }); - }, + } updateSecurityKey(id, name, disable) { return ajax("/u/security_key.json", { @@ -625,7 +630,7 @@ const User = RestModel.extend({ }, type: "PUT", }); - }, + } toggleSecondFactor(authToken, authMethod, targetMethod, enable) { return ajax("/u/second_factor.json", { @@ -637,20 +642,20 @@ const User = RestModel.extend({ }, type: "PUT", }); - }, + } generateSecondFactorCodes() { return ajax("/u/second_factors_backup.json", { type: "PUT", }); - }, + } revokeAssociatedAccount(providerName) { return ajax(userPath(`${this.username}/preferences/revoke-account`), { data: { provider_name: providerName }, type: "POST", }); - }, + } async loadUserAction(id) { const result = await ajax(`/user_actions/${id}.json`); @@ -673,16 +678,14 @@ const User = RestModel.extend({ const action = UserAction.collapseStream([UserAction.create(ua)]); this.stream.set("itemsLoaded", this.stream.get("itemsLoaded") + 1); this.stream.get("content").insertAt(0, action[0]); - }, + } inAllStream(ua) { return ( ua.action_type === UserAction.TYPES.posts || ua.action_type === UserAction.TYPES.topics ); - }, - - numGroupsToDisplay: 2, + } @discourseComputed("groups.[]") filteredGroups() { @@ -691,15 +694,13 @@ const User = RestModel.extend({ return groups.filter((group) => { return !group.automatic || group.name === "moderators"; }); - }, - - groupsWithMessages: filterBy("groups", "has_messages", true), + } @discourseComputed("filteredGroups", "numGroupsToDisplay") displayGroups(filteredGroups, numGroupsToDisplay) { const groups = filteredGroups.slice(0, numGroupsToDisplay); return groups.length === 0 ? null : groups; - }, + } // NOTE: This only includes groups *visible* to the user via the serializer, // so be wary when using this. @@ -713,7 +714,7 @@ const User = RestModel.extend({ groupIds.includes(0) || this.groups.mapBy("id").some((groupId) => groupIds.includes(groupId)) ); - }, + } // The user's stat count, excluding PMs. @discourseComputed("statsExcludingPms.@each.count") @@ -728,7 +729,7 @@ const User = RestModel.extend({ } }); return count; - }, + } // The user's stats, excluding PMs. @discourseComputed("stats.@each.isPM") @@ -737,7 +738,7 @@ const User = RestModel.extend({ return []; } return this.stats.rejectBy("isPM"); - }, + } findDetails(options) { const user = this; @@ -803,7 +804,7 @@ const User = RestModel.extend({ user.setProperties(json.user); return user; }); - }, + } findStaffInfo() { if (!User.currentProp("staff")) { @@ -814,21 +815,21 @@ const User = RestModel.extend({ this.setProperties(info); } ); - }, + } pickAvatar(upload_id, type) { return ajax(userPath(`${this.username_lower}/preferences/avatar/pick`), { type: "PUT", data: { upload_id, type }, }); - }, + } selectAvatar(avatarUrl) { return ajax(userPath(`${this.username_lower}/preferences/avatar/select`), { type: "PUT", data: { url: avatarUrl }, }); - }, + } isAllowedToUploadAFile(type) { const settingName = type === "image" ? "embedded_media" : "attachments"; @@ -838,21 +839,21 @@ const User = RestModel.extend({ this.trust_level > 0 || this.siteSettings[`newuser_max_${settingName}`] > 0 ); - }, + } createInvite(email, group_ids, custom_message) { return ajax("/invites", { type: "POST", data: { email, group_ids, custom_message }, }); - }, + } generateInviteLink(email, group_ids, topic_id) { return ajax("/invites", { type: "POST", data: { email, skip_email: true, group_ids, topic_id }, }); - }, + } @dependentKeyCompat get mutedCategories() { @@ -866,13 +867,14 @@ const User = RestModel.extend({ } return Category.findByIds(this.get("muted_category_ids")); - }, + } + set mutedCategories(categories) { this.set( "muted_category_ids", categories.map((c) => c.id) ); - }, + } @dependentKeyCompat get regularCategories() { @@ -886,13 +888,14 @@ const User = RestModel.extend({ } return Category.findByIds(this.get("regular_category_ids")); - }, + } + set regularCategories(categories) { this.set( "regular_category_ids", categories.map((c) => c.id) ); - }, + } @dependentKeyCompat get trackedCategories() { @@ -906,13 +909,14 @@ const User = RestModel.extend({ } return Category.findByIds(this.get("tracked_category_ids")); - }, + } + set trackedCategories(categories) { this.set( "tracked_category_ids", categories.map((c) => c.id) ); - }, + } @dependentKeyCompat get watchedCategories() { @@ -926,13 +930,14 @@ const User = RestModel.extend({ } return Category.findByIds(this.get("watched_category_ids")); - }, + } + set watchedCategories(categories) { this.set( "watched_category_ids", categories.map((c) => c.id) ); - }, + } @dependentKeyCompat get watchedFirstPostCategories() { @@ -946,28 +951,29 @@ const User = RestModel.extend({ } return Category.findByIds(this.get("watched_first_post_category_ids")); - }, + } + set watchedFirstPostCategories(categories) { this.set( "watched_first_post_category_ids", categories.map((c) => c.id) ); - }, + } @discourseComputed("can_delete_account") canDeleteAccount(canDeleteAccount) { return !this.siteSettings.enable_discourse_connect && canDeleteAccount; - }, + } @dependentKeyCompat get sidebarLinkToFilteredList() { return this.get("user_option.sidebar_link_to_filtered_list"); - }, + } @dependentKeyCompat get sidebarShowCountOfNewItems() { return this.get("user_option.sidebar_show_count_of_new_items"); - }, + } delete() { if (this.can_delete_account) { @@ -978,7 +984,7 @@ const User = RestModel.extend({ } else { return Promise.reject(I18n.t("user.delete_yourself_not_allowed")); } - }, + } updateNotificationLevel({ level, expiringAt = null, actingUser = null }) { if (!actingUser) { @@ -1001,7 +1007,7 @@ const User = RestModel.extend({ actingUser.ignored_users.addObject(this.username); } }); - }, + } dismissBanner(bannerKey) { this.set("dismissed_banner_key", bannerKey); @@ -1009,7 +1015,7 @@ const User = RestModel.extend({ type: "PUT", data: { dismissed_banner_key: bannerKey }, }); - }, + } checkEmail() { return ajax(userPath(`${this.username_lower}/emails.json`), { @@ -1024,7 +1030,7 @@ const User = RestModel.extend({ }); } }); - }, + } summary() { const store = getOwnerWithFallback(this).lookup("service:store"); @@ -1072,13 +1078,13 @@ const User = RestModel.extend({ return summary; } ); - }, + } canManageGroup(group) { return group.get("automatic") ? false : group.get("can_admin_group") || group.get("is_group_owner"); - }, + } @discourseComputed("groups.@each.title", "badges.[]") availableTitles() { @@ -1105,7 +1111,7 @@ const User = RestModel.extend({ id: title, }; }); - }, + } @discourseComputed("groups.[]") availableFlairs() { @@ -1126,7 +1132,7 @@ const User = RestModel.extend({ } return flairs; - }, + } @discourseComputed("user_option.text_size_seq", "user_option.text_size") currentTextSize(serverSeq, serverSize) { @@ -1137,7 +1143,7 @@ const User = RestModel.extend({ } } return serverSize; - }, + } updateTextSizeCookie(newSize) { if (newSize) { @@ -1149,7 +1155,7 @@ const User = RestModel.extend({ } else { removeCookie(TEXT_SIZE_COOKIE_NAME, { path: "/" }); } - }, + } @discourseComputed("second_factor_enabled", "staff") enforcedSecondFactor(secondFactorEnabled, staff) { @@ -1158,7 +1164,7 @@ const User = RestModel.extend({ !secondFactorEnabled && (enforce === "all" || (enforce === "staff" && staff)) ); - }, + } resolvedTimezone() { deprecated( @@ -1171,7 +1177,7 @@ const User = RestModel.extend({ ); return this.user_option.timezone; - }, + } calculateMutedIds(notificationLevel, id, type) { const muted_ids = this.get(type); @@ -1180,14 +1186,14 @@ const User = RestModel.extend({ } else { return muted_ids.filter((existing_id) => existing_id !== id); } - }, + } setPrimaryGroup(primaryGroupId) { return ajax(`/admin/users/${this.id}/primary_group`, { type: "PUT", data: { primary_group_id: primaryGroupId }, }); - }, + } enterDoNotDisturbFor(duration) { return ajax({ @@ -1197,7 +1203,7 @@ const User = RestModel.extend({ }).then((response) => { return this.updateDoNotDisturbStatus(response.ends_at); }); - }, + } leaveDoNotDisturb() { return ajax({ @@ -1206,29 +1212,29 @@ const User = RestModel.extend({ }).then(() => { this.updateDoNotDisturbStatus(null); }); - }, + } updateDoNotDisturbStatus(ends_at) { this.set("do_not_disturb_until", ends_at); this.appEvents.trigger("do-not-disturb:changed", this.do_not_disturb_until); - }, + } updateDraftProperties(properties) { this.setProperties(properties); this.appEvents.trigger("user-drafts:changed"); - }, + } updateReviewableCount(count) { this.set("reviewable_count", count); this.appEvents.trigger("user-reviewable-count:changed", count); - }, + } isInDoNotDisturb() { return ( this.do_not_disturb_until && new Date(this.do_not_disturb_until) >= new Date() ); - }, + } @discourseComputed( "tracked_tags.[]", @@ -1237,8 +1243,8 @@ const User = RestModel.extend({ ) trackedTags(trackedTags, watchedTags, watchingFirstPostTags) { return [...trackedTags, ...watchedTags, ...watchingFirstPostTags]; - }, -}); + } +} User.reopenClass(Singleton, { // Find a `User` for a given username. @@ -1269,7 +1275,7 @@ User.reopenClass(Singleton, { const store = getOwnerWithFallback(this).lookup("service:store"); const currentUser = store.createRecord("user", userJson); - currentUser.trackStatus(); + currentUser.statusManager.trackStatus(); return currentUser; } @@ -1379,13 +1385,21 @@ User.reopenClass(Singleton, { }); // user status tracking -User.reopen(Evented, { - _subscribersCount: 0, - _clearStatusTimerId: null, +class UserStatusManager { + @service appEvents; + + user; + _subscribersCount = 0; + _clearStatusTimerId = null; + + constructor(user) { + this.user = user; + setOwner(this, getOwner(user)); + } // always call stopTrackingStatus() when done with a user trackStatus() { - if (!this.id && !isTesting()) { + if (!this.user.id && !isTesting()) { // eslint-disable-next-line no-console console.warn( "It's impossible to track user status on a user model that doesn't have id. This user model won't be receiving live user status updates." @@ -1393,17 +1407,17 @@ User.reopen(Evented, { } if (this._subscribersCount === 0) { - this.addObserver("status", this, "_statusChanged"); + this.user.addObserver("status", this, "_statusChanged"); this.appEvents.on("user-status:changed", this, this._updateStatus); - if (this.status && this.status.ends_at) { - this._scheduleStatusClearing(this.status.ends_at); + if (this.user.status?.ends_at) { + this._scheduleStatusClearing(this.user.status.ends_at); } } this._subscribersCount++; - }, + } stopTrackingStatus() { if (this._subscribersCount === 0) { @@ -1412,28 +1426,28 @@ User.reopen(Evented, { if (this._subscribersCount === 1) { // the last subscriber is unsubscribing - this.removeObserver("status", this, "_statusChanged"); + this.user.removeObserver("status", this, "_statusChanged"); this.appEvents.off("user-status:changed", this, this._updateStatus); this._unscheduleStatusClearing(); } this._subscribersCount--; - }, + } isTrackingStatus() { return this._subscribersCount > 0; - }, + } - _statusChanged(sender, key) { - this.trigger("status-changed"); + _statusChanged() { + this.user.trigger("status-changed"); - const status = this.get(key); + const status = this.user.status; if (status && status.ends_at) { this._scheduleStatusClearing(status.ends_at); } else { this._unscheduleStatusClearing(); } - }, + } _scheduleStatusClearing(endsAt) { if (isTesting()) { @@ -1451,23 +1465,23 @@ User.reopen(Evented, { "_autoClearStatus", remaining ); - }, + } _unscheduleStatusClearing() { cancel(this._clearStatusTimerId); this._clearStatusTimerId = null; - }, + } _autoClearStatus() { - this.set("status", null); - }, + this.user.set("status", null); + } _updateStatus(statuses) { - if (statuses.hasOwnProperty(this.id)) { - this.set("status", statuses[this.id]); + if (statuses.hasOwnProperty(this.user.id)) { + this.user.set("status", statuses[this.user.id]); } - }, -}); + } +} if (typeof Discourse !== "undefined") { let warned = false; @@ -1485,5 +1499,3 @@ if (typeof Discourse !== "undefined") { }, }); } - -export default User; diff --git a/app/assets/javascripts/discourse/app/routes/user.js b/app/assets/javascripts/discourse/app/routes/user.js index a5ee9627ed0..3bd9148b0b5 100644 --- a/app/assets/javascripts/discourse/app/routes/user.js +++ b/app/assets/javascripts/discourse/app/routes/user.js @@ -36,7 +36,7 @@ export default DiscourseRoute.extend({ return user .findDetails() .then(() => user.findStaffInfo()) - .then(() => user.trackStatus()) + .then(() => user.statusManager.trackStatus()) .catch(() => this.router.replaceWith("/404")); }, @@ -76,7 +76,7 @@ export default DiscourseRoute.extend({ `/u/${user.username_lower}/counters`, this.onUserCountersMessage ); - user.stopTrackingStatus(); + user.statusManager.stopTrackingStatus(); // Remove the search context this.searchService.searchContext = null; diff --git a/app/assets/javascripts/discourse/app/widgets/post-cooked.js b/app/assets/javascripts/discourse/app/widgets/post-cooked.js index 1b49496f6b9..f367c8def4b 100644 --- a/app/assets/javascripts/discourse/app/widgets/post-cooked.js +++ b/app/assets/javascripts/discourse/app/widgets/post-cooked.js @@ -407,14 +407,14 @@ export default class PostCooked { _trackMentionedUsersStatus() { this._post()?.mentioned_users?.forEach((user) => { - user.trackStatus?.(); + user.statusManager?.trackStatus?.(); user.on?.("status-changed", this, "_rerenderUserStatusOnMentions"); }); } _stopTrackingMentionedUsersStatus() { this._post()?.mentioned_users?.forEach((user) => { - user.stopTrackingStatus?.(); + user.statusManager?.stopTrackingStatus?.(); user.off?.("status-changed", this, "_rerenderUserStatusOnMentions"); }); } diff --git a/app/assets/javascripts/discourse/app/widgets/poster-name.js b/app/assets/javascripts/discourse/app/widgets/poster-name.js index a50ba850349..cfea8a2efae 100644 --- a/app/assets/javascripts/discourse/app/widgets/poster-name.js +++ b/app/assets/javascripts/discourse/app/widgets/poster-name.js @@ -61,7 +61,7 @@ export default createWidget("poster-name", { didRenderWidget() { if (this.attrs.user) { - this.attrs.user.trackStatus(); + this.attrs.user.statusManager.trackStatus(); this.attrs.user.on("status-changed", this, "scheduleRerender"); } }, @@ -69,7 +69,7 @@ export default createWidget("poster-name", { willRerenderWidget() { if (this.attrs.user) { this.attrs.user.off("status-changed", this, "scheduleRerender"); - this.attrs.user.stopTrackingStatus(); + this.attrs.user.statusManager.stopTrackingStatus(); } }, diff --git a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js index aeb00a200b0..f6fc00a081f 100644 --- a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js +++ b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js @@ -344,7 +344,7 @@ export function acceptance(name, optionsOrCallback) { updateCurrentUser(userChanges); } - User.current().trackStatus(); + User.current().statusManager.trackStatus(); } if (settingChanges) { @@ -372,7 +372,7 @@ export function acceptance(name, optionsOrCallback) { let app = getApplication(); options?.afterEach?.call(this); if (loggedIn) { - User.current().stopTrackingStatus(); + User.current().statusManager.stopTrackingStatus(); } testCleanup(this.container, app); diff --git a/app/assets/javascripts/discourse/tests/unit/models/user-test.js b/app/assets/javascripts/discourse/tests/unit/models/user-test.js index f51e0dd1a28..93acc37e470 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/user-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/user-test.js @@ -120,25 +120,25 @@ module("Unit | Model | user", function (hooks) { test("subsequent calls to trackStatus and stopTrackingStatus increase and decrease subscribers counter", function (assert) { const store = getOwner(this).lookup("service:store"); const user = store.createRecord("user"); - assert.strictEqual(user._subscribersCount, 0); + assert.strictEqual(user.statusManager._subscribersCount, 0); - user.trackStatus(); - assert.strictEqual(user._subscribersCount, 1); + user.statusManager.trackStatus(); + assert.strictEqual(user.statusManager._subscribersCount, 1); - user.trackStatus(); - assert.strictEqual(user._subscribersCount, 2); + user.statusManager.trackStatus(); + assert.strictEqual(user.statusManager._subscribersCount, 2); - user.stopTrackingStatus(); - assert.strictEqual(user._subscribersCount, 1); + user.statusManager.stopTrackingStatus(); + assert.strictEqual(user.statusManager._subscribersCount, 1); - user.stopTrackingStatus(); - assert.strictEqual(user._subscribersCount, 0); + user.statusManager.stopTrackingStatus(); + assert.strictEqual(user.statusManager._subscribersCount, 0); }); test("attempt to stop tracking status if status wasn't tracked doesn't throw", function (assert) { const store = getOwner(this).lookup("service:store"); const user = store.createRecord("user"); - user.stopTrackingStatus(); + user.statusManager.stopTrackingStatus(); assert.ok(true); }); @@ -160,8 +160,8 @@ module("Unit | Model | user", function (hooks) { const appEvents = user1.appEvents; try { - user1.trackStatus(); - user2.trackStatus(); + user1.statusManager.trackStatus(); + user2.statusManager.trackStatus(); assert.strictEqual(user1.status, status1); assert.strictEqual(user2.status, status2); @@ -173,8 +173,8 @@ module("Unit | Model | user", function (hooks) { assert.strictEqual(user1.status, null); assert.strictEqual(user2.status, null); } finally { - user1.stopTrackingStatus(); - user2.stopTrackingStatus(); + user1.statusManager.stopTrackingStatus(); + user2.statusManager.stopTrackingStatus(); } }); diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.gjs index 8e77a5f8d61..46b4d1dc3a8 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.gjs @@ -158,12 +158,12 @@ export default class ChatChannelRow extends Component { @action startTrackingStatus() { - this.#firstDirectMessageUser?.trackStatus(); + this.#firstDirectMessageUser?.statusManager.trackStatus(); } @action stopTrackingStatus() { - this.#firstDirectMessageUser?.stopTrackingStatus(); + this.#firstDirectMessageUser?.statusManager.stopTrackingStatus(); }