hide muted threads behind a CW

This commit is contained in:
Hazelnoot 2025-06-09 22:11:34 -04:00
parent 87582034b5
commit 9bebf7718f
11 changed files with 83 additions and 47 deletions

4
locales/index.d.ts vendored
View file

@ -11988,6 +11988,10 @@ export interface Locale extends ILocale {
* Boosts muted * Boosts muted
*/ */
"renoteMuted": string; "renoteMuted": string;
/**
* {name} said something in a muted thread
*/
"userSaysSomethingInMutedThread": ParameterizedString<"name">;
/** /**
* Mark all media from user as NSFW * Mark all media from user as NSFW
*/ */

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div <div
v-if="!hardMuted && muted === false" v-if="!hardMuted && muted === false && !threadMuted"
v-show="!isDeleted" v-show="!isDeleted"
ref="rootEl" ref="rootEl"
v-hotkey="keymap" v-hotkey="keymap"
@ -168,8 +168,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</article> </article>
</div> </div>
<div v-else-if="!hardMuted" :class="$style.muted" @click="muted = false"> <div v-else-if="!hardMuted" :class="$style.muted" @click.stop="muted = false">
<SkMutedNote :muted="muted" :note="appearNote"></SkMutedNote> <SkMutedNote :muted="muted" :threadMuted="threadMuted" :note="appearNote"></SkMutedNote>
</div> </div>
<div v-else> <div v-else>
<!-- <!--
@ -312,7 +312,7 @@ const isLong = shouldCollapsed(appearNote.value, urls.value);
const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong); const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
const isDeleted = ref(false); const isDeleted = ref(false);
const renoted = ref(false); const renoted = ref(false);
const { muted, hardMuted } = checkMutes(appearNote.value, props.withHardMute); const { muted, hardMuted, threadMuted } = checkMutes(appearNote, computed(() => props.withHardMute));
const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(null); const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(null);
const translating = ref(false); const translating = ref(false);
const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance); const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance);

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div <div
v-if="!muted" v-if="!muted && !threadMuted"
v-show="!isDeleted" v-show="!isDeleted"
ref="rootEl" ref="rootEl"
v-hotkey="keymap" v-hotkey="keymap"
@ -226,8 +226,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</div> </div>
</div> </div>
<div v-else class="_panel" :class="$style.muted" @click="muted = false"> <div v-else class="_panel" :class="$style.muted" @click.stop="muted = false">
<SkMutedNote :muted="muted" :note="appearNote"></SkMutedNote> <SkMutedNote :muted="muted" :threadMuted="threadMuted" :note="appearNote"></SkMutedNote>
</div> </div>
</template> </template>
@ -354,7 +354,7 @@ const mergedCW = computed(() => computeMergedCw(appearNote.value));
const renoteTooltip = computeRenoteTooltip(renoted); const renoteTooltip = computeRenoteTooltip(renoted);
const { muted } = checkMutes(appearNote.value); const { muted, threadMuted } = checkMutes(appearNote);
watch(() => props.expandAllCws, (expandAllCws) => { watch(() => props.expandAllCws, (expandAllCws) => {
if (expandAllCws !== showContent.value) showContent.value = expandAllCws; if (expandAllCws !== showContent.value) showContent.value = expandAllCws;

View file

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div v-show="!isDeleted" v-if="!muted" ref="el" :class="[$style.root, { [$style.children]: depth > 1 }]"> <div v-show="!isDeleted" v-if="!muted && !threadMuted" ref="el" :class="[$style.root, { [$style.children]: depth > 1 }]">
<div :class="$style.main"> <div :class="$style.main">
<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div> <div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
<MkAvatar :class="$style.avatar" :user="note.user" link preview/> <MkAvatar :class="$style.avatar" :user="note.user" link preview/>
@ -78,8 +78,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA> <MkA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA>
</div> </div>
</div> </div>
<div v-else :class="$style.muted" @click="muted = false"> <div v-else :class="$style.muted" @click.stop="muted = false">
<SkMutedNote :muted="muted" :note="appearNote"></SkMutedNote> <SkMutedNote :muted="muted" :threadMuted="threadMuted" :note="appearNote"></SkMutedNote>
</div> </div>
</template> </template>
@ -172,7 +172,7 @@ async function removeReply(id: Misskey.entities.Note['id']) {
} }
} }
const { muted } = checkMutes(appearNote.value); const { muted, threadMuted } = checkMutes(appearNote);
useNoteCapture({ useNoteCapture({
rootEl: el, rootEl: el,

View file

@ -18,8 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkA> </MkA>
</header> </header>
<div> <div>
<div v-if="muted" :class="[$style.text, $style.muted]"> <div v-if="muted || threadMuted" :class="[$style.text, $style.muted]">
<SkMutedNote :muted="muted" :note="note"></SkMutedNote> <SkMutedNote :muted="muted" :threadMuted="threadMuted" :note="note"></SkMutedNote>
</div> </div>
<Mfm v-else :class="$style.text" :text="getNoteSummary(note)" :isBlock="true" :plain="true" :nowrap="false" :isNote="true" nyaize="respect" :author="note.user"/> <Mfm v-else :class="$style.text" :text="getNoteSummary(note)" :isBlock="true" :plain="true" :nowrap="false" :isNote="true" nyaize="respect" :author="note.user"/>
</div> </div>
@ -35,6 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { computed } from 'vue';
import { getNoteSummary } from '@/utility/get-note-summary.js'; import { getNoteSummary } from '@/utility/get-note-summary.js';
import { userPage } from '@/filters/user.js'; import { userPage } from '@/filters/user.js';
import { notePage } from '@/filters/note.js'; import { notePage } from '@/filters/note.js';
@ -49,8 +50,7 @@ defineEmits<{
(event: 'select', user: Misskey.entities.UserLite): void (event: 'select', user: Misskey.entities.UserLite): void
}>(); }>();
// eslint-disable-next-line vue/no-setup-props-reactivity-loss const { muted, hardMuted, threadMuted } = checkMutes(computed(() => props.note));
const { muted, hardMuted } = checkMutes(props.note);
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View file

@ -4,24 +4,34 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<I18n v-if="muted === 'sensitiveMute'" :src="i18n.ts.userSaysSomethingSensitive" tag="small"> <I18n v-if="threadMuted" :src="i18n.ts.userSaysSomethingInMutedThread" tag="small">
<template #name> <template #name>
<MkUserName :user="note.user"/> <MkUserName :user="note.user"/>
</template> </template>
</I18n> </I18n>
<I18n v-else-if="!prefer.s.showSoftWordMutedWord" :src="i18n.ts.userSaysSomething" tag="small">
<br v-if="threadMuted && muted">
<template v-if="muted">
<I18n v-if="muted === 'sensitiveMute'" :src="i18n.ts.userSaysSomethingSensitive" tag="small">
<template #name> <template #name>
<MkUserName :user="note.user"/> <MkUserName :user="note.user"/>
</template> </template>
</I18n> </I18n>
<I18n v-else :src="i18n.ts.userSaysSomethingAbout" tag="small"> <I18n v-else-if="!prefer.s.showSoftWordMutedWord" :src="i18n.ts.userSaysSomething" tag="small">
<template #name>
<MkUserName :user="note.user"/>
</template>
</I18n>
<I18n v-else :src="i18n.ts.userSaysSomethingAbout" tag="small">
<template #name> <template #name>
<MkUserName :user="note.user"/> <MkUserName :user="note.user"/>
</template> </template>
<template #word> <template #word>
{{ mutedWords }} {{ mutedWords }}
</template> </template>
</I18n> </I18n>
</template>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -32,6 +42,7 @@ import { prefer } from '@/preferences.js';
const props = defineProps<{ const props = defineProps<{
muted: false | 'sensitiveMute' | string[]; muted: false | 'sensitiveMute' | string[];
threadMuted: boolean;
note: Misskey.entities.Note; note: Misskey.entities.Note;
}>(); }>();

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div <div
v-if="!hardMuted && muted === false" v-if="!hardMuted && muted === false && !threadMuted"
v-show="!isDeleted" v-show="!isDeleted"
ref="rootEl" ref="rootEl"
v-hotkey="keymap" v-hotkey="keymap"
@ -168,8 +168,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</article> </article>
</div> </div>
<div v-else-if="!hardMuted" :class="$style.muted" @click="muted = false"> <div v-else-if="!hardMuted" :class="$style.muted" @click.stop="muted = false">
<SkMutedNote :muted="muted" :note="appearNote"></SkMutedNote> <SkMutedNote :muted="muted" :threadMuted="threadMuted" :note="appearNote"></SkMutedNote>
</div> </div>
<div v-else> <div v-else>
<!-- <!--
@ -311,7 +311,7 @@ const isLong = shouldCollapsed(appearNote.value, urls.value);
const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong); const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
const isDeleted = ref(false); const isDeleted = ref(false);
const renoted = ref(false); const renoted = ref(false);
const { muted, hardMuted } = checkMutes(appearNote.value, props.withHardMute); const { muted, hardMuted, threadMuted } = checkMutes(appearNote, computed(() => props.withHardMute));
const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(null); const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(null);
const translating = ref(false); const translating = ref(false);
const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance); const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance);

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div <div
v-if="!muted" v-if="!muted && !threadMuted"
v-show="!isDeleted" v-show="!isDeleted"
ref="rootEl" ref="rootEl"
v-hotkey="keymap" v-hotkey="keymap"
@ -230,8 +230,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</div> </div>
</div> </div>
<div v-else class="_panel" :class="$style.muted" @click="muted = false"> <div v-else class="_panel" :class="$style.muted" @click.stop="muted = false">
<SkMutedNote :muted="muted" :note="appearNote"></SkMutedNote> <SkMutedNote :muted="muted" :threadMuted="threadMuted" :note="appearNote"></SkMutedNote>
</div> </div>
</template> </template>
@ -359,7 +359,7 @@ const mergedCW = computed(() => computeMergedCw(appearNote.value));
const renoteTooltip = computeRenoteTooltip(renoted); const renoteTooltip = computeRenoteTooltip(renoted);
const { muted } = checkMutes(appearNote.value); const { muted, threadMuted } = checkMutes(appearNote);
watch(() => props.expandAllCws, (expandAllCws) => { watch(() => props.expandAllCws, (expandAllCws) => {
if (expandAllCws !== showContent.value) showContent.value = expandAllCws; if (expandAllCws !== showContent.value) showContent.value = expandAllCws;

View file

@ -86,8 +86,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA> <MkA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA>
</div> </div>
</div> </div>
<div v-else :class="$style.muted" @click="muted = false"> <div v-else :class="$style.muted" @click.stop="muted = false">
<SkMutedNote :muted="muted" :note="appearNote"></SkMutedNote> <SkMutedNote :muted="muted" :threadMuted="false" :note="appearNote"></SkMutedNote>
</div> </div>
</template> </template>
@ -186,7 +186,7 @@ async function removeReply(id: Misskey.entities.Note['id']) {
} }
} }
const { muted } = checkMutes(appearNote.value); const { muted } = checkMutes(appearNote);
useNoteCapture({ useNoteCapture({
rootEl: el, rootEl: el,

View file

@ -3,14 +3,34 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { inject, ref } from 'vue'; import { computed, inject, ref } from 'vue';
import type { Ref } from 'vue'; import type { Ref, ComputedRef } from 'vue';
import { $i } from '@/i'; import { $i } from '@/i';
export function checkMutes(noteToCheck: Misskey.entities.Note, withHardMute = false) { export function checkMutes(noteToCheck: ComputedRef<Misskey.entities.Note>, withHardMute?: ComputedRef<boolean>) {
const muted = ref(checkMute(noteToCheck, $i?.mutedWords)); const muteEnable = ref(true);
const hardMuted = ref(withHardMute && checkMute(noteToCheck, $i?.hardMutedWords, true));
return { muted, hardMuted }; const muted = computed<false | string[], boolean>({
get() {
if (!muteEnable.value) return false;
return checkMute(noteToCheck.value, $i?.mutedWords);
},
set(value: boolean) {
muteEnable.value = value;
},
});
const threadMuted = computed(() => {
if (!muteEnable.value) return false;
return noteToCheck.value.isMuting;
});
const hardMuted = computed(() => {
if (!withHardMute?.value) return false;
return checkMute(noteToCheck.value, $i?.hardMutedWords, true);
});
return { muted, hardMuted, threadMuted };
} }
export function checkMute(note: Misskey.entities.Note, mutes: undefined | null): false; export function checkMute(note: Misskey.entities.Note, mutes: undefined | null): false;

View file

@ -29,6 +29,7 @@ muted: "Muted"
renoteMute: "Mute Boosts" renoteMute: "Mute Boosts"
renoteMuted: "Boosts muted" renoteMuted: "Boosts muted"
renoteUnmute: "Unmute Boosts" renoteUnmute: "Unmute Boosts"
userSaysSomethingInMutedThread: "{name} said something in a muted thread"
markAsNSFW: "Mark all media from user as NSFW" markAsNSFW: "Mark all media from user as NSFW"
markInstanceAsNSFW: "Mark as NSFW" markInstanceAsNSFW: "Mark as NSFW"
nsfwConfirm: "Are you sure that you want to mark all media from this account as NSFW?" nsfwConfirm: "Are you sure that you want to mark all media from this account as NSFW?"