Merge branch 'admin-users' into shigusegubu-themes3
This commit is contained in:
commit
9f68d151c1
50 changed files with 283 additions and 2454 deletions
24
src/App.scss
24
src/App.scss
|
|
@ -411,6 +411,14 @@ nav {
|
||||||
button:not(.button-default) {
|
button:not(.button-default) {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
|
text-align: initial;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
display: inline;
|
||||||
|
font-family: inherit;
|
||||||
|
line-height: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
|
|
@ -436,22 +444,6 @@ nav {
|
||||||
--__line-height: 1.5em;
|
--__line-height: 1.5em;
|
||||||
--__horizontal-gap: 0.75em;
|
--__horizontal-gap: 0.75em;
|
||||||
--__vertical-gap: 0.5em;
|
--__vertical-gap: 0.5em;
|
||||||
|
|
||||||
&.-non-interactive {
|
|
||||||
cursor: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
> a,
|
|
||||||
> button:not(.button-default) {
|
|
||||||
text-align: initial;
|
|
||||||
padding: 0;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
display: inline;
|
|
||||||
font-family: inherit;
|
|
||||||
line-height: unset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-unstyled {
|
.button-unstyled {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<basic-user-card :user="user">
|
<BasicUserCard :user="user">
|
||||||
<div class="block-card-content-container">
|
<div class="block-card-content-container">
|
||||||
<span
|
<span
|
||||||
v-if="blocked && blockExpiryAvailable"
|
v-if="blocked && blockExpiryAvailable"
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
:is-mute="false"
|
:is-mute="false"
|
||||||
/>
|
/>
|
||||||
</teleport>
|
</teleport>
|
||||||
</basic-user-card>
|
</BasicUserCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./block_card.js"></script>
|
<script src="./block_card.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="below">
|
||||||
|
<slot name="below" />
|
||||||
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<slot name="footerLeft" />
|
<slot name="footerLeft" />
|
||||||
<button
|
<button
|
||||||
|
|
@ -61,6 +63,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.below:not(:empty) {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
max-width: 50ch;
|
max-width: 50ch;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import ConfirmModal from './confirm_modal.vue'
|
|
||||||
//import Select from 'src/components/select/select.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
message: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
cancelText: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
confirmText: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ['hide', 'show', 'action'],
|
|
||||||
data: () => ({
|
|
||||||
showing: false,
|
|
||||||
}),
|
|
||||||
components: {
|
|
||||||
ConfirmModal,
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
show() {
|
|
||||||
this.showing = true
|
|
||||||
this.$emit('show')
|
|
||||||
},
|
|
||||||
hide() {
|
|
||||||
this.showing = false
|
|
||||||
this.$emit('hide')
|
|
||||||
},
|
|
||||||
doGeneric() {
|
|
||||||
this.$emit('action')
|
|
||||||
this.hide()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
<template>
|
|
||||||
<ConfirmModal
|
|
||||||
v-if="showing"
|
|
||||||
:title="title"
|
|
||||||
:cancel-text="cancelText"
|
|
||||||
:confirm-text="confirmText"
|
|
||||||
@accepted="doGeneric"
|
|
||||||
@cancelled="hide"
|
|
||||||
>
|
|
||||||
<template #default>
|
|
||||||
<span v-text="message" />
|
|
||||||
</template>
|
|
||||||
</ConfirmModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./generic_confirm.js" />
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.expiry-amount {
|
|
||||||
width: 4em;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
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()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
<template>
|
|
||||||
<ConfirmModal
|
|
||||||
v-if="showing"
|
|
||||||
:title="title"
|
|
||||||
:cancel-text="cancelText"
|
|
||||||
:confirm-text="confirmText"
|
|
||||||
@accepted="doWithText"
|
|
||||||
@cancelled="hide"
|
|
||||||
>
|
|
||||||
<template #default>
|
|
||||||
<span v-text="message" />
|
|
||||||
<input
|
|
||||||
v-model="text"
|
|
||||||
class="input string-input filter-input"
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</ConfirmModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./text_confirm.js" />
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.expiry-amount {
|
|
||||||
width: 4em;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -17,7 +17,7 @@ const Interactions = {
|
||||||
allowFollowingMove:
|
allowFollowingMove:
|
||||||
this.$store.state.users.currentUser.allow_following_move,
|
this.$store.state.users.currentUser.allow_following_move,
|
||||||
filterMode: tabModeDict.mentions,
|
filterMode: tabModeDict.mentions,
|
||||||
canSeeReports: this.$store.state.users.currentUser.has.has(
|
canSeeReports: this.$store.state.users.currentUser.privileges.has(
|
||||||
'reports_manage_reports',
|
'reports_manage_reports',
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ const List = {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => '',
|
default: () => '',
|
||||||
},
|
},
|
||||||
|
preSelect: {
|
||||||
|
type: Array,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
nonInteractive: {
|
nonInteractive: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
|
@ -44,7 +48,7 @@ const List = {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
items: [],
|
items: [],
|
||||||
selected: new Set([]),
|
selected: new Set(this.preSelect),
|
||||||
loading: false,
|
loading: false,
|
||||||
bottomedOut: true,
|
bottomedOut: true,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="List"
|
class="List"
|
||||||
|
role="list"
|
||||||
:class="{ '-scrollable': scrollable }"
|
:class="{ '-scrollable': scrollable }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
@ -72,9 +73,9 @@
|
||||||
/>
|
/>
|
||||||
<a
|
<a
|
||||||
v-else-if="!bottomedOut"
|
v-else-if="!bottomedOut"
|
||||||
@click="fetchEntries"
|
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
@click="fetchEntries"
|
||||||
>
|
>
|
||||||
{{ $t('general.more') }}
|
{{ $t('general.more') }}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
</div>
|
</div>
|
||||||
<tab-switcher
|
<tab-switcher
|
||||||
class="list-member-management"
|
class="list-member-management"
|
||||||
:scrollable-tabs="true"
|
:scrollable-tabs
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="id || addedUserIds.size > 0"
|
v-if="id || addedUserIds.size > 0"
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ const ENTRIES = [
|
||||||
{
|
{
|
||||||
check: 'action:statuses',
|
check: 'action:statuses',
|
||||||
label: 'user_card.admin_menu.show_statuses',
|
label: 'user_card.admin_menu.show_statuses',
|
||||||
|
conditions: ['count:1'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
separator: true,
|
separator: true,
|
||||||
|
|
@ -215,22 +216,17 @@ const ModerationTools = {
|
||||||
return () => this.setTag(`${group}:${name}`, noTag)
|
return () => this.setTag(`${group}:${name}`, noTag)
|
||||||
case 'action': {
|
case 'action': {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'delete': {
|
case 'delete':
|
||||||
return () => this.deleteUsers()
|
return () => this.deleteUsers()
|
||||||
}
|
case 'resend_confirmation':
|
||||||
case 'resend_confirmation': {
|
|
||||||
return () => this.resendConfirmationEmail()
|
return () => this.resendConfirmationEmail()
|
||||||
}
|
case 'disable_mfa':
|
||||||
case 'disable_mfa': {
|
|
||||||
return () => this.disableMFA()
|
return () => this.disableMFA()
|
||||||
}
|
case 'statuses':
|
||||||
case 'statuses': {
|
|
||||||
return () =>
|
return () =>
|
||||||
this.$router.push(`/users/\$${this.users[0].id}/admin_view`)
|
this.$router.push(`/users/\$${this.users[0].id}/admin_view`)
|
||||||
}
|
case 'require_password_change':
|
||||||
case 'require_password_change': {
|
|
||||||
return () => this.requirePasswordChange()
|
return () => this.requirePasswordChange()
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown action group: ${name}`)
|
throw new Error(`Unknown action group: ${name}`)
|
||||||
}
|
}
|
||||||
|
|
@ -277,24 +273,23 @@ const ModerationTools = {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (group) {
|
switch (group) {
|
||||||
case 'action': {
|
case 'action':
|
||||||
return true
|
if (name === 'statuses') return this.privileged('users_read')
|
||||||
}
|
else return true
|
||||||
case 'rights': {
|
case 'rights':
|
||||||
return this.canGrantRole(name, value)
|
return this.canGrantRole(name, value)
|
||||||
}
|
case 'state':
|
||||||
case 'state': {
|
|
||||||
return this.canChangeState(name, value)
|
return this.canChangeState(name, value)
|
||||||
}
|
case 'mrf_tag':
|
||||||
case 'mrf_tag': {
|
|
||||||
return this.canUseTagPolicy
|
return this.canUseTagPolicy
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
throw new Error(`Unknown moderation group: ${group}`)
|
throw new Error(`Unknown moderation group: ${group}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reduce((acc, entry, index) => {
|
.reduce((acc, entry, index) => {
|
||||||
|
// Removing any double separators as well
|
||||||
|
// as separators at very end and bery beginning
|
||||||
if (entry === 'separator') {
|
if (entry === 'separator') {
|
||||||
if (
|
if (
|
||||||
acc.length === 0 ||
|
acc.length === 0 ||
|
||||||
|
|
@ -363,12 +358,16 @@ const ModerationTools = {
|
||||||
|
|
||||||
const result = new Set()
|
const result = new Set()
|
||||||
|
|
||||||
|
// Each tag can have three states for given group of users
|
||||||
TAGS.forEach((tag) => {
|
TAGS.forEach((tag) => {
|
||||||
if (present.has(tag) && missing.has(tag)) {
|
if (present.has(tag) && missing.has(tag)) {
|
||||||
|
// Some users have tag, some don't: "~tag"
|
||||||
result.add(`~${tag}`)
|
result.add(`~${tag}`)
|
||||||
} else if (missing.has(tag)) {
|
} else if (missing.has(tag)) {
|
||||||
|
// No users have tag: "!tag"
|
||||||
result.add(`!${tag}`)
|
result.add(`!${tag}`)
|
||||||
} else {
|
} else {
|
||||||
|
// All users have tag: "tag"
|
||||||
result.add(tag)
|
result.add(tag)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -394,6 +393,7 @@ const ModerationTools = {
|
||||||
...this.stateSet,
|
...this.stateSet,
|
||||||
...this.tagsSet,
|
...this.tagsSet,
|
||||||
...this.propertySet,
|
...this.propertySet,
|
||||||
|
`count:${this.users.length}`,
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
canDeleteAccount() {
|
canDeleteAccount() {
|
||||||
|
|
@ -405,27 +405,33 @@ const ModerationTools = {
|
||||||
this.privileged('users_manage_tags')
|
this.privileged('users_manage_tags')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
isAdmin() {
|
||||||
|
this.$store.state.users.currentUser.role === 'admin'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
canGrantRole(name, value) {
|
canGrantRole(name, value) {
|
||||||
const setEntry = `${value ? '!' : ''}rights:${name}`
|
const setEntry = `${value ? '!' : ''}rights:${name}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.$store.state.users.currentUser.role === 'admin' &&
|
this.isAdmin &&
|
||||||
this.totalSet.has(setEntry)
|
this.totalSet.has(setEntry)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
canChangeState(name, value) {
|
canChangeState(name, value) {
|
||||||
let privilege
|
|
||||||
|
|
||||||
switch (name) {
|
|
||||||
// TODO detailed privileges
|
|
||||||
default: {
|
|
||||||
privilege = 'users_manage_activation_state'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setEntry = `${value ? '!' : ''}state:${name}`
|
const setEntry = `${value ? '!' : ''}state:${name}`
|
||||||
|
const privilege = (() => {
|
||||||
|
switch (name) {
|
||||||
|
case 'activated':
|
||||||
|
return 'users_manage_activation_state'
|
||||||
|
case 'approved':
|
||||||
|
return 'users_manage_invites'
|
||||||
|
case 'confirmed':
|
||||||
|
return 'users_manage_credentials'
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
return this.privileged(privilege) && this.totalSet.has(setEntry)
|
return this.privileged(privilege) && this.totalSet.has(setEntry)
|
||||||
},
|
},
|
||||||
|
|
@ -449,6 +455,7 @@ const ModerationTools = {
|
||||||
this.confirmDialogName = null
|
this.confirmDialogName = null
|
||||||
},
|
},
|
||||||
privileged(privilege) {
|
privileged(privilege) {
|
||||||
|
if (this.isAdmin) return true
|
||||||
return this.$store.state.users.currentUser.privileges.has(privilege)
|
return this.$store.state.users.currentUser.privileges.has(privilege)
|
||||||
},
|
},
|
||||||
setTag(tag, value) {
|
setTag(tag, value) {
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,19 @@
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.admin_menu.moderation') }}
|
{{ $t('user_card.admin_menu.moderation') }}
|
||||||
<FAIcon v-if="ready" icon="chevron-down" />
|
<FAIcon
|
||||||
<span v-else class="loading-spinner">
|
v-if="ready"
|
||||||
|
icon="chevron-down"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="loading-spinner"
|
||||||
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="fa-old-padding"
|
class="fa-old-padding"
|
||||||
spin
|
spin
|
||||||
icon="circle-notch"
|
icon="circle-notch"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -91,7 +97,10 @@
|
||||||
{{ $t(confirmDialogContent2) }}
|
{{ $t(confirmDialogContent2) }}
|
||||||
</p>
|
</p>
|
||||||
<ul v-if="users.length > 1">
|
<ul v-if="users.length > 1">
|
||||||
<li v-for="user in users">
|
<li
|
||||||
|
v-for="user in users"
|
||||||
|
:key="user.screen_name"
|
||||||
|
>
|
||||||
{{ user.screen_name }}
|
{{ user.screen_name }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
>
|
>
|
||||||
<td>{{ entry.instance }}</td>
|
<td>{{ entry.instance }}</td>
|
||||||
<td v-if="entry.reason === ''">
|
<td v-if="entry.reason === ''">
|
||||||
{{ $t("general.not_applicable") }}
|
{{ $t("about.mrf.simple.not_applicable") }}
|
||||||
</td>
|
</td>
|
||||||
<td v-else>
|
<td v-else>
|
||||||
{{ entry.reason }}
|
{{ entry.reason }}
|
||||||
|
|
@ -70,7 +70,7 @@
|
||||||
>
|
>
|
||||||
<td>{{ entry.instance }}</td>
|
<td>{{ entry.instance }}</td>
|
||||||
<td v-if="entry.reason === ''">
|
<td v-if="entry.reason === ''">
|
||||||
{{ $t("general.not_applicable") }}
|
{{ $t("about.mrf.simple.not_applicable") }}
|
||||||
</td>
|
</td>
|
||||||
<td v-else>
|
<td v-else>
|
||||||
{{ entry.reason }}
|
{{ entry.reason }}
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
>
|
>
|
||||||
<td>{{ entry.instance }}</td>
|
<td>{{ entry.instance }}</td>
|
||||||
<td v-if="entry.reason === ''">
|
<td v-if="entry.reason === ''">
|
||||||
{{ $t("general.not_applicable") }}
|
{{ $t("about.mrf.simple.not_applicable") }}
|
||||||
</td>
|
</td>
|
||||||
<td v-else>
|
<td v-else>
|
||||||
{{ entry.reason }}
|
{{ entry.reason }}
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
>
|
>
|
||||||
<td>{{ entry.instance }}</td>
|
<td>{{ entry.instance }}</td>
|
||||||
<td v-if="entry.reason === ''">
|
<td v-if="entry.reason === ''">
|
||||||
{{ $t("general.not_applicable") }}
|
{{ $t("about.mrf.simple.not_applicable") }}
|
||||||
</td>
|
</td>
|
||||||
<td v-else>
|
<td v-else>
|
||||||
{{ entry.reason }}
|
{{ entry.reason }}
|
||||||
|
|
@ -151,7 +151,7 @@
|
||||||
>
|
>
|
||||||
<td>{{ entry.instance }}</td>
|
<td>{{ entry.instance }}</td>
|
||||||
<td v-if="entry.reason === ''">
|
<td v-if="entry.reason === ''">
|
||||||
{{ $t("general.not_applicable") }}
|
{{ $t("about.mrf.simple.not_applicable") }}
|
||||||
</td>
|
</td>
|
||||||
<td v-else>
|
<td v-else>
|
||||||
{{ entry.reason }}
|
{{ entry.reason }}
|
||||||
|
|
@ -178,7 +178,7 @@
|
||||||
>
|
>
|
||||||
<td>{{ entry.instance }}</td>
|
<td>{{ entry.instance }}</td>
|
||||||
<td v-if="entry.reason === ''">
|
<td v-if="entry.reason === ''">
|
||||||
{{ $t("general.not_applicable") }}
|
{{ $t("about.mrf.simple.not_applicable") }}
|
||||||
</td>
|
</td>
|
||||||
<td v-else>
|
<td v-else>
|
||||||
{{ entry.reason }}
|
{{ entry.reason }}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
.AdminCard {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.right-side {
|
|
||||||
align-items: baseline;
|
|
||||||
justify-content: end;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
|
||||||
import Select from 'src/components/select/select.vue'
|
|
||||||
import Status from 'src/components/status/status.vue'
|
|
||||||
|
|
||||||
import { parseStatus } from 'src/services/entity_normalizer/entity_normalizer.service.js'
|
|
||||||
|
|
||||||
const AdminStatusCard = {
|
|
||||||
props: {
|
|
||||||
statusDetails: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
validator(u) {
|
|
||||||
return typeof u.id === 'string'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
jsonExpanded: false,
|
|
||||||
statusCache: undefined,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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 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
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="setting-item">
|
|
||||||
<h2> {{ $t('admin_dash.users.title_info') }}: </h2>
|
|
||||||
<ul
|
|
||||||
class="setting-list"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<span> {{ $t('admin_dash.users.status_id') }}: {{ statusDetails.id }} </span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span> {{ $t('admin_dash.users.created_at') }}: {{ new Date(statusDetails.created_at).toLocaleString() }} </span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span v-if="statusDetails.edited_at !== null"> {{ $t('admin_dash.users.edited_at') }}: {{ new Date(statusDetails.edited_at).toLocaleString() }} </span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h2> {{ $t('admin_dash.users.title_content') }}: </h2>
|
|
||||||
<ul
|
|
||||||
class="setting-list"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<Status
|
|
||||||
v-if="typeof(statusCache) !== 'undefined'"
|
|
||||||
class="Notification"
|
|
||||||
:compact="true"
|
|
||||||
:statusoid="statusCache"
|
|
||||||
@interacted="false"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
<p> action dropdown thingy </p>
|
|
||||||
<!--
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
class="button button-default btn"
|
|
||||||
type="button"
|
|
||||||
@click="deleteStatus(status.id)"
|
|
||||||
>
|
|
||||||
{{ $t('admin_dash.users.delete_status') }}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Checkbox
|
|
||||||
:model-value="isSensitive"
|
|
||||||
@update:model-value="v => changeSensitivity(v)"
|
|
||||||
>
|
|
||||||
{{ $t('admin_dash.users.content_nsfw') }}
|
|
||||||
</Checkbox>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Select
|
|
||||||
:model-value="visibility"
|
|
||||||
@update:model-value="v => changeVisibility(v)"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
value="public"
|
|
||||||
>
|
|
||||||
{{ $t('admin_dash.users.scope_public') }}
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="unlisted"
|
|
||||||
>
|
|
||||||
{{ $t('admin_dash.users.scope_unlisted') }}
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="private"
|
|
||||||
>
|
|
||||||
{{ $t('admin_dash.users.scope_private') }}
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="direct"
|
|
||||||
>
|
|
||||||
{{ $t('admin_dash.users.scope_direct') }}
|
|
||||||
</option>
|
|
||||||
</Select>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a :href="statusDetails.url"> {{ $t('admin_dash.users.link_source') }} </a>
|
|
||||||
</li>
|
|
||||||
-->
|
|
||||||
</ul>
|
|
||||||
<!--
|
|
||||||
<div v-if="!jsonExpanded">
|
|
||||||
<button
|
|
||||||
class="button button-default btn"
|
|
||||||
type="button"
|
|
||||||
@click="jsonExpanded = !jsonExpanded"
|
|
||||||
>
|
|
||||||
{{ $t('admin_dash.users.expand_raw_info') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<button
|
|
||||||
class="button button-default btn"
|
|
||||||
type="button"
|
|
||||||
@click="jsonExpanded = !jsonExpanded"
|
|
||||||
>
|
|
||||||
{{ $t('admin_dash.users.collapse_raw_info') }}
|
|
||||||
</button>
|
|
||||||
<h2> {{ $t('admin_dash.users.title_details') }} </h2>
|
|
||||||
<pre> {{ JSON.stringify(statusDetails, null, 2) }} </pre>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./admin_status_card.js"></script>
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { defineAsyncComponent } from 'vue'
|
||||||
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
|
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
|
||||||
import ModerationTools from 'src/components/moderation_tools/moderation_tools.vue'
|
import ModerationTools from 'src/components/moderation_tools/moderation_tools.vue'
|
||||||
|
|
||||||
const AdminCard = {
|
const AdminUserCard = {
|
||||||
props: {
|
props: {
|
||||||
userId: {
|
userId: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -17,9 +17,6 @@ const AdminCard = {
|
||||||
user() {
|
user() {
|
||||||
return this.$store.getters.findUser(this.userId)
|
return this.$store.getters.findUser(this.userId)
|
||||||
},
|
},
|
||||||
relationship() {
|
|
||||||
return this.$store.getters.relationship(this.userId)
|
|
||||||
},
|
|
||||||
isAdmin() {
|
isAdmin() {
|
||||||
return this.user.rights.admin
|
return this.user.rights.admin
|
||||||
},
|
},
|
||||||
|
|
@ -32,4 +29,4 @@ const AdminCard = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminCard
|
export default AdminUserCard
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<BasicUserCard
|
<BasicUserCard
|
||||||
class="AdminCard"
|
class="AdminUserCard"
|
||||||
:user="user"
|
:user="user"
|
||||||
show-line-labels
|
show-line-labels
|
||||||
>
|
>
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
</strong>
|
</strong>
|
||||||
{{ ' ' }}
|
{{ ' ' }}
|
||||||
<span
|
<span
|
||||||
class="faint"
|
|
||||||
v-if="user.adminData.email == null"
|
v-if="user.adminData.email == null"
|
||||||
|
class="faint"
|
||||||
>
|
>
|
||||||
{{ $t('general.not_available') }}
|
{{ $t('general.not_available') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
{{ $t('user_card.admin_data.registration_reason') }}
|
{{ $t('user_card.admin_data.registration_reason') }}
|
||||||
</summary>
|
</summary>
|
||||||
<span>
|
<span>
|
||||||
{{ user.adminData.registration_reason }}
|
{{ user.adminData.registration_reason }}
|
||||||
</span>
|
</span>
|
||||||
</details>
|
</details>
|
||||||
<div class="right-side">
|
<div class="right-side">
|
||||||
|
|
@ -102,6 +102,6 @@
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./admin_card.js"></script>
|
<script src="./admin_user_card.js"></script>
|
||||||
|
|
||||||
<style lang="scss" src="./admin_card.scss"></style>
|
<style lang="scss" src="./admin_user_card.scss"></style>
|
||||||
|
|
@ -105,7 +105,7 @@ const FrontendsTab = {
|
||||||
const ref = suggestRef || this.getSuggestedRef(frontend)
|
const ref = suggestRef || this.getSuggestedRef(frontend)
|
||||||
const { name } = frontend
|
const { name } = frontend
|
||||||
|
|
||||||
useAdminSettingsStore.updateAdminDraft({
|
useAdminSettingsStore().updateAdminDraft({
|
||||||
path: [':pleroma', ':frontends', ':primary'],
|
path: [':pleroma', ':frontends', ':primary'],
|
||||||
value: { name, ref },
|
value: { name, ref },
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -112,19 +112,19 @@ const LinksTab = {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
checkRel(e) {
|
checkRel(e) {
|
||||||
useAdminSettingsStore.updateAdminDraft({
|
useAdminSettingsStore().updateAdminDraft({
|
||||||
path: [':pleroma', 'Pleroma.Formatter', ':rel'],
|
path: [':pleroma', 'Pleroma.Formatter', ':rel'],
|
||||||
value: e ? '' : false,
|
value: e ? '' : false,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
checkClass(e) {
|
checkClass(e) {
|
||||||
useAdminSettingsStore.updateAdminDraft({
|
useAdminSettingsStore().updateAdminDraft({
|
||||||
path: [':pleroma', 'Pleroma.Formatter', ':class'],
|
path: [':pleroma', 'Pleroma.Formatter', ':class'],
|
||||||
value: e ? '' : false,
|
value: e ? '' : false,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
checkTruncate(e) {
|
checkTruncate(e) {
|
||||||
useAdminSettingsStore.updateAdminDraft({
|
useAdminSettingsStore().updateAdminDraft({
|
||||||
path: [':pleroma', 'Pleroma.Formatter', ':truncate'],
|
path: [':pleroma', 'Pleroma.Formatter', ':truncate'],
|
||||||
value: e ? 20 : false,
|
value: e ? 20 : false,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,10 @@ import { isEmpty } from 'lodash'
|
||||||
|
|
||||||
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
|
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
|
||||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
import GenericConfirm from 'src/components/confirm_modal/generic_confirm.vue'
|
|
||||||
import List from 'src/components/list/list.vue'
|
import List from 'src/components/list/list.vue'
|
||||||
import ModerationTools from 'src/components/moderation_tools/moderation_tools.vue'
|
import ModerationTools from 'src/components/moderation_tools/moderation_tools.vue'
|
||||||
import ProgressButton from 'src/components/progress_button/progress_button.vue'
|
|
||||||
import Select from 'src/components/select/select.vue'
|
import Select from 'src/components/select/select.vue'
|
||||||
import AdminCard from 'src/components/settings_modal/admin_tabs/admin_card.vue'
|
import AdminUserCard from 'src/components/settings_modal/admin_tabs/admin_user_card.vue'
|
||||||
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
|
||||||
|
|
||||||
import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
|
import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
|
||||||
|
|
||||||
|
|
@ -18,17 +15,8 @@ const UsersTab = {
|
||||||
Select,
|
Select,
|
||||||
BasicUserCard,
|
BasicUserCard,
|
||||||
List,
|
List,
|
||||||
ProgressButton,
|
AdminUserCard,
|
||||||
AdminCard,
|
|
||||||
TabSwitcher,
|
|
||||||
ModerationTools,
|
ModerationTools,
|
||||||
GenericConfirm,
|
|
||||||
},
|
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
defaultDraftMode: true,
|
|
||||||
defaultSource: 'admin',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -120,12 +108,11 @@ const UsersTab = {
|
||||||
...this.fetchOptions,
|
...this.fetchOptions,
|
||||||
page,
|
page,
|
||||||
})
|
})
|
||||||
.then(({ count, users }) => ({ count, items: users }))
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
fetchOptions() {
|
fetchOptions() {
|
||||||
this.$refs.usersList.reset()
|
this.$refs.usersList?.reset()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
|
|
||||||
.filters-section {
|
.filters-section {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: repeat(auto-fit, minmax(15em, 1fr));
|
||||||
|
grid-auto-columns: 1fr;
|
||||||
|
grid-auto-flow: row;
|
||||||
gap: 0.5em 1em;
|
gap: 0.5em 1em;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
|
|
|
||||||
|
|
@ -123,15 +123,15 @@
|
||||||
<List
|
<List
|
||||||
ref="usersList"
|
ref="usersList"
|
||||||
:fetch-function="fetchUsers"
|
:fetch-function="fetchUsers"
|
||||||
@select="onSelect"
|
|
||||||
selectable
|
selectable
|
||||||
scrollable
|
scrollable
|
||||||
|
@select="onSelect"
|
||||||
>
|
>
|
||||||
<template #header="{selected}">
|
<template #header="{selected}">
|
||||||
<ModerationTools :users="selected" />
|
<ModerationTools :users="selected" />
|
||||||
</template>
|
</template>
|
||||||
<template #item="{item}">
|
<template #item="{item}">
|
||||||
<AdminCard :user-id="item.id" />
|
<AdminUserCard :user-id="item.id" />
|
||||||
</template>
|
</template>
|
||||||
<template #load>
|
<template #load>
|
||||||
<span> loading </span>
|
<span> loading </span>
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ export default {
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
if (this.realSource === 'admin' || this.path == null) {
|
if (this.realSource === 'admin' || this.path == null) {
|
||||||
useAdminSettingsStore.updateAdminDraft({
|
useAdminSettingsStore().updateAdminDraft({
|
||||||
path: this.canonPath,
|
path: this.canonPath,
|
||||||
value,
|
value,
|
||||||
})
|
})
|
||||||
|
|
@ -254,7 +254,7 @@ export default {
|
||||||
this.$store.dispatch('setProfileOption', { name: k, value: v })
|
this.$store.dispatch('setProfileOption', { name: k, value: v })
|
||||||
case 'admin':
|
case 'admin':
|
||||||
return (k, v) =>
|
return (k, v) =>
|
||||||
useAdminSettingsStore.pushAdminSetting({ path: k, value: v })
|
useAdminSettingsStore().pushAdminSetting({ path: k, value: v })
|
||||||
default:
|
default:
|
||||||
return (readPath, value) => {
|
return (readPath, value) => {
|
||||||
const writePath = `${readPath}`
|
const writePath = `${readPath}`
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
|
|
||||||
&.-full-height {
|
&.-full-height {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,11 @@ const SettingsModal = {
|
||||||
closeModal() {
|
closeModal() {
|
||||||
useInterfaceStore().closeSettingsModal()
|
useInterfaceStore().closeSettingsModal()
|
||||||
},
|
},
|
||||||
peekModal() {
|
toggleMinimizeModal(state) {
|
||||||
useInterfaceStore().togglePeekSettingsModal()
|
useInterfaceStore().toggleMinimizeSettingsModal()
|
||||||
|
},
|
||||||
|
minimizeModal() {
|
||||||
|
useInterfaceStore().setSettingsModalState('minimized')
|
||||||
},
|
},
|
||||||
importValidator(data) {
|
importValidator(data) {
|
||||||
if (!Array.isArray(data._pleroma_settings_version)) {
|
if (!Array.isArray(data._pleroma_settings_version)) {
|
||||||
|
|
@ -233,10 +236,10 @@ const SettingsModal = {
|
||||||
return clone
|
return clone
|
||||||
},
|
},
|
||||||
resetAdminDraft() {
|
resetAdminDraft() {
|
||||||
useAdminSettingsStore.resetAdminDraft()
|
useAdminSettingsStore().resetAdminDraft()
|
||||||
},
|
},
|
||||||
pushAdminDraft() {
|
pushAdminDraft() {
|
||||||
useAdminSettingsStore.pushAdminDraft()
|
useAdminSettingsStore().pushAdminDraft()
|
||||||
},
|
},
|
||||||
...mapActions(useInterfaceStore, [
|
...mapActions(useInterfaceStore, [
|
||||||
'temporaryChangesRevert',
|
'temporaryChangesRevert',
|
||||||
|
|
@ -251,7 +254,7 @@ const SettingsModal = {
|
||||||
modalMode: (store) => store.settingsModalMode,
|
modalMode: (store) => store.settingsModalMode,
|
||||||
modalOpenedOnceUser: (store) => store.settingsModalLoadedUser,
|
modalOpenedOnceUser: (store) => store.settingsModalLoadedUser,
|
||||||
modalOpenedOnceAdmin: (store) => store.settingsModalLoadedAdmin,
|
modalOpenedOnceAdmin: (store) => store.settingsModalLoadedAdmin,
|
||||||
modalPeeked: (store) => store.settingsModalState === 'minimized',
|
modalMinimized: (store) => store.settingsModalState === 'minimized',
|
||||||
}),
|
}),
|
||||||
expertLevel: {
|
expertLevel: {
|
||||||
get() {
|
get() {
|
||||||
|
|
@ -271,6 +274,11 @@ const SettingsModal = {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
$route(r) {
|
||||||
|
this.minimizeModal()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsModal
|
export default SettingsModal
|
||||||
|
|
|
||||||
|
|
@ -300,7 +300,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.peek {
|
&.minimize {
|
||||||
.settings-modal-panel {
|
.settings-modal-panel {
|
||||||
/* Explanation:
|
/* Explanation:
|
||||||
* Modal is positioned vertically centered.
|
* Modal is positioned vertically centered.
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
<Modal
|
<Modal
|
||||||
:is-open="modalActivated"
|
:is-open="modalActivated"
|
||||||
class="settings-modal"
|
class="settings-modal"
|
||||||
:class="{ peek: modalPeeked }"
|
:class="{ minimize: modalMinimized }"
|
||||||
:no-background="modalPeeked"
|
:no-background="modalMinimized"
|
||||||
>
|
>
|
||||||
<div class="settings-modal-panel panel">
|
<div class="settings-modal-panel panel">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
|
|
@ -22,8 +22,8 @@
|
||||||
</transition>
|
</transition>
|
||||||
<button
|
<button
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
:title="$t('general.peek')"
|
:title="$t('general.minimize')"
|
||||||
@click="peekModal"
|
@click="toggleMinimizeModal"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
:icon="['far', 'window-minimize']"
|
:icon="['far', 'window-minimize']"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
ref="tabSwitcher"
|
ref="tabSwitcher"
|
||||||
class="settings-admin-content settings_tab-switcher"
|
class="settings-admin-content settings_tab-switcher"
|
||||||
:side-tab-bar="true"
|
:side-tab-bar="true"
|
||||||
:scrollable-tabs="true"
|
:scrollable-tabs
|
||||||
:render-only-focused="true"
|
:render-only-focused="true"
|
||||||
:body-scroll-lock="bodyLock"
|
:body-scroll-lock="bodyLock"
|
||||||
>
|
>
|
||||||
|
|
@ -50,7 +50,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="adminDbLoaded"
|
|
||||||
:label="$t('admin_dash.tabs.users')"
|
:label="$t('admin_dash.tabs.users')"
|
||||||
icon="user"
|
icon="user"
|
||||||
data-tab-name="users"
|
data-tab-name="users"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<vertical-tab-switcher
|
<vertical-tab-switcher
|
||||||
ref="tabSwitcher"
|
ref="tabSwitcher"
|
||||||
class="settings_tab-switcher"
|
class="settings_tab-switcher"
|
||||||
:scrollable-tabs="true"
|
:scrollable-tabs
|
||||||
:body-scroll-lock="bodyLock"
|
:body-scroll-lock="bodyLock"
|
||||||
:hide-header="navHideHeader"
|
:hide-header="navHideHeader"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,6 @@ const MutesAndBlocks = {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeTab: 'profile',
|
activeTab: 'profile',
|
||||||
mutesLoading: false,
|
|
||||||
mutesError: null,
|
|
||||||
mutesBottomedOut: false,
|
|
||||||
blocksLoading: false,
|
|
||||||
blocksError: null,
|
|
||||||
blocksBottomedOut: false,
|
|
||||||
domainsLoading: false,
|
|
||||||
domainsError: null,
|
|
||||||
domainsBottomedOut: false,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<tab-switcher
|
<tab-switcher
|
||||||
class="mutes-and-blocks-tab"
|
class="mutes-and-blocks-tab"
|
||||||
:scrollable-tabs="true"
|
:scrollable-tabs
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="blocks"
|
class="blocks"
|
||||||
:label="$t('settings.user_blocks')"
|
:label="$t('settings.user_blocks')"
|
||||||
|
|
@ -60,7 +60,10 @@
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mutes" :label="$t('settings.user_mutes2')">
|
<div
|
||||||
|
class="mutes"
|
||||||
|
:label="$t('settings.user_mutes2')"
|
||||||
|
>
|
||||||
<div class="usersearch-wrapper">
|
<div class="usersearch-wrapper">
|
||||||
<Autosuggest
|
<Autosuggest
|
||||||
:filter="filterUnMutedUsers"
|
:filter="filterUnMutedUsers"
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="visibility in availableScopes"
|
v-for="visibility in availableScopes"
|
||||||
|
:key="visibility"
|
||||||
class="menu-item dropdown-item extra-action -icon"
|
class="menu-item dropdown-item extra-action -icon"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
|
@ -32,7 +33,7 @@
|
||||||
:icon="visibilityIcon(visibility)"
|
:icon="visibilityIcon(visibility)"
|
||||||
fixed-width
|
fixed-width
|
||||||
/>
|
/>
|
||||||
{{ $t('general.scope_in_timeline.' + visibility) }}
|
{{ $t('general.scope_in_timeline.' + visibility) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -47,7 +48,7 @@
|
||||||
icon="eye"
|
icon="eye"
|
||||||
fixed-width
|
fixed-width
|
||||||
/>
|
/>
|
||||||
{{ $t('status.mark_as_non-sensitive') }}
|
{{ $t('status.mark_as_non-sensitive') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -62,7 +63,7 @@
|
||||||
icon="eye-slash"
|
icon="eye-slash"
|
||||||
fixed-width
|
fixed-width
|
||||||
/>
|
/>
|
||||||
{{ $t('status.mark_as_sensitive') }}
|
{{ $t('status.mark_as_sensitive') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -299,10 +299,12 @@ export const BUTTONS = [
|
||||||
label: 'user_card.report',
|
label: 'user_card.report',
|
||||||
if: ({ loggedIn }) => loggedIn,
|
if: ({ loggedIn }) => loggedIn,
|
||||||
action({ status }) {
|
action({ status }) {
|
||||||
return useReportsStore().openUserReportingModal({
|
useReportsStore().openUserReportingModal({
|
||||||
userId: status.user.id,
|
userId: status.user.id,
|
||||||
statusIds: [status.id],
|
statusIds: [status.id],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
].map((button) => {
|
].map((button) => {
|
||||||
|
|
|
||||||
|
|
@ -348,8 +348,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="admin-data"
|
|
||||||
v-if="user.adminData && !hideBio"
|
v-if="user.adminData && !hideBio"
|
||||||
|
class="admin-data"
|
||||||
>
|
>
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>
|
||||||
|
|
@ -454,7 +454,7 @@
|
||||||
:key="tag"
|
:key="tag"
|
||||||
>
|
>
|
||||||
<code>
|
<code>
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</code>
|
</code>
|
||||||
{{ ' ' }}
|
{{ ' ' }}
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-admin-view {
|
&.-admin-view {
|
||||||
.godmode {
|
.filter {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,11 @@ library.add(faCircleNotch)
|
||||||
const UserProfileAdminView = {
|
const UserProfileAdminView = {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
userId: null,
|
|
||||||
godmode: false,
|
godmode: false,
|
||||||
|
showReblogs: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.userId = this.$route.params.id
|
|
||||||
this.$store.dispatch('fetchUserIfMissing', this.userId)
|
this.$store.dispatch('fetchUserIfMissing', this.userId)
|
||||||
useInterfaceStore().setForeignProfileBackground(this.user?.background_image)
|
useInterfaceStore().setForeignProfileBackground(this.user?.background_image)
|
||||||
},
|
},
|
||||||
|
|
@ -38,12 +37,15 @@ const UserProfileAdminView = {
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
godmode: this.godmode,
|
godmode: this.godmode,
|
||||||
id: this.userId,
|
id: this.userId,
|
||||||
withReblogs: false,
|
withReblogs: this.showReblogs,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
user() {
|
user() {
|
||||||
return this.$store.getters.findUser(this.userId)
|
return this.$store.getters.findUser(this.userId)
|
||||||
},
|
},
|
||||||
|
userId() {
|
||||||
|
return this.$route.params.id
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchStatuses(page) {
|
fetchStatuses(page) {
|
||||||
|
|
@ -60,7 +62,7 @@ const UserProfileAdminView = {
|
||||||
Checkbox,
|
Checkbox,
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
godmode() {
|
fetchOptions() {
|
||||||
this.$refs.list.reset()
|
this.$refs.list.reset()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,18 +10,30 @@
|
||||||
hide-bio
|
hide-bio
|
||||||
hide-buttons
|
hide-buttons
|
||||||
/>
|
/>
|
||||||
<Checkbox class="godmode" v-model="godmode">
|
<Checkbox
|
||||||
{{ $t('admin_dash.users.godmode') }}
|
v-model="godmode"
|
||||||
|
class="filter"
|
||||||
|
>
|
||||||
|
{{ $t('admin_dash.users.filters.show_direct') }}
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
v-model="showReblogs"
|
||||||
|
class="filter"
|
||||||
|
>
|
||||||
|
{{ $t('admin_dash.users.filters.show_reblogs') }}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
<List
|
<List
|
||||||
ref="list"
|
ref="list"
|
||||||
:fetch-function="fetchStatuses"
|
:fetch-function="fetchStatuses"
|
||||||
@select="onSelect"
|
|
||||||
scrollable
|
scrollable
|
||||||
>
|
>
|
||||||
<template #item="{item}">
|
<template #item="{item}">
|
||||||
<Status :statusoid="item" />
|
<Status
|
||||||
|
:statusoid="item"
|
||||||
|
:in-conversation="false"
|
||||||
|
:focused="false"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { mapState } from 'pinia'
|
||||||
|
|
||||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
import List from 'src/components/list/list.vue'
|
import List from 'src/components/list/list.vue'
|
||||||
import Modal from 'src/components/modal/modal.vue'
|
import Modal from 'src/components/modal/modal.vue'
|
||||||
|
|
@ -16,19 +18,17 @@ const UserReportingModal = {
|
||||||
return {
|
return {
|
||||||
comment: '',
|
comment: '',
|
||||||
forward: false,
|
forward: false,
|
||||||
statusIdsToReport: [],
|
statusIdsToReport: new Set(),
|
||||||
processing: false,
|
processing: false,
|
||||||
error: false,
|
error: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
reportModal() {
|
|
||||||
return useReportsStore().reportModal
|
|
||||||
},
|
|
||||||
isLoggedIn() {
|
isLoggedIn() {
|
||||||
return !!this.$store.state.users.currentUser
|
return !!this.$store.state.users.currentUser
|
||||||
},
|
},
|
||||||
isOpen() {
|
isOpen() {
|
||||||
|
console.log(this.reportModal)
|
||||||
return this.isLoggedIn && this.reportModal.activated
|
return this.isLoggedIn && this.reportModal.activated
|
||||||
},
|
},
|
||||||
userId() {
|
userId() {
|
||||||
|
|
@ -43,31 +43,26 @@ const UserReportingModal = {
|
||||||
this.user.screen_name.substr(this.user.screen_name.indexOf('@') + 1)
|
this.user.screen_name.substr(this.user.screen_name.indexOf('@') + 1)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
statuses() {
|
...mapState(useReportsStore, ['reportModal']),
|
||||||
return this.reportModal.statuses
|
|
||||||
},
|
|
||||||
preTickedIds() {
|
|
||||||
return this.reportModal.preTickedIds
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
userId: 'resetState',
|
userId: 'resetState',
|
||||||
preTickedIds(newValue) {
|
|
||||||
this.statusIdsToReport = newValue
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resetState() {
|
resetState() {
|
||||||
// Reset state
|
// Reset state
|
||||||
this.comment = ''
|
this.comment = ''
|
||||||
this.forward = false
|
this.forward = false
|
||||||
this.statusIdsToReport = this.preTickedIds
|
this.statusIdsToReport = new Set(this.reportModal.preTickedIds)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.error = false
|
this.error = false
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
useReportsStore().closeUserReportingModal()
|
useReportsStore().closeUserReportingModal()
|
||||||
},
|
},
|
||||||
|
onListSelect(selected) {
|
||||||
|
this.statusIdsToReport = selected
|
||||||
|
},
|
||||||
reportUser() {
|
reportUser() {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
this.error = false
|
this.error = false
|
||||||
|
|
@ -75,7 +70,7 @@ const UserReportingModal = {
|
||||||
userId: this.userId,
|
userId: this.userId,
|
||||||
comment: this.comment,
|
comment: this.comment,
|
||||||
forward: this.forward,
|
forward: this.forward,
|
||||||
statusIds: this.statusIdsToReport,
|
statusIds: [...this.statusIdsToReport],
|
||||||
}
|
}
|
||||||
this.$store.state.api.backendInteractor
|
this.$store.state.api.backendInteractor
|
||||||
.reportUser({ ...params })
|
.reportUser({ ...params })
|
||||||
|
|
@ -92,23 +87,6 @@ const UserReportingModal = {
|
||||||
clearError() {
|
clearError() {
|
||||||
this.error = false
|
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) {
|
resize(e) {
|
||||||
const target = e.target || e
|
const target = e.target || e
|
||||||
if (!(target instanceof window.Element)) {
|
if (!(target instanceof window.Element)) {
|
||||||
|
|
|
||||||
|
|
@ -51,19 +51,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-reporting-panel-right">
|
<div class="user-reporting-panel-right">
|
||||||
<List :items="statuses">
|
<List
|
||||||
|
:external-items="reportModal.statuses"
|
||||||
|
:pre-select="reportModal.preTickedIds"
|
||||||
|
selectable
|
||||||
|
@select="onListSelect"
|
||||||
|
>
|
||||||
<template #item="{item}">
|
<template #item="{item}">
|
||||||
<div class="status-fadein user-reporting-panel-sitem">
|
<Status
|
||||||
<Status
|
:in-conversation="false"
|
||||||
:in-conversation="false"
|
:focused="false"
|
||||||
:focused="false"
|
:statusoid="item"
|
||||||
:statusoid="item"
|
/>
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
:model-value="isChecked(item.id)"
|
|
||||||
@update:model-value="checked => toggleStatus(checked, item.id)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -136,20 +135,6 @@
|
||||||
overflow-y: auto;
|
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) {
|
@media all and (width >= 801px) {
|
||||||
.panel-body {
|
.panel-body {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
||||||
|
|
@ -11,62 +11,64 @@
|
||||||
<p>
|
<p>
|
||||||
{{ $t(isMute ? 'user_card.expire_mute_message' : 'user_card.expire_block_message', [user.screen_name]) }}
|
{{ $t(isMute ? 'user_card.expire_mute_message' : 'user_card.expire_block_message', [user.screen_name]) }}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<template #below>
|
||||||
{{ $t('user_card.expire_in') }}
|
<div>
|
||||||
<span class="expirationTime">
|
{{ $t('user_card.expire_in') }}
|
||||||
<input
|
<span class="expirationTime">
|
||||||
id="userFilterExpires"
|
<input
|
||||||
v-model="expiration"
|
id="userFilterExpires"
|
||||||
class="input input-expire-in"
|
v-model="expiration"
|
||||||
:class="{ disabled: forever }"
|
class="input input-expire-in"
|
||||||
:disabled="forever"
|
:class="{ disabled: forever }"
|
||||||
min="1"
|
:disabled="forever"
|
||||||
type="number"
|
min="1"
|
||||||
>
|
type="number"
|
||||||
<Select
|
|
||||||
id="userFilterExpiresUnit"
|
|
||||||
v-model="expirationUnit"
|
|
||||||
class="input unit-input unstyled"
|
|
||||||
:disabled="forever"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
key="s"
|
|
||||||
value="s"
|
|
||||||
>
|
>
|
||||||
{{ $t('time.unit.seconds_suffix') }}
|
<Select
|
||||||
</option>
|
id="userFilterExpiresUnit"
|
||||||
<option
|
v-model="expirationUnit"
|
||||||
key="m"
|
class="input unit-input unstyled"
|
||||||
value="m"
|
:disabled="forever"
|
||||||
>
|
>
|
||||||
{{ $t('time.unit.minutes_suffix') }}
|
<option
|
||||||
</option>
|
key="s"
|
||||||
<option
|
value="s"
|
||||||
key="h"
|
>
|
||||||
value="h"
|
{{ $t('time.unit.seconds_suffix') }}
|
||||||
>
|
</option>
|
||||||
{{ $t('time.unit.hours_suffix') }}
|
<option
|
||||||
</option>
|
key="m"
|
||||||
<option
|
value="m"
|
||||||
key="d"
|
>
|
||||||
value="d"
|
{{ $t('time.unit.minutes_suffix') }}
|
||||||
>
|
</option>
|
||||||
{{ $t('time.unit.days_suffix') }}
|
<option
|
||||||
</option>
|
key="h"
|
||||||
</Select>
|
value="h"
|
||||||
</span>
|
>
|
||||||
|
{{ $t('time.unit.hours_suffix') }}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
key="d"
|
||||||
|
value="d"
|
||||||
|
>
|
||||||
|
{{ $t('time.unit.days_suffix') }}
|
||||||
|
</option>
|
||||||
|
</Select>
|
||||||
|
</span>
|
||||||
|
|
||||||
{{ $t('user_card.mute_or') }}
|
{{ $t('user_card.mute_or') }}
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="forever"
|
id="forever"
|
||||||
v-model="forever"
|
v-model="forever"
|
||||||
name="forever"
|
name="forever"
|
||||||
class="input-forever"
|
class="input-forever"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.mute_block_never') }}
|
{{ $t('user_card.mute_block_never') }}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #footerLeft>
|
<template #footerLeft>
|
||||||
<div class="footer-left-checkbox">
|
<div class="footer-left-checkbox">
|
||||||
|
|
|
||||||
|
|
@ -385,9 +385,6 @@
|
||||||
"selectable_list": {
|
"selectable_list": {
|
||||||
"select_all": "Select all"
|
"select_all": "Select all"
|
||||||
},
|
},
|
||||||
"page_list": {
|
|
||||||
"load_more": "Load more"
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"invalid_settings_imported": "Error importing settings",
|
"invalid_settings_imported": "Error importing settings",
|
||||||
"add_language": "Add fallback language",
|
"add_language": "Add fallback language",
|
||||||
|
|
@ -1298,7 +1295,6 @@
|
||||||
"users": {
|
"users": {
|
||||||
"title": "Users",
|
"title": "Users",
|
||||||
"local_id": "Local ID",
|
"local_id": "Local ID",
|
||||||
"godmode": "Show direct messages",
|
|
||||||
"labels": {
|
"labels": {
|
||||||
"query": "Search",
|
"query": "Search",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
|
@ -1329,10 +1325,8 @@
|
||||||
"only_unconfirmed": "Exclude Confirmed"
|
"only_unconfirmed": "Exclude Confirmed"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"show_direct": "Show Direct Posts",
|
"show_direct": "Show Direct Messages",
|
||||||
"show_reblogs": "Show Reblogs",
|
"show_reblogs": "Show Reblogs"
|
||||||
"ascending": "Oldest First",
|
|
||||||
"descending": "Newest First"
|
|
||||||
},
|
},
|
||||||
"indicator": {
|
"indicator": {
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
|
|
|
||||||
|
|
@ -591,7 +591,7 @@ const statuses = {
|
||||||
pagination,
|
pagination,
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
commit('addNewStatuses', {
|
return commit('addNewStatuses', {
|
||||||
statuses,
|
statuses,
|
||||||
showImmediately,
|
showImmediately,
|
||||||
timeline,
|
timeline,
|
||||||
|
|
@ -683,7 +683,7 @@ const statuses = {
|
||||||
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
|
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
|
||||||
},
|
},
|
||||||
unpinStatus({ rootState, dispatch }, statusId) {
|
unpinStatus({ rootState, dispatch }, statusId) {
|
||||||
rootState.api.backendInteractor
|
return rootState.api.backendInteractor
|
||||||
.unpinOwnStatus({ id: statusId })
|
.unpinOwnStatus({ id: statusId })
|
||||||
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
|
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
|
||||||
},
|
},
|
||||||
|
|
@ -822,7 +822,7 @@ const statuses = {
|
||||||
'addNewUsers',
|
'addNewUsers',
|
||||||
data.statuses.map((s) => s.user).filter((u) => u),
|
data.statuses.map((s) => s.user).filter((u) => u),
|
||||||
)
|
)
|
||||||
store.commit('addNewStatuses', { statuses: data.statuses })
|
data.statuses = store.commit('addNewStatuses', { statuses: data.statuses })
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -307,8 +307,12 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
|
||||||
opts,
|
opts,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const statuses = activities.map(parseStatus)
|
||||||
|
|
||||||
|
await window.vuex.dispatch('addNewStatuses', { statuses })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: activities.map(parseStatus),
|
items: statuses,
|
||||||
count: total,
|
count: total,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -318,10 +322,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
|
||||||
})
|
})
|
||||||
const status = parseStatus(raw)
|
const status = parseStatus(raw)
|
||||||
|
|
||||||
await window.vuex.dispatch('addNewStatuses', {
|
await window.vuex.dispatch('addNewStatuses', { statuses: [status] })
|
||||||
statuses: [status],
|
|
||||||
userId: false,
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Users stuff
|
// Users stuff
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,23 @@ export const useInterfaceStore = defineStore('interface', {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setSettingsModalState(newState) {
|
||||||
|
const oldState = this.settingsModalState
|
||||||
|
const legal = (() => {
|
||||||
|
switch (oldState) {
|
||||||
|
case 'minimized':
|
||||||
|
return true
|
||||||
|
case 'visible':
|
||||||
|
return true
|
||||||
|
case 'hidden':
|
||||||
|
return newState === 'visible'
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
if (legal) {
|
||||||
|
this.settingsModalState = newState
|
||||||
|
}
|
||||||
|
},
|
||||||
togglePeekSettingsModal() {
|
togglePeekSettingsModal() {
|
||||||
switch (this.settingsModalState) {
|
switch (this.settingsModalState) {
|
||||||
case 'minimized':
|
case 'minimized':
|
||||||
|
|
@ -141,8 +158,10 @@ export const useInterfaceStore = defineStore('interface', {
|
||||||
case 'visible':
|
case 'visible':
|
||||||
this.settingsModalState = 'minimized'
|
this.settingsModalState = 'minimized'
|
||||||
return
|
return
|
||||||
|
case 'hidden':
|
||||||
|
return
|
||||||
default:
|
default:
|
||||||
throw new Error('Illegal minimization state of settings modal')
|
throw new Error(`Illegal minimization state of settings modal: ${this.settingsModalState}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearSettingsModalTargetTab() {
|
clearSettingsModalTargetTab() {
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,12 @@ export const useReportsStore = defineStore('reports', {
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
openUserReportingModal({ userId, statusIds = [] }) {
|
openUserReportingModal({ userId, statusIds = [] }) {
|
||||||
|
console.log('ASS')
|
||||||
const preTickedStatuses = statusIds.map(
|
const preTickedStatuses = statusIds.map(
|
||||||
(id) => window.vuex.state.statuses.allStatusesObject[id],
|
(id) => window.vuex.state.statuses.allStatusesObject[id],
|
||||||
)
|
)
|
||||||
const preTickedIds = statusIds
|
const preTickedIds = statusIds
|
||||||
|
console.log(preTickedStatuses)
|
||||||
const statuses = preTickedStatuses.concat(
|
const statuses = preTickedStatuses.concat(
|
||||||
filter(
|
filter(
|
||||||
window.vuex.state.statuses.allStatuses,
|
window.vuex.state.statuses.allStatuses,
|
||||||
|
|
|
||||||
1640
test/fixtures/statuses.json
vendored
1640
test/fixtures/statuses.json
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -5,76 +5,6 @@ import {
|
||||||
parseUser,
|
parseUser,
|
||||||
} from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js'
|
} from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js'
|
||||||
import mastoapidata from '../../../../fixtures/mastoapi.json'
|
import mastoapidata from '../../../../fixtures/mastoapi.json'
|
||||||
import qvitterapidata from '../../../../fixtures/statuses.json'
|
|
||||||
|
|
||||||
const makeMockStatusQvitter = (overrides = {}) => {
|
|
||||||
return Object.assign(
|
|
||||||
{
|
|
||||||
activity_type: 'post',
|
|
||||||
attachments: [],
|
|
||||||
attentions: [],
|
|
||||||
created_at: 'Tue Jan 15 13:57:56 +0000 2019',
|
|
||||||
external_url: 'https://ap.example/whatever',
|
|
||||||
fave_num: 1,
|
|
||||||
favorited: false,
|
|
||||||
id: '10335970',
|
|
||||||
in_reply_to_ostatus_uri: null,
|
|
||||||
in_reply_to_profileurl: null,
|
|
||||||
in_reply_to_screen_name: null,
|
|
||||||
in_reply_to_status_id: null,
|
|
||||||
in_reply_to_user_id: null,
|
|
||||||
is_local: false,
|
|
||||||
is_post_verb: true,
|
|
||||||
possibly_sensitive: false,
|
|
||||||
repeat_num: 0,
|
|
||||||
repeated: false,
|
|
||||||
statusnet_conversation_id: '16300488',
|
|
||||||
summary: null,
|
|
||||||
tags: [],
|
|
||||||
text: 'haha benis',
|
|
||||||
uri: 'https://ap.example/whatever',
|
|
||||||
user: makeMockUserQvitter(),
|
|
||||||
visibility: 'public',
|
|
||||||
},
|
|
||||||
overrides,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeMockUserQvitter = (overrides = {}) => {
|
|
||||||
return Object.assign(
|
|
||||||
{
|
|
||||||
background_image: null,
|
|
||||||
cover_photo: '',
|
|
||||||
created_at: 'Mon Jan 14 13:56:51 +0000 2019',
|
|
||||||
default_scope: 'public',
|
|
||||||
description: 'ebin',
|
|
||||||
description_html: '<p>ebin</p>',
|
|
||||||
favourites_count: 0,
|
|
||||||
fields: [],
|
|
||||||
followers_count: 1,
|
|
||||||
following: true,
|
|
||||||
follows_you: true,
|
|
||||||
friends_count: 1,
|
|
||||||
id: '60717',
|
|
||||||
is_local: false,
|
|
||||||
locked: false,
|
|
||||||
name: 'Spurdo :ebin:',
|
|
||||||
name_html: 'Spurdo <img src="whatever">',
|
|
||||||
no_rich_text: false,
|
|
||||||
pleroma: { confirmation_pending: false, tags: [] },
|
|
||||||
profile_image_url: 'https://ap.example/whatever',
|
|
||||||
profile_image_url_https: 'https://ap.example/whatever',
|
|
||||||
profile_image_url_original: 'https://ap.example/whatever',
|
|
||||||
profile_image_url_profile_size: 'https://ap.example/whatever',
|
|
||||||
rights: { delete_others_notice: false },
|
|
||||||
screen_name: 'spurdo@ap.example',
|
|
||||||
statuses_count: 46,
|
|
||||||
statusnet_blocking: false,
|
|
||||||
statusnet_profile_url: '',
|
|
||||||
},
|
|
||||||
overrides,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeMockUserMasto = (overrides = {}) => {
|
const makeMockUserMasto = (overrides = {}) => {
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
|
|
@ -151,19 +81,6 @@ const makeMockStatusMasto = (overrides = {}) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeMockNotificationQvitter = (overrides = {}) => {
|
|
||||||
return Object.assign(
|
|
||||||
{
|
|
||||||
notice: makeMockStatusQvitter(),
|
|
||||||
ntype: 'follow',
|
|
||||||
from_profile: makeMockUserQvitter(),
|
|
||||||
is_seen: 0,
|
|
||||||
id: 123,
|
|
||||||
},
|
|
||||||
overrides,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeMockEmojiMasto = (overrides = [{}]) => {
|
const makeMockEmojiMasto = (overrides = [{}]) => {
|
||||||
return [
|
return [
|
||||||
Object.assign(
|
Object.assign(
|
||||||
|
|
@ -189,78 +106,6 @@ const makeMockEmojiMasto = (overrides = [{}]) => {
|
||||||
|
|
||||||
describe('API Entities normalizer', () => {
|
describe('API Entities normalizer', () => {
|
||||||
describe('parseStatus', () => {
|
describe('parseStatus', () => {
|
||||||
describe('QVitter preprocessing', () => {
|
|
||||||
it("doesn't blow up", () => {
|
|
||||||
const parsed = qvitterapidata.map(parseStatus)
|
|
||||||
expect(parsed.length).to.eq(qvitterapidata.length)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('identifies favorites', () => {
|
|
||||||
const fav = {
|
|
||||||
uri: 'tag:soykaf.com,2016-08-21:fave:2558:note:339495:2016-08-21T16:54:04+00:00',
|
|
||||||
is_post_verb: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
const mastoFav = {
|
|
||||||
uri: 'tag:mastodon.social,2016-11-27:objectId=73903:objectType=Favourite',
|
|
||||||
is_post_verb: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(parseStatus(makeMockStatusQvitter(fav))).to.have.property(
|
|
||||||
'type',
|
|
||||||
'favorite',
|
|
||||||
)
|
|
||||||
expect(parseStatus(makeMockStatusQvitter(mastoFav))).to.have.property(
|
|
||||||
'type',
|
|
||||||
'favorite',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('processes repeats correctly', () => {
|
|
||||||
const post = makeMockStatusQvitter({
|
|
||||||
retweeted_status: null,
|
|
||||||
id: 'deadbeef',
|
|
||||||
})
|
|
||||||
const repeat = makeMockStatusQvitter({
|
|
||||||
retweeted_status: post,
|
|
||||||
is_post_verb: false,
|
|
||||||
id: 'foobar',
|
|
||||||
})
|
|
||||||
|
|
||||||
const parsedPost = parseStatus(post)
|
|
||||||
const parsedRepeat = parseStatus(repeat)
|
|
||||||
|
|
||||||
expect(parsedPost).to.have.property('type', 'status')
|
|
||||||
expect(parsedRepeat).to.have.property('type', 'retweet')
|
|
||||||
expect(parsedRepeat).to.have.property('retweeted_status')
|
|
||||||
expect(parsedRepeat).to.have.nested.property(
|
|
||||||
'retweeted_status.id',
|
|
||||||
'deadbeef',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('sets nsfw for statuses with the #nsfw tag', () => {
|
|
||||||
const safe = makeMockStatusQvitter({ id: '1', text: 'Hello oniichan' })
|
|
||||||
const nsfw = makeMockStatusQvitter({
|
|
||||||
id: '1',
|
|
||||||
text: 'Hello oniichan #nsfw',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(parseStatus(safe).nsfw).to.eq(false)
|
|
||||||
expect(parseStatus(nsfw).nsfw).to.eq(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('leaves existing nsfw settings alone', () => {
|
|
||||||
const nsfw = makeMockStatusQvitter({
|
|
||||||
id: '1',
|
|
||||||
text: 'Hello oniichan #nsfw',
|
|
||||||
nsfw: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(parseStatus(nsfw).nsfw).to.eq(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Mastoapi preprocessing and converting', () => {
|
describe('Mastoapi preprocessing and converting', () => {
|
||||||
it("doesn't blow up", () => {
|
it("doesn't blow up", () => {
|
||||||
const parsed = mastoapidata.map(parseStatus)
|
const parsed = mastoapidata.map(parseStatus)
|
||||||
|
|
@ -344,60 +189,6 @@ describe('API Entities normalizer', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// We currently use QvitterAPI notifications only, and especially due to MastoAPI lacking is_seen, support for MastoAPI
|
|
||||||
// is more of an afterthought
|
|
||||||
describe('parseNotifications (QvitterAPI)', () => {
|
|
||||||
it("correctly normalizes data to FE's format", () => {
|
|
||||||
const notif = makeMockNotificationQvitter({
|
|
||||||
id: 123,
|
|
||||||
notice: makeMockStatusQvitter({ id: 444 }),
|
|
||||||
from_profile: makeMockUserQvitter({ id: 'spurdo' }),
|
|
||||||
})
|
|
||||||
expect(parseNotification(notif)).to.have.property('id', 123)
|
|
||||||
expect(parseNotification(notif)).to.have.property('seen', false)
|
|
||||||
expect(parseNotification(notif)).to.have.nested.property(
|
|
||||||
'status.id',
|
|
||||||
'444',
|
|
||||||
)
|
|
||||||
expect(parseNotification(notif)).to.have.nested.property(
|
|
||||||
'action.id',
|
|
||||||
'444',
|
|
||||||
)
|
|
||||||
expect(parseNotification(notif)).to.have.nested.property(
|
|
||||||
'from_profile.id',
|
|
||||||
'spurdo',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('correctly normalizes favorite notifications', () => {
|
|
||||||
const notif = makeMockNotificationQvitter({
|
|
||||||
id: 123,
|
|
||||||
ntype: 'like',
|
|
||||||
notice: makeMockStatusQvitter({
|
|
||||||
id: 444,
|
|
||||||
favorited_status: makeMockStatusQvitter({ id: 4412 }),
|
|
||||||
}),
|
|
||||||
is_seen: 1,
|
|
||||||
from_profile: makeMockUserQvitter({ id: 'spurdo' }),
|
|
||||||
})
|
|
||||||
expect(parseNotification(notif)).to.have.property('id', 123)
|
|
||||||
expect(parseNotification(notif)).to.have.property('type', 'like')
|
|
||||||
expect(parseNotification(notif)).to.have.property('seen', true)
|
|
||||||
expect(parseNotification(notif)).to.have.nested.property(
|
|
||||||
'status.id',
|
|
||||||
'4412',
|
|
||||||
)
|
|
||||||
expect(parseNotification(notif)).to.have.nested.property(
|
|
||||||
'action.id',
|
|
||||||
'444',
|
|
||||||
)
|
|
||||||
expect(parseNotification(notif)).to.have.nested.property(
|
|
||||||
'from_profile.id',
|
|
||||||
'spurdo',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Link header pagination', () => {
|
describe('Link header pagination', () => {
|
||||||
it('Parses min and max ids as integers', () => {
|
it('Parses min and max ids as integers', () => {
|
||||||
const linkHeader =
|
const linkHeader =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue