From ce43c81ce81518dc2b91860c4a534f90163aec33 Mon Sep 17 00:00:00 2001 From: luce Date: Tue, 15 Jul 2025 14:30:46 +0200 Subject: [PATCH 01/83] list users, fetch updates, make them clickable --- .../selectable_list/selectable_list.js | 4 + .../selectable_list/selectable_list.vue | 21 ++++ .../settings_modal/admin_tabs/admin_card.js | 25 +++++ .../settings_modal/admin_tabs/admin_card.vue | 45 ++++++++ .../settings_modal/admin_tabs/users_tab.js | 100 ++++++++++++++++++ .../settings_modal/admin_tabs/users_tab.vue | 14 +++ .../settings_modal_admin_content.js | 2 + .../settings_modal_admin_content.vue | 8 ++ src/i18n/en.json | 13 +++ src/modules/adminSettings.js | 3 + src/services/api/api.service.js | 10 +- 11 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 src/components/settings_modal/admin_tabs/admin_card.js create mode 100644 src/components/settings_modal/admin_tabs/admin_card.vue create mode 100644 src/components/settings_modal/admin_tabs/users_tab.js create mode 100644 src/components/settings_modal/admin_tabs/users_tab.vue diff --git a/src/components/selectable_list/selectable_list.js b/src/components/selectable_list/selectable_list.js index 10980d46a..b097e8927 100644 --- a/src/components/selectable_list/selectable_list.js +++ b/src/components/selectable_list/selectable_list.js @@ -7,6 +7,10 @@ const SelectableList = { Checkbox }, props: { + boxOnly: { + type: Boolean, + default: false + }, items: { type: Array, default: () => [] diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue index 3d3a5ff04..eeea348ee 100644 --- a/src/components/selectable_list/selectable_list.vue +++ b/src/components/selectable_list/selectable_list.vue @@ -27,6 +27,7 @@ > @@ -227,70 +243,84 @@ :title="$t('admin_dash.users.bulk_actions.activate')" :cancel-text="$t('admin_dash.users.bulk_actions.no')" :confirm-text="$t('admin_dash.users.bulk_actions.yes')" - @callback="activateSelectedConfirmed" + @callback="selectionConfirmed('adminActivateUser')" /> + + diff --git a/src/i18n/en.json b/src/i18n/en.json index 6b5630a07..a90ed8200 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1173,7 +1173,9 @@ "revoke_moderator": "Revoke Moderator Privileges From Selected Users?", "approve": "Approve Selected Users?", "confirm": "Confirm Selected Users?", - "require_password_change": "Require Password Change For Selected Users?" + "require_password_change": "Require Password Change For Selected Users?", + "resend_confirmation_email": "Resend Confirmation Email For Selected Users?", + "disable_mfa": "Disable MFA For Selected Users?" }, "activate": "Activate", "deactivate": "Deactivate", @@ -1186,6 +1188,7 @@ "confirm_user": "Confirm", "resend_confirmation_email": "Resend Confirmation Email", "require_password_change": "Require Password Change", + "disable_mfa": "Disable MFA", "delete": "Delete", "loading_user": "Loading user...", "delete_user": "Delete User", diff --git a/src/modules/adminSettings.js b/src/modules/adminSettings.js index 70210a1eb..25d0a1f84 100644 --- a/src/modules/adminSettings.js +++ b/src/modules/adminSettings.js @@ -114,6 +114,9 @@ const adminSettingsStorage = { adminChangeStatusScope (store, { opts }) { return store.rootState.api.backendInteractor.adminChangeStatusScope({ opts }) }, + adminDisableMFA (store, { opts }) { + return store.rootState.api.backendInteractor.adminDisableMFA({ opts }) + }, loadFrontendsStuff ({ rootState, commit }) { rootState.api.backendInteractor.fetchAvailableFrontends() .then(frontends => commit('setAvailableFrontends', { frontends })) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 56b324635..31ea7acf5 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -146,6 +146,7 @@ const PLEROMA_ADMIN_APPROVE_URL = '/api/v1/pleroma/admin/users/approve' const PLEROMA_ADMIN_LIST_STATUSES_URL = (id, pageSize, godmode, withReblogs) => `/api/v1/pleroma/admin/users/${id}/statuses?page_size=${pageSize}&godmode=${godmode}&with_reblogs=${withReblogs}` const PLEROMA_ADMIN_CHANGE_STATUS_SCOPE_URL = (id) => `/api/v1/pleroma/admin/statuses/${id}` const PLEROMA_ADMIN_REQUIRE_PASSWORD_CHANGE_URL = '/api/v1/pleroma/admin/users/force_password_reset' +const PLEROMA_ADMIN_DISABLE_MFA_URL = '/api/v1/pleroma/admin/users/disable_mfa' const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji' const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import' const PLEROMA_EMOJI_PACKS_URL = (page, pageSize) => `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}` @@ -1600,6 +1601,18 @@ const adminRequirePasswordChange = ({ user: { screen_name: nickname }, credentia }) } +const adminDisableMFA = ({ user : { screen_name: nickname }, credentials }) => { + const url = PLEROMA_ADMIN_DISABLE_MFA_URL + return promisedRequest({ + url, + credentials, + method: 'PUT', + payload: { + nickname + } + }) +} + const announcementToPayload = ({ content, startsAt, endsAt, allDay }) => { const payload = { content } @@ -2282,7 +2295,8 @@ const apiService = { adminApproveUser, adminListStatuses, adminChangeStatusScope, - adminRequirePasswordChange + adminRequirePasswordChange, + adminDisableMFA } export default apiService From 05b4cbe6d6e5050e72198aecd2dbcef1510084e6 Mon Sep 17 00:00:00 2001 From: luce Date: Tue, 9 Sep 2025 11:38:25 +0200 Subject: [PATCH 42/83] adding jsdoc --- src/components/page_list/page_list.js | 25 ++++ .../settings_modal/admin_tabs/admin_card.js | 88 +++++++++++- .../settings_modal/admin_tabs/admin_card.vue | 39 ++---- .../admin_tabs/admin_status_card.js | 131 ++++++++++++------ .../settings_modal/admin_tabs/users_tab.js | 47 ++++++- 5 files changed, 250 insertions(+), 80 deletions(-) diff --git a/src/components/page_list/page_list.js b/src/components/page_list/page_list.js index 86bdd6801..5a28a847d 100644 --- a/src/components/page_list/page_list.js +++ b/src/components/page_list/page_list.js @@ -5,18 +5,30 @@ const PageList = { SelectableList }, props: { + /** + * only make the checkbox clickable to toggle, not the whole area + */ boxOnly: { type: Boolean, default: false }, + /** + * how many entries to fetch at once + */ pageSize: { type: Number, default: 50 }, + /** + * the function/callback used to fetch new entries (one page) + */ fetchPage: { type: Function, default: async () => [] }, + /** + * wether or not this is a single page list (so it won't allow fetching more pages) + */ singlePage: { type: Boolean, default: false @@ -31,6 +43,9 @@ const PageList = { } }, methods: { + /** + * reset and load first page + */ reset () { this.canLoadMore = true this.pageIndex = 1 @@ -38,6 +53,9 @@ const PageList = { this.isLoading = false this.loadMore() // load one page }, + /** + * load another page + */ loadMore () { if (!this.isLoading && this.canLoadMore) { this.isLoading = true @@ -50,10 +68,17 @@ const PageList = { }) } }, + /** + * get currently selected elements + * @returns {Array} + */ getSelected () { return this.$refs.list.selected } }, + /** + * auto-load first page when mounted + */ mounted () { this.loadMore() } diff --git a/src/components/settings_modal/admin_tabs/admin_card.js b/src/components/settings_modal/admin_tabs/admin_card.js index 6c9ed46db..cc5afc0d6 100644 --- a/src/components/settings_modal/admin_tabs/admin_card.js +++ b/src/components/settings_modal/admin_tabs/admin_card.js @@ -6,9 +6,23 @@ import Modal from 'src/components/modal/modal.vue' const AdminCard = { props: { + /** + * minimal user info + * @type {import('vue').PropType<{ + * id: string, + * _original: { + * is_approved: boolean; + * is_confirmed: boolean; + * }; + * }>} + */ userDetails: { type: Object, required: true, + /** + * @param {any} u + * @returns {u is { id: string; _original: { is_approved; is_confirmed: boolean; } } } + */ validator (u) { return ( typeof(u.id) === 'string' && @@ -22,7 +36,8 @@ const AdminCard = { data () { return { progress: false, - topLevelExpanded: false, + detailsExpanded: false, + topLevelExpanded: false, // REMOVE jsonExpanded: false, timelineExpanded: false, justApproved: false, @@ -31,15 +46,28 @@ const AdminCard = { } }, computed: { + /** + * checks if the user is defined + * @returns {boolean} + */ isLoaded () { return typeof(this.user) !== 'undefined' }, + /** + * @returns {object} user info + */ user () { return this.$store.getters.findUser(this.userDetails.id) }, + /** + * @returns {object} user relationship + */ relationship () { return this.$store.getters.relationship(this.userDetails.id) }, + /** + * @returns {boolean} is user local + */ isLocal () { const u = this.$store.getters.findUser(this.userDetails.id) if (typeof(u) !== 'undefined') { @@ -47,6 +75,9 @@ const AdminCard = { } return false }, + /** + * @returns {boolean} is user admin + */ isAdmin () { const u = this.$store.getters.findUser(this.userDetails.id) if (typeof(u) !== 'undefined') { @@ -54,6 +85,9 @@ const AdminCard = { } return false }, + /** + * @returns {boolean} is user moderator + */ isModerator () { const u = this.$store.getters.findUser(this.userDetails.id) if (typeof(u) !== 'undefined') { @@ -61,6 +95,9 @@ const AdminCard = { } return false }, + /** + * @returns {boolean} is user active + */ isActivated () { const u = this.$store.getters.findUser(this.userDetails.id) if (typeof(u) !== 'undefined') { @@ -68,10 +105,16 @@ const AdminCard = { } return false }, + /** + * @returns {boolean} has this user been confirmed + */ isConfirmed () { const u = this.$store.getters.findUser(this.userDetails.id) return (u._original.is_confirmed === true) || (this.justConfirmed === true) }, + /** + * @returns {boolean} has this user been approved + */ isApproved () { return (this.userDetails._original.is_approved === true) || (this.justApproved === true) } @@ -84,7 +127,10 @@ const AdminCard = { Modal }, methods: { - toggleAdmin (v) { + /** + * @param {boolean} v set admin status + */ + setAdmin (v) { const u = this.$store.getters.findUser(this.userDetails.id) if (v === true) { this.$store.dispatch('adminAddUserToAdminGroup', u) @@ -92,7 +138,10 @@ const AdminCard = { this.$store.dispatch('adminRemoveUserFromAdminGroup', u) } }, - toggleModerator (v) { + /** + * @param {boolean} v set moderator status + */ + setModerator (v) { const u = this.$store.getters.findUser(this.userDetails.id) if (v === true) { this.$store.dispatch('adminAddUserToModeratorGroup', u) @@ -100,7 +149,10 @@ const AdminCard = { this.$store.dispatch('adminRemoveUserFromModeratorGroup', u) } }, - toggleActivation (v) { + /** + * @param {boolean} v set activation status + */ + setActivation (v) { const u = this.$store.getters.findUser(this.userDetails.id) if (v === true) { this.$store.dispatch('adminActivateUser', u) @@ -108,28 +160,47 @@ const AdminCard = { this.$store.dispatch('adminDeactivateUser', u) } }, + /** + * confirm this user + */ confirmUser () { const u = this.$store.getters.findUser(this.userDetails.id) this.$store.dispatch('adminConfirmUser', u) this.just_confirmed = true }, + /** + * try resending the confirmation email + */ resendConfirmationEmail () { const u = this.$store.getters.findUser(this.userDetails.id) this.$store.dispatch('adminResendConfirmationEmail', u) }, - toggleApproval () { + /** + * approve this user + */ + approveUser () { const u = this.$store.getters.findUser(this.userDetails.id) this.$store.dispatch('adminApproveUser', u) }, + /** + * update user info from server + */ forceUpdateUser () { this.$store.dispatch('fetchUser', this.userDetails.id) }, + /** + * delete selected statuses + */ deleteSelection () { const l = this.$refs.timelineList const s = l.getSelected() s.forEach(p => this.$store.dispatch('deleteStatus', p)) l.reset() }, + /** + * delete this user. keep in mind that user deletion is not intuitive in pleroma backend. + * it actually deletes all content of a user. the user itself will keep showing up in search results. + */ deleteUser () { if (!this.justDeleted) { const u = this.$store.getters.findUser(this.userDetails.id) @@ -137,7 +208,12 @@ const AdminCard = { this.justDeleted = true } }, - fetchStatuses (store, opts) { + /** + * @param {object} store + * @param {object} opts + * @returns {Promise>} statuses + */ + async fetchStatuses (store, opts) { const u = this.$store.getters.findUser(this.userDetails.id) const res = store.dispatch('adminListStatuses', { user: u, opts: { pageSize: opts.pageSize, godmode: true, withReblogs: true}}) return res.then(r => r.activities) diff --git a/src/components/settings_modal/admin_tabs/admin_card.vue b/src/components/settings_modal/admin_tabs/admin_card.vue index bea1f83dc..e3ca14d0a 100644 --- a/src/components/settings_modal/admin_tabs/admin_card.vue +++ b/src/components/settings_modal/admin_tabs/admin_card.vue @@ -5,37 +5,26 @@
-
- -
+
    -
  • - -
  • {{ $t('admin_dash.users.is_admin') }} @@ -45,7 +34,7 @@ > {{ $t('admin_dash.users.is_moderator') }} @@ -78,7 +67,7 @@ @@ -86,7 +75,7 @@
  • {{ $t('admin_dash.users.is_active') }} @@ -195,7 +184,7 @@ {{ $t('admin_dash.users.expand_raw_info') }}
-
diff --git a/src/components/settings_modal/admin_tabs/admin_status_card.js b/src/components/settings_modal/admin_tabs/admin_status_card.js index aa7a5f2ef..e7e5932b8 100644 --- a/src/components/settings_modal/admin_tabs/admin_status_card.js +++ b/src/components/settings_modal/admin_tabs/admin_status_card.js @@ -4,53 +4,92 @@ import Status from 'src/components/status/status.vue' import { parseStatus } from 'src/services/entity_normalizer/entity_normalizer.service.js' const AdminStatusCard = { - props: ['statusDetails'], - data () { - return { - jsonExpanded: false, - statusCache: undefined, + props: { + /** + * minimal status info + * @type {import('vue').PropType<{ + * id: string + * }>} + */ + statusDetails: { + type: Object, + required: true, + /** + * @param {any} u + * @returns {u is { id: string }} + */ + validator (u) { + return typeof(u.id) === 'string' } - }, - computed: { - isSensitive () { - return this.statusDetails.sensitive === true - }, - visibility () { - return this.statusDetails.visibility - } - }, - methods: { - changeSensitivity (v) { - this.$store.dispatch('adminChangeStatusScope', { opts: { id: this.statusDetails.id, sensitive: v }}).then(res => parseStatus(res)).then(s => this.statusCache = s) - }, - changeVisibility (v) { - this.$store.dispatch('adminChangeStatusScope', { opts: { id: this.statusDetails.id, visibility: v }}).then(res => parseStatus(res)).then(s => this.statusCache = s) - }, - // show popup - confirmSelection(box) { - this.$refs[box].show() - this.$refs.dropdown.hidePopover() - }, - // do the thing - selectionConfirmed(action, opts) { - const restricted = [] - const s = this.$refs.userList.getSelected() - s.forEach(u => { - if (restricted.includes(action) !== false || u.id !== this.$store.state.users.currentUser.id) { - this.$store.dispatch(action, { id: this.statusDetails.id, ...(opts || {}) }) - } - }) - this.reset() - } - }, - components: { - Checkbox, - Select, - Status, - }, - mounted () { - this.$store.dispatch('adminChangeStatusScope', { opts: { id: this.statusDetails.id }}).then(res => parseStatus(res)).then(s => this.statusCache = s) - } + } + }, + data () { + return { + jsonExpanded: false, + statusCache: undefined, + } + }, + computed: { + /** + * @returns {boolean} is this status sensitive? + */ + isSensitive () { + return this.statusDetails.sensitive === true + }, + /** + * @returns {'public' | 'unlisted' | 'private' | 'direct'} status visibility + */ + visibility () { + return this.statusDetails.visibility + } + }, + methods: { + /** + * @param {boolean} v set sensitive + */ + changeSensitivity (v) { + this.$store.dispatch('adminChangeStatusScope', { opts: { id: this.statusDetails.id, sensitive: v }}).then(res => parseStatus(res)).then(s => this.statusCache = s) + }, + /** + * @param {boolean} v set visible + */ + changeVisibility (v) { + this.$store.dispatch('adminChangeStatusScope', { opts: { id: this.statusDetails.id, visibility: v }}).then(res => parseStatus(res)).then(s => this.statusCache = s) + }, + /** + * show the confirmation box for bulk actions. + * @param {string} box ref name specified for the confirm component + */ + confirmSelection(box) { + this.$refs[box].show() + this.$refs.dropdown.hidePopover() + }, + /** + * called when a bulk action was confirmed + * @param {string} action + */ + selectionConfirmed(action, opts) { + const restricted = [] + const s = this.$refs.userList.getSelected() + s.forEach(u => { + if (restricted.includes(action) !== false || u.id !== this.$store.state.users.currentUser.id) { + this.$store.dispatch(action, { id: this.statusDetails.id, ...(opts || {}) }) + } + }) + this.reset() + } + }, + components: { + Checkbox, + Select, + Status, + }, + /** + * fetch and cache status info + */ + mounted () { + this.$store.dispatch('adminChangeStatusScope', { opts: { id: this.statusDetails.id }}).then(res => parseStatus(res)).then(s => this.statusCache = s) + } } export default AdminStatusCard diff --git a/src/components/settings_modal/admin_tabs/users_tab.js b/src/components/settings_modal/admin_tabs/users_tab.js index 3486122e5..6245369c4 100644 --- a/src/components/settings_modal/admin_tabs/users_tab.js +++ b/src/components/settings_modal/admin_tabs/users_tab.js @@ -31,21 +31,45 @@ const UsersTab = { } }, computed: { + /** + * do we filter for admins? + * @returns {boolean} + */ filtersIsAdmin () { return this.filtersPrivileges === 'admin' || this.filtersPrivileges === 'modsnadmins' }, + /** + * do we filter for moderators? + * @returns {boolean} + */ filtersIsModerator () { return this.filtersPrivileges === 'moderator' || this.filtersPrivileges === 'modsnadmins' }, + /** + * do we filter for active users? + * @returns {boolean} + */ filtersActive () { return this.filtersActivity === 'active' }, + /** + * do we filter for deactivated users? + * @returns {boolean} + */ filtersDeactivated () { return this.filtersActivity === 'deactivated' }, + /** + * do we filter for local users? + * @returns {boolean} + */ filtersLocal () { return this.filtersOrigin === 'local' }, + /** + * do we filter for external users? + * @return {boolean} + */ filtersExternal () { return this.filtersOrigin === 'external' } @@ -55,13 +79,18 @@ const UsersTab = { Select, BasicUserCard, PageList, - ProgressButton, + ProgressButton, AdminCard, TabSwitcher, Popover, GenericConfirm }, methods: { + /** + * fetch a new page of users via admin-api + * @param {object} store + * @param {object} opts + */ fetchPage (store, opts) { if(!this.init) return new Promise(() => []) const filters = { @@ -83,15 +112,24 @@ const UsersTab = { const users = store.dispatch('fetchAdminUsers', nopts) return users }, + /** + * reset the userlist explicitly + */ reset () { this.$refs.userList.reset() }, - // show popup + /** + * show the confirmation box for bulk actions. + * @param {string} box ref name specified for the confirm component + */ confirmSelection(box) { this.$refs[box].show() this.$refs.dropdown.hidePopover() }, - // do the thing + /** + * called when a bulk action was confirmed + * @param {string} action + */ selectionConfirmed(action) { const restricted = [] const s = this.$refs.userList.getSelected() @@ -103,6 +141,9 @@ const UsersTab = { this.reset() } }, + /** + * mark as initialized and reset user list + */ mounted () { this.init = true this.reset() From 3a6dac7ce591202d0f34113ec48f7c3555aea3d3 Mon Sep 17 00:00:00 2001 From: luce Date: Tue, 9 Sep 2025 18:36:30 +0200 Subject: [PATCH 43/83] wip, ignore, i just want this pushed --- .../confirm_modal/generic_confirm.js | 9 +- .../confirm_modal/generic_confirm.vue | 2 +- .../settings_modal/admin_tabs/admin_card.js | 20 +- .../settings_modal/admin_tabs/admin_card.scss | 15 ++ .../settings_modal/admin_tabs/admin_card.vue | 215 +++++++++++++++++- .../settings_modal/admin_tabs/users_tab.js | 3 +- src/i18n/en.json | 9 +- 7 files changed, 254 insertions(+), 19 deletions(-) create mode 100644 src/components/settings_modal/admin_tabs/admin_card.scss diff --git a/src/components/confirm_modal/generic_confirm.js b/src/components/confirm_modal/generic_confirm.js index 21bf3f61b..7ac07581b 100644 --- a/src/components/confirm_modal/generic_confirm.js +++ b/src/components/confirm_modal/generic_confirm.js @@ -3,8 +3,9 @@ import ConfirmModal from './confirm_modal.vue' export default { props: { - callback: { - type: Function + action: { + type: Function, + require: true }, title: { type: String @@ -31,6 +32,10 @@ export default { hide () { this.showing = false this.$emit('hide') + }, + doGeneric () { + this.action() + this.hide() } } } diff --git a/src/components/confirm_modal/generic_confirm.vue b/src/components/confirm_modal/generic_confirm.vue index d0cbae2ca..794a15490 100644 --- a/src/components/confirm_modal/generic_confirm.vue +++ b/src/components/confirm_modal/generic_confirm.vue @@ -4,7 +4,7 @@ :title="title" :cancel-text="cancelText" :confirm-text="confirmText" - @accepted="callback" + @accepted="doGeneric" @cancelled="hide" /> diff --git a/src/components/settings_modal/admin_tabs/admin_card.js b/src/components/settings_modal/admin_tabs/admin_card.js index cc5afc0d6..b83426f0e 100644 --- a/src/components/settings_modal/admin_tabs/admin_card.js +++ b/src/components/settings_modal/admin_tabs/admin_card.js @@ -3,6 +3,8 @@ import Checkbox from 'src/components/checkbox/checkbox.vue' import PageList from 'src/components/page_list/page_list.vue' import AdminStatusCard from 'src/components/settings_modal/admin_tabs/admin_status_card.vue' import Modal from 'src/components/modal/modal.vue' +import Popover from 'src/components/popover/popover.vue' +import GenericConfirm from 'src/components/confirm_modal/generic_confirm.vue' const AdminCard = { props: { @@ -124,7 +126,9 @@ const AdminCard = { Checkbox, PageList, AdminStatusCard, - Modal + Modal, + Popover, + GenericConfirm }, methods: { /** @@ -217,6 +221,20 @@ const AdminCard = { const u = this.$store.getters.findUser(this.userDetails.id) const res = store.dispatch('adminListStatuses', { user: u, opts: { pageSize: opts.pageSize, godmode: true, withReblogs: true}}) return res.then(r => r.activities) + }, + /** + * ... + */ + confirmAction (box) { + this.$refs[box].show() + this.$refs.dropdownuser.hidePopover() + }, + /** + * ... + */ + actionConfirmed (action) { + console.log(action) + this.$store.dispatch(action, this.$store.getters.findUser(this.userDetails.id)) } } } diff --git a/src/components/settings_modal/admin_tabs/admin_card.scss b/src/components/settings_modal/admin_tabs/admin_card.scss new file mode 100644 index 000000000..dddfad29f --- /dev/null +++ b/src/components/settings_modal/admin_tabs/admin_card.scss @@ -0,0 +1,15 @@ +.inline-layout { + display: flex; + align-items: center; + width: 100%; + gap: 8px; +} + +.alert { + text-shadow: none; +} + +.user-role { + display: inline-block; + vertical-align: baseline; +} diff --git a/src/components/settings_modal/admin_tabs/admin_card.vue b/src/components/settings_modal/admin_tabs/admin_card.vue index e3ca14d0a..aa9953e22 100644 --- a/src/components/settings_modal/admin_tabs/admin_card.vue +++ b/src/components/settings_modal/admin_tabs/admin_card.vue @@ -4,20 +4,194 @@ {{ $t('admin_dash.users.loading_user') }}
- - +
+ + + + + + + + + + + +
+
+
    +
  • show statuses
  • +
+
+
+
+ +
- - + diff --git a/src/components/settings_modal/admin_tabs/users_tab.js b/src/components/settings_modal/admin_tabs/users_tab.js index 6245369c4..f8bc83ea7 100644 --- a/src/components/settings_modal/admin_tabs/users_tab.js +++ b/src/components/settings_modal/admin_tabs/users_tab.js @@ -109,8 +109,7 @@ const UsersTab = { name: this.filtersName, email: this.filtersEmail }} - const users = store.dispatch('fetchAdminUsers', nopts) - return users + return store.dispatch('fetchAdminUsers', nopts) }, /** * reset the userlist explicitly diff --git a/src/i18n/en.json b/src/i18n/en.json index 778bcd719..827a1988b 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1229,7 +1229,14 @@ "title_database": "Database", "title_details": "Details", "title_users": "Users", - "user_has_no_posts": "User has no posts" + "title_actions": "Actions", + "user_has_no_posts": "User has no posts", + "indicator_admin": "Admin", + "indicator_moderator": "Moderator", + "indicator_active": "Active", + "indicator_deactivated": "Deactivated", + "indicator_approved": "Approved", + "indicator_confirmed": "Confirmed" }, "limits": { "arbitrary_limits": "Arbitrary limits", From c3e854c1f9b241319829b40dde60924118101b3a Mon Sep 17 00:00:00 2001 From: luce Date: Wed, 10 Sep 2025 15:08:47 +0200 Subject: [PATCH 44/83] wip, added popovers and dialogs for user options, made modal timeline a little bit less painful to look at --- .../confirm_modal/generic_confirm.js | 8 +- .../settings_modal/admin_tabs/admin_card.js | 16 +- .../settings_modal/admin_tabs/admin_card.vue | 187 ++++++++++++++---- .../admin_tabs/admin_status_card.vue | 5 + .../settings_modal/admin_tabs/users_tab.vue | 24 +-- src/i18n/en.json | 61 +++++- src/modules/adminSettings.js | 2 +- 7 files changed, 233 insertions(+), 70 deletions(-) diff --git a/src/components/confirm_modal/generic_confirm.js b/src/components/confirm_modal/generic_confirm.js index 7ac07581b..a964a9805 100644 --- a/src/components/confirm_modal/generic_confirm.js +++ b/src/components/confirm_modal/generic_confirm.js @@ -3,10 +3,6 @@ import ConfirmModal from './confirm_modal.vue' export default { props: { - action: { - type: Function, - require: true - }, title: { type: String }, @@ -17,7 +13,7 @@ export default { type: String } }, - emits: ['hide', 'show'], + emits: ['hide', 'show', 'action'], data: () => ({ showing: false }), @@ -34,7 +30,7 @@ export default { this.$emit('hide') }, doGeneric () { - this.action() + this.$emit('action') this.hide() } } diff --git a/src/components/settings_modal/admin_tabs/admin_card.js b/src/components/settings_modal/admin_tabs/admin_card.js index b83426f0e..a773b7bb4 100644 --- a/src/components/settings_modal/admin_tabs/admin_card.js +++ b/src/components/settings_modal/admin_tabs/admin_card.js @@ -222,19 +222,19 @@ const AdminCard = { const res = store.dispatch('adminListStatuses', { user: u, opts: { pageSize: opts.pageSize, godmode: true, withReblogs: true}}) return res.then(r => r.activities) }, - /** - * ... - */ confirmAction (box) { this.$refs[box].show() this.$refs.dropdownuser.hidePopover() }, - /** - * ... - */ - actionConfirmed (action) { - console.log(action) + userActionConfirmed (action) { this.$store.dispatch(action, this.$store.getters.findUser(this.userDetails.id)) + }, + statusActionConfirmed (action, opts) { + const s = this.$refs.statusList.getSelected() + s.forEach(p => { + this.$store.dispatch(action, { id: p.id, ...(opts || {})}) + }) + this.reset() } } } diff --git a/src/components/settings_modal/admin_tabs/admin_card.vue b/src/components/settings_modal/admin_tabs/admin_card.vue index aa9953e22..cab2d2bec 100644 --- a/src/components/settings_modal/admin_tabs/admin_card.vue +++ b/src/components/settings_modal/admin_tabs/admin_card.vue @@ -56,7 +56,10 @@ diff --git a/src/components/settings_modal/admin_tabs/admin_status_card.vue b/src/components/settings_modal/admin_tabs/admin_status_card.vue index 388d1c58f..f56df92e9 100644 --- a/src/components/settings_modal/admin_tabs/admin_status_card.vue +++ b/src/components/settings_modal/admin_tabs/admin_status_card.vue @@ -27,6 +27,8 @@ @interacted="false" /> +

action dropdown thingy

+ + diff --git a/src/components/settings_modal/admin_tabs/users_tab.vue b/src/components/settings_modal/admin_tabs/users_tab.vue index 54d77989b..6a021d3c2 100644 --- a/src/components/settings_modal/admin_tabs/users_tab.vue +++ b/src/components/settings_modal/admin_tabs/users_tab.vue @@ -243,84 +243,84 @@ :title="$t('admin_dash.users.bulk_actions.activate')" :cancel-text="$t('admin_dash.users.bulk_actions.no')" :confirm-text="$t('admin_dash.users.bulk_actions.yes')" - @callback="selectionConfirmed('adminActivateUser')" + @action="selectionConfirmed('adminActivateUser')" /> diff --git a/src/i18n/en.json b/src/i18n/en.json index 827a1988b..8f9bf1ff1 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1172,13 +1172,67 @@ "only_unapproved": "Unapproved Only", "only_unconfirmed": "Unconfirmed Only", "refresh": "Refresh", + + "actions": { + "button": { + "title": "Actions", + "yes": "Confirm", + "no": "Abort", + "activate": "Activate", + "deactivate": "Deactivate", + "delete_user": "Delete", + "delete_status": "Delete", + "grant_admin": "Grant Admin", + "revoke_admin": "Revoke Admin", + "grant_moderator": "Grant Moderator", + "revoke_moderator": "Revoke Moderator", + "approve": "Approve", + "confirm": "Confirm", + "require_password_change": "Require Password Change", + "resend_confirmation_email": "Resend Confirmation Email", + "disable_mfa": "Disable MFA" + }, + "confirm_single": { + "activate": "Activate User?", + "deactivate": "Deactivate User?", + "delete_user": "Delete User?", + "delete_status": "Delete Status?", + "grant_admin": "Grant Admin Privileges?", + "revoke_admin": "Revoke Admin Privileges?", + "grant_moderator": "Grant Moderator Privileges?", + "revoke_moderator": "Revoke Moderator Privileges?", + "approve": "Approve User?", + "confirm": "Confirm User?", + "require_password_change": "Require Password Change?", + "resend_confirmation_email": "Resend Confirmation Email?", + "disable_mfa": "Disable MFA?" + }, + "confirm_multi": { + "activate": "Actiate Selected Users?", + "deactivate": "Deactivate Selected Users?", + "delete_user": "Delete Selected Users?", + "delete_status": "Delete Selected Statuses?", + "grant_admin": "Grant Admin Privileges For Selected Users?", + "revoke_admin": "Revoke Admin Privileges For Selected Users?", + "grant_moderator": "Grant Moderator Privileges For Selected Users?", + "revoke_moderator": "Revoke Moderator Privileges For Selected Users?", + "approve": "Approve Selected Users?", + "confirm": "Confirm Selected Users?", + "require_password_change": "Require Password Change For Selected Users?", + "resend_confirmation_email": "Resend Confirmation Email For Selected Users?", + "disable_mfa": "Disable MFA For Selected Users?" + + } + }, + "bulk_actions": { "title": "Bulk Actions", "yes": "Yes", "no": "No", "activate": "Activate Selected Users?", "deactivate": "Deactivate Selected Users?", - "delete": "Delete Selected Users?", + "delete_user": "Delete Selected Users?", + "delete_status": "Delete Selected Statuses?", "grant_admin": "Grant Admin Privileges To Selected Users?", "revoke_admin": "Revoke Admin Privileges From Selected Users?", "grant_moderator": "Grant Moderator Privileges To Selected Users?", @@ -1189,8 +1243,9 @@ "resend_confirmation_email": "Resend Confirmation Email For Selected Users?", "disable_mfa": "Disable MFA For Selected Users?" }, - "activate": "Activate", - "deactivate": "Deactivate", + "activate": "Activate?", + "deactivate": "Deactivate?", + "delete_user": "Delete User?", "resend_confirmation_email": "Resend confirmation email", "approve": "Approve", "grant_admin": "Grant Admin", diff --git a/src/modules/adminSettings.js b/src/modules/adminSettings.js index 25d0a1f84..a18764c75 100644 --- a/src/modules/adminSettings.js +++ b/src/modules/adminSettings.js @@ -71,7 +71,7 @@ const adminSettingsStorage = { }, adminRemoveUserFromAdminGroup (store, user) { // prevent revokation of own rights - if (user.id !== store.state.users.currentUser.id) { + if (user.id !== store.rootState.users.currentUser.id) { return store.rootState.api.backendInteractor.adminRemoveUserFromAdminGroup({ user }) .then(res => store.commit('updateRight', { user, right: 'admin', value: res.is_admin })) } From 53537e194f1bbfbf5e9a7b45642488459ccd3daa Mon Sep 17 00:00:00 2001 From: luce Date: Fri, 12 Sep 2025 15:20:27 +0200 Subject: [PATCH 45/83] filter and sorting options, fix status width in admin_dash --- .../confirm_modal/generic_confirm.js | 3 + .../confirm_modal/generic_confirm.vue | 6 +- src/components/list/list.vue | 1 + src/components/page_list/page_list.js | 1 + .../selectable_list/selectable_list.vue | 3 + .../settings_modal/admin_tabs/admin_card.js | 19 ++++- .../settings_modal/admin_tabs/admin_card.vue | 77 ++++++++++++++++++- .../settings_modal/admin_tabs/users_tab.vue | 9 ++- src/i18n/en.json | 28 ++++++- 9 files changed, 135 insertions(+), 12 deletions(-) diff --git a/src/components/confirm_modal/generic_confirm.js b/src/components/confirm_modal/generic_confirm.js index a964a9805..53b41e83e 100644 --- a/src/components/confirm_modal/generic_confirm.js +++ b/src/components/confirm_modal/generic_confirm.js @@ -6,6 +6,9 @@ export default { title: { type: String }, + message: { + type: String + }, cancelText: { type: String }, diff --git a/src/components/confirm_modal/generic_confirm.vue b/src/components/confirm_modal/generic_confirm.vue index 794a15490..d223d6ea9 100644 --- a/src/components/confirm_modal/generic_confirm.vue +++ b/src/components/confirm_modal/generic_confirm.vue @@ -6,7 +6,11 @@ :confirm-text="confirmText" @accepted="doGeneric" @cancelled="hide" - /> + > + + + - - - + + - - - - - - - - - - - - + + + + + + + + + + + + From ba577aec5281c31e69e32de59796fe8a9f417912 Mon Sep 17 00:00:00 2001 From: luce Date: Mon, 15 Sep 2025 13:37:08 +0200 Subject: [PATCH 48/83] tag edit --- src/components/confirm_modal/text_confirm.js | 41 +++++ src/components/confirm_modal/text_confirm.vue | 27 +++ .../settings_modal/admin_tabs/admin_card.js | 20 ++- .../settings_modal/admin_tabs/admin_card.vue | 69 ++++++-- .../settings_modal/admin_tabs/users_tab.vue | 151 ++++++++-------- src/i18n/en.json | 163 ++++++------------ src/modules/adminSettings.js | 6 + 7 files changed, 285 insertions(+), 192 deletions(-) create mode 100644 src/components/confirm_modal/text_confirm.js create mode 100644 src/components/confirm_modal/text_confirm.vue diff --git a/src/components/confirm_modal/text_confirm.js b/src/components/confirm_modal/text_confirm.js new file mode 100644 index 000000000..11319da39 --- /dev/null +++ b/src/components/confirm_modal/text_confirm.js @@ -0,0 +1,41 @@ +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, + text: "" + }), + components: { + ConfirmModal + }, + methods: { + show () { + this.showing = true + this.$emit('show') + }, + hide () { + this.showing = false + this.$emit('hide') + }, + doWithText () { + this.$emit('action', this.text) + this.hide() + } + } +} diff --git a/src/components/confirm_modal/text_confirm.vue b/src/components/confirm_modal/text_confirm.vue new file mode 100644 index 000000000..b4770c686 --- /dev/null +++ b/src/components/confirm_modal/text_confirm.vue @@ -0,0 +1,27 @@ + + + diff --git a/src/components/settings_modal/admin_tabs/users_tab.vue b/src/components/settings_modal/admin_tabs/users_tab.vue index 56a0d62a2..17e2cc9fd 100644 --- a/src/components/settings_modal/admin_tabs/users_tab.vue +++ b/src/components/settings_modal/admin_tabs/users_tab.vue @@ -3,22 +3,22 @@
-

{{ $t('admin_dash.users.title_users') }}

+

{{ $t('admin_dash.users.title') }}

  • - + - + - +
  • - + - + - +
  • @@ -100,13 +100,13 @@ class="query-label" @update:model-value="v => {filtersNeedApproval = v; reset();}" > - {{ $t('admin_dash.users.only_unapproved') }} + {{ $t('admin_dash.users.options.only_unapproved') }} - {{ $t('admin_dash.users.only_unconfirmed') }} + {{ $t('admin_dash.users.options.only_unconfirmed') }}
@@ -128,7 +128,7 @@ diff --git a/src/components/settings_modal/admin_tabs/users_tab.js b/src/components/settings_modal/admin_tabs/users_tab.js index f8bc83ea7..292a08368 100644 --- a/src/components/settings_modal/admin_tabs/users_tab.js +++ b/src/components/settings_modal/admin_tabs/users_tab.js @@ -134,6 +134,8 @@ const UsersTab = { const s = this.$refs.userList.getSelected() s.forEach(u => { if (restricted.includes(action) !== false || u.id !== this.$store.state.users.currentUser.id) { + const uf = this.$store.getters.findUser(u.id) + console.log('user: ', uf) this.$store.dispatch(action, this.$store.getters.findUser(u.id)) } }) diff --git a/src/components/settings_modal/admin_tabs/users_tab.vue b/src/components/settings_modal/admin_tabs/users_tab.vue index 17e2cc9fd..19f31757f 100644 --- a/src/components/settings_modal/admin_tabs/users_tab.vue +++ b/src/components/settings_modal/admin_tabs/users_tab.vue @@ -335,7 +335,7 @@ Date: Fri, 27 Mar 2026 04:51:55 +0700 Subject: [PATCH 50/83] entity_normalizer: treat gifv attachment like it is video as we have handled this already. --- src/components/emoji_reactions/emoji_reactions.scss | 4 ++-- src/components/rich_content/rich_content.scss | 2 +- src/services/entity_normalizer/entity_normalizer.service.js | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/emoji_reactions/emoji_reactions.scss b/src/components/emoji_reactions/emoji_reactions.scss index 8ea45e1b4..f4b7d3fb0 100644 --- a/src/components/emoji_reactions/emoji_reactions.scss +++ b/src/components/emoji_reactions/emoji_reactions.scss @@ -41,7 +41,7 @@ margin: 0; .reaction-emoji { - width: var(--emoji-size); + width: auto; height: var(--emoji-size); margin-right: 0.25em; line-height: var(--emoji-size); @@ -55,7 +55,7 @@ .reaction-emoji-content { max-width: 100%; max-height: 100%; - width: var(--emoji-size); + width: auto; height: var(--emoji-size); line-height: inherit; overflow: hidden; diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss index d2ec77dcb..b1a9337e0 100644 --- a/src/components/rich_content/rich_content.scss +++ b/src/components/rich_content/rich_content.scss @@ -73,7 +73,7 @@ .emoji { display: inline-block; - width: var(--emoji-size, 32px); + width: auto; height: var(--emoji-size, 32px); } diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 909a014ce..9864cb302 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -299,7 +299,8 @@ export const parseAttachment = (data) => { } if (data.type !== 'unknown') { - output.type = data.type + // treat gifv like it is "video" + output.type = data.type === 'gifv' ? 'video' : data.type } else { output.type = fileTypeService.fileType(output.mimetype) } From c1c285a1b948b8bd7c6abdf6ac73851a309b222e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 2 Jun 2026 23:58:09 +0300 Subject: [PATCH 51/83] changelog --- changelog.d/profilebg.add | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/profilebg.add 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) From 3f2ce5e7aba0774a8e5dfa718427c156005b5f61 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 5 Jun 2026 14:53:33 +0300 Subject: [PATCH 52/83] lint --- src/App.js | 11 +++++++++-- .../status_action_buttons/action_button_container.vue | 2 +- .../status_action_buttons/status_action_buttons.vue | 2 +- src/components/user_profile/user_profile.js | 2 +- src/modules/default_config_state.js | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/App.js b/src/App.js index 36df2d84e..a24ff4255 100644 --- a/src/App.js +++ b/src/App.js @@ -150,7 +150,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 @@ -158,7 +161,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/components/status_action_buttons/action_button_container.vue b/src/components/status_action_buttons/action_button_container.vue index 348b158a7..da0beed79 100644 --- a/src/components/status_action_buttons/action_button_container.vue +++ b/src/components/status_action_buttons/action_button_container.vue @@ -79,7 +79,7 @@ :button="button" :status="status" v-bind="$attrs" - @emojiPickerShown="e => $emit('emojiPickerShown', e)" + @emoji-picker-shown="e => $emit('emojiPickerShown', e)" />