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`
This commit is contained in:
David Taylor 2024-02-13 10:49:18 +00:00 committed by GitHub
parent 9883e6a0c8
commit 061e79297f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 221 additions and 209 deletions

View File

@ -207,7 +207,7 @@ export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
); );
} }
this.setProperties({ user }); this.setProperties({ user });
this.user.trackStatus(); this.user.statusManager.trackStatus();
return user; return user;
}) })
.catch(() => this._close()) .catch(() => this._close())
@ -216,7 +216,7 @@ export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
_close() { _close() {
if (this.user) { if (this.user) {
this.user.stopTrackingStatus(); this.user.statusManager.stopTrackingStatus();
} }
this.setProperties({ this.setProperties({

View File

@ -14,12 +14,12 @@ export default Component.extend({
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
this.user?.trackStatus?.(); this.user?.statusManager?.trackStatus();
}, },
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
this.user?.stopTrackingStatus?.(); this.user?.statusManager?.stopTrackingStatus();
}, },
@discourseComputed("user.username") @discourseComputed("user.username")

View File

@ -1,3 +1,4 @@
import { getOwner, setOwner } from "@ember/application";
import { A } from "@ember/array"; import { A } from "@ember/array";
import EmberObject, { computed, get, getProperties } from "@ember/object"; import EmberObject, { computed, get, getProperties } from "@ember/object";
import { dependentKeyCompat } from "@ember/object/compat"; import { dependentKeyCompat } from "@ember/object/compat";
@ -169,74 +170,83 @@ function userOption(userOptionKey) {
}); });
} }
const User = RestModel.extend({ export default class User extends RestModel.extend(Evented) {
appEvents: service(), @service appEvents;
userTips: service(), @service userTips;
mailing_list_mode: userOption("mailing_list_mode"), @userOption("mailing_list_mode") mailing_list_mode;
external_links_in_new_tab: userOption("external_links_in_new_tab"), @userOption("external_links_in_new_tab") external_links_in_new_tab;
enable_quoting: userOption("enable_quoting"), @userOption("enable_quoting") enable_quoting;
dynamic_favicon: userOption("dynamic_favicon"), @userOption("dynamic_favicon") dynamic_favicon;
automatically_unpin_topics: userOption("automatically_unpin_topics"), @userOption("automatically_unpin_topics") automatically_unpin_topics;
likes_notifications_disabled: userOption("likes_notifications_disabled"), @userOption("likes_notifications_disabled") likes_notifications_disabled;
hide_profile_and_presence: userOption("hide_profile_and_presence"), @userOption("hide_profile_and_presence") hide_profile_and_presence;
title_count_mode: userOption("title_count_mode"), @userOption("title_count_mode") title_count_mode;
enable_defer: userOption("enable_defer"), @userOption("enable_defer") enable_defer;
timezone: userOption("timezone"), @userOption("timezone") timezone;
skip_new_user_tips: userOption("skip_new_user_tips"), @userOption("skip_new_user_tips") skip_new_user_tips;
default_calendar: userOption("default_calendar"), @userOption("default_calendar") default_calendar;
bookmark_auto_delete_preference: userOption( @userOption("bookmark_auto_delete_preference")
"bookmark_auto_delete_preference" bookmark_auto_delete_preference;
), @userOption("seen_popups") seen_popups;
seen_popups: userOption("seen_popups"), @userOption("should_be_redirected_to_top") should_be_redirected_to_top;
should_be_redirected_to_top: userOption("should_be_redirected_to_top"), @userOption("redirected_to_top") redirected_to_top;
redirected_to_top: userOption("redirected_to_top"), @userOption("treat_as_new_topic_start_date") treat_as_new_topic_start_date;
treat_as_new_topic_start_date: userOption("treat_as_new_topic_start_date"),
hasPMs: gt("private_messages_stats.all", 0), @gt("private_messages_stats.all", 0) hasPMs;
hasStartedPMs: gt("private_messages_stats.mine", 0), @gt("private_messages_stats.mine", 0) hasStartedPMs;
hasUnreadPMs: gt("private_messages_stats.unread", 0), @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") @discourseComputed("can_be_deleted", "post_count")
canBeDeleted(canBeDeleted, postCount) { canBeDeleted(canBeDeleted, postCount) {
const maxPostCount = this.siteSettings.delete_all_posts_max; const maxPostCount = this.siteSettings.delete_all_posts_max;
return canBeDeleted && postCount <= maxPostCount; return canBeDeleted && postCount <= maxPostCount;
}, }
@discourseComputed() @discourseComputed()
stream() { stream() {
return UserStream.create({ user: this }); return UserStream.create({ user: this });
}, }
@discourseComputed() @discourseComputed()
bookmarks() { bookmarks() {
return Bookmark.create({ user: this }); return Bookmark.create({ user: this });
}, }
@discourseComputed() @discourseComputed()
postsStream() { postsStream() {
return UserPostsStream.create({ user: this }); return UserPostsStream.create({ user: this });
}, }
@discourseComputed() @discourseComputed()
userDraftsStream() { userDraftsStream() {
return UserDraftsStream.create({ user: this }); return UserDraftsStream.create({ user: this });
}, }
staff: computed("admin", "moderator", { @computed("admin", "moderator")
get() { get staff() {
return this.admin || this.moderator; return this.admin || this.moderator;
}, }
// prevents staff property to be overridden // prevents staff property to be overridden
set() { set staff(value) {}
return this.admin || this.moderator;
},
}),
destroySession() { destroySession() {
return ajax(`/session/${this.username}`, { type: "DELETE" }); return ajax(`/session/${this.username}`, { type: "DELETE" });
}, }
@discourseComputed("username_lower") @discourseComputed("username_lower")
searchContext(username) { searchContext(username) {
@ -245,7 +255,7 @@ const User = RestModel.extend({
id: username, id: username,
user: this, user: this,
}; };
}, }
@discourseComputed("username", "name") @discourseComputed("username", "name")
displayName(username, name) { displayName(username, name) {
@ -253,7 +263,7 @@ const User = RestModel.extend({
return name; return name;
} }
return username; return username;
}, }
@discourseComputed("profile_background_upload_url") @discourseComputed("profile_background_upload_url")
profileBackgroundUrl(bgUrl) { profileBackgroundUrl(bgUrl) {
@ -261,13 +271,13 @@ const User = RestModel.extend({
return htmlSafe(""); return htmlSafe("");
} }
return htmlSafe("background-image: url(" + getURLWithCDN(bgUrl) + ")"); return htmlSafe("background-image: url(" + getURLWithCDN(bgUrl) + ")");
}, }
@discourseComputed() @discourseComputed()
path() { path() {
// no need to observe, requires a hard refresh to update // no need to observe, requires a hard refresh to update
return userPath(this.username_lower); return userPath(this.username_lower);
}, }
@discourseComputed() @discourseComputed()
userApiKeys() { userApiKeys() {
@ -287,7 +297,7 @@ const User = RestModel.extend({
return obj; return obj;
}); });
} }
}, }
revokeApiKey(key) { revokeApiKey(key) {
return ajax("/user-api-key/revoke", { return ajax("/user-api-key/revoke", {
@ -296,7 +306,7 @@ const User = RestModel.extend({
}).then(() => { }).then(() => {
key.set("revoked", true); key.set("revoked", true);
}); });
}, }
undoRevokeApiKey(key) { undoRevokeApiKey(key) {
return ajax("/user-api-key/undo-revoke", { return ajax("/user-api-key/undo-revoke", {
@ -305,7 +315,7 @@ const User = RestModel.extend({
}).then(() => { }).then(() => {
key.set("revoked", false); key.set("revoked", false);
}); });
}, }
pmPath(topic) { pmPath(topic) {
const userId = this.id; const userId = this.id;
@ -323,35 +333,33 @@ const User = RestModel.extend({
return userPath(`${username}/messages/group/${groups[0].name}`); return userPath(`${username}/messages/group/${groups[0].name}`);
} }
} }
}, }
adminPath: url("id", "username_lower", "/admin/users/%@1/%@2"),
@discourseComputed() @discourseComputed()
mutedTopicsPath() { mutedTopicsPath() {
return defaultHomepage() === "latest" return defaultHomepage() === "latest"
? getURL("/?state=muted") ? getURL("/?state=muted")
: getURL("/latest?state=muted"); : getURL("/latest?state=muted");
}, }
@discourseComputed() @discourseComputed()
watchingTopicsPath() { watchingTopicsPath() {
return defaultHomepage() === "latest" return defaultHomepage() === "latest"
? getURL("/?state=watching") ? getURL("/?state=watching")
: getURL("/latest?state=watching"); : getURL("/latest?state=watching");
}, }
@discourseComputed() @discourseComputed()
trackingTopicsPath() { trackingTopicsPath() {
return defaultHomepage() === "latest" return defaultHomepage() === "latest"
? getURL("/?state=tracking") ? getURL("/?state=tracking")
: getURL("/latest?state=tracking"); : getURL("/latest?state=tracking");
}, }
@discourseComputed("username") @discourseComputed("username")
username_lower(username) { username_lower(username) {
return username.toLowerCase(); return username.toLowerCase();
}, }
@discourseComputed("trust_level") @discourseComputed("trust_level")
trustLevel(trustLevel) { trustLevel(trustLevel) {
@ -359,36 +367,37 @@ const User = RestModel.extend({
"id", "id",
parseInt(trustLevel, 10) 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") @discourseComputed("previous_visit_at")
previousVisitAt(previous_visit_at) { previousVisitAt(previous_visit_at) {
return new Date(previous_visit_at); return new Date(previous_visit_at);
}, }
@discourseComputed("suspended_till") @discourseComputed("suspended_till")
suspended(suspendedTill) { suspended(suspendedTill) {
return suspendedTill && moment(suspendedTill).isAfter(); return suspendedTill && moment(suspendedTill).isAfter();
}, }
@discourseComputed("suspended_till") @discourseComputed("suspended_till")
suspendedForever: isForever, suspendedForever(suspendedTill) {
return isForever(suspendedTill);
}
@discourseComputed("silenced_till") @discourseComputed("silenced_till")
silencedForever: isForever, silencedForever(silencedTill) {
isForever(silencedTill);
}
@discourseComputed("suspended_till") @discourseComputed("suspended_till")
suspendedTillDate: longDate, suspendedTillDate(silencedTill) {
return longDate(silencedTill);
}
@discourseComputed("silenced_till") @discourseComputed("silenced_till")
silencedTillDate: longDate, silencedTillDate(silencedTill) {
return longDate(silencedTill);
sidebarCategoryIds: alias("sidebar_category_ids"), }
@discourseComputed("sidebar_tags.[]") @discourseComputed("sidebar_tags.[]")
sidebarTags(sidebarTags) { sidebarTags(sidebarTags) {
@ -399,18 +408,14 @@ const User = RestModel.extend({
return sidebarTags.sort((a, b) => { return sidebarTags.sort((a, b) => {
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}); });
}, }
sidebarSections: alias("sidebar_sections"),
sidebarTagNames: mapBy("sidebarTags", "name"),
changeUsername(new_username) { changeUsername(new_username) {
return ajax(userPath(`${this.username_lower}/preferences/username`), { return ajax(userPath(`${this.username_lower}/preferences/username`), {
type: "PUT", type: "PUT",
data: { new_username }, data: { new_username },
}); });
}, }
addEmail(email) { addEmail(email) {
return ajax(userPath(`${this.username_lower}/preferences/email`), { return ajax(userPath(`${this.username_lower}/preferences/email`), {
@ -423,7 +428,7 @@ const User = RestModel.extend({
this.unconfirmed_emails.pushObject(email); this.unconfirmed_emails.pushObject(email);
}); });
}, }
changeEmail(email) { changeEmail(email) {
return ajax(userPath(`${this.username_lower}/preferences/email`), { return ajax(userPath(`${this.username_lower}/preferences/email`), {
@ -436,7 +441,7 @@ const User = RestModel.extend({
this.unconfirmed_emails.pushObject(email); this.unconfirmed_emails.pushObject(email);
}); });
}, }
save(fields) { save(fields) {
const data = this.getProperties( const data = this.getProperties(
@ -506,7 +511,7 @@ const User = RestModel.extend({
.finally(() => { .finally(() => {
this.set("isSaving", false); this.set("isSaving", false);
}); });
}, }
setPrimaryEmail(email) { setPrimaryEmail(email) {
return ajax(userPath(`${this.username}/preferences/primary-email.json`), { return ajax(userPath(`${this.username}/preferences/primary-email.json`), {
@ -517,7 +522,7 @@ const User = RestModel.extend({
this.secondary_emails.pushObject(this.email); this.secondary_emails.pushObject(this.email);
this.set("email", email); this.set("email", email);
}); });
}, }
destroyEmail(email) { destroyEmail(email) {
return ajax(userPath(`${this.username}/preferences/email.json`), { return ajax(userPath(`${this.username}/preferences/email.json`), {
@ -530,7 +535,7 @@ const User = RestModel.extend({
this.secondary_emails.removeObject(email); this.secondary_emails.removeObject(email);
} }
}); });
}, }
changePassword() { changePassword() {
return ajax("/session/forgot_password", { return ajax("/session/forgot_password", {
@ -538,55 +543,55 @@ const User = RestModel.extend({
data: { login: this.email || this.username }, data: { login: this.email || this.username },
type: "POST", type: "POST",
}); });
}, }
loadSecondFactorCodes() { loadSecondFactorCodes() {
return ajax("/u/second_factors.json", { return ajax("/u/second_factors.json", {
type: "POST", type: "POST",
}); });
}, }
requestSecurityKeyChallenge() { requestSecurityKeyChallenge() {
return ajax("/u/create_second_factor_security_key.json", { return ajax("/u/create_second_factor_security_key.json", {
type: "POST", type: "POST",
}); });
}, }
registerSecurityKey(credential) { registerSecurityKey(credential) {
return ajax("/u/register_second_factor_security_key.json", { return ajax("/u/register_second_factor_security_key.json", {
data: credential, data: credential,
type: "POST", type: "POST",
}); });
}, }
trustedSession() { trustedSession() {
return ajax("/u/trusted-session.json"); return ajax("/u/trusted-session.json");
}, }
createPasskey() { createPasskey() {
return ajax("/u/create_passkey.json", { return ajax("/u/create_passkey.json", {
type: "POST", type: "POST",
}); });
}, }
registerPasskey(credential) { registerPasskey(credential) {
return ajax("/u/register_passkey.json", { return ajax("/u/register_passkey.json", {
data: credential, data: credential,
type: "POST", type: "POST",
}); });
}, }
deletePasskey(id) { deletePasskey(id) {
return ajax(`/u/delete_passkey/${id}`, { return ajax(`/u/delete_passkey/${id}`, {
type: "DELETE", type: "DELETE",
}); });
}, }
createSecondFactorTotp() { createSecondFactorTotp() {
return ajax("/u/create_second_factor_totp.json", { return ajax("/u/create_second_factor_totp.json", {
type: "POST", type: "POST",
}); });
}, }
enableSecondFactorTotp(authToken, name) { enableSecondFactorTotp(authToken, name) {
return ajax("/u/enable_second_factor_totp.json", { return ajax("/u/enable_second_factor_totp.json", {
@ -596,13 +601,13 @@ const User = RestModel.extend({
}, },
type: "POST", type: "POST",
}); });
}, }
disableAllSecondFactors() { disableAllSecondFactors() {
return ajax("/u/disable_second_factor.json", { return ajax("/u/disable_second_factor.json", {
type: "PUT", type: "PUT",
}); });
}, }
updateSecondFactor(id, name, disable, targetMethod) { updateSecondFactor(id, name, disable, targetMethod) {
return ajax("/u/second_factor.json", { return ajax("/u/second_factor.json", {
@ -614,7 +619,7 @@ const User = RestModel.extend({
}, },
type: "PUT", type: "PUT",
}); });
}, }
updateSecurityKey(id, name, disable) { updateSecurityKey(id, name, disable) {
return ajax("/u/security_key.json", { return ajax("/u/security_key.json", {
@ -625,7 +630,7 @@ const User = RestModel.extend({
}, },
type: "PUT", type: "PUT",
}); });
}, }
toggleSecondFactor(authToken, authMethod, targetMethod, enable) { toggleSecondFactor(authToken, authMethod, targetMethod, enable) {
return ajax("/u/second_factor.json", { return ajax("/u/second_factor.json", {
@ -637,20 +642,20 @@ const User = RestModel.extend({
}, },
type: "PUT", type: "PUT",
}); });
}, }
generateSecondFactorCodes() { generateSecondFactorCodes() {
return ajax("/u/second_factors_backup.json", { return ajax("/u/second_factors_backup.json", {
type: "PUT", type: "PUT",
}); });
}, }
revokeAssociatedAccount(providerName) { revokeAssociatedAccount(providerName) {
return ajax(userPath(`${this.username}/preferences/revoke-account`), { return ajax(userPath(`${this.username}/preferences/revoke-account`), {
data: { provider_name: providerName }, data: { provider_name: providerName },
type: "POST", type: "POST",
}); });
}, }
async loadUserAction(id) { async loadUserAction(id) {
const result = await ajax(`/user_actions/${id}.json`); const result = await ajax(`/user_actions/${id}.json`);
@ -673,16 +678,14 @@ const User = RestModel.extend({
const action = UserAction.collapseStream([UserAction.create(ua)]); const action = UserAction.collapseStream([UserAction.create(ua)]);
this.stream.set("itemsLoaded", this.stream.get("itemsLoaded") + 1); this.stream.set("itemsLoaded", this.stream.get("itemsLoaded") + 1);
this.stream.get("content").insertAt(0, action[0]); this.stream.get("content").insertAt(0, action[0]);
}, }
inAllStream(ua) { inAllStream(ua) {
return ( return (
ua.action_type === UserAction.TYPES.posts || ua.action_type === UserAction.TYPES.posts ||
ua.action_type === UserAction.TYPES.topics ua.action_type === UserAction.TYPES.topics
); );
}, }
numGroupsToDisplay: 2,
@discourseComputed("groups.[]") @discourseComputed("groups.[]")
filteredGroups() { filteredGroups() {
@ -691,15 +694,13 @@ const User = RestModel.extend({
return groups.filter((group) => { return groups.filter((group) => {
return !group.automatic || group.name === "moderators"; return !group.automatic || group.name === "moderators";
}); });
}, }
groupsWithMessages: filterBy("groups", "has_messages", true),
@discourseComputed("filteredGroups", "numGroupsToDisplay") @discourseComputed("filteredGroups", "numGroupsToDisplay")
displayGroups(filteredGroups, numGroupsToDisplay) { displayGroups(filteredGroups, numGroupsToDisplay) {
const groups = filteredGroups.slice(0, numGroupsToDisplay); const groups = filteredGroups.slice(0, numGroupsToDisplay);
return groups.length === 0 ? null : groups; return groups.length === 0 ? null : groups;
}, }
// NOTE: This only includes groups *visible* to the user via the serializer, // NOTE: This only includes groups *visible* to the user via the serializer,
// so be wary when using this. // so be wary when using this.
@ -713,7 +714,7 @@ const User = RestModel.extend({
groupIds.includes(0) || groupIds.includes(0) ||
this.groups.mapBy("id").some((groupId) => groupIds.includes(groupId)) this.groups.mapBy("id").some((groupId) => groupIds.includes(groupId))
); );
}, }
// The user's stat count, excluding PMs. // The user's stat count, excluding PMs.
@discourseComputed("statsExcludingPms.@each.count") @discourseComputed("statsExcludingPms.@each.count")
@ -728,7 +729,7 @@ const User = RestModel.extend({
} }
}); });
return count; return count;
}, }
// The user's stats, excluding PMs. // The user's stats, excluding PMs.
@discourseComputed("stats.@each.isPM") @discourseComputed("stats.@each.isPM")
@ -737,7 +738,7 @@ const User = RestModel.extend({
return []; return [];
} }
return this.stats.rejectBy("isPM"); return this.stats.rejectBy("isPM");
}, }
findDetails(options) { findDetails(options) {
const user = this; const user = this;
@ -803,7 +804,7 @@ const User = RestModel.extend({
user.setProperties(json.user); user.setProperties(json.user);
return user; return user;
}); });
}, }
findStaffInfo() { findStaffInfo() {
if (!User.currentProp("staff")) { if (!User.currentProp("staff")) {
@ -814,21 +815,21 @@ const User = RestModel.extend({
this.setProperties(info); this.setProperties(info);
} }
); );
}, }
pickAvatar(upload_id, type) { pickAvatar(upload_id, type) {
return ajax(userPath(`${this.username_lower}/preferences/avatar/pick`), { return ajax(userPath(`${this.username_lower}/preferences/avatar/pick`), {
type: "PUT", type: "PUT",
data: { upload_id, type }, data: { upload_id, type },
}); });
}, }
selectAvatar(avatarUrl) { selectAvatar(avatarUrl) {
return ajax(userPath(`${this.username_lower}/preferences/avatar/select`), { return ajax(userPath(`${this.username_lower}/preferences/avatar/select`), {
type: "PUT", type: "PUT",
data: { url: avatarUrl }, data: { url: avatarUrl },
}); });
}, }
isAllowedToUploadAFile(type) { isAllowedToUploadAFile(type) {
const settingName = type === "image" ? "embedded_media" : "attachments"; const settingName = type === "image" ? "embedded_media" : "attachments";
@ -838,21 +839,21 @@ const User = RestModel.extend({
this.trust_level > 0 || this.trust_level > 0 ||
this.siteSettings[`newuser_max_${settingName}`] > 0 this.siteSettings[`newuser_max_${settingName}`] > 0
); );
}, }
createInvite(email, group_ids, custom_message) { createInvite(email, group_ids, custom_message) {
return ajax("/invites", { return ajax("/invites", {
type: "POST", type: "POST",
data: { email, group_ids, custom_message }, data: { email, group_ids, custom_message },
}); });
}, }
generateInviteLink(email, group_ids, topic_id) { generateInviteLink(email, group_ids, topic_id) {
return ajax("/invites", { return ajax("/invites", {
type: "POST", type: "POST",
data: { email, skip_email: true, group_ids, topic_id }, data: { email, skip_email: true, group_ids, topic_id },
}); });
}, }
@dependentKeyCompat @dependentKeyCompat
get mutedCategories() { get mutedCategories() {
@ -866,13 +867,14 @@ const User = RestModel.extend({
} }
return Category.findByIds(this.get("muted_category_ids")); return Category.findByIds(this.get("muted_category_ids"));
}, }
set mutedCategories(categories) { set mutedCategories(categories) {
this.set( this.set(
"muted_category_ids", "muted_category_ids",
categories.map((c) => c.id) categories.map((c) => c.id)
); );
}, }
@dependentKeyCompat @dependentKeyCompat
get regularCategories() { get regularCategories() {
@ -886,13 +888,14 @@ const User = RestModel.extend({
} }
return Category.findByIds(this.get("regular_category_ids")); return Category.findByIds(this.get("regular_category_ids"));
}, }
set regularCategories(categories) { set regularCategories(categories) {
this.set( this.set(
"regular_category_ids", "regular_category_ids",
categories.map((c) => c.id) categories.map((c) => c.id)
); );
}, }
@dependentKeyCompat @dependentKeyCompat
get trackedCategories() { get trackedCategories() {
@ -906,13 +909,14 @@ const User = RestModel.extend({
} }
return Category.findByIds(this.get("tracked_category_ids")); return Category.findByIds(this.get("tracked_category_ids"));
}, }
set trackedCategories(categories) { set trackedCategories(categories) {
this.set( this.set(
"tracked_category_ids", "tracked_category_ids",
categories.map((c) => c.id) categories.map((c) => c.id)
); );
}, }
@dependentKeyCompat @dependentKeyCompat
get watchedCategories() { get watchedCategories() {
@ -926,13 +930,14 @@ const User = RestModel.extend({
} }
return Category.findByIds(this.get("watched_category_ids")); return Category.findByIds(this.get("watched_category_ids"));
}, }
set watchedCategories(categories) { set watchedCategories(categories) {
this.set( this.set(
"watched_category_ids", "watched_category_ids",
categories.map((c) => c.id) categories.map((c) => c.id)
); );
}, }
@dependentKeyCompat @dependentKeyCompat
get watchedFirstPostCategories() { get watchedFirstPostCategories() {
@ -946,28 +951,29 @@ const User = RestModel.extend({
} }
return Category.findByIds(this.get("watched_first_post_category_ids")); return Category.findByIds(this.get("watched_first_post_category_ids"));
}, }
set watchedFirstPostCategories(categories) { set watchedFirstPostCategories(categories) {
this.set( this.set(
"watched_first_post_category_ids", "watched_first_post_category_ids",
categories.map((c) => c.id) categories.map((c) => c.id)
); );
}, }
@discourseComputed("can_delete_account") @discourseComputed("can_delete_account")
canDeleteAccount(canDeleteAccount) { canDeleteAccount(canDeleteAccount) {
return !this.siteSettings.enable_discourse_connect && canDeleteAccount; return !this.siteSettings.enable_discourse_connect && canDeleteAccount;
}, }
@dependentKeyCompat @dependentKeyCompat
get sidebarLinkToFilteredList() { get sidebarLinkToFilteredList() {
return this.get("user_option.sidebar_link_to_filtered_list"); return this.get("user_option.sidebar_link_to_filtered_list");
}, }
@dependentKeyCompat @dependentKeyCompat
get sidebarShowCountOfNewItems() { get sidebarShowCountOfNewItems() {
return this.get("user_option.sidebar_show_count_of_new_items"); return this.get("user_option.sidebar_show_count_of_new_items");
}, }
delete() { delete() {
if (this.can_delete_account) { if (this.can_delete_account) {
@ -978,7 +984,7 @@ const User = RestModel.extend({
} else { } else {
return Promise.reject(I18n.t("user.delete_yourself_not_allowed")); return Promise.reject(I18n.t("user.delete_yourself_not_allowed"));
} }
}, }
updateNotificationLevel({ level, expiringAt = null, actingUser = null }) { updateNotificationLevel({ level, expiringAt = null, actingUser = null }) {
if (!actingUser) { if (!actingUser) {
@ -1001,7 +1007,7 @@ const User = RestModel.extend({
actingUser.ignored_users.addObject(this.username); actingUser.ignored_users.addObject(this.username);
} }
}); });
}, }
dismissBanner(bannerKey) { dismissBanner(bannerKey) {
this.set("dismissed_banner_key", bannerKey); this.set("dismissed_banner_key", bannerKey);
@ -1009,7 +1015,7 @@ const User = RestModel.extend({
type: "PUT", type: "PUT",
data: { dismissed_banner_key: bannerKey }, data: { dismissed_banner_key: bannerKey },
}); });
}, }
checkEmail() { checkEmail() {
return ajax(userPath(`${this.username_lower}/emails.json`), { return ajax(userPath(`${this.username_lower}/emails.json`), {
@ -1024,7 +1030,7 @@ const User = RestModel.extend({
}); });
} }
}); });
}, }
summary() { summary() {
const store = getOwnerWithFallback(this).lookup("service:store"); const store = getOwnerWithFallback(this).lookup("service:store");
@ -1072,13 +1078,13 @@ const User = RestModel.extend({
return summary; return summary;
} }
); );
}, }
canManageGroup(group) { canManageGroup(group) {
return group.get("automatic") return group.get("automatic")
? false ? false
: group.get("can_admin_group") || group.get("is_group_owner"); : group.get("can_admin_group") || group.get("is_group_owner");
}, }
@discourseComputed("groups.@each.title", "badges.[]") @discourseComputed("groups.@each.title", "badges.[]")
availableTitles() { availableTitles() {
@ -1105,7 +1111,7 @@ const User = RestModel.extend({
id: title, id: title,
}; };
}); });
}, }
@discourseComputed("groups.[]") @discourseComputed("groups.[]")
availableFlairs() { availableFlairs() {
@ -1126,7 +1132,7 @@ const User = RestModel.extend({
} }
return flairs; return flairs;
}, }
@discourseComputed("user_option.text_size_seq", "user_option.text_size") @discourseComputed("user_option.text_size_seq", "user_option.text_size")
currentTextSize(serverSeq, serverSize) { currentTextSize(serverSeq, serverSize) {
@ -1137,7 +1143,7 @@ const User = RestModel.extend({
} }
} }
return serverSize; return serverSize;
}, }
updateTextSizeCookie(newSize) { updateTextSizeCookie(newSize) {
if (newSize) { if (newSize) {
@ -1149,7 +1155,7 @@ const User = RestModel.extend({
} else { } else {
removeCookie(TEXT_SIZE_COOKIE_NAME, { path: "/" }); removeCookie(TEXT_SIZE_COOKIE_NAME, { path: "/" });
} }
}, }
@discourseComputed("second_factor_enabled", "staff") @discourseComputed("second_factor_enabled", "staff")
enforcedSecondFactor(secondFactorEnabled, staff) { enforcedSecondFactor(secondFactorEnabled, staff) {
@ -1158,7 +1164,7 @@ const User = RestModel.extend({
!secondFactorEnabled && !secondFactorEnabled &&
(enforce === "all" || (enforce === "staff" && staff)) (enforce === "all" || (enforce === "staff" && staff))
); );
}, }
resolvedTimezone() { resolvedTimezone() {
deprecated( deprecated(
@ -1171,7 +1177,7 @@ const User = RestModel.extend({
); );
return this.user_option.timezone; return this.user_option.timezone;
}, }
calculateMutedIds(notificationLevel, id, type) { calculateMutedIds(notificationLevel, id, type) {
const muted_ids = this.get(type); const muted_ids = this.get(type);
@ -1180,14 +1186,14 @@ const User = RestModel.extend({
} else { } else {
return muted_ids.filter((existing_id) => existing_id !== id); return muted_ids.filter((existing_id) => existing_id !== id);
} }
}, }
setPrimaryGroup(primaryGroupId) { setPrimaryGroup(primaryGroupId) {
return ajax(`/admin/users/${this.id}/primary_group`, { return ajax(`/admin/users/${this.id}/primary_group`, {
type: "PUT", type: "PUT",
data: { primary_group_id: primaryGroupId }, data: { primary_group_id: primaryGroupId },
}); });
}, }
enterDoNotDisturbFor(duration) { enterDoNotDisturbFor(duration) {
return ajax({ return ajax({
@ -1197,7 +1203,7 @@ const User = RestModel.extend({
}).then((response) => { }).then((response) => {
return this.updateDoNotDisturbStatus(response.ends_at); return this.updateDoNotDisturbStatus(response.ends_at);
}); });
}, }
leaveDoNotDisturb() { leaveDoNotDisturb() {
return ajax({ return ajax({
@ -1206,29 +1212,29 @@ const User = RestModel.extend({
}).then(() => { }).then(() => {
this.updateDoNotDisturbStatus(null); this.updateDoNotDisturbStatus(null);
}); });
}, }
updateDoNotDisturbStatus(ends_at) { updateDoNotDisturbStatus(ends_at) {
this.set("do_not_disturb_until", ends_at); this.set("do_not_disturb_until", ends_at);
this.appEvents.trigger("do-not-disturb:changed", this.do_not_disturb_until); this.appEvents.trigger("do-not-disturb:changed", this.do_not_disturb_until);
}, }
updateDraftProperties(properties) { updateDraftProperties(properties) {
this.setProperties(properties); this.setProperties(properties);
this.appEvents.trigger("user-drafts:changed"); this.appEvents.trigger("user-drafts:changed");
}, }
updateReviewableCount(count) { updateReviewableCount(count) {
this.set("reviewable_count", count); this.set("reviewable_count", count);
this.appEvents.trigger("user-reviewable-count:changed", count); this.appEvents.trigger("user-reviewable-count:changed", count);
}, }
isInDoNotDisturb() { isInDoNotDisturb() {
return ( return (
this.do_not_disturb_until && this.do_not_disturb_until &&
new Date(this.do_not_disturb_until) >= new Date() new Date(this.do_not_disturb_until) >= new Date()
); );
}, }
@discourseComputed( @discourseComputed(
"tracked_tags.[]", "tracked_tags.[]",
@ -1237,8 +1243,8 @@ const User = RestModel.extend({
) )
trackedTags(trackedTags, watchedTags, watchingFirstPostTags) { trackedTags(trackedTags, watchedTags, watchingFirstPostTags) {
return [...trackedTags, ...watchedTags, ...watchingFirstPostTags]; return [...trackedTags, ...watchedTags, ...watchingFirstPostTags];
}, }
}); }
User.reopenClass(Singleton, { User.reopenClass(Singleton, {
// Find a `User` for a given username. // Find a `User` for a given username.
@ -1269,7 +1275,7 @@ User.reopenClass(Singleton, {
const store = getOwnerWithFallback(this).lookup("service:store"); const store = getOwnerWithFallback(this).lookup("service:store");
const currentUser = store.createRecord("user", userJson); const currentUser = store.createRecord("user", userJson);
currentUser.trackStatus(); currentUser.statusManager.trackStatus();
return currentUser; return currentUser;
} }
@ -1379,13 +1385,21 @@ User.reopenClass(Singleton, {
}); });
// user status tracking // user status tracking
User.reopen(Evented, { class UserStatusManager {
_subscribersCount: 0, @service appEvents;
_clearStatusTimerId: null,
user;
_subscribersCount = 0;
_clearStatusTimerId = null;
constructor(user) {
this.user = user;
setOwner(this, getOwner(user));
}
// always call stopTrackingStatus() when done with a user // always call stopTrackingStatus() when done with a user
trackStatus() { trackStatus() {
if (!this.id && !isTesting()) { if (!this.user.id && !isTesting()) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.warn( 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." "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) { if (this._subscribersCount === 0) {
this.addObserver("status", this, "_statusChanged"); this.user.addObserver("status", this, "_statusChanged");
this.appEvents.on("user-status:changed", this, this._updateStatus); this.appEvents.on("user-status:changed", this, this._updateStatus);
if (this.status && this.status.ends_at) { if (this.user.status?.ends_at) {
this._scheduleStatusClearing(this.status.ends_at); this._scheduleStatusClearing(this.user.status.ends_at);
} }
} }
this._subscribersCount++; this._subscribersCount++;
}, }
stopTrackingStatus() { stopTrackingStatus() {
if (this._subscribersCount === 0) { if (this._subscribersCount === 0) {
@ -1412,28 +1426,28 @@ User.reopen(Evented, {
if (this._subscribersCount === 1) { if (this._subscribersCount === 1) {
// the last subscriber is unsubscribing // 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.appEvents.off("user-status:changed", this, this._updateStatus);
this._unscheduleStatusClearing(); this._unscheduleStatusClearing();
} }
this._subscribersCount--; this._subscribersCount--;
}, }
isTrackingStatus() { isTrackingStatus() {
return this._subscribersCount > 0; return this._subscribersCount > 0;
}, }
_statusChanged(sender, key) { _statusChanged() {
this.trigger("status-changed"); this.user.trigger("status-changed");
const status = this.get(key); const status = this.user.status;
if (status && status.ends_at) { if (status && status.ends_at) {
this._scheduleStatusClearing(status.ends_at); this._scheduleStatusClearing(status.ends_at);
} else { } else {
this._unscheduleStatusClearing(); this._unscheduleStatusClearing();
} }
}, }
_scheduleStatusClearing(endsAt) { _scheduleStatusClearing(endsAt) {
if (isTesting()) { if (isTesting()) {
@ -1451,23 +1465,23 @@ User.reopen(Evented, {
"_autoClearStatus", "_autoClearStatus",
remaining remaining
); );
}, }
_unscheduleStatusClearing() { _unscheduleStatusClearing() {
cancel(this._clearStatusTimerId); cancel(this._clearStatusTimerId);
this._clearStatusTimerId = null; this._clearStatusTimerId = null;
}, }
_autoClearStatus() { _autoClearStatus() {
this.set("status", null); this.user.set("status", null);
}, }
_updateStatus(statuses) { _updateStatus(statuses) {
if (statuses.hasOwnProperty(this.id)) { if (statuses.hasOwnProperty(this.user.id)) {
this.set("status", statuses[this.id]); this.user.set("status", statuses[this.user.id]);
} }
}, }
}); }
if (typeof Discourse !== "undefined") { if (typeof Discourse !== "undefined") {
let warned = false; let warned = false;
@ -1485,5 +1499,3 @@ if (typeof Discourse !== "undefined") {
}, },
}); });
} }
export default User;

View File

@ -36,7 +36,7 @@ export default DiscourseRoute.extend({
return user return user
.findDetails() .findDetails()
.then(() => user.findStaffInfo()) .then(() => user.findStaffInfo())
.then(() => user.trackStatus()) .then(() => user.statusManager.trackStatus())
.catch(() => this.router.replaceWith("/404")); .catch(() => this.router.replaceWith("/404"));
}, },
@ -76,7 +76,7 @@ export default DiscourseRoute.extend({
`/u/${user.username_lower}/counters`, `/u/${user.username_lower}/counters`,
this.onUserCountersMessage this.onUserCountersMessage
); );
user.stopTrackingStatus(); user.statusManager.stopTrackingStatus();
// Remove the search context // Remove the search context
this.searchService.searchContext = null; this.searchService.searchContext = null;

View File

@ -407,14 +407,14 @@ export default class PostCooked {
_trackMentionedUsersStatus() { _trackMentionedUsersStatus() {
this._post()?.mentioned_users?.forEach((user) => { this._post()?.mentioned_users?.forEach((user) => {
user.trackStatus?.(); user.statusManager?.trackStatus?.();
user.on?.("status-changed", this, "_rerenderUserStatusOnMentions"); user.on?.("status-changed", this, "_rerenderUserStatusOnMentions");
}); });
} }
_stopTrackingMentionedUsersStatus() { _stopTrackingMentionedUsersStatus() {
this._post()?.mentioned_users?.forEach((user) => { this._post()?.mentioned_users?.forEach((user) => {
user.stopTrackingStatus?.(); user.statusManager?.stopTrackingStatus?.();
user.off?.("status-changed", this, "_rerenderUserStatusOnMentions"); user.off?.("status-changed", this, "_rerenderUserStatusOnMentions");
}); });
} }

View File

@ -61,7 +61,7 @@ export default createWidget("poster-name", {
didRenderWidget() { didRenderWidget() {
if (this.attrs.user) { if (this.attrs.user) {
this.attrs.user.trackStatus(); this.attrs.user.statusManager.trackStatus();
this.attrs.user.on("status-changed", this, "scheduleRerender"); this.attrs.user.on("status-changed", this, "scheduleRerender");
} }
}, },
@ -69,7 +69,7 @@ export default createWidget("poster-name", {
willRerenderWidget() { willRerenderWidget() {
if (this.attrs.user) { if (this.attrs.user) {
this.attrs.user.off("status-changed", this, "scheduleRerender"); this.attrs.user.off("status-changed", this, "scheduleRerender");
this.attrs.user.stopTrackingStatus(); this.attrs.user.statusManager.stopTrackingStatus();
} }
}, },

View File

@ -344,7 +344,7 @@ export function acceptance(name, optionsOrCallback) {
updateCurrentUser(userChanges); updateCurrentUser(userChanges);
} }
User.current().trackStatus(); User.current().statusManager.trackStatus();
} }
if (settingChanges) { if (settingChanges) {
@ -372,7 +372,7 @@ export function acceptance(name, optionsOrCallback) {
let app = getApplication(); let app = getApplication();
options?.afterEach?.call(this); options?.afterEach?.call(this);
if (loggedIn) { if (loggedIn) {
User.current().stopTrackingStatus(); User.current().statusManager.stopTrackingStatus();
} }
testCleanup(this.container, app); testCleanup(this.container, app);

View File

@ -120,25 +120,25 @@ module("Unit | Model | user", function (hooks) {
test("subsequent calls to trackStatus and stopTrackingStatus increase and decrease subscribers counter", function (assert) { test("subsequent calls to trackStatus and stopTrackingStatus increase and decrease subscribers counter", function (assert) {
const store = getOwner(this).lookup("service:store"); const store = getOwner(this).lookup("service:store");
const user = store.createRecord("user"); const user = store.createRecord("user");
assert.strictEqual(user._subscribersCount, 0); assert.strictEqual(user.statusManager._subscribersCount, 0);
user.trackStatus(); user.statusManager.trackStatus();
assert.strictEqual(user._subscribersCount, 1); assert.strictEqual(user.statusManager._subscribersCount, 1);
user.trackStatus(); user.statusManager.trackStatus();
assert.strictEqual(user._subscribersCount, 2); assert.strictEqual(user.statusManager._subscribersCount, 2);
user.stopTrackingStatus(); user.statusManager.stopTrackingStatus();
assert.strictEqual(user._subscribersCount, 1); assert.strictEqual(user.statusManager._subscribersCount, 1);
user.stopTrackingStatus(); user.statusManager.stopTrackingStatus();
assert.strictEqual(user._subscribersCount, 0); assert.strictEqual(user.statusManager._subscribersCount, 0);
}); });
test("attempt to stop tracking status if status wasn't tracked doesn't throw", function (assert) { test("attempt to stop tracking status if status wasn't tracked doesn't throw", function (assert) {
const store = getOwner(this).lookup("service:store"); const store = getOwner(this).lookup("service:store");
const user = store.createRecord("user"); const user = store.createRecord("user");
user.stopTrackingStatus(); user.statusManager.stopTrackingStatus();
assert.ok(true); assert.ok(true);
}); });
@ -160,8 +160,8 @@ module("Unit | Model | user", function (hooks) {
const appEvents = user1.appEvents; const appEvents = user1.appEvents;
try { try {
user1.trackStatus(); user1.statusManager.trackStatus();
user2.trackStatus(); user2.statusManager.trackStatus();
assert.strictEqual(user1.status, status1); assert.strictEqual(user1.status, status1);
assert.strictEqual(user2.status, status2); assert.strictEqual(user2.status, status2);
@ -173,8 +173,8 @@ module("Unit | Model | user", function (hooks) {
assert.strictEqual(user1.status, null); assert.strictEqual(user1.status, null);
assert.strictEqual(user2.status, null); assert.strictEqual(user2.status, null);
} finally { } finally {
user1.stopTrackingStatus(); user1.statusManager.stopTrackingStatus();
user2.stopTrackingStatus(); user2.statusManager.stopTrackingStatus();
} }
}); });

View File

@ -158,12 +158,12 @@ export default class ChatChannelRow extends Component {
@action @action
startTrackingStatus() { startTrackingStatus() {
this.#firstDirectMessageUser?.trackStatus(); this.#firstDirectMessageUser?.statusManager.trackStatus();
} }
@action @action
stopTrackingStatus() { stopTrackingStatus() {
this.#firstDirectMessageUser?.stopTrackingStatus(); this.#firstDirectMessageUser?.statusManager.stopTrackingStatus();
} }
<template> <template>

View File

@ -249,11 +249,11 @@ export default class ChatMessage extends Component {
@action @action
initMentionedUsers() { initMentionedUsers() {
this.args.message.mentionedUsers.forEach((user) => { this.args.message.mentionedUsers.forEach((user) => {
if (user.isTrackingStatus()) { if (user.statusManager.isTrackingStatus()) {
return; return;
} }
user.trackStatus(); user.statusManager.trackStatus();
user.on("status-changed", this, "refreshStatusOnMentions"); user.on("status-changed", this, "refreshStatusOnMentions");
}); });
} }
@ -486,7 +486,7 @@ export default class ChatMessage extends Component {
#teardownMentionedUsers() { #teardownMentionedUsers() {
this.args.message.mentionedUsers.forEach((user) => { this.args.message.mentionedUsers.forEach((user) => {
user.stopTrackingStatus(); user.statusManager.stopTrackingStatus();
user.off("status-changed", this, "refreshStatusOnMentions"); user.off("status-changed", this, "refreshStatusOnMentions");
}); });
} }

View File

@ -20,12 +20,12 @@ export default class ChatMessageInfo extends Component {
@bind @bind
trackStatus() { trackStatus() {
this.#user?.trackStatus?.(); this.#user?.statusManager.trackStatus();
} }
@bind @bind
stopTrackingStatus() { stopTrackingStatus() {
this.#user?.stopTrackingStatus?.(); this.#user?.statusManager.stopTrackingStatus();
} }
get usernameClasses() { get usernameClasses() {

View File

@ -292,7 +292,7 @@ export default {
if (this.oneOnOneMessage) { if (this.oneOnOneMessage) {
const user = this.channel.chatable.users[0]; const user = this.channel.chatable.users[0];
if (user.username !== I18n.t("chat.deleted_chat_username")) { if (user.username !== I18n.t("chat.deleted_chat_username")) {
user.trackStatus(); user.statusManager.trackStatus();
} }
} }
} }
@ -300,7 +300,7 @@ export default {
@bind @bind
willDestroy() { willDestroy() {
if (this.oneOnOneMessage) { if (this.oneOnOneMessage) {
this.channel.chatable.users[0].stopTrackingStatus(); this.channel.chatable.users[0].statusManager.stopTrackingStatus();
} }
} }