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) {
|
||||
color: var(--text);
|
||||
font-size: 100%;
|
||||
text-align: initial;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
display: inline;
|
||||
font-family: inherit;
|
||||
line-height: unset;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
|
|
@ -436,22 +444,6 @@ nav {
|
|||
--__line-height: 1.5em;
|
||||
--__horizontal-gap: 0.75em;
|
||||
--__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 {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<basic-user-card :user="user">
|
||||
<BasicUserCard :user="user">
|
||||
<div class="block-card-content-container">
|
||||
<span
|
||||
v-if="blocked && blockExpiryAvailable"
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
:is-mute="false"
|
||||
/>
|
||||
</teleport>
|
||||
</basic-user-card>
|
||||
</BasicUserCard>
|
||||
</template>
|
||||
|
||||
<script src="./block_card.js"></script>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@
|
|||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="below">
|
||||
<slot name="below" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<slot name="footerLeft" />
|
||||
<button
|
||||
|
|
@ -61,6 +63,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.below:not(:empty) {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.text {
|
||||
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:
|
||||
this.$store.state.users.currentUser.allow_following_move,
|
||||
filterMode: tabModeDict.mentions,
|
||||
canSeeReports: this.$store.state.users.currentUser.has.has(
|
||||
canSeeReports: this.$store.state.users.currentUser.privileges.has(
|
||||
'reports_manage_reports',
|
||||
),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ const List = {
|
|||
type: Function,
|
||||
default: () => '',
|
||||
},
|
||||
preSelect: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
nonInteractive: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
|
@ -44,7 +48,7 @@ const List = {
|
|||
data() {
|
||||
return {
|
||||
items: [],
|
||||
selected: new Set([]),
|
||||
selected: new Set(this.preSelect),
|
||||
loading: false,
|
||||
bottomedOut: true,
|
||||
error: null,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
class="List"
|
||||
role="list"
|
||||
:class="{ '-scrollable': scrollable }"
|
||||
>
|
||||
<div
|
||||
|
|
@ -72,9 +73,9 @@
|
|||
/>
|
||||
<a
|
||||
v-else-if="!bottomedOut"
|
||||
@click="fetchEntries"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="fetchEntries"
|
||||
>
|
||||
{{ $t('general.more') }}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
</div>
|
||||
<tab-switcher
|
||||
class="list-member-management"
|
||||
:scrollable-tabs="true"
|
||||
:scrollable-tabs
|
||||
>
|
||||
<div
|
||||
v-if="id || addedUserIds.size > 0"
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ const ENTRIES = [
|
|||
{
|
||||
check: 'action:statuses',
|
||||
label: 'user_card.admin_menu.show_statuses',
|
||||
conditions: ['count:1'],
|
||||
},
|
||||
{
|
||||
separator: true,
|
||||
|
|
@ -215,22 +216,17 @@ const ModerationTools = {
|
|||
return () => this.setTag(`${group}:${name}`, noTag)
|
||||
case 'action': {
|
||||
switch (name) {
|
||||
case 'delete': {
|
||||
case 'delete':
|
||||
return () => this.deleteUsers()
|
||||
}
|
||||
case 'resend_confirmation': {
|
||||
case 'resend_confirmation':
|
||||
return () => this.resendConfirmationEmail()
|
||||
}
|
||||
case 'disable_mfa': {
|
||||
case 'disable_mfa':
|
||||
return () => this.disableMFA()
|
||||
}
|
||||
case 'statuses': {
|
||||
case 'statuses':
|
||||
return () =>
|
||||
this.$router.push(`/users/\$${this.users[0].id}/admin_view`)
|
||||
}
|
||||
case 'require_password_change': {
|
||||
case 'require_password_change':
|
||||
return () => this.requirePasswordChange()
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown action group: ${name}`)
|
||||
}
|
||||
|
|
@ -277,24 +273,23 @@ const ModerationTools = {
|
|||
}
|
||||
|
||||
switch (group) {
|
||||
case 'action': {
|
||||
return true
|
||||
}
|
||||
case 'rights': {
|
||||
case 'action':
|
||||
if (name === 'statuses') return this.privileged('users_read')
|
||||
else return true
|
||||
case 'rights':
|
||||
return this.canGrantRole(name, value)
|
||||
}
|
||||
case 'state': {
|
||||
case 'state':
|
||||
return this.canChangeState(name, value)
|
||||
}
|
||||
case 'mrf_tag': {
|
||||
case 'mrf_tag':
|
||||
return this.canUseTagPolicy
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknown moderation group: ${group}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
.reduce((acc, entry, index) => {
|
||||
// Removing any double separators as well
|
||||
// as separators at very end and bery beginning
|
||||
if (entry === 'separator') {
|
||||
if (
|
||||
acc.length === 0 ||
|
||||
|
|
@ -363,12 +358,16 @@ const ModerationTools = {
|
|||
|
||||
const result = new Set()
|
||||
|
||||
// Each tag can have three states for given group of users
|
||||
TAGS.forEach((tag) => {
|
||||
if (present.has(tag) && missing.has(tag)) {
|
||||
// Some users have tag, some don't: "~tag"
|
||||
result.add(`~${tag}`)
|
||||
} else if (missing.has(tag)) {
|
||||
// No users have tag: "!tag"
|
||||
result.add(`!${tag}`)
|
||||
} else {
|
||||
// All users have tag: "tag"
|
||||
result.add(tag)
|
||||
}
|
||||
})
|
||||
|
|
@ -394,6 +393,7 @@ const ModerationTools = {
|
|||
...this.stateSet,
|
||||
...this.tagsSet,
|
||||
...this.propertySet,
|
||||
`count:${this.users.length}`,
|
||||
])
|
||||
},
|
||||
canDeleteAccount() {
|
||||
|
|
@ -405,27 +405,33 @@ const ModerationTools = {
|
|||
this.privileged('users_manage_tags')
|
||||
)
|
||||
},
|
||||
isAdmin() {
|
||||
this.$store.state.users.currentUser.role === 'admin'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
canGrantRole(name, value) {
|
||||
const setEntry = `${value ? '!' : ''}rights:${name}`
|
||||
|
||||
return (
|
||||
this.$store.state.users.currentUser.role === 'admin' &&
|
||||
this.isAdmin &&
|
||||
this.totalSet.has(setEntry)
|
||||
)
|
||||
},
|
||||
canChangeState(name, value) {
|
||||
let privilege
|
||||
|
||||
switch (name) {
|
||||
// TODO detailed privileges
|
||||
default: {
|
||||
privilege = 'users_manage_activation_state'
|
||||
}
|
||||
}
|
||||
|
||||
const setEntry = `${value ? '!' : ''}state:${name}`
|
||||
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)
|
||||
},
|
||||
|
|
@ -449,6 +455,7 @@ const ModerationTools = {
|
|||
this.confirmDialogName = null
|
||||
},
|
||||
privileged(privilege) {
|
||||
if (this.isAdmin) return true
|
||||
return this.$store.state.users.currentUser.privileges.has(privilege)
|
||||
},
|
||||
setTag(tag, value) {
|
||||
|
|
|
|||
|
|
@ -46,13 +46,19 @@
|
|||
:disabled="disabled"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.moderation') }}
|
||||
<FAIcon v-if="ready" icon="chevron-down" />
|
||||
<span v-else class="loading-spinner">
|
||||
<FAIcon
|
||||
v-if="ready"
|
||||
icon="chevron-down"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="loading-spinner"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-old-padding"
|
||||
spin
|
||||
icon="circle-notch"
|
||||
/>
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
|
|
@ -91,7 +97,10 @@
|
|||
{{ $t(confirmDialogContent2) }}
|
||||
</p>
|
||||
<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 }}
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
>
|
||||
<td>{{ entry.instance }}</td>
|
||||
<td v-if="entry.reason === ''">
|
||||
{{ $t("general.not_applicable") }}
|
||||
{{ $t("about.mrf.simple.not_applicable") }}
|
||||
</td>
|
||||
<td v-else>
|
||||
{{ entry.reason }}
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
>
|
||||
<td>{{ entry.instance }}</td>
|
||||
<td v-if="entry.reason === ''">
|
||||
{{ $t("general.not_applicable") }}
|
||||
{{ $t("about.mrf.simple.not_applicable") }}
|
||||
</td>
|
||||
<td v-else>
|
||||
{{ entry.reason }}
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
>
|
||||
<td>{{ entry.instance }}</td>
|
||||
<td v-if="entry.reason === ''">
|
||||
{{ $t("general.not_applicable") }}
|
||||
{{ $t("about.mrf.simple.not_applicable") }}
|
||||
</td>
|
||||
<td v-else>
|
||||
{{ entry.reason }}
|
||||
|
|
@ -124,7 +124,7 @@
|
|||
>
|
||||
<td>{{ entry.instance }}</td>
|
||||
<td v-if="entry.reason === ''">
|
||||
{{ $t("general.not_applicable") }}
|
||||
{{ $t("about.mrf.simple.not_applicable") }}
|
||||
</td>
|
||||
<td v-else>
|
||||
{{ entry.reason }}
|
||||
|
|
@ -151,7 +151,7 @@
|
|||
>
|
||||
<td>{{ entry.instance }}</td>
|
||||
<td v-if="entry.reason === ''">
|
||||
{{ $t("general.not_applicable") }}
|
||||
{{ $t("about.mrf.simple.not_applicable") }}
|
||||
</td>
|
||||
<td v-else>
|
||||
{{ entry.reason }}
|
||||
|
|
@ -178,7 +178,7 @@
|
|||
>
|
||||
<td>{{ entry.instance }}</td>
|
||||
<td v-if="entry.reason === ''">
|
||||
{{ $t("general.not_applicable") }}
|
||||
{{ $t("about.mrf.simple.not_applicable") }}
|
||||
</td>
|
||||
<td v-else>
|
||||
{{ 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 ModerationTools from 'src/components/moderation_tools/moderation_tools.vue'
|
||||
|
||||
const AdminCard = {
|
||||
const AdminUserCard = {
|
||||
props: {
|
||||
userId: {
|
||||
type: String,
|
||||
|
|
@ -17,9 +17,6 @@ const AdminCard = {
|
|||
user() {
|
||||
return this.$store.getters.findUser(this.userId)
|
||||
},
|
||||
relationship() {
|
||||
return this.$store.getters.relationship(this.userId)
|
||||
},
|
||||
isAdmin() {
|
||||
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 v-else>
|
||||
<BasicUserCard
|
||||
class="AdminCard"
|
||||
class="AdminUserCard"
|
||||
:user="user"
|
||||
show-line-labels
|
||||
>
|
||||
|
|
@ -18,8 +18,8 @@
|
|||
</strong>
|
||||
{{ ' ' }}
|
||||
<span
|
||||
class="faint"
|
||||
v-if="user.adminData.email == null"
|
||||
class="faint"
|
||||
>
|
||||
{{ $t('general.not_available') }}
|
||||
</span>
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
{{ $t('user_card.admin_data.registration_reason') }}
|
||||
</summary>
|
||||
<span>
|
||||
{{ user.adminData.registration_reason }}
|
||||
{{ user.adminData.registration_reason }}
|
||||
</span>
|
||||
</details>
|
||||
<div class="right-side">
|
||||
|
|
@ -102,6 +102,6 @@
|
|||
</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 { name } = frontend
|
||||
|
||||
useAdminSettingsStore.updateAdminDraft({
|
||||
useAdminSettingsStore().updateAdminDraft({
|
||||
path: [':pleroma', ':frontends', ':primary'],
|
||||
value: { name, ref },
|
||||
})
|
||||
|
|
|
|||
|
|
@ -112,19 +112,19 @@ const LinksTab = {
|
|||
},
|
||||
methods: {
|
||||
checkRel(e) {
|
||||
useAdminSettingsStore.updateAdminDraft({
|
||||
useAdminSettingsStore().updateAdminDraft({
|
||||
path: [':pleroma', 'Pleroma.Formatter', ':rel'],
|
||||
value: e ? '' : false,
|
||||
})
|
||||
},
|
||||
checkClass(e) {
|
||||
useAdminSettingsStore.updateAdminDraft({
|
||||
useAdminSettingsStore().updateAdminDraft({
|
||||
path: [':pleroma', 'Pleroma.Formatter', ':class'],
|
||||
value: e ? '' : false,
|
||||
})
|
||||
},
|
||||
checkTruncate(e) {
|
||||
useAdminSettingsStore.updateAdminDraft({
|
||||
useAdminSettingsStore().updateAdminDraft({
|
||||
path: [':pleroma', 'Pleroma.Formatter', ':truncate'],
|
||||
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 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 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 AdminCard from 'src/components/settings_modal/admin_tabs/admin_card.vue'
|
||||
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
||||
import AdminUserCard from 'src/components/settings_modal/admin_tabs/admin_user_card.vue'
|
||||
|
||||
import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
|
||||
|
||||
|
|
@ -18,17 +15,8 @@ const UsersTab = {
|
|||
Select,
|
||||
BasicUserCard,
|
||||
List,
|
||||
ProgressButton,
|
||||
AdminCard,
|
||||
TabSwitcher,
|
||||
AdminUserCard,
|
||||
ModerationTools,
|
||||
GenericConfirm,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
defaultDraftMode: true,
|
||||
defaultSource: 'admin',
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -120,12 +108,11 @@ const UsersTab = {
|
|||
...this.fetchOptions,
|
||||
page,
|
||||
})
|
||||
.then(({ count, users }) => ({ count, items: users }))
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
fetchOptions() {
|
||||
this.$refs.usersList.reset()
|
||||
this.$refs.usersList?.reset()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@
|
|||
|
||||
.filters-section {
|
||||
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;
|
||||
|
||||
> div {
|
||||
|
|
|
|||
|
|
@ -123,15 +123,15 @@
|
|||
<List
|
||||
ref="usersList"
|
||||
:fetch-function="fetchUsers"
|
||||
@select="onSelect"
|
||||
selectable
|
||||
scrollable
|
||||
@select="onSelect"
|
||||
>
|
||||
<template #header="{selected}">
|
||||
<ModerationTools :users="selected" />
|
||||
</template>
|
||||
<template #item="{item}">
|
||||
<AdminCard :user-id="item.id" />
|
||||
<AdminUserCard :user-id="item.id" />
|
||||
</template>
|
||||
<template #load>
|
||||
<span> loading </span>
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export default {
|
|||
},
|
||||
set(value) {
|
||||
if (this.realSource === 'admin' || this.path == null) {
|
||||
useAdminSettingsStore.updateAdminDraft({
|
||||
useAdminSettingsStore().updateAdminDraft({
|
||||
path: this.canonPath,
|
||||
value,
|
||||
})
|
||||
|
|
@ -254,7 +254,7 @@ export default {
|
|||
this.$store.dispatch('setProfileOption', { name: k, value: v })
|
||||
case 'admin':
|
||||
return (k, v) =>
|
||||
useAdminSettingsStore.pushAdminSetting({ path: k, value: v })
|
||||
useAdminSettingsStore().pushAdminSetting({ path: k, value: v })
|
||||
default:
|
||||
return (readPath, value) => {
|
||||
const writePath = `${readPath}`
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
|
||||
&.-full-height {
|
||||
height: 100%;
|
||||
|
||||
> * {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,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)) {
|
||||
|
|
@ -233,10 +236,10 @@ const SettingsModal = {
|
|||
return clone
|
||||
},
|
||||
resetAdminDraft() {
|
||||
useAdminSettingsStore.resetAdminDraft()
|
||||
useAdminSettingsStore().resetAdminDraft()
|
||||
},
|
||||
pushAdminDraft() {
|
||||
useAdminSettingsStore.pushAdminDraft()
|
||||
useAdminSettingsStore().pushAdminDraft()
|
||||
},
|
||||
...mapActions(useInterfaceStore, [
|
||||
'temporaryChangesRevert',
|
||||
|
|
@ -251,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() {
|
||||
|
|
@ -271,6 +274,11 @@ const SettingsModal = {
|
|||
)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route(r) {
|
||||
this.minimizeModal()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default SettingsModal
|
||||
|
|
|
|||
|
|
@ -300,7 +300,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.peek {
|
||||
&.minimize {
|
||||
.settings-modal-panel {
|
||||
/* Explanation:
|
||||
* Modal is positioned vertically centered.
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
<Modal
|
||||
:is-open="modalActivated"
|
||||
class="settings-modal"
|
||||
:class="{ peek: modalPeeked }"
|
||||
:no-background="modalPeeked"
|
||||
:class="{ minimize: modalMinimized }"
|
||||
:no-background="modalMinimized"
|
||||
>
|
||||
<div class="settings-modal-panel panel">
|
||||
<div class="panel-heading">
|
||||
|
|
@ -22,8 +22,8 @@
|
|||
</transition>
|
||||
<button
|
||||
class="btn button-default"
|
||||
:title="$t('general.peek')"
|
||||
@click="peekModal"
|
||||
:title="$t('general.minimize')"
|
||||
@click="toggleMinimizeModal"
|
||||
>
|
||||
<FAIcon
|
||||
:icon="['far', 'window-minimize']"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
ref="tabSwitcher"
|
||||
class="settings-admin-content settings_tab-switcher"
|
||||
:side-tab-bar="true"
|
||||
:scrollable-tabs="true"
|
||||
:scrollable-tabs
|
||||
:render-only-focused="true"
|
||||
:body-scroll-lock="bodyLock"
|
||||
>
|
||||
|
|
@ -50,7 +50,6 @@
|
|||
</div>
|
||||
|
||||
<div
|
||||
v-if="adminDbLoaded"
|
||||
:label="$t('admin_dash.tabs.users')"
|
||||
icon="user"
|
||||
data-tab-name="users"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<vertical-tab-switcher
|
||||
ref="tabSwitcher"
|
||||
class="settings_tab-switcher"
|
||||
:scrollable-tabs="true"
|
||||
:scrollable-tabs
|
||||
:body-scroll-lock="bodyLock"
|
||||
:hide-header="navHideHeader"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -16,15 +16,6 @@ const MutesAndBlocks = {
|
|||
data() {
|
||||
return {
|
||||
activeTab: 'profile',
|
||||
mutesLoading: false,
|
||||
mutesError: null,
|
||||
mutesBottomedOut: false,
|
||||
blocksLoading: false,
|
||||
blocksError: null,
|
||||
blocksBottomedOut: false,
|
||||
domainsLoading: false,
|
||||
domainsError: null,
|
||||
domainsBottomedOut: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<tab-switcher
|
||||
class="mutes-and-blocks-tab"
|
||||
:scrollable-tabs="true"
|
||||
>
|
||||
:scrollable-tabs
|
||||
>
|
||||
<div
|
||||
class="blocks"
|
||||
:label="$t('settings.user_blocks')"
|
||||
|
|
@ -60,7 +60,10 @@
|
|||
</List>
|
||||
</div>
|
||||
|
||||
<div class="mutes" :label="$t('settings.user_mutes2')">
|
||||
<div
|
||||
class="mutes"
|
||||
:label="$t('settings.user_mutes2')"
|
||||
>
|
||||
<div class="usersearch-wrapper">
|
||||
<Autosuggest
|
||||
:filter="filterUnMutedUsers"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
>
|
||||
<div
|
||||
v-for="visibility in availableScopes"
|
||||
:key="visibility"
|
||||
class="menu-item dropdown-item extra-action -icon"
|
||||
>
|
||||
<button
|
||||
|
|
@ -32,7 +33,7 @@
|
|||
:icon="visibilityIcon(visibility)"
|
||||
fixed-width
|
||||
/>
|
||||
{{ $t('general.scope_in_timeline.' + visibility) }}
|
||||
{{ $t('general.scope_in_timeline.' + visibility) }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -47,7 +48,7 @@
|
|||
icon="eye"
|
||||
fixed-width
|
||||
/>
|
||||
{{ $t('status.mark_as_non-sensitive') }}
|
||||
{{ $t('status.mark_as_non-sensitive') }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -62,7 +63,7 @@
|
|||
icon="eye-slash"
|
||||
fixed-width
|
||||
/>
|
||||
{{ $t('status.mark_as_sensitive') }}
|
||||
{{ $t('status.mark_as_sensitive') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -299,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) => {
|
||||
|
|
|
|||
|
|
@ -348,8 +348,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="admin-data"
|
||||
v-if="user.adminData && !hideBio"
|
||||
class="admin-data"
|
||||
>
|
||||
<details>
|
||||
<summary>
|
||||
|
|
@ -454,7 +454,7 @@
|
|||
:key="tag"
|
||||
>
|
||||
<code>
|
||||
{{ tag }}
|
||||
{{ tag }}
|
||||
</code>
|
||||
{{ ' ' }}
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
}
|
||||
|
||||
&.-admin-view {
|
||||
.godmode {
|
||||
.filter {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,11 @@ library.add(faCircleNotch)
|
|||
const UserProfileAdminView = {
|
||||
data() {
|
||||
return {
|
||||
userId: null,
|
||||
godmode: false,
|
||||
showReblogs: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.userId = this.$route.params.id
|
||||
this.$store.dispatch('fetchUserIfMissing', this.userId)
|
||||
useInterfaceStore().setForeignProfileBackground(this.user?.background_image)
|
||||
},
|
||||
|
|
@ -38,12 +37,15 @@ const UserProfileAdminView = {
|
|||
pageSize: 20,
|
||||
godmode: this.godmode,
|
||||
id: this.userId,
|
||||
withReblogs: false,
|
||||
withReblogs: this.showReblogs,
|
||||
}
|
||||
},
|
||||
user() {
|
||||
return this.$store.getters.findUser(this.userId)
|
||||
},
|
||||
userId() {
|
||||
return this.$route.params.id
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchStatuses(page) {
|
||||
|
|
@ -60,7 +62,7 @@ const UserProfileAdminView = {
|
|||
Checkbox,
|
||||
},
|
||||
watch: {
|
||||
godmode() {
|
||||
fetchOptions() {
|
||||
this.$refs.list.reset()
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,18 +10,30 @@
|
|||
hide-bio
|
||||
hide-buttons
|
||||
/>
|
||||
<Checkbox class="godmode" v-model="godmode">
|
||||
{{ $t('admin_dash.users.godmode') }}
|
||||
<Checkbox
|
||||
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>
|
||||
</div>
|
||||
<List
|
||||
ref="list"
|
||||
:fetch-function="fetchStatuses"
|
||||
@select="onSelect"
|
||||
scrollable
|
||||
>
|
||||
<template #item="{item}">
|
||||
<Status :statusoid="item" />
|
||||
<Status
|
||||
:statusoid="item"
|
||||
:in-conversation="false"
|
||||
:focused="false"
|
||||
/>
|
||||
</template>
|
||||
</List>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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,19 +18,17 @@ 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
|
||||
},
|
||||
isOpen() {
|
||||
console.log(this.reportModal)
|
||||
return this.isLoggedIn && this.reportModal.activated
|
||||
},
|
||||
userId() {
|
||||
|
|
@ -43,31 +43,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 +70,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 +87,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)) {
|
||||
|
|
|
|||
|
|
@ -51,19 +51,18 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="user-reporting-panel-right">
|
||||
<List :items="statuses">
|
||||
<List
|
||||
:external-items="reportModal.statuses"
|
||||
:pre-select="reportModal.preTickedIds"
|
||||
selectable
|
||||
@select="onListSelect"
|
||||
>
|
||||
<template #item="{item}">
|
||||
<div class="status-fadein user-reporting-panel-sitem">
|
||||
<Status
|
||||
:in-conversation="false"
|
||||
:focused="false"
|
||||
:statusoid="item"
|
||||
/>
|
||||
<Checkbox
|
||||
:model-value="isChecked(item.id)"
|
||||
@update:model-value="checked => toggleStatus(checked, item.id)"
|
||||
/>
|
||||
</div>
|
||||
<Status
|
||||
:in-conversation="false"
|
||||
:focused="false"
|
||||
:statusoid="item"
|
||||
/>
|
||||
</template>
|
||||
</List>
|
||||
</div>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -11,62 +11,64 @@
|
|||
<p>
|
||||
{{ $t(isMute ? 'user_card.expire_mute_message' : 'user_card.expire_block_message', [user.screen_name]) }}
|
||||
</p>
|
||||
<div>
|
||||
{{ $t('user_card.expire_in') }}
|
||||
<span class="expirationTime">
|
||||
<input
|
||||
id="userFilterExpires"
|
||||
v-model="expiration"
|
||||
class="input input-expire-in"
|
||||
:class="{ disabled: forever }"
|
||||
:disabled="forever"
|
||||
min="1"
|
||||
type="number"
|
||||
>
|
||||
<Select
|
||||
id="userFilterExpiresUnit"
|
||||
v-model="expirationUnit"
|
||||
class="input unit-input unstyled"
|
||||
:disabled="forever"
|
||||
>
|
||||
<option
|
||||
key="s"
|
||||
value="s"
|
||||
<template #below>
|
||||
<div>
|
||||
{{ $t('user_card.expire_in') }}
|
||||
<span class="expirationTime">
|
||||
<input
|
||||
id="userFilterExpires"
|
||||
v-model="expiration"
|
||||
class="input input-expire-in"
|
||||
:class="{ disabled: forever }"
|
||||
:disabled="forever"
|
||||
min="1"
|
||||
type="number"
|
||||
>
|
||||
{{ $t('time.unit.seconds_suffix') }}
|
||||
</option>
|
||||
<option
|
||||
key="m"
|
||||
value="m"
|
||||
<Select
|
||||
id="userFilterExpiresUnit"
|
||||
v-model="expirationUnit"
|
||||
class="input unit-input unstyled"
|
||||
:disabled="forever"
|
||||
>
|
||||
{{ $t('time.unit.minutes_suffix') }}
|
||||
</option>
|
||||
<option
|
||||
key="h"
|
||||
value="h"
|
||||
>
|
||||
{{ $t('time.unit.hours_suffix') }}
|
||||
</option>
|
||||
<option
|
||||
key="d"
|
||||
value="d"
|
||||
>
|
||||
{{ $t('time.unit.days_suffix') }}
|
||||
</option>
|
||||
</Select>
|
||||
</span>
|
||||
<option
|
||||
key="s"
|
||||
value="s"
|
||||
>
|
||||
{{ $t('time.unit.seconds_suffix') }}
|
||||
</option>
|
||||
<option
|
||||
key="m"
|
||||
value="m"
|
||||
>
|
||||
{{ $t('time.unit.minutes_suffix') }}
|
||||
</option>
|
||||
<option
|
||||
key="h"
|
||||
value="h"
|
||||
>
|
||||
{{ $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
|
||||
id="forever"
|
||||
v-model="forever"
|
||||
name="forever"
|
||||
class="input-forever"
|
||||
>
|
||||
{{ $t('user_card.mute_block_never') }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<Checkbox
|
||||
id="forever"
|
||||
v-model="forever"
|
||||
name="forever"
|
||||
class="input-forever"
|
||||
>
|
||||
{{ $t('user_card.mute_block_never') }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footerLeft>
|
||||
<div class="footer-left-checkbox">
|
||||
|
|
|
|||
|
|
@ -385,9 +385,6 @@
|
|||
"selectable_list": {
|
||||
"select_all": "Select all"
|
||||
},
|
||||
"page_list": {
|
||||
"load_more": "Load more"
|
||||
},
|
||||
"settings": {
|
||||
"invalid_settings_imported": "Error importing settings",
|
||||
"add_language": "Add fallback language",
|
||||
|
|
@ -1298,7 +1295,6 @@
|
|||
"users": {
|
||||
"title": "Users",
|
||||
"local_id": "Local ID",
|
||||
"godmode": "Show direct messages",
|
||||
"labels": {
|
||||
"query": "Search",
|
||||
"name": "Name",
|
||||
|
|
@ -1329,10 +1325,8 @@
|
|||
"only_unconfirmed": "Exclude Confirmed"
|
||||
},
|
||||
"filters": {
|
||||
"show_direct": "Show Direct Posts",
|
||||
"show_reblogs": "Show Reblogs",
|
||||
"ascending": "Oldest First",
|
||||
"descending": "Newest First"
|
||||
"show_direct": "Show Direct Messages",
|
||||
"show_reblogs": "Show Reblogs"
|
||||
},
|
||||
"indicator": {
|
||||
"admin": "Admin",
|
||||
|
|
|
|||
|
|
@ -591,7 +591,7 @@ const statuses = {
|
|||
pagination,
|
||||
},
|
||||
) {
|
||||
commit('addNewStatuses', {
|
||||
return commit('addNewStatuses', {
|
||||
statuses,
|
||||
showImmediately,
|
||||
timeline,
|
||||
|
|
@ -683,7 +683,7 @@ const statuses = {
|
|||
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
|
||||
},
|
||||
unpinStatus({ rootState, dispatch }, statusId) {
|
||||
rootState.api.backendInteractor
|
||||
return rootState.api.backendInteractor
|
||||
.unpinOwnStatus({ id: statusId })
|
||||
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
|
||||
},
|
||||
|
|
@ -822,7 +822,7 @@ const statuses = {
|
|||
'addNewUsers',
|
||||
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
|
||||
})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -307,8 +307,12 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
|
|||
opts,
|
||||
})
|
||||
|
||||
const statuses = activities.map(parseStatus)
|
||||
|
||||
await window.vuex.dispatch('addNewStatuses', { statuses })
|
||||
|
||||
return {
|
||||
items: activities.map(parseStatus),
|
||||
items: statuses,
|
||||
count: total,
|
||||
}
|
||||
},
|
||||
|
|
@ -318,10 +322,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
|
|||
})
|
||||
const status = parseStatus(raw)
|
||||
|
||||
await window.vuex.dispatch('addNewStatuses', {
|
||||
statuses: [status],
|
||||
userId: false,
|
||||
})
|
||||
await window.vuex.dispatch('addNewStatuses', { statuses: [status] })
|
||||
},
|
||||
|
||||
// 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() {
|
||||
switch (this.settingsModalState) {
|
||||
case 'minimized':
|
||||
|
|
@ -141,8 +158,10 @@ export const useInterfaceStore = defineStore('interface', {
|
|||
case 'visible':
|
||||
this.settingsModalState = 'minimized'
|
||||
return
|
||||
case 'hidden':
|
||||
return
|
||||
default:
|
||||
throw new Error('Illegal minimization state of settings modal')
|
||||
throw new Error(`Illegal minimization state of settings modal: ${this.settingsModalState}`)
|
||||
}
|
||||
},
|
||||
clearSettingsModalTargetTab() {
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ export const useReportsStore = defineStore('reports', {
|
|||
}),
|
||||
actions: {
|
||||
openUserReportingModal({ userId, statusIds = [] }) {
|
||||
console.log('ASS')
|
||||
const preTickedStatuses = statusIds.map(
|
||||
(id) => window.vuex.state.statuses.allStatusesObject[id],
|
||||
)
|
||||
const preTickedIds = statusIds
|
||||
console.log(preTickedStatuses)
|
||||
const statuses = preTickedStatuses.concat(
|
||||
filter(
|
||||
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,
|
||||
} from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js'
|
||||
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 = {}) => {
|
||||
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 = [{}]) => {
|
||||
return [
|
||||
Object.assign(
|
||||
|
|
@ -189,78 +106,6 @@ const makeMockEmojiMasto = (overrides = [{}]) => {
|
|||
|
||||
describe('API Entities normalizer', () => {
|
||||
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', () => {
|
||||
it("doesn't blow up", () => {
|
||||
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', () => {
|
||||
it('Parses min and max ids as integers', () => {
|
||||
const linkHeader =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue