diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 063141273a..5de1f87667 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -785,6 +785,72 @@ export class ClientServerService {
//#endregion
//#region embed pages
+ fastify.get<{ Params: { user: string; } }>('/embed/user-timeline/:user', async (request, reply) => {
+ reply.removeHeader('X-Frame-Options');
+
+ const user = await this.usersRepository.findOneBy({
+ id: request.params.user,
+ });
+
+ if (user == null) return;
+ if (user.host != null) return;
+
+ const _user = await this.userEntityService.pack(user);
+
+ reply.header('Cache-Control', 'public, max-age=3600');
+ return await reply.view('base-embed', {
+ title: this.meta.name ?? 'Misskey',
+ ...await this.generateCommonPugData(this.meta),
+ embedCtx: htmlSafeJsonStringify({
+ user: _user,
+ }),
+ });
+ });
+
+ fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => {
+ reply.removeHeader('X-Frame-Options');
+
+ const note = await this.notesRepository.findOneBy({
+ id: request.params.note,
+ });
+
+ if (note == null) return;
+ if (note.visibility !== 'public') return;
+ if (note.userHost != null) return;
+
+ const _note = await this.noteEntityService.pack(note, null, { detail: true });
+
+ reply.header('Cache-Control', 'public, max-age=3600');
+ return await reply.view('base-embed', {
+ title: this.meta.name ?? 'Misskey',
+ ...await this.generateCommonPugData(this.meta),
+ embedCtx: htmlSafeJsonStringify({
+ note: _note,
+ }),
+ });
+ });
+
+ fastify.get<{ Params: { clip: string; } }>('/embed/clips/:clip', async (request, reply) => {
+ reply.removeHeader('X-Frame-Options');
+
+ const clip = await this.clipsRepository.findOneBy({
+ id: request.params.clip,
+ });
+
+ if (clip == null) return;
+
+ const _clip = await this.clipEntityService.pack(clip);
+
+ reply.header('Cache-Control', 'public, max-age=3600');
+ return await reply.view('base-embed', {
+ title: this.meta.name ?? 'Misskey',
+ ...await this.generateCommonPugData(this.meta),
+ embedCtx: htmlSafeJsonStringify({
+ clip: _clip,
+ }),
+ });
+ });
+
fastify.get('/embed/*', async (request, reply) => {
reply.removeHeader('X-Frame-Options');
diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug
index d773f2676a..2bab20a36c 100644
--- a/packages/backend/src/server/web/views/base-embed.pug
+++ b/packages/backend/src/server/web/views/base-embed.pug
@@ -43,6 +43,9 @@ html(class='embed')
script(type='application/json' id='misskey_meta' data-generated-at=now)
!= metaJson
+ script(type='application/json' id='misskey_embedCtx' data-generated-at=now)
+ != embedCtx
+
script
include ../boot.embed.js
diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts
index fcea7d32ea..00c7944eb3 100644
--- a/packages/frontend-embed/src/boot.ts
+++ b/packages/frontend-embed/src/boot.ts
@@ -20,16 +20,19 @@ import { serverMetadata } from '@/server-metadata.js';
import { url } from '@@/js/config.js';
import { parseEmbedParams } from '@@/js/embed-page.js';
import { postMessageToParentWindow, setIframeId } from '@/post-message.js';
+import { serverContext } from '@/server-context.js';
import type { Theme } from '@/theme.js';
console.log('Misskey Embed');
+//#region Embedパラメータの取得・パース
const params = new URLSearchParams(location.search);
const embedParams = parseEmbedParams(params);
-
if (_DEV_) console.log(embedParams);
+//#endregion
+//#region テーマ
function parseThemeOrNull(theme: string | null): Theme | null {
if (theme == null) return null;
try {
@@ -65,6 +68,7 @@ if (embedParams.colorMode === 'dark') {
}
});
}
+//#endregion
// サイズの制限
document.documentElement.style.maxWidth = '500px';
@@ -89,6 +93,10 @@ const app = createApp(
app.provide(DI.mediaProxy, new MediaProxy(serverMetadata, url));
+app.provide(DI.serverMetadata, serverMetadata);
+
+app.provide(DI.serverContext, serverContext);
+
app.provide(DI.embedParams, embedParams);
// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
diff --git a/packages/frontend-embed/src/components/EmNoteDetailed.vue b/packages/frontend-embed/src/components/EmNoteDetailed.vue
index 8169f500a9..a233011af7 100644
--- a/packages/frontend-embed/src/components/EmNoteDetailed.vue
+++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue
@@ -142,8 +142,8 @@ import EmAcct from '@/components/EmAcct.vue';
import { userPage } from '@/utils.js';
import { notePage } from '@/utils.js';
import { i18n } from '@/i18n.js';
+import { DI } from '@/di.js';
import { shouldCollapsed } from '@@/js/collapsed.js';
-import { serverMetadata } from '@/server-metadata.js';
import { url } from '@@/js/config.js';
import EmMfm from '@/components/EmMfm.js';
@@ -151,6 +151,8 @@ const props = defineProps<{
note: Misskey.entities.Note;
}>();
+const serverMetadata = inject(DI.serverMetadata)!;
+
const inChannel = inject('inChannel', null);
const note = ref(props.note);
diff --git a/packages/frontend-embed/src/components/EmNotes.vue b/packages/frontend-embed/src/components/EmNotes.vue
index 6370f4aeae..3418d97f77 100644
--- a/packages/frontend-embed/src/components/EmNotes.vue
+++ b/packages/frontend-embed/src/components/EmNotes.vue
@@ -20,12 +20,12 @@ SPDX-License-Identifier: AGPL-3.0-only