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 commit05ca36f400
. * 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 commit937ae44521
. * 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 commit12bab1c6fb
. * Revert "Update preview.ts" This reverts commit5c0ce01dbd
. * Revert "Update preview.ts" This reverts commitf4863524d7
. * Revert "fix storybook instance imports" This reverts commited8eabb246
. * Revert "wip" This reverts commitd3c1926519
. * Revert "Update page.vue" This reverts commit27c7900b0c
. * Revert "Update branding.vue" This reverts commitc08ccb65ba
. * Revert "Update roles.vue" This reverts commit1488b67066
. * Revert "wip" This reverts commitaab1c76981
. * 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>
This commit is contained in:
@@ -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),
|
||||
|
@@ -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', {
|
||||
|
219
packages/backend/src/server/web/boot.embed.js
Normal file
219
packages/backend/src/server/web/boot.embed.js
Normal file
@@ -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;
|
||||
}`);
|
||||
}
|
||||
})();
|
@@ -3,17 +3,6 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* BOOT LOADER
|
||||
* サーバーからレスポンスされるHTMLに埋め込まれるスクリプトで、以下の役割を持ちます。
|
||||
* - 翻訳ファイルをフェッチする。
|
||||
* - バージョンに基づいて適切なメインスクリプトを読み込む。
|
||||
* - キャッシュされたコンパイル済みテーマを適用する。
|
||||
* - クライアントの設定値に基づいて対応するHTMLクラス等を設定する。
|
||||
* テーマをこの段階で設定するのは、メインスクリプトが読み込まれる間もテーマを適用したいためです。
|
||||
* 注: webpackは介さないため、このファイルではrequireやimportは使えません。
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
|
||||
|
@@ -47,6 +47,7 @@ html {
|
||||
transform: translateY(70px);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
#splashSpinner > .spinner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
99
packages/backend/src/server/web/style.embed.css
Normal file
99
packages/backend/src/server/web/style.embed.css
Normal file
@@ -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);
|
||||
}
|
||||
}
|
67
packages/backend/src/server/web/views/base-embed.pug
Normal file
67
packages/backend/src/server/web/views/base-embed.pug
Normal file
@@ -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
|
@@ -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
|
||||
|
Reference in New Issue
Block a user