summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2024-02-16 15:39:48 +0900
committerGitHub <noreply@github.com>2024-02-16 15:39:48 +0900
commitf5459a25df1694b5d13ae0b966e7e1489bc51cc0 (patch)
tree3c48a4c53d89359fb7db86d77b6186b34b2cee1c /packages/frontend/src
parentNew Crowdin updates (#13267) (diff)
downloadmisskey-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.vue74
-rw-r--r--packages/frontend/src/scripts/merge.ts10
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');
}