diff options
| author | かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> | 2024-02-16 15:39:48 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-16 15:39:48 +0900 |
| commit | f5459a25df1694b5d13ae0b966e7e1489bc51cc0 (patch) | |
| tree | 3c48a4c53d89359fb7db86d77b6186b34b2cee1c /packages/frontend/src | |
| parent | New Crowdin updates (#13267) (diff) | |
| download | misskey-f5459a25df1694b5d13ae0b966e7e1489bc51cc0.tar.gz misskey-f5459a25df1694b5d13ae0b966e7e1489bc51cc0.tar.bz2 misskey-f5459a25df1694b5d13ae0b966e7e1489bc51cc0.zip | |
fix(frontend): vue v3.4.16以降でタイムラインが正常に表示できない問題を修正 (#13248)
* fix(frontend): vue v3.4.16でタイムラインが正常に表示できない問題を修正
* type
* Revert "fix: downgrade vue to 3.4.15"
This reverts commit e12369ac13a906321397dfee5142f2af4b12f5b6.
* Update pnpm-lock.yaml
---------
Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
Diffstat (limited to 'packages/frontend/src')
| -rw-r--r-- | packages/frontend/src/pages/timeline.vue | 74 | ||||
| -rw-r--r-- | packages/frontend/src/scripts/merge.ts | 10 |
2 files changed, 46 insertions, 38 deletions
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index ffe513cf50..efe86be2f3 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template> <MkSpacer :contentMax="800"> <MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin"> - <div :key="src + withRenotes + withReplies + onlyFiles" ref="rootEl" v-hotkey.global="keymap"> + <div :key="src" ref="rootEl" v-hotkey.global="keymap"> <MkInfo v-if="['home', 'local', 'social', 'global'].includes(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()"> {{ i18n.ts._timelineDescription[src] }} </MkInfo> @@ -50,6 +50,7 @@ import { $i } from '@/account.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { antennasCache, userListsCache } from '@/cache.js'; import { deviceKind } from '@/scripts/device-kind.js'; +import { deepMerge } from '@/scripts/merge.js'; import { MenuItem } from '@/types/menu.js'; import { miLocalStorage } from '@/local-storage.js'; @@ -65,50 +66,65 @@ const tlComponent = shallowRef<InstanceType<typeof MkTimeline>>(); const rootEl = shallowRef<HTMLElement>(); const queue = ref(0); -const srcWhenNotSignin = ref(isLocalTimelineAvailable ? 'local' : 'global'); -const src = computed({ +const srcWhenNotSignin = ref<'local' | 'global'>(isLocalTimelineAvailable ? 'local' : 'global'); +const src = computed<'home' | 'local' | 'social' | 'global' | `list:${string}`>({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin.value), set: (x) => saveSrc(x), }); -const withRenotes = computed({ +const withRenotes = computed<boolean>({ get: () => defaultStore.reactiveState.tl.value.filter.withRenotes, - set: (x: boolean) => saveTlFilter('withRenotes', x), + set: (x) => saveTlFilter('withRenotes', x), }); -const withReplies = computed({ + +// computed内での無限ループを防ぐためのフラグ +const localSocialTLFilterSwitchStore = ref<'withReplies' | 'onlyFiles' | false>('withReplies'); + +const withReplies = computed<boolean>({ get: () => { if (!$i) return false; - if (['local', 'social'].includes(src.value) && onlyFiles.value) { + if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'onlyFiles') { return false; } else { return defaultStore.reactiveState.tl.value.filter.withReplies; } }, - set: (x: boolean) => saveTlFilter('withReplies', x), + set: (x) => saveTlFilter('withReplies', x), }); -const onlyFiles = computed({ +const onlyFiles = computed<boolean>({ get: () => { - if (['local', 'social'].includes(src.value) && withReplies.value) { + if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'withReplies') { return false; } else { return defaultStore.reactiveState.tl.value.filter.onlyFiles; } }, - set: (x: boolean) => saveTlFilter('onlyFiles', x), + set: (x) => saveTlFilter('onlyFiles', x), }); -const withSensitive = computed({ - get: () => defaultStore.reactiveState.tl.value.filter.withSensitive, - set: (x: boolean) => { - saveTlFilter('withSensitive', x); - // これだけはクライアント側で完結する処理なので手動でリロード - tlComponent.value?.reloadTimeline(); - }, +watch([withReplies, onlyFiles], ([withRepliesTo, onlyFilesTo]) => { + if (withRepliesTo) { + localSocialTLFilterSwitchStore.value = 'withReplies'; + } else if (onlyFilesTo) { + localSocialTLFilterSwitchStore.value = 'onlyFiles'; + } else { + localSocialTLFilterSwitchStore.value = false; + } +}); + +const withSensitive = computed<boolean>({ + get: () => defaultStore.reactiveState.tl.value.filter.withSensitive, + set: (x) => saveTlFilter('withSensitive', x), }); watch(src, () => { queue.value = 0; }); +watch(withSensitive, () => { + // これだけはクライアント側で完結する処理なので手動でリロード + tlComponent.value?.reloadTimeline(); +}); + function queueUpdated(q: number): void { queue.value = q; } @@ -184,10 +200,7 @@ async function chooseChannel(ev: MouseEvent): Promise<void> { } function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global' | `list:${string}`): void { - const out = { - ...defaultStore.state.tl, - src: newSrc, - }; + const out = deepMerge({ src: newSrc }, defaultStore.state.tl); if (newSrc.startsWith('userList:')) { const id = newSrc.substring('userList:'.length); @@ -195,25 +208,16 @@ function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global' | `list:${string } defaultStore.set('tl', out); - srcWhenNotSignin.value = newSrc; + if (['local', 'global'].includes(newSrc)) { + srcWhenNotSignin.value = newSrc as 'local' | 'global'; + } } 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; + const out = deepMerge({ filter: { [key]: newValue } }, defaultStore.state.tl); defaultStore.set('tl', out); } - return newValue; } async function timetravel(): Promise<void> { diff --git a/packages/frontend/src/scripts/merge.ts b/packages/frontend/src/scripts/merge.ts index 3dc13aca49..4e39a0fa06 100644 --- a/packages/frontend/src/scripts/merge.ts +++ b/packages/frontend/src/scripts/merge.ts @@ -6,6 +6,10 @@ import { deepClone } from './clone.js'; import type { Cloneable } from './clone.js'; +type DeepPartial<T> = { + [P in keyof T]?: T[P] extends Record<string | number | symbol, unknown> ? DeepPartial<T[P]> : T[P]; +}; + function isPureObject(value: unknown): value is Record<string | number | symbol, unknown> { return typeof value === 'object' && value !== null && !Array.isArray(value); } @@ -14,18 +18,18 @@ function isPureObject(value: unknown): value is Record<string | number | symbol, * valueにないキーをdefからもらう(再帰的)\ * nullはそのまま、undefinedはdefの値 **/ -export function deepMerge<X extends Record<string | number | symbol, unknown>>(value: X, def: X): X { +export function deepMerge<X extends Record<string | number | symbol, unknown>>(value: DeepPartial<X>, def: X): X { if (isPureObject(value) && isPureObject(def)) { const result = deepClone(value as Cloneable) as X; for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) { if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) { result[k] = v; } else if (isPureObject(v) && isPureObject(result[k])) { - const child = deepClone(result[k] as Cloneable) as X[keyof X] & Record<string | number | symbol, unknown>; + const child = deepClone(result[k] as Cloneable) as DeepPartial<X[keyof X] & Record<string | number | symbol, unknown>>; result[k] = deepMerge<typeof v>(child, v); } } return result; } - return value; + throw new Error('deepMerge: value and def must be pure objects'); } |