add "reject quotes" toggle at user and instance level
+ improve, cleanup, and de-duplicate quote resolution + add warning message when quote cannot be loaded + add "process error" framework to display warnings when a note cannot be correctly loaded from another instance
This commit is contained in:
parent
93ffd4611c
commit
292d3b9229
36 changed files with 466 additions and 88 deletions
|
|
@ -81,6 +81,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div class="_gaps">
|
||||
<MkSwitch v-model="silenced" @update:modelValue="toggleSilence">{{ i18n.ts.silence }}</MkSwitch>
|
||||
<MkSwitch v-if="!isSystem" v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
||||
<MkSwitch v-model="rejectQuotes" @update:modelValue="toggleRejectQuotes">{{ i18n.ts.rejectQuotesUser }}</MkSwitch>
|
||||
<MkSwitch v-model="markedAsNSFW" @update:modelValue="toggleNSFW">{{ i18n.ts.markAsNSFW }}</MkSwitch>
|
||||
|
||||
<MkInput v-model="mandatoryCW" type="text" manualSave @update:modelValue="onMandatoryCWChanged">
|
||||
|
|
@ -247,6 +248,7 @@ const moderator = ref(false);
|
|||
const silenced = ref(false);
|
||||
const approved = ref(false);
|
||||
const suspended = ref(false);
|
||||
const rejectQuotes = ref(false);
|
||||
const markedAsNSFW = ref(false);
|
||||
const moderationNote = ref('');
|
||||
const mandatoryCW = ref<string | null>(null);
|
||||
|
|
@ -287,6 +289,7 @@ function createFetcher() {
|
|||
approved.value = info.value.approved;
|
||||
markedAsNSFW.value = info.value.alwaysMarkNsfw;
|
||||
suspended.value = info.value.isSuspended;
|
||||
rejectQuotes.value = user.value.rejectQuotes ?? false;
|
||||
moderationNote.value = info.value.moderationNote;
|
||||
mandatoryCW.value = user.value.mandatoryCW;
|
||||
});
|
||||
|
|
@ -368,6 +371,22 @@ async function toggleSuspend(v) {
|
|||
}
|
||||
}
|
||||
|
||||
async function toggleRejectQuotes(v: boolean): Promise<void> {
|
||||
const confirm = await os.confirm({
|
||||
type: 'warning',
|
||||
text: v ? i18n.ts.rejectQuotesConfirm : i18n.ts.allowQuotesConfirm,
|
||||
});
|
||||
if (confirm.canceled) {
|
||||
rejectQuotes.value = !v;
|
||||
} else {
|
||||
await misskeyApi('admin/reject-quotes', {
|
||||
userId: props.userId,
|
||||
rejectQuotes: v,
|
||||
});
|
||||
await refreshUser();
|
||||
}
|
||||
}
|
||||
|
||||
async function unsetUserAvatar() {
|
||||
const confirm = await os.confirm({
|
||||
type: 'warning',
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
'unsetRemoteInstanceNSFW',
|
||||
'rejectRemoteInstanceReports',
|
||||
'acceptRemoteInstanceReports',
|
||||
'rejectQuotesUser',
|
||||
'acceptQuotesUser',
|
||||
].includes(log.type),
|
||||
[$style.logRed]: [
|
||||
'suspend',
|
||||
|
|
@ -53,6 +55,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<span v-if="log.type === 'updateUserNote'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'suspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'approve'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'rejectQuotesUser'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'acceptQuotesUser'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'rejectQuotesInstance'">: {{ log.info.host }}</span>
|
||||
<span v-else-if="log.type === 'acceptQuotesInstance'">: {{ log.info.host }}</span>
|
||||
<span v-else-if="log.type === 'decline'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'unsuspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'resetPassword'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
|
|
@ -134,6 +140,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template v-else-if="log.type === 'unsuspend'">
|
||||
<div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
|
||||
</template>
|
||||
<template v-else-if="log.type === 'rejectQuotesUser'">
|
||||
<div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
|
||||
</template>
|
||||
<template v-else-if="log.type === 'acceptQuotesUser'">
|
||||
<div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
|
||||
</template>
|
||||
<template v-else-if="log.type === 'updateRole'">
|
||||
<div :class="$style.diff">
|
||||
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkInfo v-if="isBaseSilenced" warn>{{ i18n.ts.silencedByBase }}</MkInfo>
|
||||
<MkSwitch v-model="isSilenced" :disabled="!meta || !instance || isBaseSilenced" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
|
||||
<MkSwitch v-model="isNSFW" :disabled="!instance" @update:modelValue="toggleNSFW">{{ i18n.ts.markInstanceAsNSFW }}</MkSwitch>
|
||||
<MkSwitch v-model="rejectQuotes" :disabled="!instance" @update:modelValue="toggleRejectQuotes">{{ i18n.ts.rejectQuotesInstance }}</MkSwitch>
|
||||
<MkSwitch v-model="rejectReports" :disabled="!instance" @update:modelValue="toggleRejectReports">{{ i18n.ts.rejectReports }}</MkSwitch>
|
||||
<MkInfo v-if="isBaseMediaSilenced" warn>{{ i18n.ts.mediaSilencedByBase }}</MkInfo>
|
||||
<MkSwitch v-model="isMediaSilenced" :disabled="!meta || !instance || isBaseMediaSilenced" @update:modelValue="toggleMediaSilenced">{{ i18n.ts.mediaSilenceThisInstance }}</MkSwitch>
|
||||
|
|
@ -211,6 +212,7 @@ const isSuspended = ref(false);
|
|||
const isBlocked = ref(false);
|
||||
const isSilenced = ref(false);
|
||||
const isNSFW = ref(false);
|
||||
const rejectQuotes = ref(false);
|
||||
const rejectReports = ref(false);
|
||||
const isMediaSilenced = ref(false);
|
||||
const faviconUrl = ref<string | null>(null);
|
||||
|
|
@ -282,6 +284,7 @@ async function fetch(): Promise<void> {
|
|||
isSilenced.value = instance.value?.isSilenced ?? false;
|
||||
isNSFW.value = instance.value?.isNSFW ?? false;
|
||||
rejectReports.value = instance.value?.rejectReports ?? false;
|
||||
rejectQuotes.value = instance.value?.rejectQuotes ?? false;
|
||||
isMediaSilenced.value = instance.value?.isMediaSilenced ?? false;
|
||||
faviconUrl.value = getProxiedImageUrlNullable(instance.value?.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.value?.iconUrl, 'preview');
|
||||
moderationNote.value = instance.value?.moderationNote ?? '';
|
||||
|
|
@ -347,6 +350,15 @@ async function toggleRejectReports(): Promise<void> {
|
|||
});
|
||||
}
|
||||
|
||||
async function toggleRejectQuotes(): Promise<void> {
|
||||
if (!iAmModerator) return;
|
||||
if (!instance.value) throw new Error('No instance?');
|
||||
await misskeyApi('admin/federation/update-instance', {
|
||||
host: instance.value.host,
|
||||
rejectQuotes: rejectQuotes.value,
|
||||
});
|
||||
}
|
||||
|
||||
function refreshMetadata(): void {
|
||||
if (!iAmModerator) return;
|
||||
if (!instance.value) throw new Error('No instance?');
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<div class="_margin _gaps_s">
|
||||
<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
|
||||
<SkErrorList :errors="note.processErrors"/>
|
||||
<MkNoteDetailed :key="note.id" v-model:note="note" :initialTab="initialTab" :class="$style.note" :expandAllCws="expandAllCws"/>
|
||||
</div>
|
||||
<div v-if="clips && clips.length > 0" class="_margin">
|
||||
|
|
@ -60,6 +61,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
|||
import { i18n } from '@/i18n.js';
|
||||
import { dateString } from '@/filters/date.js';
|
||||
import MkClipPreview from '@/components/MkClipPreview.vue';
|
||||
import SkErrorList from '@/components/SkErrorList.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { pleaseLogin } from '@/scripts/please-login.js';
|
||||
import { serverContext, assertServerContext } from '@/server-context.js';
|
||||
|
|
@ -69,9 +71,9 @@ import { $i } from '@/account.js';
|
|||
const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
|
||||
|
||||
const MkNoteDetailed = defineAsyncComponent(() =>
|
||||
(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNoteDetailed.vue') :
|
||||
(defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNoteDetailed.vue') :
|
||||
null
|
||||
(defaultStore.state.noteDesign === 'misskey')
|
||||
? import('@/components/MkNoteDetailed.vue')
|
||||
: import('@/components/SkNoteDetailed.vue'),
|
||||
);
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue