add mandatory CW for instances

This commit is contained in:
Hazelnoot 2025-06-27 23:20:59 -04:00
parent 595c004a74
commit 5e0115335a
26 changed files with 282 additions and 8 deletions

View file

@ -26,6 +26,14 @@ Displays a placeholder for a muted note.
{{ mute.userMandatoryCW }}
</template>
</I18n>
<I18n v-if="mute.instanceMandatoryCW" :src="i18n.ts.instanceIsFlaggedAs" tag="small">
<template #name>
{{ note.user.instance?.name ?? note.user.host }}
</template>
<template #cw>
{{ mute.instanceMandatoryCW }}
</template>
</I18n>
<!-- Muted notes/threads -->
<I18n v-if="mute.noteMuted" :src="i18n.ts.userSaysSomethingInMutedNote" tag="small">
@ -89,7 +97,7 @@ const expandNote = ref(false);
const mute = computed(() => checkMute(props.note, props.withHardMute));
const mutedWords = computed(() => mute.value.softMutedWords?.join(', '));
const isMuted = computed(() => mute.value.hardMuted || mutedWords.value || mute.value.noteMandatoryCW || mute.value.userMandatoryCW || mute.value.noteMuted || mute.value.threadMuted || mute.value.sensitiveMuted);
const isMuted = computed(() => mute.value.hardMuted || mutedWords.value || mute.value.noteMandatoryCW || mute.value.userMandatoryCW || mute.value.instanceMandatoryCW || mute.value.noteMuted || mute.value.threadMuted || mute.value.sensitiveMuted);
const isExpanded = computed(() => expandNote.value || !isMuted.value);
const rootClass = computed(() => isExpanded.value ? props.expandedClass : undefined);

View file

@ -28,6 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
'resetPassword',
'setMandatoryCW',
'setMandatoryCWForNote',
'setMandatoryCWForInstance',
'suspendRemoteInstance',
'setRemoteInstanceNSFW',
'unsetRemoteInstanceNSFW',
@ -81,6 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="log.type === 'resetPassword'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'setMandatoryCW'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'setMandatoryCWForNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
<span v-else-if="log.type === 'setMandatoryCWForInstance'">: {{ log.info.host }}</span>
<span v-else-if="log.type === 'assignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} <i class="ti ti-arrow-right"></i> {{ log.info.roleName }}</span>
<span v-else-if="log.type === 'unassignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} <i class="ti ti-equal-not"></i> {{ log.info.roleName }}</span>
<span v-else-if="log.type === 'createRole'">: {{ log.info.role.name }}</span>
@ -217,6 +219,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<CodeDiff :context="0" :hideHeader="true" :oldString="log.info.oldCW ?? ''" :newString="log.info.newCW ?? ''" maxHeight="150px"/>
</div>
</template>
<template v-else-if="log.type === 'setMandatoryCWForInstance'">
<div :class="$style.diff">
<CodeDiff :context="0" :hideHeader="true" :oldString="log.info.oldCW ?? ''" :newString="log.info.newCW ?? ''" maxHeight="150px"/>
</div>
</template>
<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>

View file

@ -123,6 +123,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInfo v-if="isBaseMediaSilenced" warn>{{ i18n.ts.mediaSilencedByBase }}</MkInfo>
<MkSwitch v-model="isMediaSilenced" :disabled="!meta || !instance || isBaseMediaSilenced" @update:modelValue="toggleMediaSilenced">{{ i18n.ts.mediaSilenceThisInstance }}</MkSwitch>
<MkInput v-model="mandatoryCW" type="text" manualSave @update:modelValue="onMandatoryCWChanged">
<template #label>{{ i18n.ts.mandatoryCW }}</template>
<template #caption>{{ i18n.ts.mandatoryCWDescription }}</template>
</MkInput>
<div :class="$style.buttonStrip">
<MkButton inline :disabled="!instance" @click="refreshMetadata"><i class="ph-cloud-arrow-down ph-bold ph-lg"></i> {{ i18n.ts.updateRemoteUser }}</MkButton>
<MkButton inline :disabled="!instance" danger @click="deleteAllFiles"><i class="ph-trash ph-bold ph-lg"></i> {{ i18n.ts.deleteAllFiles }}</MkButton>
@ -234,6 +239,7 @@ import { copyToClipboard } from '@/utility/copy-to-clipboard';
import MkFolder from '@/components/MkFolder.vue';
import MkNumber from '@/components/MkNumber.vue';
import SkBadgeStrip from '@/components/SkBadgeStrip.vue';
import MkInput from '@/components/MkInput.vue';
const props = withDefaults(defineProps<{
host: string;
@ -259,6 +265,7 @@ const rejectReports = ref(false);
const isMediaSilenced = ref(false);
const faviconUrl = ref<string | null>(null);
const moderationNote = ref('');
const mandatoryCW = ref<string | null>(null);
const baseDomains = computed(() => {
const domains: string[] = [];
@ -306,6 +313,13 @@ const badges = computed(() => {
style: 'warning',
});
}
if (instance.value.mandatoryCW) {
arr.push({
key: 'cw',
label: i18n.ts.cw,
style: 'warning',
});
}
if (instance.value.isNSFW) {
arr.push({
key: 'nsfw',
@ -365,6 +379,13 @@ async function saveModerationNote() {
}
}
async function onMandatoryCWChanged(value: string | number) {
await os.promiseDialog(async () => {
await misskeyApi('admin/cw-instance', { host: props.host, cw: String(value) || null });
await fetch();
});
}
async function fetch(withHint = false): Promise<void> {
const [m, i] = await Promise.all([
(withHint && props.metaHint)
@ -389,6 +410,7 @@ async function fetch(withHint = false): Promise<void> {
isMediaSilenced.value = instance.value?.isMediaSilenced ?? false;
faviconUrl.value = getProxiedImageUrlNullable(instance.value?.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.value?.iconUrl, 'preview');
moderationNote.value = instance.value?.moderationNote ?? '';
mandatoryCW.value = instance.value?.mandatoryCW ?? '';
}
async function toggleBlock(): Promise<void> {

View file

@ -21,6 +21,7 @@ export interface Mute {
noteMandatoryCW?: string | null;
// TODO show this as a single block on user timelines
userMandatoryCW?: string | null;
instanceMandatoryCW?: string | null;
}
export function checkMute(note: Misskey.entities.Note, withHardMute?: boolean): Mute {
@ -35,20 +36,21 @@ export function checkMute(note: Misskey.entities.Note, withHardMute?: boolean):
const noteMuted = note.isMutingNote;
const noteMandatoryCW = note.mandatoryCW;
const userMandatoryCW = note.user.mandatoryCW;
const instanceMandatoryCW = note.user.instance?.mandatoryCW;
// Hard mute
if (withHardMute && isHardMuted(note)) {
return { hardMuted: true, sensitiveMuted, threadMuted, noteMuted, noteMandatoryCW, userMandatoryCW };
return { hardMuted: true, sensitiveMuted, threadMuted, noteMuted, noteMandatoryCW, userMandatoryCW, instanceMandatoryCW };
}
// Soft mute
const softMutedWords = isSoftMuted(note);
if (softMutedWords.length > 0) {
return { softMutedWords, sensitiveMuted, threadMuted, noteMuted, noteMandatoryCW, userMandatoryCW };
return { softMutedWords, sensitiveMuted, threadMuted, noteMuted, noteMandatoryCW, userMandatoryCW, instanceMandatoryCW };
}
// Other / no mute
return { sensitiveMuted, threadMuted, noteMuted, noteMandatoryCW, userMandatoryCW };
return { sensitiveMuted, threadMuted, noteMuted, noteMandatoryCW, userMandatoryCW, instanceMandatoryCW };
}
function isHardMuted(note: Misskey.entities.Note): boolean {