enhance: embedページではstoreの保存先を完全に分離するように
This commit is contained in:
parent
ecf7945fe8
commit
e9b3b5ffcd
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue