diff options
Diffstat (limited to '')
| -rw-r--r-- | packages/frontend/src/components/MkNote.vue | 30 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkTimeline.vue | 1 | ||||
| -rw-r--r-- | packages/frontend/src/pages/timeline.vue | 89 | ||||
| -rw-r--r-- | packages/frontend/src/store.ts | 10 |
4 files changed, 103 insertions, 27 deletions
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index d7bb64661b..7448fd34e3 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div - v-if="!hardMuted && !muted" + v-if="!hardMuted && muted === false" v-show="!isDeleted" ref="el" v-hotkey="keymap" @@ -134,7 +134,14 @@ SPDX-License-Identifier: AGPL-3.0-only </article> </div> <div v-else-if="!hardMuted" :class="$style.muted" @click="muted = false"> - <I18n :src="i18n.ts.userSaysSomething" tag="small"> + <I18n v-if="muted === 'sensitiveMute'" :src="i18n.ts.userSaysSomethingSensitive" tag="small"> + <template #name> + <MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)"> + <MkUserName :user="appearNote.user"/> + </MkA> + </template> + </I18n> + <I18n v-else :src="i18n.ts.userSaysSomething" tag="small"> <template #name> <MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)"> <MkUserName :user="appearNote.user"/> @@ -203,6 +210,7 @@ const emit = defineEmits<{ (ev: 'removeReaction', emoji: string): void; }>(); +const inTimeline = inject<boolean>('inTimeline', false); const inChannel = inject('inChannel', null); const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null); @@ -250,19 +258,27 @@ const isLong = shouldCollapsed(appearNote.value, urls.value ?? []); const collapsed = ref(appearNote.value.cw == null && isLong); const isDeleted = ref(false); const muted = ref(checkMute(appearNote.value, $i?.mutedWords)); -const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords)); +const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords, true)); const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null); const translating = ref(false); const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i.id)); const renoteCollapsed = ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || (appearNote.value.myReaction != null))); -function checkMute(note: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null): boolean { +/* Overload FunctionにLintが対応していないのでコメントアウト +function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean; +function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute'; +*/ +function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' { if (mutedWords == null) return false; - if (checkWordMute(note, $i, mutedWords)) return true; - if (note.reply && checkWordMute(note.reply, $i, mutedWords)) return true; - if (note.renote && checkWordMute(note.renote, $i, mutedWords)) return true; + if (checkWordMute(noteToCheck, $i, mutedWords)) return true; + if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true; + if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true; + + if (checkOnly) return false; + + if (inTimeline && !defaultStore.state.tl.filter.withSensitive && noteToCheck.files?.some((v) => v.isSensitive)) return 'sensitiveMute'; return false; } diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 8a5076ea1d..9ea654213e 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -49,6 +49,7 @@ const emit = defineEmits<{ (ev: 'queue', count: number): void; }>(); +provide('inTimeline', true); provide('inChannel', computed(() => props.src === 'channel')); type TimelineQueryType = { diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 666a9968b2..3481113f87 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -66,17 +66,51 @@ const rootEl = shallowRef<HTMLElement>(); const queue = ref(0); const srcWhenNotSignin = ref(isLocalTimelineAvailable ? 'local' : 'global'); -const src = computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin.value), set: (x) => saveSrc(x) }); -const withRenotes = ref(true); -const withReplies = ref($i ? defaultStore.state.tlWithReplies : false); -const onlyFiles = ref(false); +const src = computed({ + get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin.value), + set: (x) => saveSrc(x), +}); +const withRenotes = computed({ + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + get: () => (defaultStore.reactiveState.tl.value.filter?.withRenotes ?? saveTlFilter('withRenotes', true)), + set: (x) => saveTlFilter('withRenotes', x), +}); +const withReplies = computed({ + get: () => { + if (!$i) return false; + if (['local', 'social'].includes(src.value) && onlyFiles.value) { + return false; + } else { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return defaultStore.reactiveState.tl.value.filter?.withReplies ?? saveTlFilter('withReplies', true); + } + }, + set: (x) => saveTlFilter('withReplies', x), +}); +const onlyFiles = computed({ + get: () => { + if (['local', 'social'].includes(src.value) && withReplies.value) { + return false; + } else { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return defaultStore.reactiveState.tl.value.filter?.onlyFiles ?? saveTlFilter('onlyFiles', false); + } + }, + set: (x) => saveTlFilter('onlyFiles', x), +}); +const withSensitive = computed({ + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + get: () => (defaultStore.reactiveState.tl.value.filter?.withSensitive ?? saveTlFilter('withSensitive', true)), + set: (x) => { + saveTlFilter('withSensitive', x); -watch(src, () => { - queue.value = 0; + // これだけはクライアント側で完結する処理なので手動でリロード + tlComponent.value?.reloadTimeline(); + }, }); -watch(withReplies, (x) => { - if ($i) defaultStore.set('tlWithReplies', x); +watch(src, () => { + queue.value = 0; }); function queueUpdated(q: number): void { @@ -154,18 +188,38 @@ async function chooseChannel(ev: MouseEvent): Promise<void> { } function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global' | `list:${string}`): void { - let userList = null; + const out = { + ...defaultStore.state.tl, + src: newSrc, + }; + if (newSrc.startsWith('userList:')) { const id = newSrc.substring('userList:'.length); - userList = defaultStore.reactiveState.pinnedUserLists.value.find(l => l.id === id); + out.userList = defaultStore.reactiveState.pinnedUserLists.value.find(l => l.id === id) ?? null; } - defaultStore.set('tl', { - src: newSrc, - userList, - }); + + defaultStore.set('tl', out); srcWhenNotSignin.value = newSrc; } +function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue: boolean) { + if (key !== 'withReplies' || $i) { + const out = { ...defaultStore.state.tl }; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!out.filter) { + out.filter = { + withRenotes: true, + withReplies: true, + withSensitive: true, + onlyFiles: false, + }; + } + out.filter[key] = newValue; + defaultStore.set('tl', out); + } + return newValue; +} + async function timetravel(): Promise<void> { const { canceled, result: date } = await os.inputDate({ title: i18n.ts.date, @@ -203,6 +257,10 @@ const headerActions = computed(() => { disabled: onlyFiles, } : undefined, { type: 'switch', + text: i18n.ts.withSensitive, + ref: withSensitive, + }, { + type: 'switch', text: i18n.ts.fileAttachedOnly, ref: onlyFiles, disabled: src.value === 'local' || src.value === 'social' ? withReplies : false, @@ -215,8 +273,7 @@ const headerActions = computed(() => { icon: 'ti ti-refresh', text: i18n.ts.reload, handler: (ev: Event) => { - console.log('called'); - tlComponent.value.reloadTimeline(); + tlComponent.value?.reloadTimeline(); }, }); } diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 21b796caa1..5635e99650 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -184,6 +184,12 @@ export const defaultStore = markRaw(new Storage('base', { default: { src: 'home' as 'home' | 'local' | 'social' | 'global' | `list:${string}`, userList: null as Misskey.entities.UserList | null, + filter: { + withReplies: true, + withRenotes: true, + withSensitive: true, + onlyFiles: false, + }, }, }, pinnedUserLists: { @@ -391,10 +397,6 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: false, }, - tlWithReplies: { - where: 'device', - default: false, - }, defaultWithReplies: { where: 'account', default: false, |