enhance: embedページではstoreの保存先を完全に分離するように
This commit is contained in:
		| @@ -751,7 +751,7 @@ export class ClientServerService { | ||||
| 		}); | ||||
| 		//#endregion | ||||
|  | ||||
| 		//region noindex pages | ||||
| 		//#region noindex pages | ||||
| 		// Tags | ||||
| 		fastify.get<{ Params: { clip: string; } }>('/tags/:tag', async (request, reply) => { | ||||
| 			return await renderBase(reply, { noindex: true }); | ||||
| @@ -761,7 +761,13 @@ export class ClientServerService { | ||||
| 		fastify.get<{ Params: { clip: string; } }>('/user-tags/:tag', async (request, reply) => { | ||||
| 			return await renderBase(reply, { noindex: true }); | ||||
| 		}); | ||||
| 		//endregion | ||||
| 		//#endregion | ||||
|  | ||||
| 		//#region embed pages | ||||
| 		fastify.get('/embed/:path(.*)', async (request, reply) => { | ||||
| 			reply.removeHeader('X-Frame-Options'); | ||||
| 			return await renderBase(reply, { noindex: true }); | ||||
| 		}); | ||||
|  | ||||
| 		fastify.get('/_info_card_', async (request, reply) => { | ||||
| 			const meta = await this.metaService.fetch(true); | ||||
| @@ -776,6 +782,7 @@ export class ClientServerService { | ||||
| 				originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }), | ||||
| 			}); | ||||
| 		}); | ||||
| 		//#endregion | ||||
|  | ||||
| 		fastify.get('/bios', async (request, reply) => { | ||||
| 			return await reply.view('bios', { | ||||
|   | ||||
| @@ -9,10 +9,19 @@ import 'vite/modulepreload-polyfill'; | ||||
| import '@/style.scss'; | ||||
| import { mainBoot } from '@/boot/main-boot.js'; | ||||
| import { subBoot } from '@/boot/sub-boot.js'; | ||||
| import { isEmbedPage } from '@/scripts/embed-page.js'; | ||||
|  | ||||
| const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete']; | ||||
| const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete', '/embed']; | ||||
|  | ||||
| if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) { | ||||
| 	if (isEmbedPage()) { | ||||
| 		const params = new URLSearchParams(location.search); | ||||
| 		const color = params.get('color'); | ||||
| 		if (color && ['light', 'dark'].includes(color)) { | ||||
| 			subBoot({ forceColorMode: color as 'light' | 'dark' }); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	subBoot(); | ||||
| } else { | ||||
| 	mainBoot(); | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import { apiUrl } from '@/config.js'; | ||||
| import { waiting, popup, popupMenu, success, alert } from '@/os.js'; | ||||
| import { misskeyApi } from '@/scripts/misskey-api.js'; | ||||
| import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js'; | ||||
| import { isEmbedPage } from '@/scripts/embed-page.js'; | ||||
|  | ||||
| // TODO: 他のタブと永続化されたstateを同期 | ||||
|  | ||||
| @@ -21,8 +22,14 @@ type Account = Misskey.entities.MeDetailed & { token: string }; | ||||
|  | ||||
| const accountData = miLocalStorage.getItem('account'); | ||||
|  | ||||
| function initAccount() { | ||||
| 	if (isEmbedPage()) return null; | ||||
| 	if (accountData) return reactive(JSON.parse(accountData) as Account); | ||||
| 	return null; | ||||
| } | ||||
|  | ||||
| // TODO: 外部からはreadonlyに | ||||
| export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null; | ||||
| export const $i = initAccount(); | ||||
|  | ||||
| export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true); | ||||
| export const iAmAdmin = $i != null && $i.isAdmin; | ||||
| @@ -78,10 +85,14 @@ export async function signout() { | ||||
| } | ||||
|  | ||||
| export async function getAccounts(): Promise<{ id: Account['id'], token: Account['token'] }[]> { | ||||
| 	if (isEmbedPage()) return []; | ||||
|  | ||||
| 	return (await get('accounts')) || []; | ||||
| } | ||||
|  | ||||
| export async function addAccount(id: Account['id'], token: Account['token']) { | ||||
| 	if (isEmbedPage()) return; | ||||
|  | ||||
| 	const accounts = await getAccounts(); | ||||
| 	if (!accounts.some(x => x.id === id)) { | ||||
| 		await set('accounts', accounts.concat([{ id, token }])); | ||||
| @@ -183,6 +194,8 @@ export async function refreshAccount() { | ||||
| } | ||||
|  | ||||
| export async function login(token: Account['token'], redirect?: string) { | ||||
| 	if (isEmbedPage()) return; | ||||
|  | ||||
| 	const showing = ref(true); | ||||
| 	popup(defineAsyncComponent(() => import('@/components/MkWaitingDialog.vue')), { | ||||
| 		success: false, | ||||
|   | ||||
| @@ -24,7 +24,17 @@ import { miLocalStorage } from '@/local-storage.js'; | ||||
| import { fetchCustomEmojis } from '@/custom-emojis.js'; | ||||
| import { setupRouter } from '@/router/definition.js'; | ||||
|  | ||||
| export async function common(createVue: () => App<Element>) { | ||||
| export type CommonBootOptions = { | ||||
| 	forceColorMode?: 'dark' | 'light' | 'auto'; | ||||
| }; | ||||
|  | ||||
| const defaultCommonBootOptions: CommonBootOptions = { | ||||
| 	forceColorMode: 'auto', | ||||
| }; | ||||
|  | ||||
| export async function common(createVue: () => App<Element>, partialOptions?: Partial<CommonBootOptions>) { | ||||
| 	const bootOptions = Object.assign(defaultCommonBootOptions, partialOptions); | ||||
|  | ||||
| 	console.info(`Misskey v${version}`); | ||||
|  | ||||
| 	if (_DEV_) { | ||||
| @@ -166,15 +176,19 @@ export async function common(createVue: () => App<Element>) { | ||||
| 	}); | ||||
|  | ||||
| 	//#region Sync dark mode | ||||
| 	if (ColdDeviceStorage.get('syncDeviceDarkMode')) { | ||||
| 	if (ColdDeviceStorage.get('syncDeviceDarkMode') && bootOptions.forceColorMode === 'auto') { | ||||
| 		defaultStore.set('darkMode', isDeviceDarkmode()); | ||||
| 	} | ||||
|  | ||||
| 	window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => { | ||||
| 		if (ColdDeviceStorage.get('syncDeviceDarkMode')) { | ||||
| 		if (ColdDeviceStorage.get('syncDeviceDarkMode') && bootOptions.forceColorMode === 'auto') { | ||||
| 			defaultStore.set('darkMode', mql.matches); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	if (bootOptions.forceColorMode !== 'auto') { | ||||
| 		defaultStore.set('darkMode', bootOptions.forceColorMode === 'dark'); | ||||
| 	} | ||||
| 	//#endregion | ||||
|  | ||||
| 	fetchInstanceMetaPromise.then(() => { | ||||
|   | ||||
| @@ -5,9 +5,10 @@ | ||||
|  | ||||
| import { createApp, defineAsyncComponent } from 'vue'; | ||||
| import { common } from './common.js'; | ||||
| import type { CommonBootOptions } from './common.js'; | ||||
|  | ||||
| export async function subBoot() { | ||||
| export async function subBoot(options?: CommonBootOptions) { | ||||
| 	const { isClientUpdated } = await common(() => createApp( | ||||
| 		defineAsyncComponent(() => import('@/ui/minimum.vue')), | ||||
| 	)); | ||||
| 	), options); | ||||
| } | ||||
|   | ||||
| @@ -2,8 +2,9 @@ | ||||
|  * SPDX-FileCopyrightText: syuilo and misskey-project | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| import { isEmbedPage, initEmbedPageLocalStorage } from "@/scripts/embed-page.js"; | ||||
|  | ||||
| type Keys = | ||||
| export type Keys = | ||||
| 	'v' | | ||||
| 	'lastVersion' | | ||||
| 	'instance' | | ||||
| @@ -38,12 +39,33 @@ type Keys = | ||||
| 	`aiscript:${string}` | | ||||
| 	'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~) | ||||
| 	'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~); | ||||
| 	`channelLastReadedAt:${string}` | ||||
| 	`channelLastReadedAt:${string}` | | ||||
| 	`idbfallback::${string}` | ||||
|  | ||||
| // セッション毎に廃棄されるLocalStorage代替(embedなどで使用) | ||||
| const safeSessionStorage = new Map<Keys, string>(); | ||||
|  | ||||
| export const miLocalStorage = { | ||||
| 	getItem: (key: Keys): string | null => window.localStorage.getItem(key), | ||||
| 	setItem: (key: Keys, value: string): void => window.localStorage.setItem(key, value), | ||||
| 	removeItem: (key: Keys): void => window.localStorage.removeItem(key), | ||||
| 	getItem: (key: Keys): string | null => { | ||||
| 		if (isEmbedPage()) { | ||||
| 			return safeSessionStorage.get(key) ?? null; | ||||
| 		} | ||||
| 		return window.localStorage.getItem(key); | ||||
| 	}, | ||||
| 	setItem: (key: Keys, value: string): void => { | ||||
| 		if (isEmbedPage()) { | ||||
| 			safeSessionStorage.set(key, value); | ||||
| 		} else { | ||||
| 			window.localStorage.setItem(key, value); | ||||
| 		} | ||||
| 	}, | ||||
| 	removeItem: (key: Keys): void => { | ||||
| 		if (isEmbedPage()) { | ||||
| 			safeSessionStorage.delete(key); | ||||
| 		} else { | ||||
| 			window.localStorage.removeItem(key); | ||||
| 		} | ||||
| 	}, | ||||
| 	getItemAsJson: (key: Keys): any | undefined => { | ||||
| 		const item = miLocalStorage.getItem(key); | ||||
| 		if (item === null) { | ||||
| @@ -51,5 +73,12 @@ export const miLocalStorage = { | ||||
| 		} | ||||
| 		return JSON.parse(item); | ||||
| 	}, | ||||
| 	setItemAsJson: (key: Keys, value: any): void => window.localStorage.setItem(key, JSON.stringify(value)), | ||||
| 	setItemAsJson: (key: Keys, value: any): void => { | ||||
| 		miLocalStorage.setItem(key, JSON.stringify(value)); | ||||
| 	}, | ||||
| }; | ||||
|  | ||||
| if (isEmbedPage()) { | ||||
| 	initEmbedPageLocalStorage(); | ||||
| 	if (_DEV_) console.warn('Using safeSessionStorage as localStorage alternative'); | ||||
| } | ||||
|   | ||||
| @@ -24,6 +24,7 @@ import MkContextMenu from '@/components/MkContextMenu.vue'; | ||||
| import { MenuItem } from '@/types/menu.js'; | ||||
| import copyToClipboard from '@/scripts/copy-to-clipboard.js'; | ||||
| import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; | ||||
| import { isEmbedPage } from '@/scripts/embed-page.js'; | ||||
|  | ||||
| export const openingWindowsCount = ref(0); | ||||
|  | ||||
| @@ -172,6 +173,8 @@ export async function popup<T extends Component>( | ||||
| 	events: ComponentEmit<T> = {} as ComponentEmit<T>, | ||||
| 	disposeEvent?: keyof ComponentEmit<T>, | ||||
| ): Promise<{ dispose: () => void }> { | ||||
| 	if (isEmbedPage()) return { dispose: () => {} }; | ||||
|  | ||||
| 	markRaw(component); | ||||
|  | ||||
| 	const id = ++popupIdCount; | ||||
|   | ||||
							
								
								
									
										13
									
								
								packages/frontend/src/pages/embed/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/frontend/src/pages/embed/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <template> | ||||
| 	<div> | ||||
| 		$i: {{ JSON.stringify($i) }} | ||||
| 	</div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { $i } from '@/account.js'; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|  | ||||
| </style> | ||||
| @@ -555,6 +555,10 @@ const routes: RouteDef[] = [{ | ||||
| 	path: '/reversi/g/:gameId', | ||||
| 	component: page(() => import('@/pages/reversi/game.vue')), | ||||
| 	loginRequired: false, | ||||
| }, { | ||||
| 	path: '/embed', | ||||
| 	component: page(() => import('@/pages/embed/index.vue')), | ||||
| //	children: [], | ||||
| }, { | ||||
| 	path: '/timeline', | ||||
| 	component: page(() => import('@/pages/timeline.vue')), | ||||
|   | ||||
							
								
								
									
										37
									
								
								packages/frontend/src/scripts/embed-page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								packages/frontend/src/scripts/embed-page.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: syuilo and misskey-project | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| import { miLocalStorage } from "@/local-storage.js"; | ||||
| import type { Keys } from "@/local-storage.js"; | ||||
|  | ||||
| export function isEmbedPage() { | ||||
| 	return location.pathname.startsWith('/embed'); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * EmbedページではlocalStorageを使用できないようにしているが、 | ||||
|  * 動作に必要な値はsafeSessionStorage(miLocalStorage内のやつ)に移動する | ||||
|  */ | ||||
| export function initEmbedPageLocalStorage() { | ||||
| 	if (!isEmbedPage()) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const keysToDuplicate: Keys[] = [ | ||||
| 		'v', | ||||
| 		'lastVersion', | ||||
| 		'instance', | ||||
| 		'instanceCachedAt', | ||||
| 		'lang', | ||||
| 		'locale', | ||||
| 		'localeVersion', | ||||
| 	]; | ||||
|  | ||||
| 	keysToDuplicate.forEach(key => { | ||||
| 		const value = window.localStorage.getItem(key); | ||||
| 		if (value && !miLocalStorage.getItem(key)) { | ||||
| 			miLocalStorage.setItem(key, value); | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
| @@ -10,10 +10,12 @@ import { | ||||
| 	set as iset, | ||||
| 	del as idel, | ||||
| } from 'idb-keyval'; | ||||
| import { isEmbedPage } from './embed-page.js'; | ||||
| import { miLocalStorage } from '@/local-storage.js'; | ||||
|  | ||||
| const fallbackName = (key: string) => `idbfallback::${key}`; | ||||
| const PREFIX = 'idbfallback::'; | ||||
|  | ||||
| let idbAvailable = typeof window !== 'undefined' ? !!(window.indexedDB && window.indexedDB.open) : true; | ||||
| let idbAvailable = typeof window !== 'undefined' ? !!(window.indexedDB && typeof window.indexedDB.open === 'function' && !isEmbedPage()) : true; | ||||
|  | ||||
| // iframe.contentWindow.indexedDB.deleteDatabase() がchromeのバグで使用できないため、indexedDBを無効化している。 | ||||
| // バグが治って再度有効化するのであれば、cypressのコマンド内のコメントアウトを外すこと | ||||
| @@ -38,15 +40,15 @@ if (idbAvailable) { | ||||
|  | ||||
| export async function get(key: string) { | ||||
| 	if (idbAvailable) return iget(key); | ||||
| 	return JSON.parse(window.localStorage.getItem(fallbackName(key))); | ||||
| 	return miLocalStorage.getItemAsJson(`${PREFIX}${key}`); | ||||
| } | ||||
|  | ||||
| export async function set(key: string, val: any) { | ||||
| 	if (idbAvailable) return iset(key, val); | ||||
| 	return window.localStorage.setItem(fallbackName(key), JSON.stringify(val)); | ||||
| 	return miLocalStorage.setItemAsJson(`${PREFIX}${key}`, val); | ||||
| } | ||||
|  | ||||
| export async function del(key: string) { | ||||
| 	if (idbAvailable) return idel(key); | ||||
| 	return window.localStorage.removeItem(fallbackName(key)); | ||||
| 	return miLocalStorage.removeItem(`${PREFIX}${key}`); | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  | ||||
| export const postMessageEventTypes = [ | ||||
| 	'misskey:shareForm:shareCompleted', | ||||
| 	'misskey:embed:changeHeight', | ||||
| ] as const; | ||||
|  | ||||
| export type PostMessageEventType = typeof postMessageEventTypes[number]; | ||||
| @@ -18,7 +19,7 @@ export type MiPostMessageEvent = { | ||||
|  * 親フレームにイベントを送信 | ||||
|  */ | ||||
| export function postMessageToParentWindow(type: PostMessageEventType, payload?: any): void { | ||||
| 	window.postMessage({ | ||||
| 	window.parent.postMessage({ | ||||
| 		type, | ||||
| 		payload, | ||||
| 	}, '*'); | ||||
|   | ||||
| @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| --> | ||||
|  | ||||
| <template> | ||||
| <div :class="$style.root"> | ||||
| <div :class="isEmbed ? $style.rootForEmbedPage : $style.root"> | ||||
| 	<div style="container-type: inline-size;"> | ||||
| 		<RouterView/> | ||||
| 	</div> | ||||
| @@ -20,6 +20,12 @@ import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from ' | ||||
| import { instanceName } from '@/config.js'; | ||||
| import { mainRouter } from '@/router/main.js'; | ||||
|  | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	isEmbed?: boolean; | ||||
| }>(), { | ||||
| 	isEmbed: false, | ||||
| }); | ||||
|  | ||||
| const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index'); | ||||
|  | ||||
| const pageMetadata = ref<null | PageMetadata>(null); | ||||
| @@ -38,7 +44,9 @@ provideMetadataReceiver((metadataGetter) => { | ||||
| }); | ||||
| provideReactiveMetadata(pageMetadata); | ||||
|  | ||||
| document.documentElement.style.overflowY = 'scroll'; | ||||
| if (!props.isEmbed) { | ||||
| 	document.documentElement.style.overflowY = 'scroll'; | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" module> | ||||
| @@ -46,4 +54,8 @@ document.documentElement.style.overflowY = 'scroll'; | ||||
| 	min-height: 100dvh; | ||||
| 	box-sizing: border-box; | ||||
| } | ||||
|  | ||||
| .rootForEmbedPage { | ||||
| 	box-sizing: border-box; | ||||
| } | ||||
| </style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 kakkokari-gtyih
					kakkokari-gtyih