diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 06fbf45f9..247218091 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ # This file is a template, and might need editing before it works on your project. # Official framework image. Look for the different tagged releases at: # https://hub.docker.com/r/library/node/tags/ -image: node:18 +image: node:20 stages: - check-changelog diff --git a/.node-version b/.node-version index 08b7109d0..5bd681170 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -18.20.8 +20.19.0 diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml index 059fdb1ff..af0bb98e3 100644 --- a/.woodpecker/build.yaml +++ b/.woodpecker/build.yaml @@ -16,7 +16,7 @@ labels: steps: build: - image: docker.io/node:18-alpine + image: docker.io/node:20-alpine commands: - apk add --no-cache zip git - yarn --frozen-lockfile diff --git a/.woodpecker/lint.yaml b/.woodpecker/lint.yaml index 237135ee9..257887338 100644 --- a/.woodpecker/lint.yaml +++ b/.woodpecker/lint.yaml @@ -9,7 +9,7 @@ when: steps: install-depends: image: &node-image - docker.io/node:18-alpine + docker.io/node:20-alpine commands: - yarn --frozen-lockfile diff --git a/changelog.d/profilebg.add b/changelog.d/profilebg.add new file mode 100644 index 000000000..a2c79074a --- /dev/null +++ b/changelog.d/profilebg.add @@ -0,0 +1 @@ +displaying other user's backgrounds (if supported by BE) diff --git a/changelog.d/user-management.add b/changelog.d/user-management.add new file mode 100644 index 000000000..28dfc2220 --- /dev/null +++ b/changelog.d/user-management.add @@ -0,0 +1 @@ +user management (view and modify user info, view and modify user statuses) diff --git a/src/App.js b/src/App.js index 16201e9ea..ea2271a75 100644 --- a/src/App.js +++ b/src/App.js @@ -159,7 +159,10 @@ export default { return this.currentUser.background_image }, foreignProfileBackground() { - return useMergedConfigStore().mergedConfig.allowForeignUserBackground && useInterfaceStore().foreignProfileBackground + return ( + useMergedConfigStore().mergedConfig.allowForeignUserBackground && + useInterfaceStore().foreignProfileBackground + ) }, instanceBackground() { return useMergedConfigStore().mergedConfig.hideInstanceWallpaper @@ -167,7 +170,11 @@ export default { : this.instanceBackgroundUrl }, background() { - return this.foreignProfileBackground || this.userBackground || this.instanceBackground + return ( + this.foreignProfileBackground || + this.userBackground || + this.instanceBackground + ) }, bgStyle() { if (this.background) { diff --git a/src/boot/routes.js b/src/boot/routes.js index 4a075e955..9b00a8004 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -84,6 +84,13 @@ export default (store) => { () => import('src/components/user_profile/user_profile.vue'), ), }, + { + name: 'user-profile-admin-view', + path: '/users/$:id/admin_view', + component: defineAsyncComponent( + () => import('src/components/user_profile/user_profile_admin_view.vue'), + ), + }, { name: 'interactions', path: '/users/:username/interactions', diff --git a/src/components/alert.style.js b/src/components/alert.style.js index 8a6f842ed..6876faca8 100644 --- a/src/components/alert.style.js +++ b/src/components/alert.style.js @@ -4,6 +4,7 @@ export default { validInnerComponents: ['Text', 'Icon', 'Link', 'Border', 'ButtonUnstyled'], variants: { normal: '.neutral', + info: '.info', error: '.error', warning: '.warning', success: '.success', @@ -47,5 +48,11 @@ export default { background: '--cGreen', }, }, + { + variant: 'info', + directives: { + background: '--cBlue', + }, + }, ], } diff --git a/src/components/announcement/announcement.js b/src/components/announcement/announcement.js index 7f559f8fe..ee427533f 100644 --- a/src/components/announcement/announcement.js +++ b/src/components/announcement/announcement.js @@ -31,9 +31,7 @@ const Announcement = { canEditAnnouncement() { return ( this.currentUser && - this.currentUser.privileges.includes( - 'announcements_manage_announcements', - ) + this.currentUser.privileges.has('announcements_manage_announcements') ) }, content() { diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js index 1c69f81b4..ae1f1c9c6 100644 --- a/src/components/basic_user_card/basic_user_card.js +++ b/src/components/basic_user_card/basic_user_card.js @@ -8,7 +8,15 @@ import { useMergedConfigStore } from 'src/stores/merged_config.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' const BasicUserCard = { - props: ['user'], + props: { + user: { + type: Object, + }, + showLineLabels: { + type: Boolean, + default: false, + }, + }, components: { UserPopover, UserAvatar, diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue index 572b07abb..2de8b7b36 100644 --- a/src/components/basic_user_card/basic_user_card.vue +++ b/src/components/basic_user_card/basic_user_card.vue @@ -23,6 +23,10 @@ :title="user.name" class="basic-user-card-user-name" > + + {{ $t('admin_dash.users.labels.name_colon') }} + {{ ' ' }} +
+ + {{ $t('admin_dash.users.labels.handle_colon') }} + {{ ' ' }} + - +
+ +
+ +
+
+ diff --git a/src/components/confirm_modal/generic_confirm.js b/src/components/confirm_modal/generic_confirm.js new file mode 100644 index 000000000..82bb0e54e --- /dev/null +++ b/src/components/confirm_modal/generic_confirm.js @@ -0,0 +1,40 @@ +import ConfirmModal from './confirm_modal.vue' +//import Select from 'src/components/select/select.vue' + +export default { + props: { + title: { + type: String, + }, + message: { + type: String, + }, + cancelText: { + type: String, + }, + confirmText: { + type: String, + }, + }, + emits: ['hide', 'show', 'action'], + data: () => ({ + showing: false, + }), + components: { + ConfirmModal, + }, + methods: { + show() { + this.showing = true + this.$emit('show') + }, + hide() { + this.showing = false + this.$emit('hide') + }, + doGeneric() { + this.$emit('action') + this.hide() + }, + }, +} diff --git a/src/components/confirm_modal/generic_confirm.vue b/src/components/confirm_modal/generic_confirm.vue new file mode 100644 index 000000000..d223d6ea9 --- /dev/null +++ b/src/components/confirm_modal/generic_confirm.vue @@ -0,0 +1,23 @@ + + + + + + diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js index d31002fad..6e44bf97b 100644 --- a/src/components/moderation_tools/moderation_tools.js +++ b/src/components/moderation_tools/moderation_tools.js @@ -1,6 +1,9 @@ -import DialogModal from 'src/components/dialog_modal/dialog_modal.vue' +import { last } from 'lodash' + +import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue' import Popover from 'src/components/popover/popover.vue' +import { useAdminSettingsStore } from 'src/stores/admin_settings.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' @@ -16,41 +19,382 @@ const DISABLE_REMOTE_SUBSCRIPTION = 'mrf_tag:disable-remote-subscription' const DISABLE_ANY_SUBSCRIPTION = 'mrf_tag:disable-any-subscription' const SANDBOX = 'mrf_tag:sandbox' const QUARANTINE = 'mrf_tag:quarantine' +const TAGS = new Set([ + FORCE_NSFW, + STRIP_MEDIA, + FORCE_UNLISTED, + DISABLE_REMOTE_SUBSCRIPTION, + DISABLE_ANY_SUBSCRIPTION, + SANDBOX, + QUARANTINE, +]) + +const ENTRIES = [ + { + check: '!state:activated', + label: 'user_card.admin_menu.activate_account', + }, + { + check: 'state:activated', + label: 'user_card.admin_menu.deactivate_account', + }, + { + separator: true, + }, + { + check: '!state:confirmed', + label: 'user_card.admin_menu.confirm_account', + }, + { + check: 'action:resend_confirmation', + conditions: ['!state:confirmed'], + label: 'user_card.admin_menu.resend_confirmation', + }, + // No API for revocation + // { + // check: 'state:confirmed', + // label: 'user_card.admin_menu.unconfirm_account', + // }, + { + check: '!state:approved', + conditions: ['property:local'], + label: 'user_card.admin_menu.approve_account', + }, + // No API for revocation + // { + // check: 'state:approved', + // label: 'user_card.admin_menu.unapprove_account', + // }, + { + check: '!state:suggested', + // conditions: ['property:local'], // TODO Should we allow non-local users in suggested? + label: 'user_card.admin_menu.suggest_account', + }, + { + check: 'state:suggested', + label: 'user_card.admin_menu.remove_suggested_account', + }, + { + separator: true, + }, + { + check: 'action:statuses', + label: 'user_card.admin_menu.show_statuses', + }, + { + separator: true, + }, + { + check: 'action:disable_mfa', + label: 'user_card.admin_menu.disable_mfa', + }, + { + check: 'action:require_password_change', + label: 'user_card.admin_menu.require_password_change', + }, + { + separator: true, + }, + { + check: '!rights:moderator', + label: 'user_card.admin_menu.grant_moderator', + conditions: ['property:local', 'state:activated'], + }, + { + check: 'rights:moderator', + label: 'user_card.admin_menu.revoke_moderator', + conditions: ['property:local', 'state:activated'], + }, + { + check: '!rights:admin', + label: 'user_card.admin_menu.grant_admin', + conditions: ['property:local', 'state:activated'], + }, + { + check: 'rights:admin', + label: 'user_card.admin_menu.revoke_admin', + conditions: ['property:local', 'state:activated'], + }, + { + separator: true, + }, + { + check: FORCE_NSFW, + label: 'user_card.admin_menu.force_nsfw', + }, + { + check: STRIP_MEDIA, + label: 'user_card.admin_menu.strip_media', + }, + { + check: FORCE_UNLISTED, + label: 'user_card.admin_menu.force_unlisted', + }, + { + check: SANDBOX, + label: 'user_card.admin_menu.sandbox', + }, + { + check: DISABLE_ANY_SUBSCRIPTION, + conditions: ['property:local'], + label: 'user_card.admin_menu.disable_any_subscription', + }, + { + check: DISABLE_REMOTE_SUBSCRIPTION, + conditions: ['property:local'], + label: 'user_card.admin_menu.disable_remote_subscription', + }, + { + check: QUARANTINE, + conditions: ['property:local'], + label: 'user_card.admin_menu.quarantine', + }, + { + separator: true, + }, + { + check: 'action:delete', + label: 'user_card.admin_menu.delete_account', + }, +] const ModerationTools = { - props: ['user'], + props: { + users: { + type: Array, + required: true, + }, + }, + created() { + if (this.users.length !== 1) return + useAdminSettingsStore().getUserData({ user: this.users[0] }) + }, data() { return { - tags: { - FORCE_NSFW, - STRIP_MEDIA, - FORCE_UNLISTED, - DISABLE_REMOTE_SUBSCRIPTION, - DISABLE_ANY_SUBSCRIPTION, - SANDBOX, - QUARANTINE, - }, - showDeleteUserDialog: false, - toggled: false, + open: false, + confirmDialogShow: false, + confirmDialogTitle: null, + confirmDialogContent: null, + confirmDialogConfirm: null, + confirmDialogAction: null, + confirmDialogGroup: null, + confirmDialogName: null, } }, components: { - DialogModal, + ConfirmModal, Popover, }, computed: { + ready() { + return this.users.every((u) => u.adminData) + }, + entries() { + return ENTRIES.map(({ check, label, separator, conditions }) => { + if (separator) return 'separator' + const [, negateToken, group, name] = + /^([!~]?)([a-z-_]+):([a-z-_]+)$/.exec(check) + + const hasTag = this.tagsSet.has(`${group}:${name}`) + const noTag = this.tagsSet.has(`!${group}:${name}`) + const maybeTag = this.tagsSet.has(`~${group}:${name}`) + + // We are checking for condition to show element, i.e. only show "activate" if user is "deactivated" + const checkNegated = negateToken === '!' || negateToken === '~' + + // Naturally, new value should also be the same + const value = checkNegated + + const action = (() => { + switch (group) { + case 'rights': + return () => this.setRight(name, value) + case 'state': + return () => this.setStatus(name, value) + case 'mrf_tag': + return () => this.setTag(`${group}:${name}`, noTag) + case 'action': { + switch (name) { + case 'delete': { + return () => this.deleteUsers() + } + case 'resend_confirmation': { + return () => this.resendConfirmationEmail() + } + case 'disable_mfa': { + return () => this.disableMFA() + } + case 'statuses': { + return () => + this.$router.push(`/users/\$${this.users[0].id}/admin_view`) + } + case 'require_password_change': { + return () => this.requirePasswordChange() + } + default: + throw new Error(`Unknown action group: ${name}`) + } + } + default: + throw new Error(`Unknown moderation group: ${group}`) + } + })() + + let checkboxClass = '' + if (maybeTag) { + checkboxClass = 'menu-checkbox-indeterminate' + } else if (hasTag) { + checkboxClass = 'menu-checkbox-checked' + } + + return { + check, + negateToken, + checkbox: group === 'mrf_tag', + checkboxClass, + conditions, + group, + name, + action, + label, + value, + } + }) + .filter((entry) => { + if (entry === 'separator') return true + const { group, name, value, conditions } = entry + + if (conditions) { + // Checking that all items match positive criteria + const positive = conditions.every((condition) => + this.totalSet.has(condition), + ) + // Checking that there are no items that don't match criteria + const negative = conditions.some((condition) => + this.totalSet.has('!' + condition), + ) + if (!(positive && !negative)) return false + } + + switch (group) { + case 'action': { + return true + } + case 'rights': { + return this.canGrantRole(name, value) + } + case 'state': { + return this.canChangeState(name, value) + } + case 'mrf_tag': { + return this.canUseTagPolicy + } + default: { + throw new Error(`Unknown moderation group: ${group}`) + } + } + }) + .reduce((acc, entry, index) => { + if (entry === 'separator') { + if ( + acc.length === 0 || + last(acc) === 'separator' || + index === ENTRIES.length - 1 + ) { + return acc + } + } + return [...acc, entry] + }, []) + }, + rightsSet() { + return this.users.reduce((acc, user) => { + if (user.rights.admin) { + acc.add('rights:admin') + } else { + acc.add('!rights:admin') + } + if (user.rights.moderator) { + acc.add('rights:moderator') + } else { + acc.add('!rights:moderator') + } + return acc + }, new Set()) + }, + stateSet() { + return this.users.reduce((acc, user) => { + if (!user.deactivated) { + acc.add('state:activated') + } else { + acc.add('!state:activated') + } + if (user.adminData?.is_confirmed) { + acc.add('state:confirmed') + } else { + acc.add('!state:confirmed') + } + if (user.adminData?.is_approved) { + acc.add('state:approved') + } else { + acc.add('!state:approved') + } + if (user.adminData?.is_suggested) { + acc.add('state:suggested') + } else { + acc.add('!state:suggested') + } + return acc + }, new Set()) + }, tagsSet() { - return new Set(this.user.tags) + const present = new Set() + const missing = new Set() + + this.users.forEach((user) => { + TAGS.forEach((tag) => { + if (user.tags.has(tag)) { + present.add(tag) + } else { + missing.add(tag) + } + }) + }) + + const result = new Set() + + TAGS.forEach((tag) => { + if (present.has(tag) && missing.has(tag)) { + result.add(`~${tag}`) + } else if (missing.has(tag)) { + result.add(`!${tag}`) + } else { + result.add(tag) + } + }) + + return result }, - canGrantRole() { - return ( - this.user.is_local && - !this.user.deactivated && - this.$store.state.users.currentUser.role === 'admin' - ) + propertySet() { + return this.users.reduce((acc, user) => { + if (user.is_local) { + acc.add('property:local') + } else { + acc.add('!property:local') + } + return acc + }, new Set()) }, - canChangeActivationState() { - return this.privileged('users_manage_activation_state') + disabled() { + return !this.ready || this.users.length === 0 + }, + totalSet() { + return new Set([ + ...this.rightsSet, + ...this.stateSet, + ...this.tagsSet, + ...this.propertySet, + ]) }, canDeleteAccount() { return this.privileged('users_delete') @@ -63,87 +407,260 @@ const ModerationTools = { }, }, methods: { - hasTag(tagName) { - return this.tagsSet.has(tagName) + canGrantRole(name, value) { + const setEntry = `${value ? '!' : ''}rights:${name}` + + return ( + this.$store.state.users.currentUser.role === 'admin' && + this.totalSet.has(setEntry) + ) + }, + canChangeState(name, value) { + let privilege + + switch (name) { + // TODO detailed privileges + default: { + privilege = 'users_manage_activation_state' + } + } + + const setEntry = `${value ? '!' : ''}state:${name}` + + return this.privileged(privilege) && this.totalSet.has(setEntry) + }, + doConfirmDialogAction() { + if (typeof this.confirmDialogAction !== 'function') { + console.error('Confirm Dialog action is not a function!!') + } + + this.confirmDialogAction() + this.clearConfirmDialog() + }, + clearConfirmDialog() { + this.confirmDialogShow = false + this.confirmDialogTitle = null + this.confirmDialogContent = null + this.confirmDialogContent2 = null + this.confirmDialogDanger = false + this.confirmDialogConfirm = null + this.confirmDialogAction = null + this.confirmDialogGroup = null + this.confirmDialogName = null }, privileged(privilege) { - return this.$store.state.users.currentUser.privileges.includes(privilege) + return this.$store.state.users.currentUser.privileges.has(privilege) }, - toggleTag(tag) { - const store = this.$store - if (this.tagsSet.has(tag)) { - store.state.api.backendInteractor - .untagUser({ user: this.user, tag }) - .then((response) => { - if (!response.ok) { - return - } - store.commit('untagUser', { user: this.user, tag }) - }) - } else { - store.state.api.backendInteractor - .tagUser({ user: this.user, tag }) - .then((response) => { - if (!response.ok) { - return - } - store.commit('tagUser', { user: this.user, tag }) - }) - } - }, - toggleRight(right) { - const store = this.$store - if (this.user.rights[right]) { - store.state.api.backendInteractor - .deleteRight({ user: this.user, right }) - .then((response) => { - if (!response.ok) { - return - } - store.commit('updateRight', { - user: this.user, - right, - value: false, - }) - }) - } else { - store.state.api.backendInteractor - .addRight({ user: this.user, right }) - .then((response) => { - if (!response.ok) { - return - } - store.commit('updateRight', { user: this.user, right, value: true }) - }) - } - }, - toggleActivationStatus() { - this.$store.dispatch('toggleActivationStatus', { user: this.user }) - }, - deleteUserDialog(show) { - this.showDeleteUserDialog = show - }, - deleteUser() { - const store = this.$store - const user = this.user - const { id, name } = user - store.state.api.backendInteractor.deleteUser({ user }).then(() => { - this.$store.dispatch( - 'markStatusesAsDeleted', - (status) => user.id === status.user.id, - ) - const isProfile = - this.$route.name === 'external-user-profile' || - this.$route.name === 'user-profile' - const isTargetUser = - this.$route.params.name === name || this.$route.params.id === id - if (isProfile && isTargetUser) { - window.history.back() - } + setTag(tag, value) { + useAdminSettingsStore().setUsersTags({ + users: this.users, + value, + tags: [tag], }) }, - setToggled(value) { - this.toggled = value + setRight(right, value) { + useAdminSettingsStore().setUsersRight({ users: this.users, value, right }) + }, + setStatus(name, value) { + const noun = (() => { + switch (name) { + case 'activated': + return 'Activation' + case 'confirmed': + return 'Confirmation' + case 'approved': + return 'Approval' + case 'suggested': + return 'Suggestion' + } + })() + + useAdminSettingsStore()[`setUsers${noun}Status`]({ + users: this.users, + value, + }) + }, + resendConfirmationEmail() { + useAdminSettingsStore().resendConfirmationEmail({ users: this.users }) + }, + requirePasswordChange() { + useAdminSettingsStore().requirePasswordChange({ users: this.users }) + }, + disableMFA() { + this.users.forEach((user) => { + useAdminSettingsStore().disableMFA({ user }) + }) + }, + deleteUsers() { + const { id, name } = this.users[0] + + useAdminSettingsStore() + .deleteUsers({ users: this.users }) + .then((userIds) => { + if (userIds.length > 1) return + + const isProfile = + this.$route.name === 'external-user-profile' || + this.$route.name === 'user-profile' + const isTargetUser = + this.$route.params.name === name || this.$route.params.id === id + + if (isProfile && isTargetUser) { + window.history.back() + } + }) + }, + setOpen(value) { + this.open = value + }, + maybeShowConfirm(close, { group, name, action, value }) { + close() + this.confirmDialogName = name + this.confirmDialogGroup = group + this.confirmDialogAction = () => action() + + switch (group) { + case 'action': { + switch (name) { + case 'delete': { + this.confirmDialogShow = true + this.confirmDialogTitle = this.$t( + 'user_card.admin_menu.confirm_modal.delete_title', + ) + this.confirmDialogDanger = true + this.confirmDialogContent = + 'user_card.admin_menu.confirm_modal.delete_content' + this.confirmDialogContent2 = + 'user_card.admin_menu.confirm_modal.delete_content_2' + this.confirmDialogConfirm = this.$t( + 'user_card.admin_menu.confirm_modal.delete', + ) + break + } + case 'resend_confirmation': { + this.confirmDialogShow = true + this.confirmDialogTitle = this.$t( + 'user_card.admin_menu.confirm_modal.resend_confirmation_title', + ) + this.confirmDialogContent = + 'user_card.admin_menu.confirm_modal.resend_confirmation_content' + this.confirmDialogConfirm = this.$t( + 'user_card.admin_menu.confirm_modal.send', + ) + break + } + case 'disable_mfa': { + this.confirmDialogShow = true + this.confirmDialogTitle = this.$t( + 'user_card.admin_menu.confirm_modal.disable_mfa_title', + ) + this.confirmDialogContent = + 'user_card.admin_menu.confirm_modal.disable_mfa_content' + this.confirmDialogConfirm = this.$t('settings.confirm') + break + } + case 'require_password_change': { + this.confirmDialogShow = true + this.confirmDialogTitle = this.$t( + 'user_card.admin_menu.confirm_modal.require_password_change_title', + ) + this.confirmDialogContent = + 'user_card.admin_menu.confirm_modal.require_password_change_content' + this.confirmDialogConfirm = this.$t('settings.confirm') + break + } + } + break + } + case 'state': { + switch (name) { + case 'activated': { + this.confirmDialogShow = true + + this.confirmDialogTitle = this.$t( + 'user_card.admin_menu.confirm_modal.activate_title', + ) + this.confirmDialogContent = value + ? 'user_card.admin_menu.confirm_modal.activate_content' + : 'user_card.admin_menu.confirm_modal.deactivate_content' + this.confirmDialogConfirm = value + ? this.$t('user_card.admin_menu.confirm_modal.activate') + : this.$t('user_card.admin_menu.confirm_modal.deactivate') + break + } + // Confirmation and Approval statuses cannot be revokedn(no API) + case 'confirmed': { + this.confirmDialogTitle = this.$t( + 'user_card.admin_menu.confirm_modal.confirm_title', + ) + this.confirmDialogContent = //value + /*?*/ 'user_card.admin_menu.confirm_modal.confirm_content' + //: 'user_card.admin_menu.confirm_modal.confirm_revoke_content' + this.confirmDialogConfirm = value + /*?*/ this.$t('user_card.admin_menu.confirm_modal.confirm') + //: this.$t('user_card.admin_menu.confirm_modal.revoke') + break + } + case 'approved': { + this.confirmDialogTitle = this.$t( + 'user_card.admin_menu.confirm_modal.approval_title', + ) + this.confirmDialogContent = //value + /*?*/ 'user_card.admin_menu.confirm_modal.approval_content' + //: 'user_card.admin_menu.confirm_modal.approval_revoke_content' + this.confirmDialogConfirm = value + /*?*/ this.$t('user_card.admin_menu.confirm_modal.approve') + //: this.$t('user_card.admin_menu.confirm_modal.revoke') + break + } + case 'suggested': { + this.confirmDialogTitle = this.$t( + 'user_card.admin_menu.confirm_modal.suggest_title', + ) + this.confirmDialogContent = value + ? 'user_card.admin_menu.confirm_modal.add_suggest_content' + : 'user_card.admin_menu.confirm_modal.remove_suggest_content' + this.confirmDialogConfirm = value + ? this.$t('user_card.admin_menu.confirm_modal.add') + : this.$t('user_card.admin_menu.confirm_modal.remove') + break + } + } + break + } + case 'rights': { + this.confirmDialogTitle = this.$t( + 'user_card.admin_menu.confirm_modal.user_rights_title', + ) + this.confirmDialogContent = value + ? 'user_card.admin_menu.confirm_modal.grant_role_content' + : 'user_card.admin_menu.confirm_modal.revoke_role_content' + this.confirmDialogConfirm = value + ? this.$t('user_card.admin_menu.confirm_modal.grant') + : this.$t('user_card.admin_menu.confirm_modal.revoke') + break + } + case 'mrf_tag': { + this.confirmDialogTitle = this.$t( + 'user_card.admin_menu.user_tag_title', + ) + this.confirmDialogContent = value + ? 'user_card.admin_menu.confirm_modal.assign_tag_content' + : 'user_card.admin_menu.confirm_modal.unassign_tag_content' + this.confirmDialogConfirm = value + ? this.$t('user_card.admin_menu.confirm_modal.assign') + : this.$t('user_card.admin_menu.confirm_modal.unassign') + break + } + } + + if (this.users.length > 1) { + this.confirmDialogShow = true + } + + if (!this.confirmDialogShow) { + this.doConfirmDialogAction() + } }, }, } diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue index da33b89dc..43612c693 100644 --- a/src/components/moderation_tools/moderation_tools.vue +++ b/src/components/moderation_tools/moderation_tools.vue @@ -3,154 +3,37 @@ -