-
+
diff --git a/src/components/popover/popover.scss b/src/components/popover/popover.scss
index a166e2196..7dfaa28a5 100644
--- a/src/components/popover/popover.scss
+++ b/src/components/popover/popover.scss
@@ -96,6 +96,11 @@
content: "✓";
}
+ &.menu-checkbox-indeterminate::after {
+ font-size: 1.25em;
+ content: "–";
+ }
+
&.-radio {
border-radius: 9999px;
@@ -103,6 +108,11 @@
font-size: 2em;
content: "•";
}
+
+ &.menu-checkbox-indeterminate::after {
+ font-size: 2em;
+ content: "–";
+ }
}
}
}
diff --git a/src/components/selectable_list/selectable_list.js b/src/components/selectable_list/selectable_list.js
deleted file mode 100644
index b82b3d40c..000000000
--- a/src/components/selectable_list/selectable_list.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import Checkbox from 'src/components/checkbox/checkbox.vue'
-import List from 'src/components/list/list.vue'
-
-const SelectableList = {
- components: {
- List,
- Checkbox,
- },
- props: {
- items: {
- type: Array,
- default: () => [],
- },
- getKey: {
- type: Function,
- default: (item) => item.id,
- },
- },
- data() {
- return {
- selected: [],
- }
- },
- computed: {
- allKeys() {
- return this.items.map(this.getKey)
- },
- filteredSelected() {
- return this.allKeys.filter((key) => this.selected.indexOf(key) !== -1)
- },
- allSelected() {
- return this.filteredSelected.length === this.items.length
- },
- noneSelected() {
- return this.filteredSelected.length === 0
- },
- someSelected() {
- return !this.allSelected && !this.noneSelected
- },
- },
- methods: {
- isSelected(item) {
- return this.filteredSelected.indexOf(this.getKey(item)) !== -1
- },
- toggle(checked, item) {
- const key = this.getKey(item)
- const oldChecked = this.isSelected(key)
- if (checked !== oldChecked) {
- if (checked) {
- this.selected.push(key)
- } else {
- this.selected.splice(this.selected.indexOf(key), 1)
- }
- }
- },
- toggleAll(value) {
- if (value) {
- this.selected = this.allKeys.slice(0)
- } else {
- this.selected = []
- }
- },
- },
-}
-
-export default SelectableList
diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
deleted file mode 100644
index 3d3a5ff04..000000000
--- a/src/components/selectable_list/selectable_list.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-
-
-
-
-
-
-
- toggle(checked, item)"
- @click.stop
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/settings_modal/admin_tabs/admin_user_card.js b/src/components/settings_modal/admin_tabs/admin_user_card.js
new file mode 100644
index 000000000..a075db010
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/admin_user_card.js
@@ -0,0 +1,32 @@
+import { defineAsyncComponent } from 'vue'
+
+import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
+import ModerationTools from 'src/components/moderation_tools/moderation_tools.vue'
+
+const AdminUserCard = {
+ props: {
+ userId: {
+ type: String,
+ },
+ },
+ components: {
+ BasicUserCard,
+ ModerationTools,
+ },
+ computed: {
+ user() {
+ return this.$store.getters.findUser(this.userId)
+ },
+ isAdmin() {
+ return this.user.rights.admin
+ },
+ isModerator() {
+ return this.user.rights.moderator
+ },
+ isActivated() {
+ return !this.user.deactivated
+ },
+ },
+}
+
+export default AdminUserCard
diff --git a/src/components/settings_modal/admin_tabs/admin_user_card.scss b/src/components/settings_modal/admin_tabs/admin_user_card.scss
new file mode 100644
index 000000000..a9010afdc
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/admin_user_card.scss
@@ -0,0 +1,14 @@
+.AdminUserCard {
+ .right-side {
+ align-items: baseline;
+ justify-content: end;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5em;
+ margin-top: 0.5em;
+
+ .alert {
+ margin: 0;
+ }
+ }
+}
diff --git a/src/components/settings_modal/admin_tabs/admin_user_card.vue b/src/components/settings_modal/admin_tabs/admin_user_card.vue
new file mode 100644
index 000000000..00405dfa1
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/admin_user_card.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+ {{ $t('user_card.admin_data.registration_reason') }}
+
+
+ {{ user.adminData.registration_reason }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/settings_modal/admin_tabs/auth_tab.js b/src/components/settings_modal/admin_tabs/auth_tab.js
index 627150587..7ccae2ca0 100644
--- a/src/components/settings_modal/admin_tabs/auth_tab.js
+++ b/src/components/settings_modal/admin_tabs/auth_tab.js
@@ -9,6 +9,8 @@ import SharedComputedObject from '../helpers/shared_computed_object.js'
import StringSetting from '../helpers/string_setting.vue'
import TupleSetting from '../helpers/tuple_setting.vue'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
+
const AuthTab = {
provide() {
return {
@@ -30,9 +32,7 @@ const AuthTab = {
computed: {
...SharedComputedObject(),
LDAPEnabled() {
- return this.$store.state.adminSettings.draft[':pleroma'][':ldap'][
- ':enabled'
- ]
+ return useAdminSettingsStore().draft[':pleroma'][':ldap'][':enabled']
},
},
}
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
index 2f3b5aa40..fd5b53caa 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.scss
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -3,6 +3,11 @@
margin: 0.5em 2em;
}
+ .setting-section {
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+ }
+
.toolbar {
display: flex;
flex-wrap: wrap;
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.js b/src/components/settings_modal/admin_tabs/frontends_tab.js
index b76cd9015..9bec3d763 100644
--- a/src/components/settings_modal/admin_tabs/frontends_tab.js
+++ b/src/components/settings_modal/admin_tabs/frontends_tab.js
@@ -7,6 +7,7 @@ import IntegerSetting from '../helpers/integer_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import StringSetting from '../helpers/string_setting.vue'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -37,13 +38,13 @@ const FrontendsTab = {
},
created() {
if (this.user.rights.admin) {
- this.$store.dispatch('loadFrontendsStuff')
+ useAdminSettingsStore().loadFrontendsStuff()
}
},
computed: {
...SharedComputedObject(),
frontends() {
- return this.$store.state.adminSettings.frontends
+ return useAdminSettingsStore().frontends
},
},
methods: {
@@ -76,7 +77,7 @@ const FrontendsTab = {
this.working = false
})
.then(async (response) => {
- this.$store.dispatch('loadFrontendsStuff')
+ useAdminSettingsStore().loadFrontendsStuff()
if (response.error) {
const reason = await response.error.json()
useInterfaceStore().pushGlobalNotice({
@@ -104,7 +105,7 @@ const FrontendsTab = {
const ref = suggestRef || this.getSuggestedRef(frontend)
const { name } = frontend
- this.$store.commit('updateAdminDraft', {
+ useAdminSettingsStore().updateAdminDraft({
path: [':pleroma', ':frontends', ':primary'],
value: { name, ref },
})
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.scss b/src/components/settings_modal/admin_tabs/frontends_tab.scss
index 27c1fdc55..8b7b1f4e3 100644
--- a/src/components/settings_modal/admin_tabs/frontends_tab.scss
+++ b/src/components/settings_modal/admin_tabs/frontends_tab.scss
@@ -1,4 +1,6 @@
.FrontendsTab {
+ padding: 0 1em;
+
.cards-list {
padding: 0;
}
@@ -34,7 +36,8 @@
h5 {
margin: 0;
- font-size: 1.15em
+ font-size: 1.15em;
+ text-transform: capitalize;
}
dl {
diff --git a/src/components/settings_modal/admin_tabs/http_tab.js b/src/components/settings_modal/admin_tabs/http_tab.js
index ea76ebe6f..1e45763f9 100644
--- a/src/components/settings_modal/admin_tabs/http_tab.js
+++ b/src/components/settings_modal/admin_tabs/http_tab.js
@@ -12,6 +12,8 @@ import SharedComputedObject from '../helpers/shared_computed_object.js'
import StringSetting from '../helpers/string_setting.vue'
import TupleSetting from '../helpers/tuple_setting.vue'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
+
const HTTPTab = {
provide() {
return {
@@ -35,7 +37,7 @@ const HTTPTab = {
...SharedComputedObject(),
sslOptions() {
const desc = get(
- this.$store.state.adminSettings.descriptions,
+ useAdminSettingsStore().descriptions,
':pleroma.:http.:adapter.:ssl_options.:versions',
)
return new Set(
diff --git a/src/components/settings_modal/admin_tabs/instance_tab.js b/src/components/settings_modal/admin_tabs/instance_tab.js
index 67d04c303..a428a78f9 100644
--- a/src/components/settings_modal/admin_tabs/instance_tab.js
+++ b/src/components/settings_modal/admin_tabs/instance_tab.js
@@ -12,6 +12,8 @@ import PWAManifestIconsSetting from '../helpers/pwa_manifest_icons_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import StringSetting from '../helpers/string_setting.vue'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
+
const InstanceTab = {
provide() {
return {
@@ -34,7 +36,7 @@ const InstanceTab = {
computed: {
...SharedComputedObject(),
providersOptions() {
- const desc = get(this.$store.state.adminSettings.descriptions, [
+ const desc = get(useAdminSettingsStore().descriptions, [
':pleroma',
'Pleroma.Web.Metadata',
':providers',
@@ -47,7 +49,7 @@ const InstanceTab = {
)
},
limitLocalContentOptions() {
- const desc = get(this.$store.state.adminSettings.descriptions, [
+ const desc = get(useAdminSettingsStore().descriptions, [
':pleroma',
':instance',
':limit_to_local_content',
diff --git a/src/components/settings_modal/admin_tabs/links_tab.js b/src/components/settings_modal/admin_tabs/links_tab.js
index b3b5f1f2e..a6b99483b 100644
--- a/src/components/settings_modal/admin_tabs/links_tab.js
+++ b/src/components/settings_modal/admin_tabs/links_tab.js
@@ -10,6 +10,8 @@ import ListSetting from '../helpers/list_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import StringSetting from '../helpers/string_setting.vue'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
+
const LinksTab = {
provide() {
return {
@@ -30,27 +32,27 @@ const LinksTab = {
computed: {
classIsPresent() {
return (
- this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][
+ useAdminSettingsStore().draft[':pleroma']['Pleroma.Formatter'][
':class'
] !== false
)
},
relIsPresent() {
return (
- this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][
+ useAdminSettingsStore().draft[':pleroma']['Pleroma.Formatter'][
':rel'
] !== false
)
},
truncateIsPresent() {
return (
- this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][
+ useAdminSettingsStore().draft[':pleroma']['Pleroma.Formatter'][
':truncate'
] !== false
)
},
truncateDescription() {
- return get(this.$store.state.adminSettings.descriptions, [
+ return get(useAdminSettingsStore().descriptions, [
':pleroma',
'Pleroma.Formatter',
':truncate',
@@ -58,7 +60,7 @@ const LinksTab = {
},
ttlSettersOptions() {
const desc = get(
- this.$store.state.adminSettings.descriptions,
+ useAdminSettingsStore().descriptions,
':pleroma.:rich_media.:ttl_setters',
)
return new Set(
@@ -70,7 +72,7 @@ const LinksTab = {
},
parsersOptions() {
const desc = get(
- this.$store.state.adminSettings.descriptions,
+ useAdminSettingsStore().descriptions,
':pleroma.:rich_media.:parsers',
)
return new Set(
@@ -97,12 +99,12 @@ const LinksTab = {
]
},
mediaProxyEnabled() {
- return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][
+ return useAdminSettingsStore().draft[':pleroma'][':media_proxy'][
':enabled'
]
},
mediaInvalidationProvider() {
- return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][
+ return useAdminSettingsStore().draft[':pleroma'][':media_proxy'][
':invalidation'
][':provider']
},
@@ -110,19 +112,19 @@ const LinksTab = {
},
methods: {
checkRel(e) {
- this.$store.commit('updateAdminDraft', {
+ useAdminSettingsStore().updateAdminDraft({
path: [':pleroma', 'Pleroma.Formatter', ':rel'],
value: e ? '' : false,
})
},
checkClass(e) {
- this.$store.commit('updateAdminDraft', {
+ useAdminSettingsStore().updateAdminDraft({
path: [':pleroma', 'Pleroma.Formatter', ':class'],
value: e ? '' : false,
})
},
checkTruncate(e) {
- this.$store.commit('updateAdminDraft', {
+ useAdminSettingsStore().updateAdminDraft({
path: [':pleroma', 'Pleroma.Formatter', ':truncate'],
value: e ? 20 : false,
})
diff --git a/src/components/settings_modal/admin_tabs/mailer_tab.js b/src/components/settings_modal/admin_tabs/mailer_tab.js
index 0b909334b..b0750474b 100644
--- a/src/components/settings_modal/admin_tabs/mailer_tab.js
+++ b/src/components/settings_modal/admin_tabs/mailer_tab.js
@@ -7,6 +7,8 @@ import IntegerSetting from '../helpers/integer_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import StringSetting from '../helpers/string_setting.vue'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
+
const MailerTab = {
provide() {
return {
@@ -26,7 +28,7 @@ const MailerTab = {
computed: {
adaptersLabels() {
const prefix = 'Swoosh.Adapters.'
- const descriptions = this.$store.state.adminSettings.descriptions
+ const descriptions = useAdminSettingsStore().descriptions
const options =
descriptions[':pleroma']['Pleroma.Emails.Mailer'][':adapter']
.suggestions
@@ -46,20 +48,20 @@ const MailerTab = {
// ]))
},
adapter() {
- return this.$store.state.adminSettings.draft[':pleroma'][
- 'Pleroma.Emails.Mailer'
- ][':adapter']
+ return useAdminSettingsStore().draft[':pleroma']['Pleroma.Emails.Mailer'][
+ ':adapter'
+ ]
},
mailerEnabled() {
- return this.$store.state.adminSettings.draft[':pleroma'][
- 'Pleroma.Emails.Mailer'
- ][':enabled']
+ return useAdminSettingsStore().draft[':pleroma']['Pleroma.Emails.Mailer'][
+ ':enabled'
+ ]
},
...SharedComputedObject(),
},
methods: {
adapterHasKey(key) {
- const descriptions = this.$store.state.adminSettings.descriptions
+ const descriptions = useAdminSettingsStore().descriptions
const mailerStuff = descriptions[':pleroma']['Pleroma.Emails.Mailer']
const adapterStuff = mailerStuff[':subgroup,' + this.adapter]
return Object.hasOwn(adapterStuff, key)
diff --git a/src/components/settings_modal/admin_tabs/media_proxy_tab.js b/src/components/settings_modal/admin_tabs/media_proxy_tab.js
index 6c7231312..9f8a7140e 100644
--- a/src/components/settings_modal/admin_tabs/media_proxy_tab.js
+++ b/src/components/settings_modal/admin_tabs/media_proxy_tab.js
@@ -7,6 +7,8 @@ import ListSetting from '../helpers/list_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import StringSetting from '../helpers/string_setting.vue'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
+
const MediaProxyTab = {
provide() {
return {
@@ -25,12 +27,12 @@ const MediaProxyTab = {
},
computed: {
mediaProxyEnabled() {
- return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][
+ return useAdminSettingsStore().draft[':pleroma'][':media_proxy'][
':enabled'
]
},
mediaInvalidationProvider() {
- return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][
+ return useAdminSettingsStore().draft[':pleroma'][':media_proxy'][
':invalidation'
][':provider']
},
diff --git a/src/components/settings_modal/admin_tabs/uploads_tab.js b/src/components/settings_modal/admin_tabs/uploads_tab.js
index 760206499..90be43a17 100644
--- a/src/components/settings_modal/admin_tabs/uploads_tab.js
+++ b/src/components/settings_modal/admin_tabs/uploads_tab.js
@@ -4,6 +4,8 @@ import IntegerSetting from '../helpers/integer_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import StringSetting from '../helpers/string_setting.vue'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
+
const UploadsTab = {
provide() {
return {
@@ -40,9 +42,9 @@ const UploadsTab = {
},
computed: {
uploader() {
- return this.$store.state.adminSettings.draft[':pleroma'][
- 'Pleroma.Upload'
- ][':uploader']
+ return useAdminSettingsStore().draft[':pleroma']['Pleroma.Upload'][
+ ':uploader'
+ ]
},
...SharedComputedObject(),
},
diff --git a/src/components/settings_modal/admin_tabs/users_tab.js b/src/components/settings_modal/admin_tabs/users_tab.js
new file mode 100644
index 000000000..ab40c723a
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/users_tab.js
@@ -0,0 +1,119 @@
+import { isEmpty } from 'lodash'
+
+import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+import List from 'src/components/list/list.vue'
+import ModerationTools from 'src/components/moderation_tools/moderation_tools.vue'
+import Select from 'src/components/select/select.vue'
+import AdminUserCard from 'src/components/settings_modal/admin_tabs/admin_user_card.vue'
+
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
+
+const UsersTab = {
+ components: {
+ Checkbox,
+ Select,
+ BasicUserCard,
+ List,
+ AdminUserCard,
+ ModerationTools,
+ },
+ data() {
+ return {
+ filtersOrigin: 'local',
+ filtersActivity: 'all',
+ filtersPrivileges: 'all',
+ filtersNeedApproval: false,
+ filtersUnconfirmed: false,
+ filtersQuery: '',
+ filtersName: '',
+ filtersEmail: '',
+ expandedUser: null,
+ }
+ },
+ 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'
+ },
+ fetchOptions() {
+ const filters = {
+ isAdmin: this.filtersIsAdmin,
+ isModerator: this.filtersIsModerator,
+ active: this.filtersActive,
+ deactivated: this.filtersDeactivated,
+ local: this.filtersLocal,
+ external: this.filtersExternal,
+ needApproval: this.filtersNeedApproval,
+ unconfirmed: this.filtersUnconfirmed,
+ }
+
+ return {
+ query: this.filtersQuery,
+ name: this.filtersName,
+ email: this.filtersEmail,
+ pageSize: 50,
+ filters,
+ }
+ },
+ },
+ methods: {
+ fetchUsers(page) {
+ return useAdminSettingsStore().fetchUsers({
+ ...this.fetchOptions,
+ page,
+ })
+ },
+ },
+ watch: {
+ fetchOptions() {
+ this.$refs.usersList?.reset()
+ },
+ },
+}
+
+export default UsersTab
diff --git a/src/components/settings_modal/admin_tabs/users_tab.scss b/src/components/settings_modal/admin_tabs/users_tab.scss
new file mode 100644
index 000000000..fe370cd71
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/users_tab.scss
@@ -0,0 +1,49 @@
+.UsersTab {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+
+ h3 {
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+ }
+
+ .splitter {
+ height: 100%;
+ display: flex;
+ overflow-y: hidden;
+ }
+
+ .filters-section {
+ display: flex;
+ flex-direction: column;
+ gap: 1em;
+ border-right: 1px solid var(--border);
+ padding: 1em;
+ margin-right: 1em;
+ overflow: auto visible;
+ flex: 0 0 auto;
+
+ > div {
+ flex: 0 1 auto;
+ }
+
+ .filter {
+ display: block;
+ min-width: 14em;
+
+ .query-label {
+ margin-bottom: 0.5em;
+ }
+
+ > input {
+ width: 100%;
+ }
+ }
+ }
+
+ .users-list {
+ flex: 1 0 30em;
+ }
+}
diff --git a/src/components/settings_modal/admin_tabs/users_tab.vue b/src/components/settings_modal/admin_tabs/users_tab.vue
new file mode 100644
index 000000000..a40d6f995
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/users_tab.vue
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+ {{ $t('admin_dash.users.labels.origin') }}
+
+
+
+
+
+ {{ $t('admin_dash.users.labels.activity') }}
+
+
+
+
+
+ {{ $t('admin_dash.users.labels.privileges') }}
+
+
+
+
+
+ {{ $t('admin_dash.users.options.only_unapproved') }}
+
+
+
+
+ {{ $t('admin_dash.users.options.only_unconfirmed') }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('admin_dash.users.no_users_found') }}
+
+
+
+
+
+
+
diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js
index af8ebecbd..b9ff08bb5 100644
--- a/src/components/settings_modal/helpers/setting.js
+++ b/src/components/settings_modal/helpers/setting.js
@@ -4,6 +4,7 @@ import DraftButtons from './draft_buttons.vue'
import LocalSettingIndicator from './local_setting_indicator.vue'
import ModifiedIndicator from './modified_indicator.vue'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useLocalConfigStore } from 'src/stores/local_config.js'
@@ -125,14 +126,14 @@ export default {
draft: {
get() {
if (this.realSource === 'admin' || this.path == null) {
- return get(this.$store.state.adminSettings.draft, this.canonPath)
+ return get(useAdminSettingsStore().draft, this.canonPath)
} else {
return this.localDraft
}
},
set(value) {
if (this.realSource === 'admin' || this.path == null) {
- this.$store.commit('updateAdminDraft', {
+ useAdminSettingsStore().updateAdminDraft({
path: this.canonPath,
value,
})
@@ -164,10 +165,7 @@ export default {
: this.draftMode
},
backendDescription() {
- return get(
- this.$store.state.adminSettings.descriptions,
- this.descriptionPath,
- )
+ return get(useAdminSettingsStore().descriptions, this.descriptionPath)
},
backendDescriptionLabel() {
if (this.realSource !== 'admin') return ''
@@ -221,10 +219,7 @@ export default {
let parentValue = null
if (this.parentPath !== undefined && this.realSource === 'admin') {
if (this.realDraftMode) {
- parentValue = get(
- this.$store.state.adminSettings.draft,
- this.parentPath,
- )
+ parentValue = get(useAdminSettingsStore().draft, this.parentPath)
} else {
parentValue = get(this.configSource, this.parentPath)
}
@@ -243,7 +238,7 @@ export default {
case 'profile':
return this.$store.state.profileConfig
case 'admin':
- return this.$store.state.adminSettings.config
+ return useAdminSettingsStore().config
default:
return useMergedConfigStore().mergedConfig
}
@@ -259,7 +254,7 @@ export default {
this.$store.dispatch('setProfileOption', { name: k, value: v })
case 'admin':
return (k, v) =>
- this.$store.dispatch('pushAdminSetting', { path: k, value: v })
+ useAdminSettingsStore().pushAdminSetting({ path: k, value: v })
default:
return (readPath, value) => {
const writePath = `${readPath}`
@@ -372,9 +367,7 @@ export default {
canHardReset() {
return (
this.realSource === 'admin' &&
- this.$store.state.adminSettings.modifiedPaths?.has(
- this.canonPath.join(' -> '),
- )
+ useAdminSettingsStore().modifiedPaths?.has(this.canonPath.join(' -> '))
)
},
matchesExpertLevel() {
diff --git a/src/components/settings_modal/helpers/shared_computed_object.js b/src/components/settings_modal/helpers/shared_computed_object.js
index 39e0b5b9c..4a3b9d4f8 100644
--- a/src/components/settings_modal/helpers/shared_computed_object.js
+++ b/src/components/settings_modal/helpers/shared_computed_object.js
@@ -1,6 +1,7 @@
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
const SharedComputedObject = () => ({
@@ -8,9 +9,11 @@ const SharedComputedObject = () => ({
...mapPiniaState(useMergedConfigStore, {
expertLevel: (store) => store.mergedConfig.expertLevel,
}),
+ ...mapPiniaState(useAdminSettingsStore, {
+ adminConfig: (store) => store.config,
+ adminDraft: (store) => store.draft,
+ }),
...mapState({
- adminConfig: (state) => state.adminSettings.config,
- adminDraft: (state) => state.adminSettings.draft,
user: (state) => state.users.currentUser,
}),
})
diff --git a/src/components/settings_modal/helpers/vertical_tab_switcher.jsx b/src/components/settings_modal/helpers/vertical_tab_switcher.jsx
index e407d29e5..cbe1be158 100644
--- a/src/components/settings_modal/helpers/vertical_tab_switcher.jsx
+++ b/src/components/settings_modal/helpers/vertical_tab_switcher.jsx
@@ -160,19 +160,25 @@ export default {
'tab-content-wrapper',
active ? '-active' : '-hidden',
]
+ const slotWrapperClasses = [
+ 'tab-slot-wrapper',
+ active ? '-active' : '-hidden',
+ ]
const contentClasses = ['tab-content']
- if (props['full-width'] || props['full-width'] === '') {
+ if (props['full-width'] || props['full-width'] != null) {
contentClasses.push('-full-width')
wrapperClasses.push('-full-width')
+ slotWrapperClasses.push('-full-width')
}
- if (props['full-height'] || props['full-width'] === '') {
+ if (props['full-height'] || props['full-width'] != null) {
contentClasses.push('-full-height')
wrapperClasses.push('-full-height')
+ slotWrapperClasses.push('-full-height')
}
return (
-
diff --git a/src/components/settings_modal/helpers/vertical_tab_switcher.scss b/src/components/settings_modal/helpers/vertical_tab_switcher.scss
index 67da9305b..3fff96688 100644
--- a/src/components/settings_modal/helpers/vertical_tab_switcher.scss
+++ b/src/components/settings_modal/helpers/vertical_tab_switcher.scss
@@ -40,6 +40,7 @@
.tab-slot-wrapper {
flex: 1 1 auto;
+ position: relative;
height: 100%;
padding: 0 1em;
overflow-y: auto;
@@ -48,6 +49,16 @@
grid-template-areas: ". content .";
flex-direction: column;
+ &.-full-width {
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ &.-full-height {
+ padding-top: 0;
+ padding-bottom: 0;
+ }
+
.tab-content {
grid-area: content;
@@ -56,10 +67,19 @@
}
&.-full-height {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+
> * {
height: 100%;
}
}
+
+ &.-full-width.-full-height {
+ position: absolute;
+ inset: 0;
+ }
}
}
diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index e2df79c26..216ce1b7c 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -8,6 +8,7 @@ import Modal from 'src/components/modal/modal.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
import Popover from 'src/components/popover/popover.vue'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useLocalConfigStore } from 'src/stores/local_config.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
@@ -92,8 +93,11 @@ const SettingsModal = {
closeModal() {
useInterfaceStore().closeSettingsModal()
},
- peekModal() {
- useInterfaceStore().togglePeekSettingsModal()
+ toggleMinimizeModal(state) {
+ useInterfaceStore().toggleMinimizeSettingsModal()
+ },
+ minimizeModal() {
+ useInterfaceStore().setSettingsModalState('minimized')
},
importValidator(data) {
if (!Array.isArray(data._pleroma_settings_version)) {
@@ -232,10 +236,10 @@ const SettingsModal = {
return clone
},
resetAdminDraft() {
- this.$store.commit('resetAdminDraft')
+ useAdminSettingsStore().resetAdminDraft()
},
pushAdminDraft() {
- this.$store.dispatch('pushAdminDraft')
+ useAdminSettingsStore().pushAdminDraft()
},
...mapActions(useInterfaceStore, [
'temporaryChangesRevert',
@@ -250,7 +254,7 @@ const SettingsModal = {
modalMode: (store) => store.settingsModalMode,
modalOpenedOnceUser: (store) => store.settingsModalLoadedUser,
modalOpenedOnceAdmin: (store) => store.settingsModalLoadedAdmin,
- modalPeeked: (store) => store.settingsModalState === 'minimized',
+ modalMinimized: (store) => store.settingsModalState === 'minimized',
}),
expertLevel: {
get() {
@@ -265,11 +269,16 @@ const SettingsModal = {
},
adminDraftAny() {
return !isEqual(
- this.$store.state.adminSettings.config,
- this.$store.state.adminSettings.draft,
+ useAdminSettingsStore().config,
+ useAdminSettingsStore().draft,
)
},
},
+ watch: {
+ $route(r) {
+ this.minimizeModal()
+ },
+ },
}
export default SettingsModal
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
index 7d6e17244..b4f644065 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -36,6 +36,38 @@
margin-top: 0.75em;
}
+ .toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5em;
+ align-items: end;
+
+ .header-buttons {
+ display: flex;
+ flex: 1 0 auto;
+ justify-content: end;
+ align-items: end;
+
+ &:not(.btn-group) {
+ gap: 0.5em;
+ }
+
+ .header-text {
+ flex: 1 0 auto;
+ }
+ }
+
+ .button-default {
+ flex: 0 0 auto;
+ padding: 0.5em;
+ font-size: 1rem;
+ }
+
+ .popover-wrapper {
+ display: flex;
+ }
+ }
+
p {
line-height: 1.5;
margin-left: 2em;
@@ -268,7 +300,7 @@
}
}
- &.peek {
+ &.minimize {
.settings-modal-panel {
/* Explanation:
* Modal is positioned vertically centered.
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
index d2b4f60ee..d5d0a93d9 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -2,8 +2,8 @@
@@ -22,8 +22,8 @@
+
+
+
+
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
index 3b82bbb4c..0d889ce54 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
@@ -1,45 +1,17 @@
-import { get, map, reject } from 'lodash'
+import { get, isEmpty, map, reject } from 'lodash'
-import withLoadMore from 'src/components/../hocs/with_load_more/with_load_more'
-import withSubscription from 'src/components/../hocs/with_subscription/with_subscription'
import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
import BlockCard from 'src/components/block_card/block_card.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import DomainMuteCard from 'src/components/domain_mute_card/domain_mute_card.vue'
+import List from 'src/components/list/list.vue'
import MuteCard from 'src/components/mute_card/mute_card.vue'
import ProgressButton from 'src/components/progress_button/progress_button.vue'
-import SelectableList from 'src/components/selectable_list/selectable_list.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import { useInstanceStore } from 'src/stores/instance.js'
import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js'
-const BlockList = withLoadMore({
- fetch: (props, $store) => $store.dispatch('fetchBlocks'),
- select: (props, $store) =>
- get($store.state.users.currentUser, 'blockIds', []),
- destroy: () => {
- /* no-op */
- },
- childPropName: 'items',
-})(SelectableList)
-
-const MuteList = withLoadMore({
- fetch: (props, $store) => $store.dispatch('fetchMutes'),
- select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
- destroy: () => {
- /* no-op */
- },
- childPropName: 'items',
-})(SelectableList)
-
-const DomainMuteList = withSubscription({
- fetch: (props, $store) => $store.dispatch('fetchDomainMutes'),
- select: (props, $store) =>
- get($store.state.users.currentUser, 'domainMutes', []),
- childPropName: 'items',
-})(SelectableList)
-
const MutesAndBlocks = {
data() {
return {
@@ -52,12 +24,10 @@ const MutesAndBlocks = {
},
components: {
TabSwitcher,
- BlockList,
- MuteList,
- DomainMuteList,
- BlockCard,
- MuteCard,
DomainMuteCard,
+ BlockCard,
+ List,
+ MuteCard,
ProgressButton,
Autosuggest,
Checkbox,
@@ -69,8 +39,20 @@ const MutesAndBlocks = {
user() {
return this.$store.state.users.currentUser
},
+ blocks() {
+ return get(this.$store.state.users.currentUser, 'blockIds', [])
+ },
+ mutes() {
+ return get(this.$store.state.users.currentUser, 'muteIds', [])
+ },
+ domains() {
+ return get(this.$store.state.users.currentUser, 'domainMutes', [])
+ },
},
methods: {
+ fetchItems(group) {
+ return () => this.$store.dispatch('fetch' + group, this.userId)
+ },
importFollows(file) {
return this.$store.state.api.backendInteractor
.importFollows({ file })
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss b/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss
index 5fa3a27b1..c07de6be1 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss
@@ -1,5 +1,5 @@
.mutes-and-blocks-tab {
- height: 100%;
+ min-height: 100%;
.usersearch-wrapper {
padding: 1em;
@@ -26,4 +26,13 @@
margin-top: 1em;
width: 10em;
}
+
+ .blocks,
+ .mutes {
+ display: flex;
+ flex-direction: column;
+ min-height: 100%;
+ position: absolute;
+ inset: 0;
+ }
}
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
index ed4b15a49..5b8066ef7 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
@@ -1,9 +1,12 @@
-
+
-
@@ -51,103 +57,109 @@
{{ $t('settings.no_blocks') }}
-
+
-
-
-
-
-
+
+
+
+
+
-
-
+ {{ $t('user_card.mute') }}
+
+ {{ $t('user_card.mute_progress') }}
-
+
+
+ {{ $t('user_card.unmute') }}
+
+ {{ $t('user_card.unmute_progress') }}
+
+
-
-
-
-
- {{ $t('user_card.mute') }}
-
- {{ $t('user_card.mute_progress') }}
-
-
-
- {{ $t('user_card.unmute') }}
-
- {{ $t('user_card.unmute_progress') }}
-
-
-
-
-
-
-
-
- {{ $t('settings.no_mutes') }}
-
-
-
+
+
+
+
+
+ {{ $t('settings.no_mutes') }}
+
+
+
-
-
-
+
+
+
+
+
-
-
+ {{ $t('domain_mute_card.unmute') }}
+
+ {{ $t('domain_mute_card.unmute_progress') }}
-
+
-
-
-
-
- {{ $t('domain_mute_card.unmute') }}
-
- {{ $t('domain_mute_card.unmute_progress') }}
-
-
-
-
-
-
-
-
- {{ $t('settings.no_mutes') }}
-
-
-
-
+
+
+ {{ item }}
+
+
+
+ {{ $t('settings.no_mutes') }}
+
+
diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js
index c863a7b9f..5114012a8 100644
--- a/src/components/settings_modal/tabs/notifications_tab.js
+++ b/src/components/settings_modal/tabs/notifications_tab.js
@@ -21,7 +21,7 @@ const NotificationsTab = {
if (!this.user) {
return false
}
- return this.user.privileges.includes('reports_manage_reports')
+ return this.user.privileges.has('reports_manage_reports')
},
...SharedComputedObject(),
},
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 0107bde3c..01d7facfc 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -131,40 +131,41 @@ const Status = {
Quote: defineAsyncComponent(() => import('src/components/quote/quote.vue')),
StatusActionButtons,
},
- props: [
- 'statusoid',
- 'replies',
+ props: {
+ statusoid: Object,
+ replies: Array,
- 'expandable',
- 'focused',
- 'highlight',
- 'compact',
- 'isPreview',
- 'noHeading',
- 'inlineExpanded',
- 'showPinned',
- 'inProfile',
- 'inConversation',
- 'inQuote',
- 'profileUserId',
- 'simpleTree',
- 'showOtherRepliesAsButton',
- 'dive',
- 'ignoreMute',
+ expandable: Boolean,
+ focused: Boolean,
+ highlight: Boolean,
+ compact: Boolean,
+ isPreview: Boolean,
+ noHeading: Boolean,
+ inlineExpanded: Boolean,
+ showPinned: Boolean,
+ inProfile: Boolean,
+ inConversation: Boolean,
+ inQuote: Boolean,
- 'controlledThreadDisplayStatus',
- 'controlledToggleThreadDisplay',
- 'controlledShowingTall',
- 'controlledToggleShowingTall',
- 'controlledExpandingSubject',
- 'controlledToggleExpandingSubject',
- 'controlledShowingLongSubject',
- 'controlledToggleShowingLongSubject',
- 'controlledReplying',
- 'controlledToggleReplying',
- 'controlledMediaPlaying',
- 'controlledSetMediaPlaying',
- ],
+ profileUserId: String,
+ simpleTree: Boolean,
+ showOtherRepliesAsButton: Boolean,
+ dive: Function,
+ ignoreMute: Boolean,
+
+ controlledThreadDisplayStatus: String,
+ controlledToggleThreadDisplay: Function,
+ controlledShowingTall: Boolean,
+ controlledToggleShowingTall: Function,
+ controlledExpandingSubject: Boolean,
+ controlledToggleExpandingSubject: Function,
+ controlledShowingLongSubject: Boolean,
+ controlledToggleShowingLongSubject: Function,
+ controlledReplying: Boolean,
+ controlledToggleReplying: Function,
+ controlledMediaPlaying: Boolean,
+ controlledSetMediaPlaying: Function,
+ },
emits: ['goto', 'toggleExpanded'],
data() {
return {
diff --git a/src/components/status_action_buttons/action_button.js b/src/components/status_action_buttons/action_button.js
index 661d1befd..e3cdf1fb1 100644
--- a/src/components/status_action_buttons/action_button.js
+++ b/src/components/status_action_buttons/action_button.js
@@ -19,6 +19,7 @@ import {
faChevronDown,
faChevronRight,
faExternalLinkAlt,
+ faEye,
faEyeSlash,
faHistory,
faMinus,
@@ -51,6 +52,7 @@ library.add(
faBookmark,
faBookmarkRegular,
faEyeSlash,
+ faEye,
faThumbtack,
faShareAlt,
faExternalLinkAlt,
diff --git a/src/components/status_action_buttons/action_button_container.js b/src/components/status_action_buttons/action_button_container.js
index 012844dc8..1f4cfc3f6 100644
--- a/src/components/status_action_buttons/action_button_container.js
+++ b/src/components/status_action_buttons/action_button_container.js
@@ -3,14 +3,30 @@ import { defineAsyncComponent } from 'vue'
import Popover from 'src/components/popover/popover.vue'
import ActionButton from './action_button.vue'
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
+
import { library } from '@fortawesome/fontawesome-svg-core'
import {
+ faEnvelope,
+ faEye,
+ faEyeSlash,
faFolderTree,
faGlobe,
+ faLock,
+ faLockOpen,
faUser,
} from '@fortawesome/free-solid-svg-icons'
-library.add(faUser, faGlobe, faFolderTree)
+library.add(
+ faUser,
+ faGlobe,
+ faFolderTree,
+ faEye,
+ faEyeSlash,
+ faLock,
+ faLockOpen,
+ faEnvelope,
+)
export default {
components: {
@@ -61,8 +77,27 @@ export default {
this.domain,
)
},
+ availableScopes() {
+ return ['private', 'unlisted', 'direct', 'public'].filter((scope) => {
+ return scope !== this.status.visibility
+ })
+ },
},
methods: {
+ visibilityIcon(visibility) {
+ switch (visibility) {
+ case 'private':
+ return 'lock'
+ case 'unlisted':
+ return 'lock-open'
+ case 'direct':
+ return 'envelope'
+ case 'local':
+ return 'igloo'
+ default:
+ return 'globe'
+ }
+ },
unmuteUser() {
return this.$store.dispatch('unmuteUser', this.user.id)
},
@@ -79,6 +114,18 @@ export default {
this.$refs.confirmUser.optionallyPrompt()
}
},
+ setScope(visibility) {
+ return useAdminSettingsStore().changeStatusScope({
+ id: this.status.id,
+ visibility,
+ })
+ },
+ setSensitive(sensitive) {
+ useAdminSettingsStore().changeStatusScope({
+ id: this.status.id,
+ sensitive,
+ })
+ },
toggleConversationMute() {
if (this.conversationIsMuted) {
this.unmuteConversation()
diff --git a/src/components/status_action_buttons/action_button_container.vue b/src/components/status_action_buttons/action_button_container.vue
index da0beed79..24f9a3c3b 100644
--- a/src/components/status_action_buttons/action_button_container.vue
+++ b/src/components/status_action_buttons/action_button_container.vue
@@ -14,6 +14,59 @@
/>
+
useMergedConfigStore().mergedConfig.modalOnDelete,
@@ -243,6 +243,26 @@ export const BUTTONS = [
return dispatch('deleteStatus', { id: status.id })
},
},
+ {
+ // =========
+ // CHANGE SCOPE
+ // =========
+ name: 'changeScope',
+ icon: 'eye',
+ label: 'status.admin_change_scope',
+ if({ status, loggedIn, currentUser }) {
+ return (
+ loggedIn &&
+ (status.user.id === currentUser.id ||
+ currentUser.privileges.has('messages_delete'))
+ )
+ },
+ toggleable: false,
+ dropdown: true,
+ action({ status, dispatch, emit }) {
+ /* prevent hiding */
+ },
+ },
{
// =========
// SHARE/COPY
@@ -279,10 +299,12 @@ export const BUTTONS = [
label: 'user_card.report',
if: ({ loggedIn }) => loggedIn,
action({ status }) {
- return useReportsStore().openUserReportingModal({
+ useReportsStore().openUserReportingModal({
userId: status.user.id,
statusIds: [status.id],
})
+
+ return Promise.resolve()
},
},
].map((button) => {
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index f788ea30e..755ff9a78 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -49,12 +49,14 @@
.contents.scrollable-tabs {
flex-basis: 0;
+ position: relative;
}
}
.contents {
flex: 1 0 auto;
min-height: 0;
+ position: relative;
.hidden {
display: none;
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index d987695dd..acb2c4ea1 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -84,6 +84,12 @@ export default {
default: false,
type: Boolean,
},
+ // Hide action buttons
+ hideButtons: {
+ required: false,
+ default: false,
+ type: Boolean,
+ },
// default - open profile, 'zoom' - zoom, function - call function
avatarAction: {
required: false,
@@ -280,7 +286,7 @@ export default {
},
},
visibleRole() {
- if (!this.newShowRole) {
+ if (!this.user.show_role && !this.user.adminData) {
return
}
const rights = this.user.rights
diff --git a/src/components/user_card/user_card.scss b/src/components/user_card/user_card.scss
index e676db766..b724c335b 100644
--- a/src/components/user_card/user_card.scss
+++ b/src/components/user_card/user_card.scss
@@ -22,6 +22,23 @@
}
}
+ summary {
+ font-weight: bold;
+ cursor: pointer;
+ }
+
+ details {
+ margin: 0.5em 0;
+
+ &[open] summary {
+ margin-bottom: 0.5em;
+ }
+
+ ul {
+ margin: 0;
+ }
+ }
+
.input.bio {
height: auto; // override settings default textarea size
}
@@ -56,7 +73,7 @@
.user-card-bio {
text-align: center;
- margin: 0 0.6em;
+ margin: 0.6em;
&.input {
margin: 0 1em;
@@ -81,15 +98,18 @@
--_still-image-label-visibility: hidden;
}
+ .admin-data,
.personal-marks {
- margin: 0.6em;
- padding: 0.6em;
+ margin: 0 0.6em;
+ padding: 0 0.6em;
&:not(:last-child) {
border-bottom: 1px dotted var(--border);
}
.highlighter {
+ margin-bottom: 0.5em;
+
h4 {
margin-top: 0.6em;
}
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 3eb142954..af779823c 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -104,7 +104,7 @@
/>
@@ -228,7 +228,7 @@
+
+
+
+ {{ $t('user_card.admin_data.data') }}
+
+
+
+ -
+ {{ $t('admin_dash.users.local_id') }}
+
+ -
+ {{ user.adminData.id }}
+
+
+
+ -
+ {{ $t('admin_dash.users.labels.email') }}
+
+ -
+ {{ user.adminData.email == null ? $t('general.not_available') : user.adminData.email }}
+
+
+
+ -
+ {{ $t('general.role.admin') }}
+
+ -
+ {{ $t('general.' + (user.adminData.roles.admin ? 'yes' : 'no')) }}
+
+
+
+ -
+ {{ $t('general.role.moderator') }}
+
+ -
+ {{ $t('general.' + (user.adminData.roles.moderator ? 'yes' : 'no')) }}
+
+
+
+ -
+ {{ $t('admin_dash.users.indicator.confirmed') }}
+
+ -
+ {{ $t('general.' + (user.adminData.is_confirmed ? 'yes' : 'no')) }}
+
+
+
+ -
+ {{ $t('admin_dash.users.indicator.approved') }}
+
+ -
+ {{ $t('general.' + (user.adminData.is_approved ? 'yes' : 'no')) }}
+
+
+
+ -
+ {{ $t('admin_dash.users.indicator.suggested') }}
+
+ -
+ {{ $t('general.' + (user.adminData.is_suggested ? 'yes' : 'no')) }}
+
+
+
+
+ {{ $t('user_card.admin_data.registration_reason') }}
+
+
+ {{ user.adminData.registration_reason == null ? $t('general.not_available') : user.adminData.registration_reason }}
+
+
+
+
+ {{ $t('user_card.admin_data.tags') }}
+
+
+ -
+ {{ $t('general.none') }}
+
+ -
+
+ {{ tag }}
+
+ {{ ' ' }}
+
+
+
+
+
+
{{ $t('settings.bio') }}
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 2c3878292..d2d766a7f 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -1,13 +1,11 @@
import { get } from 'lodash'
import { mapState } from 'pinia'
-import Conversation from 'src/components/conversation/conversation.vue'
import FollowCard from 'src/components/follow_card/follow_card.vue'
import List from 'src/components/list/list.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import Timeline from 'src/components/timeline/timeline.vue'
import UserCard from 'src/components/user_card/user_card.vue'
-import withLoadMore from '../../hocs/with_load_more/with_load_more'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
@@ -19,28 +17,6 @@ import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
library.add(faCircleNotch)
-const FollowerList = withLoadMore({
- fetch: (props, $store) => $store.dispatch('fetchFollowers', props.userId),
- select: (props, $store) =>
- get($store.getters.findUser(props.userId), 'followerIds', []).map((id) =>
- $store.getters.findUser(id),
- ),
- destroy: (props, $store) => $store.dispatch('clearFollowers', props.userId),
- childPropName: 'items',
- additionalPropNames: ['userId'],
-})(List)
-
-const FriendList = withLoadMore({
- fetch: (props, $store) => $store.dispatch('fetchFriends', props.userId),
- select: (props, $store) =>
- get($store.getters.findUser(props.userId), 'friendIds', []).map((id) =>
- $store.getters.findUser(id),
- ),
- destroy: (props, $store) => $store.dispatch('clearFriends', props.userId),
- childPropName: 'items',
- additionalPropNames: ['userId'],
-})(List)
-
const defaultTabKey = 'statuses'
const UserProfile = {
@@ -64,6 +40,8 @@ const UserProfile = {
unmounted() {
this.stopFetching()
useInterfaceStore().setForeignProfileBackground(null)
+ this.$store.dispatch('clearFollowers', this.userId)
+ this.$store.dispatch('clearFriends', this.userId)
},
computed: {
timeline() {
@@ -104,11 +82,31 @@ const UserProfile = {
compactProfiles() {
return useMergedConfigStore().mergedConfig.compactProfiles
},
+ friends() {
+ return get(
+ this.$store.getters.findUser(this.userId),
+ 'friendIds',
+ [],
+ ).map((id) => this.$store.getters.findUser(id))
+ },
+ followers() {
+ return get(
+ this.$store.getters.findUser(this.userId),
+ 'followerIds',
+ [],
+ ).map((id) => this.$store.getters.findUser(id))
+ },
},
methods: {
setFooterRef(el) {
this.footerRef = el
},
+ fetchUsers(group) {
+ return () =>
+ this.$store
+ .dispatch('fetch' + group, this.userId)
+ .then((result) => ({ items: result }))
+ },
load(userNameOrId) {
const startFetchingTimeline = (timeline, userId) => {
// Clear timeline only if load another user's profile
@@ -203,11 +201,9 @@ const UserProfile = {
components: {
UserCard,
Timeline,
- FollowerList,
- FriendList,
+ List,
FollowCard,
TabSwitcher,
- Conversation,
},
}
diff --git a/src/components/user_profile/user_profile.scss b/src/components/user_profile/user_profile.scss
new file mode 100644
index 000000000..4b68b46b0
--- /dev/null
+++ b/src/components/user_profile/user_profile.scss
@@ -0,0 +1,65 @@
+.user-profile {
+ flex: 2;
+
+ .card-wrapper {
+ border-top-left-radius: var(--roundness);
+ border-top-right-radius: var(--roundness);
+ }
+
+ .panel-footer {
+ border-bottom-left-radius: var(--roundness);
+ border-bottom-right-radius: var(--roundness);
+ }
+
+ // No sticky header on user profile
+ --currentPanelStack: 0;
+
+ .userlist-placeholder {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 2em;
+ }
+
+ .user-info {
+ margin: 1.2em;
+ }
+
+ &.-admin-view {
+ .filter {
+ padding: 1em;
+ }
+
+ .list-item {
+ padding: 0;
+ border-bottom: 1px solid var(--border);
+
+ .Status{
+ width: 100%
+ }
+ }
+
+ .footer {
+ background: var(--background);
+ }
+ }
+}
+
+.user-profile-placeholder {
+ .panel-body {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 7em;
+ }
+
+ .alert {
+ padding: 0.75em 5em;
+ border-width: 2px;
+
+ .error-message {
+ color: var(--text);
+ font-weight: bold;
+ }
+ }
+}
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 72515c90d..6ca0c39bd 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -39,14 +39,14 @@
:label="$t('user_card.followees')"
:disabled="!user.friends_count"
>
-
-
+
-
-
+
-
+
diff --git a/src/components/user_profile/user_profile_admin_view.js b/src/components/user_profile/user_profile_admin_view.js
new file mode 100644
index 000000000..94092b155
--- /dev/null
+++ b/src/components/user_profile/user_profile_admin_view.js
@@ -0,0 +1,71 @@
+import { get } from 'lodash'
+import { mapState } from 'pinia'
+
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+import List from 'src/components/list/list.vue'
+import Status from 'src/components/status/status.vue'
+import UserCard from 'src/components/user_card/user_card.vue'
+
+import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
+import { useInterfaceStore } from 'src/stores/interface.js'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
+
+library.add(faCircleNotch)
+
+const UserProfileAdminView = {
+ data() {
+ return {
+ godmode: false,
+ showReblogs: false,
+ }
+ },
+ created() {
+ this.$store.dispatch('fetchUserIfMissing', this.userId)
+ useInterfaceStore().setForeignProfileBackground(this.user?.background_image)
+ },
+ updated() {
+ useInterfaceStore().setForeignProfileBackground(this.user?.background_image)
+ },
+ unmounted() {
+ useInterfaceStore().setForeignProfileBackground(null)
+ },
+ computed: {
+ fetchOptions() {
+ return {
+ pageSize: 20,
+ godmode: this.godmode,
+ id: this.userId,
+ withReblogs: this.showReblogs,
+ }
+ },
+ user() {
+ return this.$store.getters.findUser(this.userId)
+ },
+ userId() {
+ return this.$route.params.id
+ },
+ },
+ methods: {
+ fetchStatuses(page) {
+ return useAdminSettingsStore().fetchStatuses({
+ ...this.fetchOptions,
+ page,
+ })
+ },
+ },
+ components: {
+ UserCard,
+ List,
+ Status,
+ Checkbox,
+ },
+ watch: {
+ fetchOptions() {
+ this.$refs.list.reset()
+ },
+ },
+}
+
+export default UserProfileAdminView
diff --git a/src/components/user_profile/user_profile_admin_view.vue b/src/components/user_profile/user_profile_admin_view.vue
new file mode 100644
index 000000000..e5118f474
--- /dev/null
+++ b/src/components/user_profile/user_profile_admin_view.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+ {{ $t('admin_dash.users.filters.show_direct') }}
+
+
+ {{ $t('admin_dash.users.filters.show_reblogs') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
index d55b5a962..12d548054 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.js
+++ b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -1,3 +1,5 @@
+import { mapState } from 'pinia'
+
import Checkbox from 'src/components/checkbox/checkbox.vue'
import List from 'src/components/list/list.vue'
import Modal from 'src/components/modal/modal.vue'
@@ -16,15 +18,12 @@ const UserReportingModal = {
return {
comment: '',
forward: false,
- statusIdsToReport: [],
+ statusIdsToReport: new Set(),
processing: false,
error: false,
}
},
computed: {
- reportModal() {
- return useReportsStore().reportModal
- },
isLoggedIn() {
return !!this.$store.state.users.currentUser
},
@@ -43,31 +42,26 @@ const UserReportingModal = {
this.user.screen_name.substr(this.user.screen_name.indexOf('@') + 1)
)
},
- statuses() {
- return this.reportModal.statuses
- },
- preTickedIds() {
- return this.reportModal.preTickedIds
- },
+ ...mapState(useReportsStore, ['reportModal']),
},
watch: {
userId: 'resetState',
- preTickedIds(newValue) {
- this.statusIdsToReport = newValue
- },
},
methods: {
resetState() {
// Reset state
this.comment = ''
this.forward = false
- this.statusIdsToReport = this.preTickedIds
+ this.statusIdsToReport = new Set(this.reportModal.preTickedIds)
this.processing = false
this.error = false
},
closeModal() {
useReportsStore().closeUserReportingModal()
},
+ onListSelect(selected) {
+ this.statusIdsToReport = selected
+ },
reportUser() {
this.processing = true
this.error = false
@@ -75,7 +69,7 @@ const UserReportingModal = {
userId: this.userId,
comment: this.comment,
forward: this.forward,
- statusIds: this.statusIdsToReport,
+ statusIds: [...this.statusIdsToReport],
}
this.$store.state.api.backendInteractor
.reportUser({ ...params })
@@ -92,23 +86,6 @@ const UserReportingModal = {
clearError() {
this.error = false
},
- isChecked(statusId) {
- return this.statusIdsToReport.indexOf(statusId) !== -1
- },
- toggleStatus(checked, statusId) {
- if (checked === this.isChecked(statusId)) {
- return
- }
-
- if (checked) {
- this.statusIdsToReport.push(statusId)
- } else {
- this.statusIdsToReport.splice(
- this.statusIdsToReport.indexOf(statusId),
- 1,
- )
- }
- },
resize(e) {
const target = e.target || e
if (!(target instanceof window.Element)) {
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
index aa5704a97..a028ebeb6 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.vue
+++ b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -51,19 +51,18 @@
-
+
-
-
- toggleStatus(checked, item.id)"
- />
-
+
@@ -136,20 +135,6 @@
overflow-y: auto;
}
- &-sitem {
- display: flex;
- justify-content: space-between;
-
- /* TODO cleanup this */
- > .Status {
- flex: 1;
- }
-
- > .checkbox {
- margin: 0.75em;
- }
- }
-
@media all and (width >= 801px) {
.panel-body {
flex-direction: row;
diff --git a/src/components/user_timed_filter_modal/user_timed_filter_modal.vue b/src/components/user_timed_filter_modal/user_timed_filter_modal.vue
index 055c2f7f4..f9b23076c 100644
--- a/src/components/user_timed_filter_modal/user_timed_filter_modal.vue
+++ b/src/components/user_timed_filter_modal/user_timed_filter_modal.vue
@@ -11,62 +11,64 @@
{{ $t(isMute ? 'user_card.expire_mute_message' : 'user_card.expire_block_message', [user.screen_name]) }}
-
+