summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2024-09-09 20:57:36 +0900
committerGitHub <noreply@github.com>2024-09-09 20:57:36 +0900
commit2cbe1d1210a5745787f37069ecb59b8f6c03c224 (patch)
tree9acb1e675d2ae85f7f1f0f34f6acdc4965bff3f9 /packages/backend/src
parentrefactor(misskey-js): warnを除去 (#14520) (diff)
downloadsharkey-2cbe1d1210a5745787f37069ecb59b8f6c03c224.tar.gz
sharkey-2cbe1d1210a5745787f37069ecb59b8f6c03c224.tar.bz2
sharkey-2cbe1d1210a5745787f37069ecb59b8f6c03c224.zip
feat(frontend): ノート・ユーザータイムライン埋め込み (#13929)
* fix * navhookをbootに移動 * サーバーサイドのbootも分けるように * 埋め込みページかどうかの判定は最初の一回だけに * tooltipは出せるように * fix design * 埋め込み独自のtooltipを削除 * ロジックの分岐が多かったMkNoteDetailedを分離 * fix indent * プレビュー用iframeにフォーカスが当たるのを修正 * popupの制御を出す側で行うように * パラメータが逆になっていたのを修正 * Update MkEmbedCodeGenDialog.vue * fix * eliminate misskey-js lint warns * fix * add appropriate attributes to embed html * enhance: サーバーサイドのembed系をさらに分離 * enhance: embed routerを分離(route定義をboot時に変更できるようにする改修を含む) * type * lint * fix indent * server-side styleを完全に分離 * Revert "refactor: 画面サイズのしきい値をconstにまとめる" This reverts commit 05ca36f400889456981e89489ae0ae242fa09b67. * fix * revert all changes in base.pug * embedドメインをまとめた * embedドメインをまとめた * prevent calling contextmenu in embed page by stopping at the caller * fix import * fix import * improve directory structure * fix import * register timeline ui as a container * wa- * rename * wa- * Update EmMediaList.vue * Update EmMediaList.vue * Update EmMediaList.vue * Update EmMediaImage.vue * Update EmNote.vue * revert mkmedialist changes * 戻し漏れ * wip * tweak embed media ui * revert original media components * Update boot.embed.js * rename * wip * Update MkNote.vue * wip * Update MkSubNoteContent.vue * Update EmNote.vue * Update packages/frontend/src/router/definition.ts * Revert "Update packages/frontend/src/router/definition.ts" This reverts commit 937ae44521cdb0f250796943b20142b65f8ed944. * refactor EmMediaImage * fix import * remove unused imports * Update router.ts * wip * Update boot.ts * wip * wip * wip * wip * Update EmNote.vue * Update EmNote.vue * Create EmA.vue * Create EmAvatar.vue * Update EmAvatar.vue * wip * wip * wip * Create EmImgWithBlurhash.vue * Update EmImgWithBlurhash.vue * Create EmPagination.vue * wip * Update boot.ts * wip * wip * wi@p * wip * wip * wiop * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update boot.ts * wip * Update MkMisskeyFlavoredMarkdown.ts * wip * wip * wip * wip * wip * Update post-message.ts * wip * Update EmNoteDetailed.vue * Update EmNoteDetailed.vue * Create instance.ts * Update EmNoteDetailed.vue * wip * Update EmNoteDetailed.vue * wip * wip * wip * Update pnpm-lock.yaml * wip * wip * wp * wip * Update ClientServerService.ts * wip * Update boot.ts * Update vite.config.local-dev.ts * Update vite.config.ts * Create index.html * wa- * wip * Update boot.ts * wip * wip * wip * wip * wip * wip * wip * wip * wip * Create EmLink.vue * Create EmMention.vue * Update EmMfm.ts * wip * wip * wip * wip * Update vite.config.ts * Update boot.ts * Update EmA.vue * うぃp * wip * wip * Create EmError.vue * wip * Update MkEmbedCodeGenDialog.vue * Update EmNote.vue * wip * wip * Update user-timeline.vue * Update check-spdx-license-id.yml * wip * wip * style(frontend-shared): lint fixes on build.js * fix(frontend-shared): include `*.{js,json}` files in js-built * wip * use alias * refactor * refactor * Update scroll.ts * refactor * refactor * refactor * wip * wip * wip * wip * Update roles.vue * Update branding.vue * wip * wip * wip * Update page.vue * wip * fix import * add missing css variables * 絵文字をtwemojiに変更 クライアントデフォルトにあわせるため * force empoll readonly * fix compiler error * fix broken imports * tweak button style * run api extractor * fix storybook theme preloads * fix storybook instance imports * Update preview.ts * Update preview.ts * Update preview.ts * Revert "Update preview.ts" This reverts commit 12bab1c6fbd3baf753515df760ff19d027b85155. * Revert "Update preview.ts" This reverts commit 5c0ce01dbdf2194ffe94aba950f747a9968f29c4. * Revert "Update preview.ts" This reverts commit f4863524d7e5ca0f25470808849c24a72bea000a. * Revert "fix storybook instance imports" This reverts commit ed8eabb246edf731d31adffbe3c77c539e53ae9e. * Revert "wip" This reverts commit d3c1926519878155193a1654f49141e515d49683. * Revert "Update page.vue" This reverts commit 27c7900b0c1ae296b56075e8a9c22585d9cd744b. * Revert "Update branding.vue" This reverts commit c08ccb65ba66774c3e2b3dcfc6153004b5c0aa16. * Revert "Update roles.vue" This reverts commit 1488b670660cb1803d17d8f5c78f2d79e59fa52d. * Revert "wip" This reverts commit aab1c769814b08c257cad3025422a0eea3bfba4f. * refactor: use common media proxy * fix imports * fix * fix: MediaProxyの初期化を保証する(storybook対策?) * enhance(frontend-embed): improve embedParams provide * fix(backend): MK_DEV_PREFER=backendのときにembed viteが読み込めないのを修正 * fix * embed-pageを共通化 * fix import * fix import * fix import * const.jsを共通化 (たぶんrevertしすぎた) * fix type error * fix duplicated import * fix lint * fix * コメントとして残す * sharedとembedをlint対象にする * lint * attempt to fix eslint (frontend-shared) * lint fixes --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> Co-authored-by: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/config.ts24
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts46
-rw-r--r--packages/backend/src/server/web/boot.embed.js219
-rw-r--r--packages/backend/src/server/web/boot.js11
-rw-r--r--packages/backend/src/server/web/style.css1
-rw-r--r--packages/backend/src/server/web/style.embed.css99
-rw-r--r--packages/backend/src/server/web/views/base-embed.pug67
-rw-r--r--packages/backend/src/server/web/views/base.pug12
8 files changed, 450 insertions, 29 deletions
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index cff0194780..cbd6d1c086 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -160,8 +160,10 @@ export type Config = {
authUrl: string;
driveUrl: string;
userAgent: string;
- clientEntry: string;
- clientManifestExists: boolean;
+ frontendEntry: string;
+ frontendManifestExists: boolean;
+ frontendEmbedEntry: string;
+ frontendEmbedManifestExists: boolean;
mediaProxy: string;
externalMediaProxyEnabled: boolean;
videoThumbnailGenerator: string | null;
@@ -196,10 +198,16 @@ const path = process.env.MISSKEY_CONFIG_YML
export function loadConfig(): Config {
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8'));
- const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json');
- const clientManifest = clientManifestExists ?
- JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8'))
+
+ const frontendManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_vite_/manifest.json');
+ const frontendEmbedManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_embed_vite_/manifest.json');
+ const frontendManifest = frontendManifestExists ?
+ JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_vite_/manifest.json`, 'utf-8'))
: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
+ const frontendEmbedManifest = frontendEmbedManifestExists ?
+ JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8'))
+ : { 'src/boot.ts': { file: 'src/boot.ts' } };
+
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? '');
@@ -270,8 +278,10 @@ export function loadConfig(): Config {
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
: null,
userAgent: `Misskey/${version} (${config.url})`,
- clientEntry: clientManifest['src/_boot_.ts'],
- clientManifestExists: clientManifestExists,
+ frontendEntry: frontendManifest['src/_boot_.ts'],
+ frontendManifestExists: frontendManifestExists,
+ frontendEmbedEntry: frontendEmbedManifest['src/boot.ts'],
+ frontendEmbedManifestExists: frontendEmbedManifestExists,
perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index f55790b636..5e0ec390f2 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -61,7 +61,8 @@ const staticAssets = `${_dirname}/../../../assets/`;
const clientAssets = `${_dirname}/../../../../frontend/assets/`;
const assets = `${_dirname}/../../../../../built/_frontend_dist_/`;
const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`;
-const viteOut = `${_dirname}/../../../../../built/_vite_/`;
+const frontendViteOut = `${_dirname}/../../../../../built/_frontend_vite_/`;
+const frontendEmbedViteOut = `${_dirname}/../../../../../built/_frontend_embed_vite_/`;
const tarball = `${_dirname}/../../../../../built/tarball/`;
@Injectable()
@@ -277,15 +278,22 @@ export class ClientServerService {
});
//#region vite assets
- if (this.config.clientManifestExists) {
+ if (this.config.frontendEmbedManifestExists) {
fastify.register((fastify, options, done) => {
fastify.register(fastifyStatic, {
- root: viteOut,
+ root: frontendViteOut,
prefix: '/vite/',
maxAge: ms('30 days'),
immutable: true,
decorateReply: false,
});
+ fastify.register(fastifyStatic, {
+ root: frontendEmbedViteOut,
+ prefix: '/embed_vite/',
+ maxAge: ms('30 days'),
+ immutable: true,
+ decorateReply: false,
+ });
fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
done();
});
@@ -296,6 +304,13 @@ export class ClientServerService {
prefix: '/vite',
rewritePrefix: '/vite',
});
+
+ const embedPort = (process.env.EMBED_VITE_PORT ?? '5174');
+ fastify.register(fastifyProxy, {
+ upstream: 'http://localhost:' + embedPort,
+ prefix: '/embed_vite',
+ rewritePrefix: '/embed_vite',
+ });
}
//#endregion
@@ -425,6 +440,13 @@ export class ClientServerService {
// Manifest
fastify.get('/manifest.json', async (request, reply) => await this.manifestHandler(reply));
+ // Embed Javascript
+ fastify.get('/embed.js', async (request, reply) => {
+ return await reply.sendFile('/embed.js', staticAssets, {
+ maxAge: ms('1 day'),
+ });
+ });
+
fastify.get('/robots.txt', async (request, reply) => {
return await reply.sendFile('/robots.txt', staticAssets);
});
@@ -762,7 +784,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 });
@@ -772,7 +794,20 @@ 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/*', async (request, reply) => {
+ const meta = await this.metaService.fetch();
+
+ reply.removeHeader('X-Frame-Options');
+
+ reply.header('Cache-Control', 'public, max-age=3600');
+ return await reply.view('base-embed', {
+ title: meta.name ?? 'Misskey',
+ ...await this.generateCommonPugData(meta),
+ });
+ });
fastify.get('/_info_card_', async (request, reply) => {
const meta = await this.metaService.fetch(true);
@@ -787,6 +822,7 @@ export class ClientServerService {
originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }),
});
});
+ //#endregion
fastify.get('/bios', async (request, reply) => {
return await reply.view('bios', {
diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/backend/src/server/web/boot.embed.js
new file mode 100644
index 0000000000..48d1cd262b
--- /dev/null
+++ b/packages/backend/src/server/web/boot.embed.js
@@ -0,0 +1,219 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+'use strict';
+
+// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
+(async () => {
+ window.onerror = (e) => {
+ console.error(e);
+ renderError('SOMETHING_HAPPENED');
+ };
+ window.onunhandledrejection = (e) => {
+ console.error(e);
+ renderError('SOMETHING_HAPPENED_IN_PROMISE');
+ };
+
+ let forceError = localStorage.getItem('forceError');
+ if (forceError != null) {
+ renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.');
+ return;
+ }
+
+ // パラメータに応じてsplashのスタイルを変更
+ const params = new URLSearchParams(location.search);
+ if (params.has('rounded') && params.get('rounded') === 'false') {
+ document.documentElement.classList.add('norounded');
+ }
+ if (params.has('border') && params.get('border') === 'false') {
+ document.documentElement.classList.add('noborder');
+ }
+
+ //#region Detect language & fetch translations
+ if (!localStorage.hasOwnProperty('locale')) {
+ const supportedLangs = LANGS;
+ let lang = localStorage.getItem('lang');
+ if (lang == null || !supportedLangs.includes(lang)) {
+ if (supportedLangs.includes(navigator.language)) {
+ lang = navigator.language;
+ } else {
+ lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
+
+ // Fallback
+ if (lang == null) lang = 'en-US';
+ }
+ }
+
+ const metaRes = await window.fetch('/api/meta', {
+ method: 'POST',
+ body: JSON.stringify({}),
+ credentials: 'omit',
+ cache: 'no-cache',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ if (metaRes.status !== 200) {
+ renderError('META_FETCH');
+ return;
+ }
+ const meta = await metaRes.json();
+ const v = meta.version;
+ if (v == null) {
+ renderError('META_FETCH_V');
+ return;
+ }
+
+ // for https://github.com/misskey-dev/misskey/issues/10202
+ if (lang == null || lang.toString == null || lang.toString() === 'null') {
+ console.error('invalid lang value detected!!!', typeof lang, lang);
+ lang = 'en-US';
+ }
+
+ const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`);
+ if (localRes.status === 200) {
+ localStorage.setItem('lang', lang);
+ localStorage.setItem('locale', await localRes.text());
+ localStorage.setItem('localeVersion', v);
+ } else {
+ renderError('LOCALE_FETCH');
+ return;
+ }
+ }
+ //#endregion
+
+ //#region Script
+ async function importAppScript() {
+ await import(`/embed_vite/${CLIENT_ENTRY}`)
+ .catch(async e => {
+ console.error(e);
+ renderError('APP_IMPORT');
+ });
+ }
+
+ // タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある
+ if (document.readyState !== 'loading') {
+ importAppScript();
+ } else {
+ window.addEventListener('DOMContentLoaded', () => {
+ importAppScript();
+ });
+ }
+ //#endregion
+
+ async function addStyle(styleText) {
+ let css = document.createElement('style');
+ css.appendChild(document.createTextNode(styleText));
+ document.head.appendChild(css);
+ }
+
+ async function renderError(code) {
+ // Cannot set property 'innerHTML' of null を回避
+ if (document.readyState === 'loading') {
+ await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
+ }
+ document.body.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 9v4" /><path d="M12 16v.01" /></svg>
+ <div class="message">読み込みに失敗しました</div>
+ <div class="submessage">Failed to initialize Misskey</div>
+ <div class="submessage">Error Code: ${code}</div>
+ <button onclick="location.reload(!0)">
+ <div>リロード</div>
+ <div><small>Reload</small></div>
+ </button>`;
+ addStyle(`
+ #misskey_app,
+ #splash {
+ display: none !important;
+ }
+
+ html,
+ body {
+ margin: 0;
+ }
+
+ body {
+ position: relative;
+ color: #dee7e4;
+ font-family: Hiragino Maru Gothic Pro, BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
+ line-height: 1.35;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ margin: 0;
+ padding: 24px;
+ box-sizing: border-box;
+ overflow: hidden;
+
+ border-radius: var(--radius, 12px);
+ border: 1px solid rgba(231, 255, 251, 0.14);
+ }
+
+ body::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: #192320;
+ border-radius: var(--radius, 12px);
+ z-index: -1;
+ }
+
+ html.embed.norounded body,
+ html.embed.norounded body::before {
+ border-radius: 0;
+ }
+
+ html.embed.noborder body {
+ border: none;
+ }
+
+ .icon {
+ max-width: 60px;
+ width: 100%;
+ height: auto;
+ margin-bottom: 20px;
+ color: #dec340;
+ }
+
+ .message {
+ text-align: center;
+ font-size: 20px;
+ font-weight: 700;
+ margin-bottom: 20px;
+ }
+
+ .submessage {
+ text-align: center;
+ font-size: 90%;
+ margin-bottom: 7.5px;
+ }
+
+ .submessage:last-of-type {
+ margin-bottom: 20px;
+ }
+
+ button {
+ padding: 7px 14px;
+ min-width: 100px;
+ font-weight: 700;
+ font-family: Hiragino Maru Gothic Pro, BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
+ line-height: 1.35;
+ border-radius: 99rem;
+ background-color: #b4e900;
+ color: #192320;
+ border: none;
+ cursor: pointer;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ button:hover {
+ background-color: #c6ff03;
+ }`);
+ }
+})();
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index 5283596316..7c6a533429 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -3,17 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-/**
- * BOOT LOADER
- * サーバーからレスポンスされるHTMLに埋め込まれるスクリプトで、以下の役割を持ちます。
- * - 翻訳ファイルをフェッチする。
- * - バージョンに基づいて適切なメインスクリプトを読み込む。
- * - キャッシュされたコンパイル済みテーマを適用する。
- * - クライアントの設定値に基づいて対応するHTMLクラス等を設定する。
- * テーマをこの段階で設定するのは、メインスクリプトが読み込まれる間もテーマを適用したいためです。
- * 注: webpackは介さないため、このファイルではrequireやimportは使えません。
- */
-
'use strict';
// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css
index e4723c24fd..dbcc8f537c 100644
--- a/packages/backend/src/server/web/style.css
+++ b/packages/backend/src/server/web/style.css
@@ -47,6 +47,7 @@ html {
transform: translateY(70px);
color: var(--accent);
}
+
#splashSpinner > .spinner {
position: absolute;
top: 0;
diff --git a/packages/backend/src/server/web/style.embed.css b/packages/backend/src/server/web/style.embed.css
new file mode 100644
index 0000000000..a7b110d80a
--- /dev/null
+++ b/packages/backend/src/server/web/style.embed.css
@@ -0,0 +1,99 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+html {
+ background-color: var(--bg);
+ color: var(--fg);
+}
+
+html.embed {
+ box-sizing: border-box;
+ background-color: transparent;
+ color-scheme: light dark;
+ max-width: 500px;
+}
+
+#splash {
+ position: fixed;
+ z-index: 10000;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ cursor: wait;
+ background-color: var(--bg);
+ opacity: 1;
+ 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, #e8e8e8);
+}
+
+html.embed.norounded #splash {
+ border-radius: 0;
+}
+
+html.embed.noborder #splash {
+ border: none;
+}
+
+#splashIcon {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ margin: auto;
+ width: 64px;
+ height: 64px;
+ pointer-events: none;
+}
+
+#splashSpinner {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ margin: auto;
+ display: inline-block;
+ width: 28px;
+ height: 28px;
+ transform: translateY(70px);
+ color: var(--accent);
+}
+
+#splashSpinner > .spinner {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 28px;
+ height: 28px;
+ fill-rule: evenodd;
+ clip-rule: evenodd;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-miterlimit: 1.5;
+}
+#splashSpinner > .spinner.bg {
+ opacity: 0.275;
+}
+#splashSpinner > .spinner.fg {
+ animation: splashSpinner 0.5s linear infinite;
+}
+
+@keyframes splashSpinner {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug
new file mode 100644
index 0000000000..d773f2676a
--- /dev/null
+++ b/packages/backend/src/server/web/views/base-embed.pug
@@ -0,0 +1,67 @@
+block vars
+
+block loadClientEntry
+ - const entry = config.frontendEmbedEntry;
+
+doctype html
+
+html(class='embed')
+
+ head
+ meta(charset='utf-8')
+ meta(name='application-name' content='Misskey')
+ meta(name='referrer' content='origin')
+ meta(name='theme-color' content= themeColor || '#86b300')
+ meta(name='theme-color-orig' content= themeColor || '#86b300')
+ meta(name='viewport' content='width=device-width, initial-scale=1')
+ meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no')
+ link(rel='icon' href= icon || '/favicon.ico')
+ link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')
+ link(rel='modulepreload' href=`/embed_vite/${entry.file}`)
+
+ if !config.frontendEmbedManifestExists
+ script(type="module" src="/embed_vite/@vite/client")
+
+ if Array.isArray(entry.css)
+ each href in entry.css
+ link(rel='stylesheet' href=`/embed_vite/${href}`)
+
+ title
+ block title
+ = title || 'Misskey'
+
+ block meta
+ meta(name='robots' content='noindex')
+
+ style
+ include ../style.embed.css
+
+ script.
+ var VERSION = "#{version}";
+ var CLIENT_ENTRY = "#{entry.file}";
+
+ script(type='application/json' id='misskey_meta' data-generated-at=now)
+ != metaJson
+
+ script
+ include ../boot.embed.js
+
+ body
+ noscript: p
+ | JavaScriptを有効にしてください
+ br
+ | Please turn on your JavaScript
+ div#splash
+ img#splashIcon(src= icon || '/static-assets/splash.png')
+ div#splashSpinner
+ <svg class="spinner bg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
+ <g transform="matrix(1,0,0,1,12,12)">
+ <circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
+ </g>
+ </svg>
+ <svg class="spinner fg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
+ <g transform="matrix(1,0,0,1,12,12)">
+ <path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
+ </g>
+ </svg>
+ block content
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index da6d1eafd3..88714b2556 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -1,7 +1,7 @@
block vars
block loadClientEntry
- - const clientEntry = config.clientEntry;
+ - const entry = config.frontendEntry;
doctype html
@@ -36,13 +36,13 @@ html
link(rel='prefetch' href=serverErrorImageUrl)
link(rel='prefetch' href=infoImageUrl)
link(rel='prefetch' href=notFoundImageUrl)
- link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
+ link(rel='modulepreload' href=`/vite/${entry.file}`)
- if !config.clientManifestExists
+ if !config.frontendManifestExists
script(type="module" src="/vite/@vite/client")
- if Array.isArray(clientEntry.css)
- each href in clientEntry.css
+ if Array.isArray(entry.css)
+ each href in entry.css
link(rel='stylesheet' href=`/vite/${href}`)
title
@@ -68,7 +68,7 @@ html
script.
var VERSION = "#{version}";
- var CLIENT_ENTRY = "#{clientEntry.file}";
+ var CLIENT_ENTRY = "#{entry.file}";
script(type='application/json' id='misskey_meta' data-generated-at=now)
!= metaJson