Merge remote-tracking branch 'misskey/master' into feature/2024.9.0
This commit is contained in:
commit
f00576bce6
564 changed files with 19993 additions and 8169 deletions
|
|
@ -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', {
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
|
||||
|
|
|
|||
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';
|
||||
|
||||
// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
|
||||
|
|
@ -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>(Tor Browser) Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する</p>
|
||||
<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p>
|
||||
<p>(Tor Browser) 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%;
|
||||
}
|
||||
}`)
|
||||
}`);
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ html {
|
|||
transform: translateY(80px);
|
||||
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);
|
||||
}
|
||||
}
|
||||
72
packages/backend/src/server/web/views/base-embed.pug
Normal file
72
packages/backend/src/server/web/views/base-embed.pug
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue