merge: Add "force content warning" setting for user moderation (resolves #905) (!876)

View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/876

Closes #905

Approved-by: dakkar <dakkar@thenautilus.net>
Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
dakkar 2025-02-20 10:20:49 +00:00
commit 534c35cca2
46 changed files with 843 additions and 108 deletions

View file

@ -59,10 +59,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/>
<div style="container-type: inline-size;">
<bdi>
<p v-if="appearNote.cw != null" :class="$style.cw">
<p v-if="mergedCW != null" :class="$style.cw">
<Mfm
v-if="appearNote.cw != ''"
:text="appearNote.cw"
v-if="mergedCW != ''"
:text="mergedCW"
:author="appearNote.user"
:nyaize="'respect'"
:enableEmojiMenu="true"
@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
/>
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;" @click.stop/>
</p>
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
<div v-show="mergedCW == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
<div :class="$style.text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
@ -212,6 +212,7 @@ import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
import { shouldCollapsed } from '@@/js/collapsed.js';
import { host } from '@@/js/config.js';
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import type { MenuItem } from '@/types/menu.js';
import MkNoteSub from '@/components/MkNoteSub.vue';
import MkNoteHeader from '@/components/MkNoteHeader.vue';
@ -350,6 +351,8 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
url: `https://${host}/notes/${appearNote.value.id}`,
}));
const mergedCW = computed(() => computeMergedCw(appearNote.value));
const renoteTooltip = computeRenoteTooltip(renoted);
/* Overload FunctionLint

View file

@ -75,10 +75,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</header>
<div :class="$style.noteContent">
<p v-if="appearNote.cw != null" :class="$style.cw">
<p v-if="mergedCW != null" :class="$style.cw">
<Mfm
v-if="appearNote.cw != ''"
:text="appearNote.cw"
v-if="mergedCW != ''"
:text="mergedCW"
:author="appearNote.user"
:nyaize="'respect'"
:enableEmojiMenu="true"
@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
/>
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll"/>
</p>
<div v-show="appearNote.cw == null || showContent">
<div v-show="mergedCW == null || showContent">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
<Mfm
@ -245,6 +245,7 @@ import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
import { host } from '@@/js/config.js';
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import MkNoteSub from '@/components/MkNoteSub.vue';
import MkNoteSimple from '@/components/MkNoteSimple.vue';
import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
@ -348,6 +349,8 @@ const quotes = ref<Misskey.entities.Note[]>([]);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id));
const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
const mergedCW = computed(() => computeMergedCw(appearNote.value));
const renoteTooltip = computeRenoteTooltip(renoted);
watch(() => props.expandAllCws, (expandAllCws) => {

View file

@ -9,11 +9,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.main">
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
<div>
<p v-if="note.cw != null" :class="$style.cw">
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :isBlock="true" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/>
<p v-if="mergedCW != null" :class="$style.cw">
<Mfm v-if="mergedCW != ''" style="margin-right: 8px;" :text="mergedCW" :isBlock="true" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/>
<MkCwButton v-model="showContent" :text="note.text" :files="note.files" :poll="note.poll" @click.stop/>
</p>
<div v-show="note.cw == null || showContent">
<div v-show="mergedCW == null || showContent">
<MkSubNoteContent :hideFiles="hideFiles" :class="$style.text" :note="note" :expandAllCws="props.expandAllCws"/>
<div v-if="note.isSchedule" style="margin-top: 10px;">
<MkButton :class="$style.button" inline @click.stop.prevent="editScheduleNote()"><i class="ti ti-eraser"></i> {{ i18n.ts.edit }}</MkButton>
@ -26,8 +26,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { computed, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import * as os from '@/os.js';
import MkNoteHeader from '@/components/MkNoteHeader.vue';
import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
@ -48,6 +49,8 @@ const props = defineProps<{
let showContent = ref(defaultStore.state.uncollapseCW);
const isDeleted = ref(false);
const mergedCW = computed(() => computeMergedCw(props.note));
const emit = defineEmits<{
(ev: 'editScheduleNote'): void;
}>();

View file

@ -11,11 +11,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.body">
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
<div :class="$style.content">
<p v-if="note.cw != null" :class="$style.cw">
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :isBlock="true" :author="note.user" :nyaize="'respect'"/>
<p v-if="mergedCW != null" :class="$style.cw">
<Mfm v-if="mergedCW != ''" style="margin-right: 8px;" :text="mergedCW" :isBlock="true" :author="note.user" :nyaize="'respect'"/>
<MkCwButton v-model="showContent" :text="note.text" :files="note.files" :poll="note.poll"/>
</p>
<div v-show="note.cw == null || showContent">
<div v-show="mergedCW == null || showContent">
<MkSubNoteContent :class="$style.text" :note="note" :translating="translating" :translation="translation" :expandAllCws="props.expandAllCws"/>
</div>
</div>
@ -86,6 +86,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref, shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import MkNoteHeader from '@/components/MkNoteHeader.vue';
import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
@ -142,6 +143,8 @@ let appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities
const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
const replies = ref<Misskey.entities.Note[]>([]);
const mergedCW = computed(() => computeMergedCw(appearNote.value));
const isRenote = (
props.note.renote != null &&
props.note.text == null &&

View file

@ -112,6 +112,7 @@ import * as Misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor';
import { toASCII } from 'punycode.js';
import { host, url } from '@@/js/config.js';
import { appendContentWarning } from '@@/js/append-content-warning.js';
import type { MenuItem } from '@/types/menu.js';
import type { PostFormProps } from '@/types/post-form.js';
import MkNoteSimple from '@/components/MkNoteSimple.vue';
@ -373,18 +374,8 @@ if ($i.defaultCW) {
if (!cw.value || $i.defaultCWPriority === 'default') {
cw.value = $i.defaultCW;
} else if ($i.defaultCWPriority !== 'parent') {
// This is a fancy way of simulating /\bsearch\b/ without a regular expression.
// We're checking to see whether the default CW appears inside the existing CW, but *only* if there's word boundaries.
const parts = cw.value.split($i.defaultCW);
const hasExistingDefaultCW = parts.length === 2 && !/\w$/.test(parts[0]) && !/^\w/.test(parts[1]);
if (!hasExistingDefaultCW) {
// We need to merge the CWs
if ($i.defaultCWPriority === 'defaultParent') {
cw.value = `${$i.defaultCW}, ${cw.value}`;
} else if ($i.defaultCWPriority === 'parentDefault') {
cw.value = `${cw.value}, ${$i.defaultCW}`;
}
}
const putDefaultFirst = $i.defaultCWPriority === 'defaultParent';
cw.value = appendContentWarning(cw.value, $i.defaultCW, putDefaultFirst);
}
// else { do nothing, because existing CW takes priority. }
}

View file

@ -62,10 +62,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div :class="[{ [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click.stop="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
<div style="container-type: inline-size;">
<p v-if="appearNote.cw != null" :class="$style.cw">
<p v-if="mergedCW != null" :class="$style.cw">
<Mfm
v-if="appearNote.cw != ''"
:text="appearNote.cw"
v-if="mergedCW != ''"
:text="mergedCW"
:author="appearNote.user"
:nyaize="'respect'"
:enableEmojiMenu="true"
@ -74,7 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
/>
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;" @click.stop/>
</p>
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
<div v-show="mergedCW == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
<div :class="$style.text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<Mfm
@ -213,6 +213,7 @@ import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
import { shouldCollapsed } from '@@/js/collapsed.js';
import { host } from '@@/js/config.js';
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import type { MenuItem } from '@/types/menu.js';
import SkNoteSub from '@/components/SkNoteSub.vue';
import SkNoteHeader from '@/components/SkNoteHeader.vue';
@ -345,6 +346,8 @@ const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.
const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
const mergedCW = computed(() => computeMergedCw(appearNote.value));
const renoteTooltip = computeRenoteTooltip(renoted);
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({

View file

@ -81,10 +81,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</header>
<div :class="$style.noteContent">
<p v-if="appearNote.cw != null" :class="$style.cw">
<p v-if="mergedCW != null" :class="$style.cw">
<Mfm
v-if="appearNote.cw != ''"
:text="appearNote.cw"
v-if="mergedCW != ''"
:text="mergedCW"
:author="appearNote.user"
:nyaize="'respect'"
:enableEmojiMenu="true"
@ -93,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
/>
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll"/>
</p>
<div v-show="appearNote.cw == null || showContent">
<div v-show="mergedCW == null || showContent">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<Mfm
v-if="appearNote.text"
@ -250,6 +250,7 @@ import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
import { host } from '@@/js/config.js';
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import SkNoteSub from '@/components/SkNoteSub.vue';
import SkNoteSimple from '@/components/SkNoteSimple.vue';
import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
@ -354,6 +355,8 @@ const quotes = ref<Misskey.entities.Note[]>([]);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id));
const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
const mergedCW = computed(() => computeMergedCw(appearNote.value));
const renoteTooltip = computeRenoteTooltip(renoted);
watch(() => props.expandAllCws, (expandAllCws) => {

View file

@ -9,11 +9,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.main">
<MkNoteHeader :class="$style.header" :classic="true" :note="note" :mini="true"/>
<div>
<p v-if="note.cw != null" :class="$style.cw">
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :isBlock="true" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/>
<p v-if="mergedCW != null" :class="$style.cw">
<Mfm v-if="mergedCW != ''" style="margin-right: 8px;" :text="mergedCW" :isBlock="true" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/>
<MkCwButton v-model="showContent" :text="note.text" :files="note.files" :poll="note.poll" @click.stop/>
</p>
<div v-show="note.cw == null || showContent">
<div v-show="mergedCW == null || showContent">
<MkSubNoteContent :hideFiles="hideFiles" :class="$style.text" :note="note" :expandAllCws="props.expandAllCws"/>
</div>
</div>
@ -22,8 +22,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { watch, ref } from 'vue';
import { watch, ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import MkNoteHeader from '@/components/MkNoteHeader.vue';
import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
import MkCwButton from '@/components/MkCwButton.vue';
@ -37,6 +38,8 @@ const props = defineProps<{
let showContent = ref(defaultStore.state.uncollapseCW);
const mergedCW = computed(() => computeMergedCw(props.note));
watch(() => props.expandAllCws, (expandAllCws) => {
if (expandAllCws !== showContent.value) showContent.value = expandAllCws;
});

View file

@ -19,11 +19,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.body">
<SkNoteHeader :class="$style.header" :note="note" :classic="true" :mini="true"/>
<div :class="$style.content">
<p v-if="note.cw != null" :class="$style.cw">
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :isBlock="true" :author="note.user" :nyaize="'respect'"/>
<p v-if="mergedCW != null" :class="$style.cw">
<Mfm v-if="mergedCW != ''" style="margin-right: 8px;" :text="mergedCW" :isBlock="true" :author="note.user" :nyaize="'respect'"/>
<MkCwButton v-model="showContent" :text="note.text" :files="note.files" :poll="note.poll"/>
</p>
<div v-show="note.cw == null || showContent">
<div v-show="mergedCW == null || showContent">
<MkSubNoteContent :class="$style.text" :note="note" :translating="translating" :translation="translation" :expandAllCws="props.expandAllCws"/>
</div>
</div>
@ -94,6 +94,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref, shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import SkNoteHeader from '@/components/SkNoteHeader.vue';
import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
@ -156,6 +157,8 @@ let appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities
const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
const replies = ref<Misskey.entities.Note[]>([]);
const mergedCW = computed(() => computeMergedCw(appearNote.value));
const isRenote = (
props.note.renote != null &&
props.note.text == null &&

View file

@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkKeyValue>
</div>
<MkTextarea v-model="moderationNote" manualSave>
<MkTextarea v-model="moderationNote" manualSave @update:modelValue="onModerationNoteChanged">
<template #label>{{ i18n.ts.moderationNote }}</template>
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
</MkTextarea>
@ -83,6 +83,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-if="!isSystem" v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
<MkSwitch v-model="markedAsNSFW" @update:modelValue="toggleNSFW">{{ i18n.ts.markAsNSFW }}</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>
<MkButton v-if="user.host == null && !isSystem" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
</div>
@ -222,6 +227,7 @@ import { i18n } from '@/i18n.js';
import { iAmAdmin, $i, iAmModerator } from '@/account.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkInput from '@/components/MkInput.vue';
const props = withDefaults(defineProps<{
userId: string;
@ -243,6 +249,7 @@ const approved = ref(false);
const suspended = ref(false);
const markedAsNSFW = ref(false);
const moderationNote = ref('');
const mandatoryCW = ref<string | null>(null);
const isSystem = computed(() => info.value?.isSystem ?? false);
const filesPagination = {
endpoint: 'admin/drive/files' as const,
@ -281,11 +288,7 @@ function createFetcher() {
markedAsNSFW.value = info.value.alwaysMarkNsfw;
suspended.value = info.value.isSuspended;
moderationNote.value = info.value.moderationNote;
watch(moderationNote, async () => {
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
await refreshUser();
});
mandatoryCW.value = user.value.mandatoryCW;
});
}
@ -293,6 +296,16 @@ function refreshUser() {
init.value = createFetcher();
}
async function onMandatoryCWChanged(value: string) {
await os.apiWithDialog('admin/cw-user', { userId: props.userId, cw: value });
refreshUser();
}
async function onModerationNoteChanged(value: string) {
await misskeyApi('admin/update-user-note', { userId: props.userId, text: value });
refreshUser();
}
async function updateRemoteUser() {
await os.apiWithDialog('federation/update-remote-user', { userId: user.value.id });
refreshUser();

View file

@ -22,6 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.logYellow]: [
'markSensitiveDriveFile',
'resetPassword',
'setMandatoryCW',
'suspendRemoteInstance',
'setRemoteInstanceNSFW',
'unsetRemoteInstanceNSFW',
@ -55,6 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<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>
<span v-else-if="log.type === 'setMandatoryCW'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</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>
@ -93,7 +95,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="log.type === 'deleteFlash'">: @{{ log.info.flashUserUsername }}</span>
<span v-else-if="log.type === 'deleteGalleryPost'">: @{{ log.info.postUserUsername }}</span>
</template>
<template #icon>
<template v-if="log.user" #icon>
<MkAvatar :user="log.user" :class="$style.avatar"/>
</template>
<template #suffix>
@ -123,6 +125,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-else-if="log.type === 'approve'">
<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 === 'setMandatoryCW'">
<div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
<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

@ -4,6 +4,7 @@
*/
import * as Misskey from 'misskey-js';
import { appendContentWarning } from '@@/js/append-content-warning.js';
import { i18n } from '@/i18n.js';
/**
@ -25,9 +26,15 @@ export const getNoteSummary = (note?: Misskey.entities.Note | null): string => {
let summary = '';
// Append mandatory CW, if applicable
let cw = note.cw;
if (note.user.mandatoryCW) {
cw = appendContentWarning(cw, note.user.mandatoryCW);
}
// 本文
if (note.cw != null) {
summary += `CW: ${note.cw}`;
if (cw != null) {
summary += `CW: ${cw}`;
} else if (note.text) {
summary += note.text;
}