Merge remote-tracking branch 'misskey/master' into feature/2024.9.0

This commit is contained in:
dakkar 2024-10-09 15:17:22 +01:00
commit f00576bce6
564 changed files with 19993 additions and 8169 deletions

View file

@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import { createBullBoard } from '@bull-board/api';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js';
import { FastifyAdapter } from '@bull-board/fastify';
import { FastifyAdapter as BullBoardFastifyAdapter } from '@bull-board/fastify';
import ms from 'ms';
import sharp from 'sharp';
import pug from 'pug';
@ -24,7 +24,6 @@ import type { Config } from '@/config.js';
import { getNoteSummary } from '@/misc/get-note-summary.js';
import { DI } from '@/di-symbols.js';
import * as Acct from '@/misc/acct.js';
import { MetaService } from '@/core/MetaService.js';
import type {
DbQueue,
DeliverQueue,
@ -61,7 +60,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()
@ -72,6 +72,9 @@ export class ClientServerService {
@Inject(DI.config)
private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@ -108,7 +111,6 @@ export class ClientServerService {
private clipEntityService: ClipEntityService,
private channelEntityService: ChannelEntityService,
private reversiGameEntityService: ReversiGameEntityService,
private metaService: MetaService,
private urlPreviewService: UrlPreviewService,
private feedService: FeedService,
private roleService: RoleService,
@ -128,32 +130,30 @@ export class ClientServerService {
@bindThis
private async manifestHandler(reply: FastifyReply) {
const instance = await this.metaService.fetch(true);
let manifest = {
// 空文字列の場合右辺を使いたいため
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
'short_name': instance.shortName || instance.name || this.config.host,
'short_name': this.meta.shortName || this.meta.name || this.config.host,
// 空文字列の場合右辺を使いたいため
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
'name': instance.name || this.config.host,
'name': this.meta.name || this.config.host,
'start_url': '/',
'display': 'standalone',
'background_color': '#313a42',
// 空文字列の場合右辺を使いたいため
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
'theme_color': instance.themeColor || '#86b300',
'theme_color': this.meta.themeColor || '#86b300',
'icons': [{
// 空文字列の場合右辺を使いたいため
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
'src': instance.app192IconUrl || '/static-assets/icons/192.png',
'src': this.meta.app192IconUrl || '/static-assets/icons/192.png',
'sizes': '192x192',
'type': 'image/png',
'purpose': 'maskable',
}, {
// 空文字列の場合右辺を使いたいため
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
'src': instance.app512IconUrl || '/static-assets/icons/512.png',
'src': this.meta.app512IconUrl || '/static-assets/icons/512.png',
'sizes': '512x512',
'type': 'image/png',
'purpose': 'maskable',
@ -179,7 +179,7 @@ export class ClientServerService {
manifest = {
...manifest,
...JSON.parse(instance.manifestJsonOverride === '' ? '{}' : instance.manifestJsonOverride),
...JSON.parse(this.meta.manifestJsonOverride === '' ? '{}' : this.meta.manifestJsonOverride),
};
reply.header('Cache-Control', 'max-age=300');
@ -242,7 +242,7 @@ export class ClientServerService {
}
});
const serverAdapter = new FastifyAdapter();
const bullBoardServerAdapter = new BullBoardFastifyAdapter();
createBullBoard({
queues: [
@ -255,11 +255,11 @@ export class ClientServerService {
this.userWebhookDeliverQueue,
this.systemWebhookDeliverQueue,
].map(q => new BullMQAdapter(q)),
serverAdapter,
serverAdapter: bullBoardServerAdapter,
});
serverAdapter.setBasePath(bullBoardPath);
(fastify.register as any)(serverAdapter.registerPlugin(), { prefix: bullBoardPath });
bullBoardServerAdapter.setBasePath(bullBoardPath);
(fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath });
//#endregion
fastify.register(fastifyView, {
@ -280,15 +280,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();
});
@ -299,6 +306,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
@ -443,15 +457,20 @@ 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);
});
// OpenSearch XML
fastify.get('/opensearch.xml', async (request, reply) => {
const meta = await this.metaService.fetch();
const name = meta.name ?? 'Sharkey';
const name = this.meta.name ?? 'Sharkey';
let content = '';
content += '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">';
content += `<ShortName>${name}</ShortName>`;
@ -468,14 +487,13 @@ export class ClientServerService {
//#endregion
const renderBase = async (reply: FastifyReply, data: { [key: string]: any } = {}) => {
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=30');
return await reply.view('base', {
img: meta.bannerUrl,
img: this.meta.bannerUrl,
url: this.config.url,
title: meta.name ?? 'Misskey',
desc: meta.description,
...await this.generateCommonPugData(meta),
title: this.meta.name ?? 'Misskey',
desc: this.meta.description,
...await this.generateCommonPugData(this.meta),
...data,
});
};
@ -553,7 +571,6 @@ export class ClientServerService {
if (user != null) {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
const meta = await this.metaService.fetch();
const me = profile.fields
? profile.fields
.filter(filed => filed.value != null && filed.value.match(/^https?:/))
@ -569,7 +586,7 @@ export class ClientServerService {
user, profile, me,
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
sub: request.params.sub,
...await this.generateCommonPugData(meta),
...await this.generateCommonPugData(this.meta),
});
} else {
// リモートユーザーなので
@ -607,7 +624,6 @@ export class ClientServerService {
if (note) {
const _note = await this.noteEntityService.pack(note);
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15');
if (profile.preventAiLearning) {
reply.header('X-Robots-Tag', 'noimageai');
@ -619,7 +635,7 @@ export class ClientServerService {
avatarUrl: _note.user.avatarUrl,
// TODO: Let locale changeable by instance setting
summary: getNoteSummary(_note),
...await this.generateCommonPugData(meta),
...await this.generateCommonPugData(this.meta),
});
} else {
return await renderBase(reply);
@ -644,7 +660,6 @@ export class ClientServerService {
if (page) {
const _page = await this.pageEntityService.pack(page);
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: page.userId });
const meta = await this.metaService.fetch();
if (['public'].includes(page.visibility)) {
reply.header('Cache-Control', 'public, max-age=15');
} else {
@ -658,7 +673,7 @@ export class ClientServerService {
page: _page,
profile,
avatarUrl: _page.user.avatarUrl,
...await this.generateCommonPugData(meta),
...await this.generateCommonPugData(this.meta),
});
} else {
return await renderBase(reply);
@ -674,7 +689,6 @@ export class ClientServerService {
if (flash) {
const _flash = await this.flashEntityService.pack(flash);
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15');
if (profile.preventAiLearning) {
reply.header('X-Robots-Tag', 'noimageai');
@ -684,7 +698,7 @@ export class ClientServerService {
flash: _flash,
profile,
avatarUrl: _flash.user.avatarUrl,
...await this.generateCommonPugData(meta),
...await this.generateCommonPugData(this.meta),
});
} else {
return await renderBase(reply);
@ -700,7 +714,6 @@ export class ClientServerService {
if (clip && clip.isPublic) {
const _clip = await this.clipEntityService.pack(clip);
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15');
if (profile.preventAiLearning) {
reply.header('X-Robots-Tag', 'noimageai');
@ -710,7 +723,7 @@ export class ClientServerService {
clip: _clip,
profile,
avatarUrl: _clip.user.avatarUrl,
...await this.generateCommonPugData(meta),
...await this.generateCommonPugData(this.meta),
});
} else {
return await renderBase(reply);
@ -724,7 +737,6 @@ export class ClientServerService {
if (post) {
const _post = await this.galleryPostEntityService.pack(post);
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15');
if (profile.preventAiLearning) {
reply.header('X-Robots-Tag', 'noimageai');
@ -734,7 +746,7 @@ export class ClientServerService {
post: _post,
profile,
avatarUrl: _post.user.avatarUrl,
...await this.generateCommonPugData(meta),
...await this.generateCommonPugData(this.meta),
});
} else {
return await renderBase(reply);
@ -749,11 +761,10 @@ export class ClientServerService {
if (channel) {
const _channel = await this.channelEntityService.pack(channel);
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15');
return await reply.view('channel', {
channel: _channel,
...await this.generateCommonPugData(meta),
...await this.generateCommonPugData(this.meta),
});
} else {
return await renderBase(reply);
@ -768,11 +779,10 @@ export class ClientServerService {
if (game) {
const _game = await this.reversiGameEntityService.packDetail(game);
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=3600');
return await reply.view('reversi-game', {
game: _game,
...await this.generateCommonPugData(meta),
...await this.generateCommonPugData(this.meta),
});
} else {
return await renderBase(reply);
@ -780,7 +790,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 });
@ -790,21 +800,97 @@ 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<{ 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');
reply.header('Cache-Control', 'public, max-age=3600');
return await reply.view('base-embed', {
title: this.meta.name ?? 'Misskey',
...await this.generateCommonPugData(this.meta),
});
});
fastify.get('/_info_card_', async (request, reply) => {
const meta = await this.metaService.fetch(true);
reply.removeHeader('X-Frame-Options');
return await reply.view('info-card', {
version: this.config.version,
host: this.config.host,
meta: meta,
meta: this.meta,
originalUsersCount: await this.usersRepository.countBy({ host: IsNull() }),
originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }),
});
});
//#endregion
fastify.get('/bios', async (request, reply) => {
return await reply.view('bios', {

View file

@ -8,7 +8,6 @@ import { summaly } from '@misskey-dev/summaly';
import { SummalyResult } from '@misskey-dev/summaly/built/summary.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import type Logger from '@/logger.js';
import { query } from '@/misc/prelude/url.js';
@ -32,7 +31,9 @@ export class UrlPreviewService {
@Inject(DI.redis)
private redisClient: Redis.Redis,
private metaService: MetaService,
@Inject(DI.meta)
private meta: MiMeta,
private httpRequestService: HttpRequestService,
private loggerService: LoggerService,
) {
@ -75,9 +76,7 @@ export class UrlPreviewService {
return;
}
const meta = await this.metaService.fetch();
if (!meta.urlPreviewEnabled) {
if (!this.meta.urlPreviewEnabled) {
reply.code(403);
return {
error: new ApiError({
@ -98,14 +97,14 @@ export class UrlPreviewService {
return cached;
}
this.logger.info(meta.urlPreviewSummaryProxyUrl
this.logger.info(this.meta.urlPreviewSummaryProxyUrl
? `(Proxy) Getting preview of ${key} ...`
: `Getting preview of ${key} ...`);
try {
const summary = meta.urlPreviewSummaryProxyUrl
? await this.fetchSummaryFromProxy(url, meta, lang)
: await this.fetchSummary(url, meta, lang);
const summary = this.meta.urlPreviewSummaryProxyUrl
? await this.fetchSummaryFromProxy(url, this.meta, lang)
: await this.fetchSummary(url, this.meta, lang);
this.logger.succ(`Got preview of ${url}: ${summary.title}`);

View 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;
}`);
}
})();

View file

@ -3,17 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* BOOT LOADER
* サーバーからレスポンスされるHTMLに埋め込まれるスクリプトで以下の役割を持ちます
* - 翻訳ファイルをフェッチする
* - バージョンに基づいて適切なメインスクリプトを読み込む
* - キャッシュされたコンパイル済みテーマを適用する
* - クライアントの設定値に基づいて対応するHTMLクラス等を設定する
* テーマをこの段階で設定するのはメインスクリプトが読み込まれる間もテーマを適用したいためです
* : webpackは介さないためこのファイルではrequireやimportは使えません
*/
'use strict';
// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
@ -192,7 +181,7 @@
if (!errorsElement) {
document.body.innerHTML = `
<svg class="icon-warning" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-alert-triangle" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<svg class="icon-warning" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 9v2m0 4v.01"></path>
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path>
@ -202,10 +191,10 @@
<span class="button-label-big">Reload / リロード</span>
</button>
<p><b>The following actions may solve the problem. / 以下を行うと解決する可能性があります</b></p>
<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p>
<p>Update your os and browser / ブラウザおよびOSを最新バージョンに更新する</p>
<p>Disable an adblocker / アドブロッカーを無効にする</p>
<p>&#40;Tor Browser&#41; Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する</p>
<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p>
<p>&#40;Tor Browser&#41; Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する</p>
<details style="color: #86b300;">
<summary>Other options / その他のオプション</summary>
<a href="/flush">
@ -238,7 +227,7 @@
<summary>
<code>ERROR CODE: ${code}</code>
</summary>
<code>${JSON.stringify(details)}</code>`;
<code>${details.toString()} ${JSON.stringify(details)}</code>`;
errorsElement.appendChild(detailsElement);
addStyle(`
* {
@ -346,6 +335,6 @@
#errorInfo {
width: 50%;
}
}`)
}`);
}
})();

View file

@ -47,6 +47,7 @@ html {
transform: translateY(80px);
color: var(--accent);
}
#splashSpinner > .spinner {
position: absolute;
top: 0;

View 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);
}
}

View file

@ -0,0 +1,72 @@
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(property='og:site_name' content= instanceName || 'Misskey')
meta(property='instance_url' content= instanceUrl)
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(type='application/json' id='misskey_embedCtx' data-generated-at=now)
!= embedCtx
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

View file

@ -1,7 +1,7 @@
block vars
block loadClientEntry
- const clientEntry = config.clientEntry;
- const entry = config.frontendEntry;
doctype html
@ -40,16 +40,13 @@ html
link(rel='prefetch' href=serverErrorImageUrl)
link(rel='prefetch' href=infoImageUrl)
link(rel='prefetch' href=notFoundImageUrl)
//- https://github.com/misskey-dev/misskey/issues/9842
link(rel='stylesheet' href=`/assets/phosphor-icons/bold/style.css?version=${version}`)
link(rel='stylesheet' href=`/static-assets/fonts/sharkey-icons/style.css?version=${version}`)
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
@ -75,7 +72,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