diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index ae4e0445db..dd1ec2ff9d 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -30,6 +30,7 @@ import { launchPlugins } from '@/plugin.js'; import { updateCurrentAccountPartial } from '@/accounts.js'; import { signout } from '@/signout.js'; import { migrateOldSettings } from '@/pref-migrate.js'; +import { miRegistoryItem } from '@/registry-item.js'; export async function mainBoot() { const { isClientUpdated, lastVersion } = await common(async () => { @@ -288,6 +289,8 @@ export async function mainBoot() { // } //} //miLocalStorage.setItem('lastUsed', Date.now().toString()); + const channelLastReadedAt = await miRegistoryItem.get('channelsLastReadedAt'); + miLocalStorage.setItemAsJson('channelsLastReadedAt', channelLastReadedAt); const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt'); const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo'); diff --git a/packages/frontend/src/components/MkChannelPreview.vue b/packages/frontend/src/components/MkChannelPreview.vue index f79989d882..c6f5d08299 100644 --- a/packages/frontend/src/components/MkChannelPreview.vue +++ b/packages/frontend/src/components/MkChannelPreview.vue @@ -56,7 +56,7 @@ const props = defineProps<{ }>(); const getLastReadedAt = (): number | null => { - return miLocalStorage.getItemAsJson(`channelLastReadedAt:${props.channel.id}`) ?? null; + return miLocalStorage.getItemAsJson('channelsLastReadedAt')[props.channel.id] ?? null; }; const lastReadedAt = ref(getLastReadedAt()); diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts index 78fba9f7b4..17b359479f 100644 --- a/packages/frontend/src/local-storage.ts +++ b/packages/frontend/src/local-storage.ts @@ -39,7 +39,8 @@ export type Keys = ( `aiscript:${string}` | 'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~) 'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~); - `channelLastReadedAt:${string}` | + `channelLastReadedAt:${string}` | // DEPRECATED, stored channelsLastReadedAt and registry + 'channelsLastReadedAt' | `idbfallback::${string}` ); diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 6eb390f743..000b8a3bcf 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -97,6 +97,7 @@ import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { notesSearchAvailable } from '@/utility/check-permissions.js'; import { miLocalStorage } from '@/local-storage.js'; import { useRouter } from '@/router.js'; +import { miRegistoryItem } from '@/registry-item'; const router = useRouter(); @@ -131,21 +132,35 @@ watch(() => props.channelId, async () => { channel.value = await misskeyApi('channels/show', { channelId: props.channelId, }); + if (!channel.value) return; favorited.value = channel.value.isFavorited ?? false; if (favorited.value || channel.value.isFollowing) { tab.value = 'timeline'; } if ((favorited.value || channel.value.isFollowing) && channel.value.lastNotedAt) { - const lastReadedAt: number = miLocalStorage.getItemAsJson(`channelLastReadedAt:${channel.value.id}`) ?? 0; + const lastReadedAt = miLocalStorage.getItemAsJson('channelsLastReadedAt')[channel.value.id] ?? undefined; const lastNotedAt = Date.parse(channel.value.lastNotedAt); + if (!lastReadedAt) { + saveLastReadedAt(); + return; + } + if (lastNotedAt > lastReadedAt) { - miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.value.id}`, lastNotedAt); + saveLastReadedAt(); } } }, { immediate: true }); +async function saveLastReadedAt() { + if (!channel.value) return; + const tmp = await miRegistoryItem.get('channelsLastReadedAt'); + tmp[channel.value.id] = Date.now(); + await miRegistoryItem.set('channelsLastReadedAt', tmp); + miLocalStorage.setItemAsJson('channelsLastReadedAt', tmp); +} + function edit() { router.push(`/channels/${channel.value?.id}/edit`); } diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index b783f7ee0b..aaeab902f6 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -148,7 +148,7 @@ async function chooseChannel(ev: MouseEvent): Promise { const channels = await favoritedChannelsCache.fetch(); const items: MenuItem[] = [ ...channels.map(channel => { - const lastReadedAt = miLocalStorage.getItemAsJson(`channelLastReadedAt:${channel.id}`) ?? null; + const lastReadedAt = miLocalStorage.getItemAsJson('channelsLastReadedAt')[channel.id] ?? null; const hasUnreadNote = (lastReadedAt && channel.lastNotedAt) ? Date.parse(channel.lastNotedAt) > lastReadedAt : !!(!lastReadedAt && channel.lastNotedAt); return { diff --git a/packages/frontend/src/registry-item.ts b/packages/frontend/src/registry-item.ts new file mode 100644 index 0000000000..9b1a8ff546 --- /dev/null +++ b/packages/frontend/src/registry-item.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { misskeyApi } from '@/utility/misskey-api.js'; + +export type Keys = + 'channelsLastReadedAt' | // DEPRECATED, stored registory(2025.1.xxx) + 'somethingElse'; + +export const miRegistoryItem = { + async get(key: Keys) { + try { + return JSON.parse(await misskeyApi('i/registry/get', { scope: ['client'], key })); + } catch (err) { + if (err.code === 'NO_SUCH_KEY') return {}; + throw err; + } + }, + async set(key: Keys, payload) { + await misskeyApi('i/registry/set', { scope: ['client'], key, value: JSON.stringify(payload) }); + }, +}; +