diff --git a/app/assets/javascripts/discourse/app/controllers/badges/index.js b/app/assets/javascripts/discourse/app/controllers/badges/index.js index 265b5cd3e6a..7afe129467c 100644 --- a/app/assets/javascripts/discourse/app/controllers/badges/index.js +++ b/app/assets/javascripts/discourse/app/controllers/badges/index.js @@ -8,7 +8,7 @@ function badgeKey(badge) { return ("000" + pos).slice(-4) + (10 - type) + name; } -export default Controller.extend({ +export default class IndexController extends Controller { @discourseComputed("model") badgeGroups(model) { let sorted = model.sort((a, b) => badgeKey(a).localeCompare(badgeKey(b))); @@ -35,5 +35,5 @@ export default Controller.extend({ } return grouped; - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/badges/show.js b/app/assets/javascripts/discourse/app/controllers/badges/show.js index 4fff9ad701c..2742ddd4033 100644 --- a/app/assets/javascripts/discourse/app/controllers/badges/show.js +++ b/app/assets/javascripts/discourse/app/controllers/badges/show.js @@ -5,17 +5,18 @@ import UserBadge from "discourse/models/user-badge"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default Controller.extend({ - application: controller(), - queryParams: ["username"], - noMoreBadges: false, - userBadges: null, - hiddenSetTitle: true, +export default class ShowController extends Controller { + @controller application; + + queryParams = ["username"]; + noMoreBadges = false; + userBadges = null; + hiddenSetTitle = true; @discourseComputed("userBadgesAll") filteredList(userBadgesAll) { return userBadgesAll.filterBy("badge.allow_title", true); - }, + } @discourseComputed("filteredList") selectableUserBadges(filteredList) { @@ -26,29 +27,29 @@ export default Controller.extend({ }), ...filteredList.uniqBy("badge.name"), ]; - }, + } @discourseComputed("username") user(username) { if (username) { return this.userBadges[0].get("user"); } - }, + } @discourseComputed("username", "model.grant_count", "userBadges.grant_count") grantCount(username, modelCount, userCount) { return username ? userCount : modelCount; - }, + } @discourseComputed("model.grant_count", "userBadges.grant_count") othersCount(modelCount, userCount) { return modelCount - userCount; - }, + } @discourseComputed("model.allow_title", "model.has_badge", "model") canSelectTitle(hasTitleBadges, hasBadge) { return this.siteSettings.enable_badges && hasTitleBadges && hasBadge; - }, + } @discourseComputed("noMoreBadges", "grantCount", "userBadges.length") canLoadMore(noMoreBadges, grantCount, userBadgeLength) { @@ -56,12 +57,12 @@ export default Controller.extend({ return false; } return grantCount > (userBadgeLength || 0); - }, + } @discourseComputed("user", "model.grant_count") canShowOthers(user, grantCount) { return !!user && grantCount > 1; - }, + } @action loadMore() { @@ -89,10 +90,10 @@ export default Controller.extend({ .finally(() => { this.set("loadingMore", false); }); - }, + } @action toggleSetUserTitle() { return this.toggleProperty("hiddenSetTitle"); - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/account.js b/app/assets/javascripts/discourse/app/controllers/preferences/account.js index 2a3c2d6b39d..e8ed426fb1c 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/account.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/account.js @@ -14,14 +14,33 @@ import getURL from "discourse-common/lib/get-url"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default Controller.extend(CanCheckEmails, { - dialog: service(), - modal: service(), - user: controller(), - canDownloadPosts: alias("user.viewingSelf"), +export default class AccountController extends Controller.extend( + CanCheckEmails +) { + @service dialog; + @service modal; + + @controller user; + + @setting("enable_names") canEditName; + @setting("enable_user_status") canSelectUserStatus; + + @alias("user.viewingSelf") canDownloadPosts; + @not("currentUser.can_delete_account") cannotDeleteAccount; + @or("model.isSaving", "deleting", "cannotDeleteAccount") deleteDisabled; + @gt("model.availableTitles.length", 0) canSelectTitle; + @gt("model.availableFlairs.length", 0) canSelectFlair; + @propertyNotEqual("model.id", "currentUser.id") disableConnectButtons; + + canSaveUser = true; + newNameInput = null; + newTitleInput = null; + newPrimaryGroupInput = null; + newStatus = null; + revoking = null; init() { - this._super(...arguments); + super.init(...arguments); this.saveAttrNames = [ "name", @@ -31,25 +50,11 @@ export default Controller.extend(CanCheckEmails, { "status", ]; this.set("revoking", {}); - }, - - canEditName: setting("enable_names"), - canSelectUserStatus: setting("enable_user_status"), - canSaveUser: true, - - newNameInput: null, - newTitleInput: null, - newPrimaryGroupInput: null, - newStatus: null, - - revoking: null, - - cannotDeleteAccount: not("currentUser.can_delete_account"), - deleteDisabled: or("model.isSaving", "deleting", "cannotDeleteAccount"), + } reset() { this.set("passwordProgress", null); - }, + } @discourseComputed() nameInstructions() { @@ -58,10 +63,7 @@ export default Controller.extend(CanCheckEmails, { ? "user.name.instructions_required" : "user.name.instructions" ); - }, - - canSelectTitle: gt("model.availableTitles.length", 0), - canSelectFlair: gt("model.availableFlairs.length", 0), + } @discourseComputed("model.filteredGroups") canSelectPrimaryGroup(primaryGroupOptions) { @@ -69,12 +71,12 @@ export default Controller.extend(CanCheckEmails, { primaryGroupOptions.length > 0 && this.siteSettings.user_selected_primary_groups ); - }, + } @discourseComputed("model.associated_accounts") associatedAccountsLoaded(associatedAccounts) { return typeof associatedAccounts !== "undefined"; - }, + } @discourseComputed("model.associated_accounts.[]") authProviders(accounts) { @@ -88,9 +90,7 @@ export default Controller.extend(CanCheckEmails, { }); return result.filter((value) => value.account || value.method.can_connect); - }, - - disableConnectButtons: propertyNotEqual("model.id", "currentUser.id"), + } @discourseComputed( "model.email", @@ -123,7 +123,7 @@ export default Controller.extend(CanCheckEmails, { } return emails.sort((a, b) => a.email.localeCompare(b.email)); - }, + } @discourseComputed( "model.second_factor_enabled", @@ -139,7 +139,7 @@ export default Controller.extend(CanCheckEmails, { return false; } return findAll().length > 0; - }, + } @discourseComputed( "siteSettings.max_allowed_secondary_emails", @@ -147,7 +147,7 @@ export default Controller.extend(CanCheckEmails, { ) canAddEmail(maxAllowedSecondaryEmails, canEditEmail) { return maxAllowedSecondaryEmails > 0 && canEditEmail; - }, + } @action resendConfirmationEmail(email, event) { @@ -161,7 +161,7 @@ export default Controller.extend(CanCheckEmails, { .finally(() => { email.set("resending", false); }); - }, + } @action showUserStatusModal(status) { @@ -173,98 +173,100 @@ export default Controller.extend(CanCheckEmails, { deleteAction: () => this.set("newStatus", null), }, }); - }, + } - actions: { - save() { - this.set("saved", false); + @action + save() { + this.set("saved", false); - this.model.setProperties({ - name: this.newNameInput, - title: this.newTitleInput, - primary_group_id: this.newPrimaryGroupInput, - flair_group_id: this.newFlairGroupId, - status: this.newStatus, - }); + this.model.setProperties({ + name: this.newNameInput, + title: this.newTitleInput, + primary_group_id: this.newPrimaryGroupInput, + flair_group_id: this.newFlairGroupId, + status: this.newStatus, + }); - return this.model - .save(this.saveAttrNames) - .then(() => this.set("saved", true)) - .catch(popupAjaxError); - }, + return this.model + .save(this.saveAttrNames) + .then(() => this.set("saved", true)) + .catch(popupAjaxError); + } - setPrimaryEmail(email) { - this.model.setPrimaryEmail(email).catch(popupAjaxError); - }, + @action + setPrimaryEmail(email) { + this.model.setPrimaryEmail(email).catch(popupAjaxError); + } - destroyEmail(email) { - this.model.destroyEmail(email); - }, + @action + destroyEmail(email) { + this.model.destroyEmail(email); + } - delete() { - this.dialog.alert({ - message: I18n.t("user.delete_account_confirm"), - buttons: [ - { - icon: "exclamation-triangle", - label: I18n.t("user.delete_account"), - class: "btn-danger", - action: () => { - return this.model.delete().then( - () => { - next(() => { - this.dialog.alert({ - message: I18n.t("user.deleted_yourself"), - didConfirm: () => - DiscourseURL.redirectAbsolute(getURL("/")), - didCancel: () => - DiscourseURL.redirectAbsolute(getURL("/")), - }); + @action + delete() { + this.dialog.alert({ + message: I18n.t("user.delete_account_confirm"), + buttons: [ + { + icon: "exclamation-triangle", + label: I18n.t("user.delete_account"), + class: "btn-danger", + action: () => { + return this.model.delete().then( + () => { + next(() => { + this.dialog.alert({ + message: I18n.t("user.deleted_yourself"), + didConfirm: () => + DiscourseURL.redirectAbsolute(getURL("/")), + didCancel: () => DiscourseURL.redirectAbsolute(getURL("/")), }); - }, - () => { - next(() => - this.dialog.alert( - I18n.t("user.delete_yourself_not_allowed") - ) - ); - this.set("deleting", false); - } - ); - }, + }); + }, + () => { + next(() => + this.dialog.alert(I18n.t("user.delete_yourself_not_allowed")) + ); + this.set("deleting", false); + } + ); }, - { - label: I18n.t("composer.cancel"), - }, - ], - }); - }, + }, + { + label: I18n.t("composer.cancel"), + }, + ], + }); + } - revokeAccount(account) { - this.set(`revoking.${account.name}`, true); + @action + revokeAccount(account) { + this.set(`revoking.${account.name}`, true); - this.model - .revokeAssociatedAccount(account.name) - .then((result) => { - if (result.success) { - this.model.associated_accounts.removeObject(account); - } else { - this.dialog.alert(result.message); - } - }) - .catch(popupAjaxError) - .finally(() => this.set(`revoking.${account.name}`, false)); - }, + this.model + .revokeAssociatedAccount(account.name) + .then((result) => { + if (result.success) { + this.model.associated_accounts.removeObject(account); + } else { + this.dialog.alert(result.message); + } + }) + .catch(popupAjaxError) + .finally(() => this.set(`revoking.${account.name}`, false)); + } - connectAccount(method) { - method.doLogin({ reconnect: true }); - }, + @action + connectAccount(method) { + method.doLogin({ reconnect: true }); + } - exportUserArchive() { - this.dialog.yesNoConfirm({ - message: I18n.t("user.download_archive.confirm"), - didConfirm: () => exportUserArchive(), - }); - }, - }, -}); + @action + exportUserArchive() { + this.dialog.yesNoConfirm({ + message: I18n.t("user.download_archive.confirm"), + didConfirm: () => exportUserArchive(), + }); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/email.js b/app/assets/javascripts/discourse/app/controllers/preferences/email.js index d463bd4ec71..31baf1b4e07 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/email.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/email.js @@ -1,38 +1,32 @@ import Controller from "@ember/controller"; -import EmberObject from "@ember/object"; +import EmberObject, { action } from "@ember/object"; import { empty, or } from "@ember/object/computed"; import { propertyEqual } from "discourse/lib/computed"; import { emailValid } from "discourse/lib/utilities"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default Controller.extend({ - queryParams: ["new"], +export default class EmailController extends Controller { + queryParams = ["new"]; + taken = false; + saving = false; + error = false; + success = false; + oldEmail = null; + newEmail = null; + successMessage = null; - taken: false, - saving: false, - error: false, - success: false, - oldEmail: null, - newEmail: null, - successMessage: null, + @empty("newEmail") newEmailEmpty; - newEmailEmpty: empty("newEmail"), + @or("saving", "newEmailEmpty", "taken", "unchanged", "invalidEmail") + saveDisabled; - saveDisabled: or( - "saving", - "newEmailEmpty", - "taken", - "unchanged", - "invalidEmail" - ), - - unchanged: propertyEqual("newEmailLower", "oldEmail"), + @propertyEqual("newEmailLower", "oldEmail") unchanged; @discourseComputed("newEmail") newEmailLower(newEmail) { return newEmail.toLowerCase().trim(); - }, + } @discourseComputed("saving", "new") saveButtonText(saving, isNew) { @@ -43,12 +37,12 @@ export default Controller.extend({ return I18n.t("user.add_email.add"); } return I18n.t("user.change"); - }, + } @discourseComputed("newEmail") invalidEmail(newEmail) { return !emailValid(newEmail); - }, + } @discourseComputed("invalidEmail", "oldEmail", "newEmail") emailValidation(invalidEmail, oldEmail, newEmail) { @@ -58,7 +52,7 @@ export default Controller.extend({ reason: I18n.t("user.email.invalid"), }); } - }, + } reset() { this.setProperties({ @@ -68,49 +62,45 @@ export default Controller.extend({ success: false, newEmail: null, }); - }, + } - actions: { - saveEmail() { - this.set("saving", true); + @action + saveEmail() { + this.set("saving", true); - return ( - this.new - ? this.model.addEmail(this.newEmail) - : this.model.changeEmail(this.newEmail) - ).then( - () => { - this.set("success", true); + return ( + this.new + ? this.model.addEmail(this.newEmail) + : this.model.changeEmail(this.newEmail) + ).then( + () => { + this.set("success", true); - if (this.model.staff) { + if (this.model.staff) { + this.set("successMessage", I18n.t("user.change_email.success_staff")); + } else { + if (this.currentUser.admin) { this.set( "successMessage", - I18n.t("user.change_email.success_staff") + I18n.t("user.change_email.success_via_admin") ); } else { - if (this.currentUser.admin) { - this.set( - "successMessage", - I18n.t("user.change_email.success_via_admin") - ); - } else { - this.set("successMessage", I18n.t("user.change_email.success")); - } - } - }, - (e) => { - this.setProperties({ error: true, saving: false }); - if ( - e.jqXHR.responseJSON && - e.jqXHR.responseJSON.errors && - e.jqXHR.responseJSON.errors[0] - ) { - this.set("errorMessage", e.jqXHR.responseJSON.errors[0]); - } else { - this.set("errorMessage", I18n.t("user.change_email.error")); + this.set("successMessage", I18n.t("user.change_email.success")); } } - ); - }, - }, -}); + }, + (e) => { + this.setProperties({ error: true, saving: false }); + if ( + e.jqXHR.responseJSON && + e.jqXHR.responseJSON.errors && + e.jqXHR.responseJSON.errors[0] + ) { + this.set("errorMessage", e.jqXHR.responseJSON.errors[0]); + } else { + this.set("errorMessage", I18n.t("user.change_email.error")); + } + } + ); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/emails.js b/app/assets/javascripts/discourse/app/controllers/preferences/emails.js index a7b56db6785..e2d99137019 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/emails.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/emails.js @@ -1,4 +1,5 @@ import Controller from "@ember/controller"; +import { action } from "@ember/object"; import { equal } from "@ember/object/computed"; import { popupAjaxError } from "discourse/lib/ajax-error"; import discourseComputed from "discourse-common/utils/decorators"; @@ -10,56 +11,50 @@ const EMAIL_LEVELS = { NEVER: 2, }; -export default Controller.extend({ - subpageTitle: I18n.t("user.preferences_nav.emails"), - emailMessagesLevelAway: equal( - "model.user_option.email_messages_level", - EMAIL_LEVELS.ONLY_WHEN_AWAY - ), - emailLevelAway: equal( - "model.user_option.email_level", - EMAIL_LEVELS.ONLY_WHEN_AWAY - ), +export default class EmailsController extends Controller { + subpageTitle = I18n.t("user.preferences_nav.emails"); - init() { - this._super(...arguments); + @equal("model.user_option.email_messages_level", EMAIL_LEVELS.ONLY_WHEN_AWAY) + emailMessagesLevelAway; - this.saveAttrNames = [ - "email_level", - "email_messages_level", - "mailing_list_mode", - "mailing_list_mode_frequency", - "email_digests", - "email_in_reply_to", - "email_previous_replies", - "digest_after_minutes", - "include_tl0_in_digests", - ]; + @equal("model.user_option.email_level", EMAIL_LEVELS.ONLY_WHEN_AWAY) + emailLevelAway; - this.previousRepliesOptions = [ - { name: I18n.t("user.email_previous_replies.always"), value: 0 }, - { name: I18n.t("user.email_previous_replies.unless_emailed"), value: 1 }, - { name: I18n.t("user.email_previous_replies.never"), value: 2 }, - ]; + saveAttrNames = [ + "email_level", + "email_messages_level", + "mailing_list_mode", + "mailing_list_mode_frequency", + "email_digests", + "email_in_reply_to", + "email_previous_replies", + "digest_after_minutes", + "include_tl0_in_digests", + ]; - this.emailLevelOptions = [ - { name: I18n.t("user.email_level.always"), value: EMAIL_LEVELS.ALWAYS }, - { - name: I18n.t("user.email_level.only_when_away"), - value: EMAIL_LEVELS.ONLY_WHEN_AWAY, - }, - { name: I18n.t("user.email_level.never"), value: EMAIL_LEVELS.NEVER }, - ]; + previousRepliesOptions = [ + { name: I18n.t("user.email_previous_replies.always"), value: 0 }, + { name: I18n.t("user.email_previous_replies.unless_emailed"), value: 1 }, + { name: I18n.t("user.email_previous_replies.never"), value: 2 }, + ]; - this.digestFrequencies = [ - { name: I18n.t("user.email_digests.every_30_minutes"), value: 30 }, - { name: I18n.t("user.email_digests.every_hour"), value: 60 }, - { name: I18n.t("user.email_digests.daily"), value: 1440 }, - { name: I18n.t("user.email_digests.weekly"), value: 10080 }, - { name: I18n.t("user.email_digests.every_month"), value: 43200 }, - { name: I18n.t("user.email_digests.every_six_months"), value: 259200 }, - ]; - }, + emailLevelOptions = [ + { name: I18n.t("user.email_level.always"), value: EMAIL_LEVELS.ALWAYS }, + { + name: I18n.t("user.email_level.only_when_away"), + value: EMAIL_LEVELS.ONLY_WHEN_AWAY, + }, + { name: I18n.t("user.email_level.never"), value: EMAIL_LEVELS.NEVER }, + ]; + + digestFrequencies = [ + { name: I18n.t("user.email_digests.every_30_minutes"), value: 30 }, + { name: I18n.t("user.email_digests.every_hour"), value: 60 }, + { name: I18n.t("user.email_digests.daily"), value: 1440 }, + { name: I18n.t("user.email_digests.weekly"), value: 10080 }, + { name: I18n.t("user.email_digests.every_month"), value: 43200 }, + { name: I18n.t("user.email_digests.every_six_months"), value: 259200 }, + ]; @discourseComputed() frequencyEstimate() { @@ -71,7 +66,7 @@ export default Controller.extend({ dailyEmailEstimate: estimate, }); } - }, + } @discourseComputed() mailingListModeOptions() { @@ -79,7 +74,7 @@ export default Controller.extend({ { name: this.frequencyEstimate, value: 1 }, { name: I18n.t("user.mailing_list_mode.individual_no_echo"), value: 2 }, ]; - }, + } @discourseComputed() emailFrequencyInstructions() { @@ -88,17 +83,16 @@ export default Controller.extend({ count: this.siteSettings.email_time_window_mins, }) : null; - }, + } - actions: { - save() { - this.set("saved", false); - return this.model - .save(this.saveAttrNames) - .then(() => { - this.set("saved", true); - }) - .catch(popupAjaxError); - }, - }, -}); + @action + save() { + this.set("saved", false); + return this.model + .save(this.saveAttrNames) + .then(() => { + this.set("saved", true); + }) + .catch(popupAjaxError); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js index d9c47d1a62d..1829cae9f76 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js @@ -1,5 +1,5 @@ import Controller, { inject as controller } from "@ember/controller"; -import { computed } from "@ember/object"; +import { action, computed } from "@ember/object"; import { not, reads } from "@ember/object/computed"; import { service } from "@ember/service"; import { reload } from "discourse/helpers/page-reloader"; @@ -31,22 +31,35 @@ const USER_HOMES = { const TEXT_SIZES = ["smallest", "smaller", "normal", "larger", "largest"]; const TITLE_COUNT_MODES = ["notifications", "contextual"]; -export default Controller.extend({ - session: service(), +export default class InterfaceController extends Controller { + @service session; + @controller("preferences") preferencesController; - currentThemeId: -1, - previewingColorScheme: false, - selectedDarkColorSchemeId: null, - preferencesController: controller("preferences"), - makeColorSchemeDefault: true, - canPreviewColorScheme: propertyEqual("model.id", "currentUser.id"), - subpageTitle: I18n.t("user.preferences_nav.interface"), + currentThemeId = -1; + previewingColorScheme = false; + selectedDarkColorSchemeId = null; + makeColorSchemeDefault = true; + + @propertyEqual("model.id", "currentUser.id") canPreviewColorScheme; + subpageTitle = I18n.t("user.preferences_nav.interface"); + + @reads("userSelectableColorSchemes.length") showColorSchemeSelector; + + @not("currentSchemeCanBeSelected") showColorSchemeNoneItem; + + selectedColorSchemeNoneLabel = I18n.t( + "user.color_schemes.default_description" + ); init() { - this._super(...arguments); - + super.init(...arguments); this.set("selectedDarkColorSchemeId", this.session.userDarkSchemeId); - }, + this.set( + "enableDarkMode", + this.get("model.user_option.dark_scheme_id") === -1 ? false : true + ); + this.set("selectedColorSchemeId", this.getSelectedColorSchemeId()); + } @discourseComputed("makeThemeDefault") saveAttrNames(makeThemeDefault) { @@ -75,42 +88,39 @@ export default Controller.extend({ } return attrs; - }, + } @discourseComputed() availableLocales() { return JSON.parse(this.siteSettings.available_locales); - }, + } @discourseComputed defaultDarkSchemeId() { return this.siteSettings.default_dark_mode_color_scheme_id; - }, + } @discourseComputed textSizes() { return TEXT_SIZES.map((value) => { return { name: I18n.t(`user.text_size.${value}`), value }; }); - }, + } - homepageId: computed( - "model.user_option.homepage_id", - "userSelectableHome.[]", - function () { - return ( - this.model.user_option.homepage_id || - this.userSelectableHome.firstObject.value - ); - } - ), + @computed("model.user_option.homepage_id", "userSelectableHome.[]") + get homepageId() { + return ( + this.model.user_option.homepage_id || + this.userSelectableHome.firstObject.value + ); + } @discourseComputed titleCountModes() { return TITLE_COUNT_MODES.map((value) => { return { name: I18n.t(`user.title_count_mode.${value}`), value }; }); - }, + } @discourseComputed bookmarkAfterNotificationModes() { @@ -120,17 +130,17 @@ export default Controller.extend({ name: I18n.t(`bookmarks.auto_delete_preference.${key.toLowerCase()}`), }; }); - }, + } @discourseComputed userSelectableThemes() { return listThemes(this.site); - }, + } @discourseComputed("userSelectableThemes") showThemeSelector(themes) { return themes && themes.length > 1; - }, + } @discourseComputed("themeId") themeIdChanged(themeId) { @@ -140,17 +150,12 @@ export default Controller.extend({ } else { return this.currentThemeId !== themeId; } - }, + } @discourseComputed userSelectableColorSchemes() { return listColorSchemes(this.site); - }, - - showColorSchemeSelector: reads("userSelectableColorSchemes.length"), - selectedColorSchemeNoneLabel: I18n.t( - "user.color_schemes.default_description" - ), + } @discourseComputed( "userSelectableThemes", @@ -168,19 +173,17 @@ export default Controller.extend({ } return userColorSchemes.findBy("id", theme.color_scheme_id); - }, - - showColorSchemeNoneItem: not("currentSchemeCanBeSelected"), + } @discourseComputed("model.user_option.theme_ids", "themeId") showThemeSetDefault(userOptionThemes, selectedTheme) { return !userOptionThemes || userOptionThemes[0] !== selectedTheme; - }, + } @discourseComputed("model.user_option.text_size", "textSize") showTextSetDefault(userOptionTextSize, selectedTextSize) { return userOptionTextSize !== selectedTextSize; - }, + } homeChanged() { const siteHome = this.siteSettings.top_menu.split("|")[0].split(",")[0]; @@ -192,7 +195,7 @@ export default Controller.extend({ const userHome = USER_HOMES[this.get("model.user_option.homepage_id")]; setDefaultHomepage(userHome || siteHome); - }, + } @discourseComputed() userSelectableHome() { @@ -226,19 +229,19 @@ export default Controller.extend({ }); return result; - }, + } @discourseComputed showDarkModeToggle() { return this.defaultDarkSchemeId > 0 && !this.showDarkColorSchemeSelector; - }, + } @discourseComputed userSelectableDarkColorSchemes() { return listColorSchemes(this.site, { darkOnly: true, }); - }, + } @discourseComputed("userSelectableDarkColorSchemes") showDarkColorSchemeSelector(darkSchemes) { @@ -247,221 +250,201 @@ export default Controller.extend({ // but we show a checkbox in that case const minToShow = this.defaultDarkSchemeId > 0 ? 2 : 1; return darkSchemes && darkSchemes.length > minToShow; - }, + } - enableDarkMode: computed({ - set(key, value) { - return value; - }, - get() { - return this.get("model.user_option.dark_scheme_id") === -1 ? false : true; - }, - }), + getSelectedColorSchemeId() { + if (!this.session.userColorSchemeId) { + return; + } - selectedColorSchemeId: computed({ - set(key, value) { - return value; - }, - get() { - if (!this.session.userColorSchemeId) { - return; - } + const theme = this.userSelectableThemes?.findBy("id", this.themeId); - const theme = this.userSelectableThemes?.findBy("id", this.themeId); + // we don't want to display the numeric ID of a scheme + // when it is set by the theme but not marked as user selectable + if ( + theme?.color_scheme_id === this.session.userColorSchemeId && + !this.userSelectableColorSchemes.findBy( + "id", + this.session.userColorSchemeId + ) + ) { + return; + } else { + return this.session.userColorSchemeId; + } + } - // we don't want to display the numeric ID of a scheme - // when it is set by the theme but not marked as user selectable + @action + save() { + this.set("saved", false); + const makeThemeDefault = this.makeThemeDefault; + if (makeThemeDefault) { + this.set("model.user_option.theme_ids", [this.themeId]); + } + + const makeTextSizeDefault = this.makeTextSizeDefault; + if (makeTextSizeDefault) { + this.set("model.user_option.text_size", this.textSize); + } + + if (!this.showColorSchemeSelector) { + this.set("model.user_option.color_scheme_id", null); + } else if (this.makeColorSchemeDefault) { + this.set("model.user_option.color_scheme_id", this.selectedColorSchemeId); + } + + if (this.showDarkModeToggle) { + this.set( + "model.user_option.dark_scheme_id", + this.enableDarkMode ? null : -1 + ); + } else { + // if chosen dark scheme matches site dark scheme, no need to store if ( - theme?.color_scheme_id === this.session.userColorSchemeId && - !this.userSelectableColorSchemes.findBy( - "id", - this.session.userColorSchemeId - ) + this.defaultDarkSchemeId > 0 && + this.selectedDarkColorSchemeId === this.defaultDarkSchemeId ) { - return; + this.set("model.user_option.dark_scheme_id", null); } else { - return this.session.userColorSchemeId; - } - }, - }), - - actions: { - save() { - this.set("saved", false); - const makeThemeDefault = this.makeThemeDefault; - if (makeThemeDefault) { - this.set("model.user_option.theme_ids", [this.themeId]); - } - - const makeTextSizeDefault = this.makeTextSizeDefault; - if (makeTextSizeDefault) { - this.set("model.user_option.text_size", this.textSize); - } - - if (!this.showColorSchemeSelector) { - this.set("model.user_option.color_scheme_id", null); - } else if (this.makeColorSchemeDefault) { - this.set( - "model.user_option.color_scheme_id", - this.selectedColorSchemeId - ); - } - - if (this.showDarkModeToggle) { this.set( "model.user_option.dark_scheme_id", - this.enableDarkMode ? null : -1 + this.selectedDarkColorSchemeId ); - } else { - // if chosen dark scheme matches site dark scheme, no need to store - if ( - this.defaultDarkSchemeId > 0 && - this.selectedDarkColorSchemeId === this.defaultDarkSchemeId - ) { - this.set("model.user_option.dark_scheme_id", null); + } + } + + return this.model + .save(this.saveAttrNames) + .then(() => { + this.set("saved", true); + + if (makeThemeDefault) { + setLocalTheme([]); } else { - this.set( - "model.user_option.dark_scheme_id", - this.selectedDarkColorSchemeId + setLocalTheme( + [this.themeId], + this.get("model.user_option.theme_key_seq") ); } - } + if (makeTextSizeDefault) { + this.model.updateTextSizeCookie(null); + } else { + this.model.updateTextSizeCookie(this.textSize); + } - return this.model - .save(this.saveAttrNames) - .then(() => { - this.set("saved", true); + if (this.makeColorSchemeDefault) { + updateColorSchemeCookie(null); + updateColorSchemeCookie(null, { dark: true }); + } else { + updateColorSchemeCookie(this.selectedColorSchemeId); - if (makeThemeDefault) { - setLocalTheme([]); - } else { - setLocalTheme( - [this.themeId], - this.get("model.user_option.theme_key_seq") - ); - } - if (makeTextSizeDefault) { - this.model.updateTextSizeCookie(null); - } else { - this.model.updateTextSizeCookie(this.textSize); - } - - if (this.makeColorSchemeDefault) { - updateColorSchemeCookie(null); + if ( + this.defaultDarkSchemeId > 0 && + this.selectedDarkColorSchemeId === this.defaultDarkSchemeId + ) { updateColorSchemeCookie(null, { dark: true }); } else { - updateColorSchemeCookie(this.selectedColorSchemeId); - - if ( - this.defaultDarkSchemeId > 0 && - this.selectedDarkColorSchemeId === this.defaultDarkSchemeId - ) { - updateColorSchemeCookie(null, { dark: true }); - } else { - updateColorSchemeCookie(this.selectedDarkColorSchemeId, { - dark: true, - }); - } + updateColorSchemeCookie(this.selectedDarkColorSchemeId, { + dark: true, + }); } - - this.homeChanged(); - - if (this.themeId && this.themeId !== this.currentThemeId) { - reload(); - } - }) - .catch(popupAjaxError); - }, - - selectTextSize(newSize) { - const classList = document.documentElement.classList; - - TEXT_SIZES.forEach((name) => { - const className = `text-size-${name}`; - if (newSize === name) { - classList.add(className); - } else { - classList.remove(className); } - }); - // Force refresh when leaving this screen - this.session.requiresRefresh = true; - this.set("textSize", newSize); - }, + this.homeChanged(); - loadColorScheme(colorSchemeId) { - this.setProperties({ - selectedColorSchemeId: colorSchemeId, - previewingColorScheme: this.canPreviewColorScheme, - }); - - if (!this.canPreviewColorScheme) { - return; - } - - if (colorSchemeId < 0) { - const defaultTheme = this.userSelectableThemes.findBy( - "id", - this.themeId - ); - - if (defaultTheme && defaultTheme.color_scheme_id) { - colorSchemeId = defaultTheme.color_scheme_id; + if (this.themeId && this.themeId !== this.currentThemeId) { + reload(); } - } - loadColorSchemeStylesheet(colorSchemeId, this.themeId); - if (this.selectedDarkColorSchemeId === -1) { - // set this same scheme for dark mode preview when dark scheme is disabled - loadColorSchemeStylesheet(colorSchemeId, this.themeId, true); - } - }, + }) + .catch(popupAjaxError); + } - loadDarkColorScheme(colorSchemeId) { - this.setProperties({ - selectedDarkColorSchemeId: colorSchemeId, - previewingColorScheme: this.canPreviewColorScheme, - }); + @action + selectTextSize(newSize) { + const classList = document.documentElement.classList; - if (!this.canPreviewColorScheme) { - return; - } - - if (colorSchemeId === -1) { - // load preview of regular scheme when dark scheme is disabled - loadColorSchemeStylesheet( - this.selectedColorSchemeId, - this.themeId, - true - ); - this.session.set("darkModeAvailable", false); + TEXT_SIZES.forEach((name) => { + const className = `text-size-${name}`; + if (newSize === name) { + classList.add(className); } else { - loadColorSchemeStylesheet(colorSchemeId, this.themeId, true); - this.session.set("darkModeAvailable", true); + classList.remove(className); } - }, + }); - undoColorSchemePreview() { - this.setProperties({ - selectedColorSchemeId: this.session.userColorSchemeId, - selectedDarkColorSchemeId: this.session.userDarkSchemeId, - previewingColorScheme: false, - }); - const darkStylesheet = document.querySelector("link#cs-preview-dark"), - lightStylesheet = document.querySelector("link#cs-preview-light"); - if (darkStylesheet) { - darkStylesheet.remove(); + // Force refresh when leaving this screen + this.session.requiresRefresh = true; + this.set("textSize", newSize); + } + + @action + loadColorScheme(colorSchemeId) { + this.setProperties({ + selectedColorSchemeId: colorSchemeId, + previewingColorScheme: this.canPreviewColorScheme, + }); + + if (!this.canPreviewColorScheme) { + return; + } + + if (colorSchemeId < 0) { + const defaultTheme = this.userSelectableThemes.findBy("id", this.themeId); + + if (defaultTheme && defaultTheme.color_scheme_id) { + colorSchemeId = defaultTheme.color_scheme_id; } + } + loadColorSchemeStylesheet(colorSchemeId, this.themeId); + if (this.selectedDarkColorSchemeId === -1) { + // set this same scheme for dark mode preview when dark scheme is disabled + loadColorSchemeStylesheet(colorSchemeId, this.themeId, true); + } + } - if (lightStylesheet) { - lightStylesheet.remove(); - } - }, + @action + loadDarkColorScheme(colorSchemeId) { + this.setProperties({ + selectedDarkColorSchemeId: colorSchemeId, + previewingColorScheme: this.canPreviewColorScheme, + }); - resetSeenUserTips() { - this.model.set("user_option.skip_new_user_tips", false); - this.model.set("user_option.seen_popups", null); - return this.model.save(["skip_new_user_tips", "seen_popups"]); - }, - }, -}); + if (!this.canPreviewColorScheme) { + return; + } + + if (colorSchemeId === -1) { + // load preview of regular scheme when dark scheme is disabled + loadColorSchemeStylesheet(this.selectedColorSchemeId, this.themeId, true); + this.session.set("darkModeAvailable", false); + } else { + loadColorSchemeStylesheet(colorSchemeId, this.themeId, true); + this.session.set("darkModeAvailable", true); + } + } + + @action + undoColorSchemePreview() { + this.setProperties({ + selectedColorSchemeId: this.session.userColorSchemeId, + selectedDarkColorSchemeId: this.session.userDarkSchemeId, + previewingColorScheme: false, + }); + const darkStylesheet = document.querySelector("link#cs-preview-dark"), + lightStylesheet = document.querySelector("link#cs-preview-light"); + if (darkStylesheet) { + darkStylesheet.remove(); + } + + if (lightStylesheet) { + lightStylesheet.remove(); + } + } + + @action + resetSeenUserTips() { + this.model.set("user_option.skip_new_user_tips", false); + this.model.set("user_option.seen_popups", null); + return this.model.save(["skip_new_user_tips", "seen_popups"]); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/notifications.js b/app/assets/javascripts/discourse/app/controllers/preferences/notifications.js index 180c6d6100f..0035f70ea38 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/notifications.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/notifications.js @@ -1,12 +1,13 @@ import Controller from "@ember/controller"; +import { action } from "@ember/object"; import { popupAjaxError } from "discourse/lib/ajax-error"; import I18n from "discourse-i18n"; -export default Controller.extend({ - subpageTitle: I18n.t("user.preferences_nav.notifications"), +export default class NotificationsController extends Controller { + subpageTitle = I18n.t("user.preferences_nav.notifications"); init() { - this._super(...arguments); + super.init(...arguments); this.saveAttrNames = [ "muted_usernames", @@ -28,17 +29,16 @@ export default Controller.extend({ { name: I18n.t("user.like_notification_frequency.first_time"), value: 2 }, { name: I18n.t("user.like_notification_frequency.never"), value: 3 }, ]; - }, + } - actions: { - save() { - this.set("saved", false); - return this.model - .save(this.saveAttrNames) - .then(() => { - this.set("saved", true); - }) - .catch(popupAjaxError); - }, - }, -}); + @action + save() { + this.set("saved", false); + return this.model + .save(this.saveAttrNames) + .then(() => { + this.set("saved", true); + }) + .catch(popupAjaxError); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/profile.js b/app/assets/javascripts/discourse/app/controllers/preferences/profile.js index 17c58b78fb0..d9e5fa71018 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/profile.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/profile.js @@ -9,30 +9,36 @@ import { popupAjaxError } from "discourse/lib/ajax-error"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default Controller.extend({ - dialog: service(), - modal: service(), - subpageTitle: I18n.t("user.preferences_nav.profile"), - init() { - this._super(...arguments); - this.saveAttrNames = [ - "bio_raw", - "website", - "location", - "custom_fields", - "user_fields", - "profile_background_upload_url", - "card_background_upload_url", - "date_of_birth", - "timezone", - "default_calendar", - ]; +export default class ProfileController extends Controller { + @service dialog; + @service modal; - this.calendarOptions = [ - { name: I18n.t("download_calendar.google"), value: "google" }, - { name: I18n.t("download_calendar.ics"), value: "ics" }, - ]; - }, + subpageTitle = I18n.t("user.preferences_nav.profile"); + + @readOnly("model.can_change_bio") canChangeBio; + @readOnly("model.can_change_location") canChangeLocation; + @readOnly("model.can_change_website") canChangeWebsite; + @readOnly("model.can_upload_profile_header") canUploadProfileHeader; + @readOnly("model.can_upload_user_card_background") + canUploadUserCardBackground; + + saveAttrNames = [ + "bio_raw", + "website", + "location", + "custom_fields", + "user_fields", + "profile_background_upload_url", + "card_background_upload_url", + "date_of_birth", + "timezone", + "default_calendar", + ]; + + calendarOptions = [ + { name: I18n.t("download_calendar.google"), value: "google" }, + { name: I18n.t("download_calendar.ics"), value: "ics" }, + ]; @discourseComputed("model.user_fields.@each.value") userFields() { @@ -57,29 +63,17 @@ export default Controller.extend({ const value = this.model.user_fields?.[field.id.toString()]; return EmberObject.create({ field, value }); }); - }, + } @discourseComputed("currentUser.needs_required_fields_check") showEnforcedRequiredFieldsNotice(needsRequiredFieldsCheck) { return needsRequiredFieldsCheck; - }, + } @discourseComputed("model.user_option.default_calendar") canChangeDefaultCalendar(defaultCalendar) { return defaultCalendar !== "none_selected"; - }, - - canChangeBio: readOnly("model.can_change_bio"), - - canChangeLocation: readOnly("model.can_change_location"), - - canChangeWebsite: readOnly("model.can_change_website"), - - canUploadProfileHeader: readOnly("model.can_upload_profile_header"), - - canUploadUserCardBackground: readOnly( - "model.can_upload_user_card_background" - ), + } @action async showFeaturedTopicModal() { @@ -90,7 +84,7 @@ export default Controller.extend({ }, }); document.querySelector(".feature-topic-on-profile-btn")?.focus(); - }, + } _missingRequiredFields(siteFields, userFields) { return siteFields @@ -100,59 +94,61 @@ export default Controller.extend({ isEmpty(userFields[siteField.id]) ) .map((field) => EmberObject.create({ field, value: "" })); - }, + } - actions: { - clearFeaturedTopicFromProfile() { - this.dialog.yesNoConfirm({ - message: I18n.t("user.feature_topic_on_profile.clear.warning"), - didConfirm: () => { - return ajax(`/u/${this.model.username}/clear-featured-topic`, { - type: "PUT", + @action + clearFeaturedTopicFromProfile() { + this.dialog.yesNoConfirm({ + message: I18n.t("user.feature_topic_on_profile.clear.warning"), + didConfirm: () => { + return ajax(`/u/${this.model.username}/clear-featured-topic`, { + type: "PUT", + }) + .then(() => { + this.model.set("featured_topic", null); }) - .then(() => { - this.model.set("featured_topic", null); - }) - .catch(popupAjaxError); - }, - }); - }, + .catch(popupAjaxError); + }, + }); + } - useCurrentTimezone() { - this.model.set("user_option.timezone", moment.tz.guess()); - }, + @action + useCurrentTimezone() { + this.model.set("user_option.timezone", moment.tz.guess()); + } - _updateUserFields() { - const model = this.model, - userFields = this.userFields; + @action + _updateUserFields() { + const model = this.model, + userFields = this.userFields; - if (!isEmpty(userFields)) { - const modelFields = model.get("user_fields"); - if (!isEmpty(modelFields)) { - userFields.forEach(function (uf) { - const value = uf.get("value"); - modelFields[uf.get("field.id").toString()] = isEmpty(value) - ? null - : value; - }); - } - } - }, - - save() { - this.set("saved", false); - - // Update the user fields - this.send("_updateUserFields"); - - return this.model - .save(this.saveAttrNames) - .then(({ user }) => this.model.set("bio_cooked", user.bio_cooked)) - .catch(popupAjaxError) - .finally(() => { - this.currentUser.set("needs_required_fields_check", false); - this.set("saved", true); + if (!isEmpty(userFields)) { + const modelFields = model.get("user_fields"); + if (!isEmpty(modelFields)) { + userFields.forEach(function (uf) { + const value = uf.get("value"); + modelFields[uf.get("field.id").toString()] = isEmpty(value) + ? null + : value; }); - }, - }, -}); + } + } + } + + @action + save() { + this.set("saved", false); + + // Update the user fields + this.send("_updateUserFields"); + + return this.model + .save(this.saveAttrNames) + .then(({ user }) => this.model.set("bio_cooked", user.bio_cooked)) + .catch(popupAjaxError) + .finally(() => { + this.currentUser.set("needs_required_fields_check", false); + this.set("saved", true); + }); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/second-factor.js b/app/assets/javascripts/discourse/app/controllers/preferences/second-factor.js index a4be7cb75e0..87096303fac 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/second-factor.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/second-factor.js @@ -17,34 +17,35 @@ import { SECOND_FACTOR_METHODS } from "discourse/models/user"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -export default Controller.extend(CanCheckEmails, { - dialog: service(), - modal: service(), - siteSettings: service(), - loading: false, - dirty: false, - errorMessage: null, - newUsername: null, - backupEnabled: alias("model.second_factor_backup_enabled"), - secondFactorMethod: SECOND_FACTOR_METHODS.TOTP, - totps: [], - security_keys: [], +export default class SecondFactorController extends Controller.extend( + CanCheckEmails +) { + @service dialog; + @service modal; + @service siteSettings; - init() { - this._super(...arguments); - }, + loading = false; + dirty = false; + errorMessage = null; + newUsername = null; + + @alias("model.second_factor_backup_enabled") backupEnabled; + + secondFactorMethod = SECOND_FACTOR_METHODS.TOTP; + totps = []; + security_keys = []; @discourseComputed hasOAuth() { return findAll().length > 0; - }, + } @discourseComputed displayOAuthWarning() { return ( this.hasOAuth && this.siteSettings.enforce_second_factor_on_external_auth ); - }, + } @discourseComputed("currentUser") showEnforcedWithOAuthNotice(user) { @@ -54,7 +55,7 @@ export default Controller.extend(CanCheckEmails, { this.hasOAuth && !this.siteSettings.enforce_second_factor_on_external_auth ); - }, + } @discourseComputed("currentUser") showEnforcedNotice(user) { @@ -63,12 +64,12 @@ export default Controller.extend(CanCheckEmails, { user.enforcedSecondFactor && this.siteSettings.enforce_second_factor_on_external_auth ); - }, + } @discourseComputed("totps", "security_keys") hasSecondFactors(totps, security_keys) { return totps.length > 0 || security_keys.length > 0; - }, + } async createToTpModal() { try { @@ -84,7 +85,7 @@ export default Controller.extend(CanCheckEmails, { } catch (error) { popupAjaxError(error); } - }, + } async createSecurityKeyModal() { try { @@ -100,7 +101,7 @@ export default Controller.extend(CanCheckEmails, { } catch (error) { popupAjaxError(error); } - }, + } @action handleError(error) { @@ -116,17 +117,17 @@ export default Controller.extend(CanCheckEmails, { } else { popupAjaxError(error); } - }, + } @action setBackupEnabled(value) { this.set("backupEnabled", value); - }, + } @action setCodesRemaining(value) { this.model.set("second_factor_remaining_backup_codes", value); - }, + } @action loadSecondFactors() { @@ -152,12 +153,12 @@ export default Controller.extend(CanCheckEmails, { }) .catch((e) => this.handleError(e)) .finally(() => this.set("loading", false)); - }, + } @action markDirty() { this.set("dirty", true); - }, + } @action async createTotp() { @@ -177,7 +178,7 @@ export default Controller.extend(CanCheckEmails, { } catch (error) { popupAjaxError(error); } - }, + } @action async createSecurityKey() { @@ -197,175 +198,179 @@ export default Controller.extend(CanCheckEmails, { } catch (error) { popupAjaxError(error); } - }, + } - actions: { - disableAllSecondFactors() { - if (this.loading) { - return; - } + @action + disableAllSecondFactors() { + if (this.loading) { + return; + } - this.dialog.deleteConfirm({ - title: I18n.t("user.second_factor.disable_confirm"), - bodyComponent: SecondFactorConfirmPhrase, - bodyComponentModel: { - totps: this.totps, - security_keys: this.security_keys, - }, - confirmButtonLabel: "user.second_factor.disable", - confirmButtonDisabled: true, - confirmButtonIcon: "ban", - cancelButtonClass: "btn-flat", - didConfirm: () => { - this.model - .disableAllSecondFactors() - .then(() => { - const usernameLower = this.model.username.toLowerCase(); - DiscourseURL.redirectTo(userPath(`${usernameLower}/preferences`)); - }) - .catch((e) => this.handleError(e)) - .finally(() => this.set("loading", false)); - }, - }); - }, - disableSingleSecondFactor(secondFactorMethod) { - if (this.totps.concat(this.security_keys).length === 1) { - this.send("disableAllSecondFactors"); - return; - } - this.dialog.deleteConfirm({ - title: I18n.t("user.second_factor.delete_single_confirm_title"), - message: I18n.t("user.second_factor.delete_single_confirm_message", { - name: secondFactorMethod.name, - }), - confirmButtonLabel: "user.second_factor.delete", - confirmButtonIcon: "ban", - cancelButtonClass: "btn-flat", - didConfirm: () => { - if (this.totps.includes(secondFactorMethod)) { - this.currentUser - .updateSecondFactor( - secondFactorMethod.id, - secondFactorMethod.name, - true, - secondFactorMethod.method - ) - .then((response) => { - if (response.error) { - return; - } - this.markDirty(); - this.set( - "totps", - this.totps.filter( - (totp) => - totp.id !== secondFactorMethod.id || - totp.method !== secondFactorMethod.method - ) - ); - }) - .catch((e) => this.handleError(e)) - .finally(() => { - this.set("loading", false); - }); - } + this.dialog.deleteConfirm({ + title: I18n.t("user.second_factor.disable_confirm"), + bodyComponent: SecondFactorConfirmPhrase, + bodyComponentModel: { + totps: this.totps, + security_keys: this.security_keys, + }, + confirmButtonLabel: "user.second_factor.disable", + confirmButtonDisabled: true, + confirmButtonIcon: "ban", + cancelButtonClass: "btn-flat", + didConfirm: () => { + this.model + .disableAllSecondFactors() + .then(() => { + const usernameLower = this.model.username.toLowerCase(); + DiscourseURL.redirectTo(userPath(`${usernameLower}/preferences`)); + }) + .catch((e) => this.handleError(e)) + .finally(() => this.set("loading", false)); + }, + }); + } - if (this.security_keys.includes(secondFactorMethod)) { - this.currentUser - .updateSecurityKey( - secondFactorMethod.id, - secondFactorMethod.name, - true - ) - .then((response) => { - if (response.error) { - return; - } - this.markDirty(); - this.set( - "security_keys", - this.security_keys.filter( - (securityKey) => securityKey.id !== secondFactorMethod.id - ) - ); - }) - .catch((e) => this.handleError(e)) - .finally(() => { - this.set("loading", false); - }); - } - }, - }); - }, - disableSecondFactorBackup() { - this.dialog.deleteConfirm({ - title: I18n.t("user.second_factor.delete_backup_codes_confirm_title"), - message: I18n.t( - "user.second_factor.delete_backup_codes_confirm_message" - ), - confirmButtonLabel: "user.second_factor.delete", - confirmButtonIcon: "ban", - cancelButtonClass: "btn-flat", - didConfirm: () => { - this.set("backupCodes", []); - this.set("loading", true); - - this.model - .updateSecondFactor(0, "", true, SECOND_FACTOR_METHODS.BACKUP_CODE) + @action + disableSingleSecondFactor(secondFactorMethod) { + if (this.totps.concat(this.security_keys).length === 1) { + this.send("disableAllSecondFactors"); + return; + } + this.dialog.deleteConfirm({ + title: I18n.t("user.second_factor.delete_single_confirm_title"), + message: I18n.t("user.second_factor.delete_single_confirm_message", { + name: secondFactorMethod.name, + }), + confirmButtonLabel: "user.second_factor.delete", + confirmButtonIcon: "ban", + cancelButtonClass: "btn-flat", + didConfirm: () => { + if (this.totps.includes(secondFactorMethod)) { + this.currentUser + .updateSecondFactor( + secondFactorMethod.id, + secondFactorMethod.name, + true, + secondFactorMethod.method + ) .then((response) => { if (response.error) { - this.set("errorMessage", response.error); return; } - - this.set("errorMessage", null); - this.model.set("second_factor_backup_enabled", false); this.markDirty(); - this.send("closeModal"); + this.set( + "totps", + this.totps.filter( + (totp) => + totp.id !== secondFactorMethod.id || + totp.method !== secondFactorMethod.method + ) + ); }) - .catch((error) => { - this.send("closeModal"); - this.onError(error); + .catch((e) => this.handleError(e)) + .finally(() => { + this.set("loading", false); + }); + } + + if (this.security_keys.includes(secondFactorMethod)) { + this.currentUser + .updateSecurityKey( + secondFactorMethod.id, + secondFactorMethod.name, + true + ) + .then((response) => { + if (response.error) { + return; + } + this.markDirty(); + this.set( + "security_keys", + this.security_keys.filter( + (securityKey) => securityKey.id !== secondFactorMethod.id + ) + ); }) - .finally(() => this.set("loading", false)); - }, - }); - }, + .catch((e) => this.handleError(e)) + .finally(() => { + this.set("loading", false); + }); + } + }, + }); + } - async editSecurityKey(security_key) { - await this.modal.show(SecondFactorEditSecurityKey, { - model: { - securityKey: security_key, - user: this.model, - markDirty: () => this.markDirty(), - onError: (e) => this.handleError(e), - }, - }); - this.loadSecondFactors(); - }, + @action + disableSecondFactorBackup() { + this.dialog.deleteConfirm({ + title: I18n.t("user.second_factor.delete_backup_codes_confirm_title"), + message: I18n.t("user.second_factor.delete_backup_codes_confirm_message"), + confirmButtonLabel: "user.second_factor.delete", + confirmButtonIcon: "ban", + cancelButtonClass: "btn-flat", + didConfirm: () => { + this.set("backupCodes", []); + this.set("loading", true); - async editSecondFactor(second_factor) { - await this.modal.show(SecondFactorEdit, { - model: { - secondFactor: second_factor, - user: this.model, - markDirty: () => this.markDirty(), - onError: (e) => this.handleError(e), - }, - }); - this.loadSecondFactors(); - }, + this.model + .updateSecondFactor(0, "", true, SECOND_FACTOR_METHODS.BACKUP_CODE) + .then((response) => { + if (response.error) { + this.set("errorMessage", response.error); + return; + } - async editSecondFactorBackup() { - await this.modal.show(SecondFactorBackupEdit, { - model: { - secondFactor: this.model, - markDirty: () => this.markDirty(), - onError: (e) => this.handleError(e), - setBackupEnabled: (e) => this.setBackupEnabled(e), - setCodesRemaining: (e) => this.setCodesRemaining(e), - }, - }); - }, - }, -}); + this.set("errorMessage", null); + this.model.set("second_factor_backup_enabled", false); + this.markDirty(); + this.send("closeModal"); + }) + .catch((error) => { + this.send("closeModal"); + this.onError(error); + }) + .finally(() => this.set("loading", false)); + }, + }); + } + + @action + async editSecurityKey(security_key) { + await this.modal.show(SecondFactorEditSecurityKey, { + model: { + securityKey: security_key, + user: this.model, + markDirty: () => this.markDirty(), + onError: (e) => this.handleError(e), + }, + }); + this.loadSecondFactors(); + } + + @action + async editSecondFactor(second_factor) { + await this.modal.show(SecondFactorEdit, { + model: { + secondFactor: second_factor, + user: this.model, + markDirty: () => this.markDirty(), + onError: (e) => this.handleError(e), + }, + }); + this.loadSecondFactors(); + } + + @action + async editSecondFactorBackup() { + await this.modal.show(SecondFactorBackupEdit, { + model: { + secondFactor: this.model, + markDirty: () => this.markDirty(), + onError: (e) => this.handleError(e), + setBackupEnabled: (e) => this.setBackupEnabled(e), + setCodesRemaining: (e) => this.setCodesRemaining(e), + }, + }); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/security.js b/app/assets/javascripts/discourse/app/controllers/preferences/security.js index 2cc9dae7136..e7bca40e345 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/security.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/security.js @@ -16,13 +16,19 @@ import I18n from "discourse-i18n"; // Number of tokens shown by default. const DEFAULT_AUTH_TOKENS_COUNT = 2; -export default Controller.extend(CanCheckEmails, { - modal: service(), - dialog: service(), - router: service(), - passwordProgress: null, - subpageTitle: I18n.t("user.preferences_nav.security"), - showAllAuthTokens: false, +export default class SecurityController extends Controller.extend( + CanCheckEmails +) { + @service modal; + @service dialog; + @service router; + + passwordProgress = null; + subpageTitle = I18n.t("user.preferences_nav.security"); + showAllAuthTokens = false; + + @gt("model.user_auth_tokens.length", DEFAULT_AUTH_TOKENS_COUNT) + canShowAllAuthTokens; get canUsePasskeys() { return ( @@ -31,7 +37,7 @@ export default Controller.extend(CanCheckEmails, { this.siteSettings.enable_passkeys && isWebauthnSupported() ); - }, + } @discourseComputed("model.is_anonymous") canChangePassword(isAnonymous) { @@ -43,7 +49,7 @@ export default Controller.extend(CanCheckEmails, { this.siteSettings.enable_local_logins ); } - }, + } @discourseComputed("showAllAuthTokens", "model.user_auth_tokens") authTokens(showAllAuthTokens, tokens) { @@ -60,12 +66,7 @@ export default Controller.extend(CanCheckEmails, { return showAllAuthTokens ? tokens : tokens.slice(0, DEFAULT_AUTH_TOKENS_COUNT); - }, - - canShowAllAuthTokens: gt( - "model.user_auth_tokens.length", - DEFAULT_AUTH_TOKENS_COUNT - ), + } @action changePassword(event) { @@ -89,13 +90,13 @@ export default Controller.extend(CanCheckEmails, { }); }); } - }, + } @action toggleShowAllAuthTokens(event) { event?.preventDefault(); this.toggleProperty("showAllAuthTokens"); - }, + } @action revokeAuthToken(token, event) { @@ -115,7 +116,7 @@ export default Controller.extend(CanCheckEmails, { } // All sessions revoked }) .catch(popupAjaxError); - }, + } @action async manage2FA() { @@ -136,19 +137,17 @@ export default Controller.extend(CanCheckEmails, { } catch (error) { popupAjaxError(error); } - }, + } - actions: { - save() { - this.set("saved", false); + @action + save() { + this.set("saved", false); - return this.model - .then(() => this.set("saved", true)) - .catch(popupAjaxError); - }, + return this.model.then(() => this.set("saved", true)).catch(popupAjaxError); + } - showToken(token) { - this.modal.show(AuthTokenModal, { model: token }); - }, - }, -}); + @action + showToken(token) { + this.modal.show(AuthTokenModal, { model: token }); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/tags.js b/app/assets/javascripts/discourse/app/controllers/preferences/tags.js index 8bf794ae543..d8a1917ccc2 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/tags.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/tags.js @@ -1,18 +1,15 @@ import Controller from "@ember/controller"; +import { action } from "@ember/object"; import { popupAjaxError } from "discourse/lib/ajax-error"; import discourseComputed from "discourse-common/utils/decorators"; -export default Controller.extend({ - init() { - this._super(...arguments); - - this.saveAttrNames = [ - "muted_tags", - "tracked_tags", - "watched_tags", - "watching_first_post_tags", - ]; - }, +export default class TagsController extends Controller { + saveAttrNames = [ + "muted_tags", + "tracked_tags", + "watched_tags", + "watching_first_post_tags", + ]; @discourseComputed( "model.watched_tags.[]", @@ -22,17 +19,16 @@ export default Controller.extend({ ) selectedTags(watched, watchedFirst, tracked, muted) { return [].concat(watched, watchedFirst, tracked, muted).filter((t) => t); - }, + } - actions: { - save() { - this.set("saved", false); - return this.model - .save(this.saveAttrNames) - .then(() => { - this.set("saved", true); - }) - .catch(popupAjaxError); - }, - }, -}); + @action + save() { + this.set("saved", false); + return this.model + .save(this.saveAttrNames) + .then(() => { + this.set("saved", true); + }) + .catch(popupAjaxError); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/users.js b/app/assets/javascripts/discourse/app/controllers/preferences/users.js index 4c6719e3f4d..7a70d328128 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/users.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/users.js @@ -5,38 +5,37 @@ import { popupAjaxError } from "discourse/lib/ajax-error"; import { makeArray } from "discourse-common/lib/helpers"; import discourseComputed from "discourse-common/utils/decorators"; -export default Controller.extend({ - allowPmUsersEnabled: and( +export default class UsersController extends Controller { + @and( "model.user_option.enable_allowed_pm_users", "model.user_option.allow_private_messages" - ), + ) + allowPmUsersEnabled; - mutedUsernames: computed("model.muted_usernames", { - get() { - let usernames = this.model.muted_usernames; + @computed("model.muted_usernames") + get mutedUsernames() { + let usernames = this.model.muted_usernames; - if (typeof usernames === "string") { - usernames = usernames.split(",").filter(Boolean); - } + if (typeof usernames === "string") { + usernames = usernames.split(",").filter(Boolean); + } - return makeArray(usernames).uniq(); - }, - }), + return makeArray(usernames).uniq(); + } - allowedPmUsernames: computed("model.allowed_pm_usernames", { - get() { - let usernames = this.model.allowed_pm_usernames; + @computed("model.allowed_pm_usernames") + get allowedPmUsernames() { + let usernames = this.model.allowed_pm_usernames; - if (typeof usernames === "string") { - usernames = usernames.split(",").filter(Boolean); - } + if (typeof usernames === "string") { + usernames = usernames.split(",").filter(Boolean); + } - return makeArray(usernames).uniq(); - }, - }), + return makeArray(usernames).uniq(); + } init() { - this._super(...arguments); + super.init(...arguments); this.saveAttrNames = [ "allow_private_messages", @@ -44,22 +43,22 @@ export default Controller.extend({ "allowed_pm_usernames", "enable_allowed_pm_users", ]; - }, + } @action onChangeMutedUsernames(usernames) { this.model.set("muted_usernames", usernames.uniq().join(",")); - }, + } @action onChangeAllowedPmUsernames(usernames) { this.model.set("allowed_pm_usernames", usernames.uniq().join(",")); - }, + } @discourseComputed("model.user_option.allow_private_messages") disableAllowPmUsersSetting(allowPrivateMessages) { return !allowPrivateMessages; - }, + } @action save() { @@ -69,5 +68,5 @@ export default Controller.extend({ .save(this.saveAttrNames) .then(() => this.set("saved", true)) .catch(popupAjaxError); - }, -}); + } +}