feat: ノート・ユーザTL埋め込み
This commit is contained in:
		@@ -764,9 +764,9 @@ export class ClientServerService {
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		//#region embed pages
 | 
			
		||||
		fastify.get('/embed/:path(.*)', async (request, reply) => {
 | 
			
		||||
		fastify.get('/embed/*', async (request, reply) => {
 | 
			
		||||
			reply.removeHeader('X-Frame-Options');
 | 
			
		||||
			return await renderBase(reply, { noindex: true });
 | 
			
		||||
			return await renderBase(reply, { noindex: true, embed: true });
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		fastify.get('/_info_card_', async (request, reply) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,12 @@ html {
 | 
			
		||||
	color: var(--fg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html.embed {
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	background-color: transparent;
 | 
			
		||||
	max-width: 500px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#splash {
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	z-index: 10000;
 | 
			
		||||
@@ -22,6 +28,13 @@ html {
 | 
			
		||||
	transition: opacity 0.5s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html.embed #splash {
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	min-height: 300px;
 | 
			
		||||
	border-radius: var(--radius, 12px);
 | 
			
		||||
	border: 1px solid var(--divider);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#splashIcon {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -77,7 +77,7 @@ html
 | 
			
		||||
		script
 | 
			
		||||
			include ../boot.js
 | 
			
		||||
 | 
			
		||||
	body
 | 
			
		||||
	body(class=embed && 'embed')
 | 
			
		||||
		noscript: p
 | 
			
		||||
			| JavaScriptを有効にしてください
 | 
			
		||||
			br
 | 
			
		||||
 
 | 
			
		||||
@@ -7,21 +7,33 @@
 | 
			
		||||
import 'vite/modulepreload-polyfill';
 | 
			
		||||
 | 
			
		||||
import '@/style.scss';
 | 
			
		||||
import type { CommonBootOptions } from '@/boot/common.js';
 | 
			
		||||
import { mainBoot } from '@/boot/main-boot.js';
 | 
			
		||||
import { subBoot } from '@/boot/sub-boot.js';
 | 
			
		||||
import { isEmbedPage } from '@/scripts/embed-page.js';
 | 
			
		||||
import { setIframeId, postMessageToParentWindow } from '@/scripts/post-message.js';
 | 
			
		||||
 | 
			
		||||
const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete', '/embed'];
 | 
			
		||||
const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete'];
 | 
			
		||||
 | 
			
		||||
if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
 | 
			
		||||
if (isEmbedPage()) {
 | 
			
		||||
	const bootOptions: Partial<CommonBootOptions> = {};
 | 
			
		||||
 | 
			
		||||
	const params = new URLSearchParams(location.search);
 | 
			
		||||
	const color = params.get('color');
 | 
			
		||||
	if (color && ['light', 'dark'].includes(color)) {
 | 
			
		||||
			subBoot({ forceColorMode: color as 'light' | 'dark' });
 | 
			
		||||
		}
 | 
			
		||||
		bootOptions.forceColorMode = color as 'light' | 'dark';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	window.addEventListener('message', event => {
 | 
			
		||||
		if (event.data?.type === 'misskey:embedParent:registerIframeId' && event.data.payload?.iframeId != null) {
 | 
			
		||||
			setIframeId(event.data.payload.iframeId);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	subBoot(bootOptions, true).then(() => {
 | 
			
		||||
		postMessageToParentWindow('misskey:embed:ready');
 | 
			
		||||
	});
 | 
			
		||||
} else if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
 | 
			
		||||
	subBoot();
 | 
			
		||||
} else {
 | 
			
		||||
	mainBoot();
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ import { fetchCustomEmojis } from '@/custom-emojis.js';
 | 
			
		||||
import { setupRouter } from '@/router/definition.js';
 | 
			
		||||
 | 
			
		||||
export type CommonBootOptions = {
 | 
			
		||||
	forceColorMode?: 'dark' | 'light' | 'auto';
 | 
			
		||||
	forceColorMode: 'dark' | 'light' | 'auto';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const defaultCommonBootOptions: CommonBootOptions = {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,8 @@ import { createApp, defineAsyncComponent } from 'vue';
 | 
			
		||||
import { common } from './common.js';
 | 
			
		||||
import type { CommonBootOptions } from './common.js';
 | 
			
		||||
 | 
			
		||||
export async function subBoot(options?: CommonBootOptions) {
 | 
			
		||||
export async function subBoot(options?: Partial<CommonBootOptions>, isEmbedPage?: boolean) {
 | 
			
		||||
	const { isClientUpdated } = await common(() => createApp(
 | 
			
		||||
		defineAsyncComponent(() => import('@/ui/minimum.vue')),
 | 
			
		||||
		defineAsyncComponent(() => isEmbedPage ? import('@/ui/embed.vue') : import('@/ui/minimum.vue')),
 | 
			
		||||
	), options);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div></div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										33
									
								
								packages/frontend/src/pages/embed/note.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								packages/frontend/src/pages/embed/note.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div :class="$style.noteEmbedRoot">
 | 
			
		||||
		<MkLoading v-if="loading"/>
 | 
			
		||||
		<MkNote v-else-if="note" :note="note"/>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import * as Misskey from 'misskey-js';
 | 
			
		||||
import MkNote from '@/components/MkNote.vue';
 | 
			
		||||
import { misskeyApi } from '@/scripts/misskey-api.js';
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
	noteId: string;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const note = ref<Misskey.entities.Note | null>(null);
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
 | 
			
		||||
misskeyApi('notes/show', {
 | 
			
		||||
	noteId: props.noteId,
 | 
			
		||||
}).then(res => {
 | 
			
		||||
	note.value = res;
 | 
			
		||||
	loading.value = false;
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.noteEmbedRoot {
 | 
			
		||||
	background-color: var(--panel);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										57
									
								
								packages/frontend/src/pages/embed/user-timeline.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								packages/frontend/src/pages/embed/user-timeline.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div :class="$style.userTimelineRoot">
 | 
			
		||||
		<MkLoading v-if="loading"/>
 | 
			
		||||
		<template v-else-if="user">
 | 
			
		||||
			<div v-if="normalizedShowHeader" :class="$style.userHeader">
 | 
			
		||||
				<MkAvatar :user="user"/>{{ user.name }} のノート
 | 
			
		||||
			</div>
 | 
			
		||||
			<MkNotes :class="$style.userTimelineNotes" :pagination="pagination" :noGap="true"/>
 | 
			
		||||
		</template>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, computed } from 'vue';
 | 
			
		||||
import * as Misskey from 'misskey-js';
 | 
			
		||||
import MkNotes from '@/components/MkNotes.vue';
 | 
			
		||||
import type { Paging } from '@/components/MkPagination.vue';
 | 
			
		||||
import { misskeyApi } from '@/scripts/misskey-api.js';
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
	username: string;
 | 
			
		||||
	showHeader?: string;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const normalizedShowHeader = computed(() => props.showHeader !== 'false');
 | 
			
		||||
 | 
			
		||||
const user = ref<Misskey.entities.UserLite | null>(null);
 | 
			
		||||
const pagination = computed(() => ({
 | 
			
		||||
	endpoint: 'users/notes',
 | 
			
		||||
	params: {
 | 
			
		||||
		userId: user.value?.id,
 | 
			
		||||
	},
 | 
			
		||||
} as Paging));
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
 | 
			
		||||
misskeyApi('users/show', {
 | 
			
		||||
	username: props.username,
 | 
			
		||||
}).then(res => {
 | 
			
		||||
	user.value = res;
 | 
			
		||||
	loading.value = false;
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.userTimelineRoot {
 | 
			
		||||
	background-color: var(--panel);
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	max-height: var(--embedMaxHeight, none);
 | 
			
		||||
	display: flex;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.userTimelineNotes {
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -556,9 +556,14 @@ const routes: RouteDef[] = [{
 | 
			
		||||
	component: page(() => import('@/pages/reversi/game.vue')),
 | 
			
		||||
	loginRequired: false,
 | 
			
		||||
}, {
 | 
			
		||||
	path: '/embed',
 | 
			
		||||
	component: page(() => import('@/pages/embed/index.vue')),
 | 
			
		||||
//	children: [],
 | 
			
		||||
	path: '/embed/notes/:noteId',
 | 
			
		||||
	component: page(() => import('@/pages/embed/note.vue')),
 | 
			
		||||
}, {
 | 
			
		||||
	path: '/embed/user-timeline/@:username',
 | 
			
		||||
	component: page(() => import('@/pages/embed/user-timeline.vue')),
 | 
			
		||||
	query: {
 | 
			
		||||
		header: 'showHeader',
 | 
			
		||||
	}
 | 
			
		||||
}, {
 | 
			
		||||
	path: '/timeline',
 | 
			
		||||
	component: page(() => import('@/pages/timeline.vue')),
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
 | 
			
		||||
export const postMessageEventTypes = [
 | 
			
		||||
	'misskey:shareForm:shareCompleted',
 | 
			
		||||
	'misskey:embed:ready',
 | 
			
		||||
	'misskey:embed:changeHeight',
 | 
			
		||||
] as const;
 | 
			
		||||
 | 
			
		||||
@@ -12,16 +13,29 @@ export type PostMessageEventType = typeof postMessageEventTypes[number];
 | 
			
		||||
 | 
			
		||||
export type MiPostMessageEvent = {
 | 
			
		||||
	type: PostMessageEventType;
 | 
			
		||||
	iframeId?: string;
 | 
			
		||||
	payload?: any;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let defaultIframeId: string | null = null;
 | 
			
		||||
 | 
			
		||||
export function setIframeId(id: string): void {
 | 
			
		||||
	if (_DEV_) console.log('setIframeId', id);
 | 
			
		||||
	defaultIframeId = id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 親フレームにイベントを送信
 | 
			
		||||
 */
 | 
			
		||||
export function postMessageToParentWindow(type: PostMessageEventType, payload?: any): void {
 | 
			
		||||
	if (_DEV_) console.log('postMessageToParentWindow', type, payload);
 | 
			
		||||
export function postMessageToParentWindow(type: PostMessageEventType, payload?: any, iframeId: string | null = null): void {
 | 
			
		||||
	let _iframeId = iframeId;
 | 
			
		||||
	if (_iframeId == null) {
 | 
			
		||||
		_iframeId = defaultIframeId;
 | 
			
		||||
	}
 | 
			
		||||
	if (_DEV_) console.log('postMessageToParentWindow', type, _iframeId, payload);
 | 
			
		||||
	window.parent.postMessage({
 | 
			
		||||
		type,
 | 
			
		||||
		iframeId: _iframeId,
 | 
			
		||||
		payload,
 | 
			
		||||
	}, '*');
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -93,9 +93,16 @@ html {
 | 
			
		||||
 | 
			
		||||
	&.embed {
 | 
			
		||||
		background-color: transparent;
 | 
			
		||||
		overflow: hidden;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html.embed,
 | 
			
		||||
html.embed body,
 | 
			
		||||
html.embed #misskey_app {
 | 
			
		||||
	height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html._themeChanging_ {
 | 
			
		||||
	&, * {
 | 
			
		||||
		transition: background 1s ease, border 1s ease !important;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										113
									
								
								packages/frontend/src/ui/embed.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								packages/frontend/src/ui/embed.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
<!--
 | 
			
		||||
SPDX-FileCopyrightText: syuilo and misskey-project
 | 
			
		||||
SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div
 | 
			
		||||
	ref="rootEl"
 | 
			
		||||
	:class="[
 | 
			
		||||
		$style.rootForEmbedPage,
 | 
			
		||||
		{
 | 
			
		||||
			[$style.rounded]: embedRounded,
 | 
			
		||||
		}
 | 
			
		||||
	]"
 | 
			
		||||
	:style="maxHeight > 0 ? { maxHeight: `${maxHeight}px`, '--embedMaxHeight': `${maxHeight}px` } : {}"
 | 
			
		||||
>
 | 
			
		||||
	<div
 | 
			
		||||
		:class="$style.routerViewContainer"
 | 
			
		||||
	>
 | 
			
		||||
		<RouterView/>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<XCommon/>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, provide, ref, shallowRef, onMounted, onUnmounted } from 'vue';
 | 
			
		||||
import XCommon from './_common_/common.vue';
 | 
			
		||||
import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 | 
			
		||||
import { instanceName } from '@/config.js';
 | 
			
		||||
import { mainRouter } from '@/router/main.js';
 | 
			
		||||
import { postMessageToParentWindow } from '@/scripts/post-message';
 | 
			
		||||
 | 
			
		||||
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
 | 
			
		||||
 | 
			
		||||
const pageMetadata = ref<null | PageMetadata>(null);
 | 
			
		||||
 | 
			
		||||
provide('router', mainRouter);
 | 
			
		||||
provideMetadataReceiver((metadataGetter) => {
 | 
			
		||||
	const info = metadataGetter();
 | 
			
		||||
	pageMetadata.value = info;
 | 
			
		||||
	if (pageMetadata.value) {
 | 
			
		||||
		if (isRoot.value && pageMetadata.value.title === instanceName) {
 | 
			
		||||
			document.title = pageMetadata.value.title;
 | 
			
		||||
		} else {
 | 
			
		||||
			document.title = `${pageMetadata.value.title} | ${instanceName}`;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
provideReactiveMetadata(pageMetadata);
 | 
			
		||||
 | 
			
		||||
//#region Embed Style
 | 
			
		||||
const params = new URLSearchParams(location.search);
 | 
			
		||||
const embedRounded = ref(params.get('rounded') !== '0');
 | 
			
		||||
const maxHeight = ref(params.get('maxHeight') ? parseInt(params.get('maxHeight')!) : 0);
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
//#region Embed Resizer
 | 
			
		||||
const rootEl = shallowRef<HTMLElement | null>(null);
 | 
			
		||||
 | 
			
		||||
let resizeMessageThrottleTimer: number | null = null;
 | 
			
		||||
let resizeMessageThrottleFlag = false;
 | 
			
		||||
let previousHeight = 0;
 | 
			
		||||
const resizeObserver = new ResizeObserver(async () => {
 | 
			
		||||
	const height = rootEl.value!.scrollHeight + 2; // border 上下1px
 | 
			
		||||
	if (resizeMessageThrottleFlag && Math.abs(previousHeight - height) < 30) return;
 | 
			
		||||
	if (resizeMessageThrottleTimer) window.clearTimeout(resizeMessageThrottleTimer);
 | 
			
		||||
 | 
			
		||||
	postMessageToParentWindow('misskey:embed:changeHeight', {
 | 
			
		||||
		height: (maxHeight.value > 0 && height > maxHeight.value) ? maxHeight.value : height,
 | 
			
		||||
	});
 | 
			
		||||
	previousHeight = height;
 | 
			
		||||
 | 
			
		||||
	resizeMessageThrottleFlag = true;
 | 
			
		||||
 | 
			
		||||
	resizeMessageThrottleTimer = window.setTimeout(() => {
 | 
			
		||||
		resizeMessageThrottleFlag = false; // 収縮をやりすぎるとチカチカする
 | 
			
		||||
	}, 500);
 | 
			
		||||
});
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
	resizeObserver.observe(rootEl.value!);
 | 
			
		||||
});
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
	resizeObserver.disconnect();
 | 
			
		||||
});
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
document.documentElement.style.maxWidth = '500px';
 | 
			
		||||
 | 
			
		||||
// サーバー起動の場合はもとから付与されているためdevのみ
 | 
			
		||||
if (_DEV_) document.documentElement.classList.add('embed');
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.rootForEmbedPage {
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	border: 1px solid var(--divider);
 | 
			
		||||
	background-color: var(--bg);
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	height: auto;
 | 
			
		||||
 | 
			
		||||
	&.rounded {
 | 
			
		||||
		border-radius: var(--radius);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.routerViewContainer {
 | 
			
		||||
	container-type: inline-size;
 | 
			
		||||
	max-height: var(--embedMaxHeight, none);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -4,15 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div
 | 
			
		||||
	ref="rootEl"
 | 
			
		||||
	:class="isEmbed ? [
 | 
			
		||||
		$style.rootForEmbedPage,
 | 
			
		||||
		{
 | 
			
		||||
			[$style.rounded]: embedRounded,
 | 
			
		||||
		}
 | 
			
		||||
	] : [$style.root]"
 | 
			
		||||
>
 | 
			
		||||
<div :class="$style.root">
 | 
			
		||||
	<div style="container-type: inline-size;">
 | 
			
		||||
		<RouterView/>
 | 
			
		||||
	</div>
 | 
			
		||||
@@ -22,15 +14,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, provide, ref, shallowRef, onMounted, onUnmounted } from 'vue';
 | 
			
		||||
import { computed, provide, ref } from 'vue';
 | 
			
		||||
import XCommon from './_common_/common.vue';
 | 
			
		||||
import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 | 
			
		||||
import { instanceName } from '@/config.js';
 | 
			
		||||
import { mainRouter } from '@/router/main.js';
 | 
			
		||||
import { isEmbedPage } from '@/scripts/embed-page.js';
 | 
			
		||||
import { postMessageToParentWindow } from '@/scripts/post-message';
 | 
			
		||||
 | 
			
		||||
const isEmbed = isEmbedPage();
 | 
			
		||||
 | 
			
		||||
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
 | 
			
		||||
 | 
			
		||||
@@ -50,35 +38,7 @@ provideMetadataReceiver((metadataGetter) => {
 | 
			
		||||
});
 | 
			
		||||
provideReactiveMetadata(pageMetadata);
 | 
			
		||||
 | 
			
		||||
//#region Embed Style
 | 
			
		||||
const params = new URLSearchParams(location.search);
 | 
			
		||||
const embedRounded = ref(params.get('rounded') !== '0');
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
//#region Embed Resizer
 | 
			
		||||
const rootEl = shallowRef<HTMLElement | null>(null);
 | 
			
		||||
 | 
			
		||||
if (isEmbed) {
 | 
			
		||||
	const resizeObserver = new ResizeObserver(async () => {
 | 
			
		||||
		postMessageToParentWindow('misskey:embed:changeHeight', {
 | 
			
		||||
			height: rootEl.value!.scrollHeight + 2, // border 上下1px
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
	onMounted(() => {
 | 
			
		||||
		resizeObserver.observe(rootEl.value!);
 | 
			
		||||
	});
 | 
			
		||||
	onUnmounted(() => {
 | 
			
		||||
		resizeObserver.disconnect();
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
if (isEmbed) {
 | 
			
		||||
	document.documentElement.style.maxWidth = '500px';
 | 
			
		||||
	document.documentElement.classList.add('embed');
 | 
			
		||||
} else {
 | 
			
		||||
document.documentElement.style.overflowY = 'scroll';
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
@@ -86,16 +46,4 @@ if (isEmbed) {
 | 
			
		||||
	min-height: 100dvh;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.rootForEmbedPage {
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	border: 1px solid var(--divider);
 | 
			
		||||
	background-color: var(--bg);
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	position: relative;
 | 
			
		||||
 | 
			
		||||
	&.rounded {
 | 
			
		||||
		border-radius: var(--radius);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user