enhance(backend): pugをやめ、JSXベースのテンプレートに変更 (#16908)
* enhance(backend): pugをやめ、JSXベースのテンプレートに変更 (to misskey-dev dev branch) (#16889)
* wip
* wip
* wip
* wip
* fix lint
* attempt to fix test
* fix
* fix
* fix: oauthページの描画がおかしい問題を修正
* typo [ci skip]
* fix
* fix
* fix
* fix
* fix
* refactor
* fix
* fix
* fix broken lockfile
* fix: expose supported languages as global variable
* remove i18n package from root as it is no longer required [ci skip]
* fix
* fix: add i18n package.json to Docker target-builder stage for federation tests (#16909)
* Initial plan
* fix: add i18n package.json to Docker target-builder stage for federation tests
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
* fix: followup-test-federation for enh-remove-pug (#16910)
* fix: followup-test-federation for enh-remove-pug
* Revert "fix: add i18n package.json to Docker target-builder stage for federation tests (#16909)"
This reverts commit 14313468d3.
* fix: CSSが読み込まれない場合がある問題を修正
* fix [ci skip]
* fix: propsのデフォルト値をnull合体演算子から論理和演算子に変更(空文字に対処するため)
* remove @types/pug
* enhance: bootloaderを埋め込むように
* fix possible race condition
* remove esbuild
---------
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
Co-authored-by: おさむのひと <46447427+samunohito@users.noreply.github.com>
This commit is contained in:
parent
e1b6e9d4b6
commit
f222d7e24d
|
|
@ -3,6 +3,7 @@
|
||||||
"**/node_modules": true
|
"**/node_modules": true
|
||||||
},
|
},
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.test.ts": "typescript"
|
"*.test.ts": "typescript"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,6 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.39.1",
|
"@eslint/js": "9.39.1",
|
||||||
"i18n": "workspace:*",
|
|
||||||
"@misskey-dev/eslint-plugin": "2.2.0",
|
"@misskey-dev/eslint-plugin": "2.2.0",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,17 @@
|
||||||
"jsc": {
|
"jsc": {
|
||||||
"parser": {
|
"parser": {
|
||||||
"syntax": "typescript",
|
"syntax": "typescript",
|
||||||
|
"jsx": true,
|
||||||
"dynamicImport": true,
|
"dynamicImport": true,
|
||||||
"decorators": true
|
"decorators": true
|
||||||
},
|
},
|
||||||
"transform": {
|
"transform": {
|
||||||
"legacyDecorator": true,
|
"legacyDecorator": true,
|
||||||
"decoratorMetadata": true
|
"decoratorMetadata": true,
|
||||||
|
"react": {
|
||||||
|
"runtime": "automatic",
|
||||||
|
"importSource": "@kitajs/html"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"experimental": {
|
"experimental": {
|
||||||
"keepImportAssertions": true
|
"keepImportAssertions": true
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
(async () => {
|
||||||
|
const msg = document.getElementById('msg');
|
||||||
|
const successText = `\nSuccess Flush! <a href="/">Back to Misskey</a>\n成功しました。<a href="/">Misskeyを開き直してください。</a>`;
|
||||||
|
|
||||||
|
if (!document.cookie) {
|
||||||
|
message('Your site data is fully cleared by your browser.');
|
||||||
|
message(successText);
|
||||||
|
} else {
|
||||||
|
message('Your browser does not support Clear-Site-Data header. Start opportunistic flushing.');
|
||||||
|
try {
|
||||||
|
localStorage.clear();
|
||||||
|
message('localStorage cleared.');
|
||||||
|
|
||||||
|
const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => {
|
||||||
|
const delidb = indexedDB.deleteDatabase(name);
|
||||||
|
delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`));
|
||||||
|
delidb.onerror = e => rej(e)
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Promise.all(idbPromises);
|
||||||
|
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
navigator.serviceWorker.controller.postMessage('clear');
|
||||||
|
await navigator.serviceWorker.getRegistrations()
|
||||||
|
.then(registrations => {
|
||||||
|
return Promise.all(registrations.map(registration => registration.unregister()));
|
||||||
|
})
|
||||||
|
.catch(e => { throw new Error(e) });
|
||||||
|
}
|
||||||
|
|
||||||
|
message(successText);
|
||||||
|
} catch (e) {
|
||||||
|
message(`\n${e}\n\nFlush Failed. <a href="/flush">Please retry.</a>\n失敗しました。<a href="/flush">もう一度試してみてください。</a>`);
|
||||||
|
message(`\nIf you retry more than 3 times, try manually clearing the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを手動で消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`)
|
||||||
|
|
||||||
|
console.error(e);
|
||||||
|
setTimeout(() => {
|
||||||
|
location = '/';
|
||||||
|
}, 10000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function message(text) {
|
||||||
|
msg.insertAdjacentHTML('beforeend', `<p>[${(new Date()).toString()}] ${text.replace(/\n/g,'<br>')}</p>`)
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#a {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#banner {
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 24px;
|
||||||
|
padding: 0.5em 0.8em;
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
overflow: auto;
|
||||||
|
color: #353c3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
#description {
|
||||||
|
margin: 24px;
|
||||||
|
}
|
||||||
|
|
@ -205,7 +205,7 @@ module.exports = {
|
||||||
// Whether to use watchman for file crawling
|
// Whether to use watchman for file crawling
|
||||||
// watchman: true,
|
// watchman: true,
|
||||||
|
|
||||||
extensionsToTreatAsEsm: ['.ts'],
|
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
||||||
|
|
||||||
testTimeout: 60000,
|
testTimeout: 60000,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es6",
|
|
||||||
"module": "commonjs",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"jspm_packages",
|
|
||||||
"tmp",
|
|
||||||
"temp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
"@fastify/http-proxy": "11.3.0",
|
"@fastify/http-proxy": "11.3.0",
|
||||||
"@fastify/multipart": "9.3.0",
|
"@fastify/multipart": "9.3.0",
|
||||||
"@fastify/static": "8.3.0",
|
"@fastify/static": "8.3.0",
|
||||||
"@fastify/view": "11.1.1",
|
"@kitajs/html": "4.2.11",
|
||||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||||
"@misskey-dev/summaly": "5.2.5",
|
"@misskey-dev/summaly": "5.2.5",
|
||||||
"@napi-rs/canvas": "0.1.82",
|
"@napi-rs/canvas": "0.1.82",
|
||||||
|
|
@ -123,6 +123,7 @@
|
||||||
"got": "14.6.4",
|
"got": "14.6.4",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"http-link-header": "1.1.3",
|
"http-link-header": "1.1.3",
|
||||||
|
"i18n": "workspace:*",
|
||||||
"ioredis": "5.8.2",
|
"ioredis": "5.8.2",
|
||||||
"ip-cidr": "4.0.2",
|
"ip-cidr": "4.0.2",
|
||||||
"ipaddr.js": "2.2.0",
|
"ipaddr.js": "2.2.0",
|
||||||
|
|
@ -153,7 +154,6 @@
|
||||||
"pkce-challenge": "5.0.0",
|
"pkce-challenge": "5.0.0",
|
||||||
"probe-image-size": "7.2.3",
|
"probe-image-size": "7.2.3",
|
||||||
"promise-limit": "2.7.0",
|
"promise-limit": "2.7.0",
|
||||||
"pug": "3.0.3",
|
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
|
|
@ -185,6 +185,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "29.7.0",
|
"@jest/globals": "29.7.0",
|
||||||
|
"@kitajs/ts-html-plugin": "4.1.3",
|
||||||
"@nestjs/platform-express": "11.1.9",
|
"@nestjs/platform-express": "11.1.9",
|
||||||
"@sentry/vue": "10.26.0",
|
"@sentry/vue": "10.26.0",
|
||||||
"@simplewebauthn/types": "12.0.0",
|
"@simplewebauthn/types": "12.0.0",
|
||||||
|
|
@ -208,7 +209,6 @@
|
||||||
"@types/oauth2orize": "1.11.5",
|
"@types/oauth2orize": "1.11.5",
|
||||||
"@types/oauth2orize-pkce": "0.1.2",
|
"@types/oauth2orize-pkce": "0.1.2",
|
||||||
"@types/pg": "8.15.6",
|
"@types/pg": "8.15.6",
|
||||||
"@types/pug": "2.0.10",
|
|
||||||
"@types/qrcode": "1.5.6",
|
"@types/qrcode": "1.5.6",
|
||||||
"@types/random-seed": "0.3.5",
|
"@types/random-seed": "0.3.5",
|
||||||
"@types/ratelimiter": "3.4.6",
|
"@types/ratelimiter": "3.4.6",
|
||||||
|
|
@ -236,6 +236,7 @@
|
||||||
"nodemon": "3.1.11",
|
"nodemon": "3.1.11",
|
||||||
"pid-port": "2.0.0",
|
"pid-port": "2.0.0",
|
||||||
"simple-oauth2": "5.1.0",
|
"simple-oauth2": "5.1.0",
|
||||||
"supertest": "7.1.4"
|
"supertest": "7.1.4",
|
||||||
|
"vite": "7.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ async function killProc() {
|
||||||
'./node_modules/nodemon/bin/nodemon.js',
|
'./node_modules/nodemon/bin/nodemon.js',
|
||||||
[
|
[
|
||||||
'-w', 'src',
|
'-w', 'src',
|
||||||
'-e', 'ts,js,mjs,cjs,json,pug',
|
'-e', 'ts,js,mjs,cjs,tsx,json,pug',
|
||||||
'--exec', 'pnpm', 'run', 'build',
|
'--exec', 'pnpm', 'run', 'build',
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { type FastifyServerOptions } from 'fastify';
|
||||||
import type * as Sentry from '@sentry/node';
|
import type * as Sentry from '@sentry/node';
|
||||||
import type * as SentryVue from '@sentry/vue';
|
import type * as SentryVue from '@sentry/vue';
|
||||||
import type { RedisOptions } from 'ioredis';
|
import type { RedisOptions } from 'ioredis';
|
||||||
|
import type { ManifestChunk } from 'vite';
|
||||||
|
|
||||||
type RedisOptionsSource = Partial<RedisOptions> & {
|
type RedisOptionsSource = Partial<RedisOptions> & {
|
||||||
host: string;
|
host: string;
|
||||||
|
|
@ -187,9 +188,9 @@ export type Config = {
|
||||||
authUrl: string;
|
authUrl: string;
|
||||||
driveUrl: string;
|
driveUrl: string;
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
frontendEntry: { file: string | null };
|
frontendEntry: ManifestChunk;
|
||||||
frontendManifestExists: boolean;
|
frontendManifestExists: boolean;
|
||||||
frontendEmbedEntry: { file: string | null };
|
frontendEmbedEntry: ManifestChunk;
|
||||||
frontendEmbedManifestExists: boolean;
|
frontendEmbedManifestExists: boolean;
|
||||||
mediaProxy: string;
|
mediaProxy: string;
|
||||||
externalMediaProxyEnabled: boolean;
|
externalMediaProxyEnabled: boolean;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ESCAPE_LOOKUP = {
|
||||||
|
'&': '\\u0026',
|
||||||
|
'>': '\\u003e',
|
||||||
|
'<': '\\u003c',
|
||||||
|
'\u2028': '\\u2028',
|
||||||
|
'\u2029': '\\u2029',
|
||||||
|
} as Record<string, string>;
|
||||||
|
|
||||||
|
const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
|
||||||
|
|
||||||
|
export function htmlSafeJsonStringify(obj: any): string {
|
||||||
|
return JSON.stringify(obj).replace(ESCAPE_REGEX, x => ESCAPE_LOOKUP[x]);
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ import { SignupApiService } from './api/SignupApiService.js';
|
||||||
import { StreamingApiServerService } from './api/StreamingApiServerService.js';
|
import { StreamingApiServerService } from './api/StreamingApiServerService.js';
|
||||||
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
||||||
import { ClientServerService } from './web/ClientServerService.js';
|
import { ClientServerService } from './web/ClientServerService.js';
|
||||||
|
import { HtmlTemplateService } from './web/HtmlTemplateService.js';
|
||||||
import { FeedService } from './web/FeedService.js';
|
import { FeedService } from './web/FeedService.js';
|
||||||
import { UrlPreviewService } from './web/UrlPreviewService.js';
|
import { UrlPreviewService } from './web/UrlPreviewService.js';
|
||||||
import { ClientLoggerService } from './web/ClientLoggerService.js';
|
import { ClientLoggerService } from './web/ClientLoggerService.js';
|
||||||
|
|
@ -58,6 +59,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
|
||||||
providers: [
|
providers: [
|
||||||
ClientServerService,
|
ClientServerService,
|
||||||
ClientLoggerService,
|
ClientLoggerService,
|
||||||
|
HtmlTemplateService,
|
||||||
FeedService,
|
FeedService,
|
||||||
HealthServerService,
|
HealthServerService,
|
||||||
UrlPreviewService,
|
UrlPreviewService,
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ import ipaddr from 'ipaddr.js';
|
||||||
import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req, MiddlewareRequest } from 'oauth2orize';
|
import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req, MiddlewareRequest } from 'oauth2orize';
|
||||||
import oauth2Pkce from 'oauth2orize-pkce';
|
import oauth2Pkce from 'oauth2orize-pkce';
|
||||||
import fastifyCors from '@fastify/cors';
|
import fastifyCors from '@fastify/cors';
|
||||||
import fastifyView from '@fastify/view';
|
|
||||||
import pug from 'pug';
|
|
||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
import fastifyExpress from '@fastify/express';
|
import fastifyExpress from '@fastify/express';
|
||||||
import { verifyChallenge } from 'pkce-challenge';
|
import { verifyChallenge } from 'pkce-challenge';
|
||||||
|
|
@ -31,6 +29,8 @@ import { MemoryKVCache } from '@/misc/cache.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import Logger from '@/logger.js';
|
import Logger from '@/logger.js';
|
||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
|
import { HtmlTemplateService } from '@/server/web/HtmlTemplateService.js';
|
||||||
|
import { OAuthPage } from '@/server/web/views/oauth.js';
|
||||||
import type { ServerResponse } from 'node:http';
|
import type { ServerResponse } from 'node:http';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
|
||||||
|
|
@ -273,6 +273,7 @@ export class OAuth2ProviderService {
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
loggerService: LoggerService,
|
loggerService: LoggerService,
|
||||||
|
private htmlTemplateService: HtmlTemplateService,
|
||||||
) {
|
) {
|
||||||
this.#logger = loggerService.getLogger('oauth');
|
this.#logger = loggerService.getLogger('oauth');
|
||||||
|
|
||||||
|
|
@ -406,24 +407,16 @@ export class OAuth2ProviderService {
|
||||||
this.#logger.info(`Rendering authorization page for "${oauth2.client.name}"`);
|
this.#logger.info(`Rendering authorization page for "${oauth2.client.name}"`);
|
||||||
|
|
||||||
reply.header('Cache-Control', 'no-store');
|
reply.header('Cache-Control', 'no-store');
|
||||||
return await reply.view('oauth', {
|
return await HtmlTemplateService.replyHtml(reply, OAuthPage({
|
||||||
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
transactionId: oauth2.transactionID,
|
transactionId: oauth2.transactionID,
|
||||||
clientName: oauth2.client.name,
|
clientName: oauth2.client.name,
|
||||||
clientLogo: oauth2.client.logo,
|
clientLogo: oauth2.client.logo ?? undefined,
|
||||||
scope: oauth2.req.scope.join(' '),
|
scope: oauth2.req.scope,
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
fastify.post('/decision', async () => { });
|
fastify.post('/decision', async () => { });
|
||||||
|
|
||||||
fastify.register(fastifyView, {
|
|
||||||
root: fileURLToPath(new URL('../web/views', import.meta.url)),
|
|
||||||
engine: { pug },
|
|
||||||
defaultContext: {
|
|
||||||
version: this.config.version,
|
|
||||||
config: this.config,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await fastify.register(fastifyExpress);
|
await fastify.register(fastifyExpress);
|
||||||
fastify.use('/authorize', this.#server.authorize(((areq, done) => {
|
fastify.use('/authorize', this.#server.authorize(((areq, done) => {
|
||||||
(async (): Promise<Parameters<typeof done>> => {
|
(async (): Promise<Parameters<typeof done>> => {
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,16 @@ import { fileURLToPath } from 'node:url';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import pug from 'pug';
|
|
||||||
import { In, IsNull } from 'typeorm';
|
import { In, IsNull } from 'typeorm';
|
||||||
import fastifyStatic from '@fastify/static';
|
import fastifyStatic from '@fastify/static';
|
||||||
import fastifyView from '@fastify/view';
|
|
||||||
import fastifyProxy from '@fastify/http-proxy';
|
import fastifyProxy from '@fastify/http-proxy';
|
||||||
import vary from 'vary';
|
import vary from 'vary';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { getNoteSummary } from '@/misc/get-note-summary.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import * as Acct from '@/misc/acct.js';
|
import * as Acct from '@/misc/acct.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { PageEntityService } from '@/core/entities/PageEntityService.js';
|
import { PageEntityService } from '@/core/entities/PageEntityService.js';
|
||||||
import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
|
|
||||||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||||
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
||||||
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
||||||
|
|
@ -41,14 +37,33 @@ import type {
|
||||||
} from '@/models/_.js';
|
} from '@/models/_.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
|
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
|
||||||
|
import { htmlSafeJsonStringify } from '@/misc/json-stringify-html-safe.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
|
||||||
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
|
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
|
||||||
import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
|
import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
|
||||||
import { FeedService } from './FeedService.js';
|
import { FeedService } from './FeedService.js';
|
||||||
import { UrlPreviewService } from './UrlPreviewService.js';
|
import { UrlPreviewService } from './UrlPreviewService.js';
|
||||||
import { ClientLoggerService } from './ClientLoggerService.js';
|
import { ClientLoggerService } from './ClientLoggerService.js';
|
||||||
|
import { HtmlTemplateService } from './HtmlTemplateService.js';
|
||||||
|
|
||||||
|
import { BasePage } from './views/base.js';
|
||||||
|
import { UserPage } from './views/user.js';
|
||||||
|
import { NotePage } from './views/note.js';
|
||||||
|
import { PagePage } from './views/page.js';
|
||||||
|
import { ClipPage } from './views/clip.js';
|
||||||
|
import { FlashPage } from './views/flash.js';
|
||||||
|
import { GalleryPostPage } from './views/gallery-post.js';
|
||||||
|
import { ChannelPage } from './views/channel.js';
|
||||||
|
import { ReversiGamePage } from './views/reversi-game.js';
|
||||||
|
import { AnnouncementPage } from './views/announcement.js';
|
||||||
|
import { BaseEmbed } from './views/base-embed.js';
|
||||||
|
import { InfoCardPage } from './views/info-card.js';
|
||||||
|
import { BiosPage } from './views/bios.js';
|
||||||
|
import { CliPage } from './views/cli.js';
|
||||||
|
import { FlushPage } from './views/flush.js';
|
||||||
|
import { ErrorPage } from './views/error.js';
|
||||||
|
|
||||||
import type { FastifyError, FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
import type { FastifyError, FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
|
|
@ -62,20 +77,6 @@ const frontendViteOut = `${_dirname}/../../../../../built/_frontend_vite_/`;
|
||||||
const frontendEmbedViteOut = `${_dirname}/../../../../../built/_frontend_embed_vite_/`;
|
const frontendEmbedViteOut = `${_dirname}/../../../../../built/_frontend_embed_vite_/`;
|
||||||
const tarball = `${_dirname}/../../../../../built/tarball/`;
|
const tarball = `${_dirname}/../../../../../built/tarball/`;
|
||||||
|
|
||||||
const ESCAPE_LOOKUP = {
|
|
||||||
'&': '\\u0026',
|
|
||||||
'>': '\\u003e',
|
|
||||||
'<': '\\u003c',
|
|
||||||
'\u2028': '\\u2028',
|
|
||||||
'\u2029': '\\u2029',
|
|
||||||
} as Record<string, string>;
|
|
||||||
|
|
||||||
const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
|
|
||||||
|
|
||||||
function htmlSafeJsonStringify(obj: any): string {
|
|
||||||
return JSON.stringify(obj).replace(ESCAPE_REGEX, x => ESCAPE_LOOKUP[x]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ClientServerService {
|
export class ClientServerService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
@ -121,7 +122,6 @@ export class ClientServerService {
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private pageEntityService: PageEntityService,
|
private pageEntityService: PageEntityService,
|
||||||
private metaEntityService: MetaEntityService,
|
|
||||||
private galleryPostEntityService: GalleryPostEntityService,
|
private galleryPostEntityService: GalleryPostEntityService,
|
||||||
private clipEntityService: ClipEntityService,
|
private clipEntityService: ClipEntityService,
|
||||||
private channelEntityService: ChannelEntityService,
|
private channelEntityService: ChannelEntityService,
|
||||||
|
|
@ -129,7 +129,7 @@ export class ClientServerService {
|
||||||
private announcementEntityService: AnnouncementEntityService,
|
private announcementEntityService: AnnouncementEntityService,
|
||||||
private urlPreviewService: UrlPreviewService,
|
private urlPreviewService: UrlPreviewService,
|
||||||
private feedService: FeedService,
|
private feedService: FeedService,
|
||||||
private roleService: RoleService,
|
private htmlTemplateService: HtmlTemplateService,
|
||||||
private clientLoggerService: ClientLoggerService,
|
private clientLoggerService: ClientLoggerService,
|
||||||
) {
|
) {
|
||||||
//this.createServer = this.createServer.bind(this);
|
//this.createServer = this.createServer.bind(this);
|
||||||
|
|
@ -195,38 +195,10 @@ export class ClientServerService {
|
||||||
return (manifest);
|
return (manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
private async generateCommonPugData(meta: MiMeta) {
|
|
||||||
return {
|
|
||||||
instanceName: meta.name ?? 'Misskey',
|
|
||||||
icon: meta.iconUrl,
|
|
||||||
appleTouchIcon: meta.app512IconUrl,
|
|
||||||
themeColor: meta.themeColor,
|
|
||||||
serverErrorImageUrl: meta.serverErrorImageUrl ?? 'https://xn--931a.moe/assets/error.jpg',
|
|
||||||
infoImageUrl: meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg',
|
|
||||||
notFoundImageUrl: meta.notFoundImageUrl ?? 'https://xn--931a.moe/assets/not-found.jpg',
|
|
||||||
instanceUrl: this.config.url,
|
|
||||||
metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(meta)),
|
|
||||||
now: Date.now(),
|
|
||||||
federationEnabled: this.meta.federation !== 'none',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||||
const configUrl = new URL(this.config.url);
|
const configUrl = new URL(this.config.url);
|
||||||
|
|
||||||
fastify.register(fastifyView, {
|
|
||||||
root: _dirname + '/views',
|
|
||||||
engine: {
|
|
||||||
pug: pug,
|
|
||||||
},
|
|
||||||
defaultContext: {
|
|
||||||
version: this.config.version,
|
|
||||||
config: this.config,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.addHook('onRequest', (request, reply, done) => {
|
fastify.addHook('onRequest', (request, reply, done) => {
|
||||||
// クリックジャッキング防止のためiFrameの中に入れられないようにする
|
// クリックジャッキング防止のためiFrameの中に入れられないようにする
|
||||||
reply.header('X-Frame-Options', 'DENY');
|
reply.header('X-Frame-Options', 'DENY');
|
||||||
|
|
@ -427,16 +399,15 @@ export class ClientServerService {
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const renderBase = async (reply: FastifyReply, data: { [key: string]: any } = {}) => {
|
const renderBase = async (reply: FastifyReply, data: Partial<Parameters<typeof BasePage>[0]> = {}) => {
|
||||||
reply.header('Cache-Control', 'public, max-age=30');
|
reply.header('Cache-Control', 'public, max-age=30');
|
||||||
return await reply.view('base', {
|
return await HtmlTemplateService.replyHtml(reply, BasePage({
|
||||||
img: this.meta.bannerUrl,
|
img: this.meta.bannerUrl ?? undefined,
|
||||||
url: this.config.url,
|
|
||||||
title: this.meta.name ?? 'Misskey',
|
title: this.meta.name ?? 'Misskey',
|
||||||
desc: this.meta.description,
|
desc: this.meta.description ?? undefined,
|
||||||
...await this.generateCommonPugData(this.meta),
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
...data,
|
...data,
|
||||||
});
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
// URL preview endpoint
|
// URL preview endpoint
|
||||||
|
|
@ -518,11 +489,6 @@ export class ClientServerService {
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||||
const me = profile.fields
|
|
||||||
? profile.fields
|
|
||||||
.filter(filed => filed.value != null && filed.value.match(/^https?:/))
|
|
||||||
.map(field => field.value)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
if (profile.preventAiLearning) {
|
if (profile.preventAiLearning) {
|
||||||
|
|
@ -535,15 +501,15 @@ export class ClientServerService {
|
||||||
userProfile: profile,
|
userProfile: profile,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await reply.view('user', {
|
return await HtmlTemplateService.replyHtml(reply, UserPage({
|
||||||
user, profile, me,
|
user: _user,
|
||||||
avatarUrl: _user.avatarUrl,
|
profile,
|
||||||
sub: request.params.sub,
|
sub: request.params.sub,
|
||||||
...await this.generateCommonPugData(this.meta),
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
clientCtx: htmlSafeJsonStringify({
|
clientCtxJson: htmlSafeJsonStringify({
|
||||||
user: _user,
|
user: _user,
|
||||||
}),
|
}),
|
||||||
});
|
}));
|
||||||
} else {
|
} else {
|
||||||
// リモートユーザーなので
|
// リモートユーザーなので
|
||||||
// モデレータがAPI経由で参照可能にするために404にはしない
|
// モデレータがAPI経由で参照可能にするために404にはしない
|
||||||
|
|
@ -594,17 +560,14 @@ export class ClientServerService {
|
||||||
reply.header('X-Robots-Tag', 'noimageai');
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
reply.header('X-Robots-Tag', 'noai');
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
}
|
}
|
||||||
return await reply.view('note', {
|
return await HtmlTemplateService.replyHtml(reply, NotePage({
|
||||||
note: _note,
|
note: _note,
|
||||||
profile,
|
profile,
|
||||||
avatarUrl: _note.user.avatarUrl,
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
// TODO: Let locale changeable by instance setting
|
clientCtxJson: htmlSafeJsonStringify({
|
||||||
summary: getNoteSummary(_note),
|
|
||||||
...await this.generateCommonPugData(this.meta),
|
|
||||||
clientCtx: htmlSafeJsonStringify({
|
|
||||||
note: _note,
|
note: _note,
|
||||||
}),
|
}),
|
||||||
});
|
}));
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
}
|
}
|
||||||
|
|
@ -637,12 +600,11 @@ export class ClientServerService {
|
||||||
reply.header('X-Robots-Tag', 'noimageai');
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
reply.header('X-Robots-Tag', 'noai');
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
}
|
}
|
||||||
return await reply.view('page', {
|
return await HtmlTemplateService.replyHtml(reply, PagePage({
|
||||||
page: _page,
|
page: _page,
|
||||||
profile,
|
profile,
|
||||||
avatarUrl: _page.user.avatarUrl,
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
...await this.generateCommonPugData(this.meta),
|
}));
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
}
|
}
|
||||||
|
|
@ -662,12 +624,11 @@ export class ClientServerService {
|
||||||
reply.header('X-Robots-Tag', 'noimageai');
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
reply.header('X-Robots-Tag', 'noai');
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
}
|
}
|
||||||
return await reply.view('flash', {
|
return await HtmlTemplateService.replyHtml(reply, FlashPage({
|
||||||
flash: _flash,
|
flash: _flash,
|
||||||
profile,
|
profile,
|
||||||
avatarUrl: _flash.user.avatarUrl,
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
...await this.generateCommonPugData(this.meta),
|
}));
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
}
|
}
|
||||||
|
|
@ -687,15 +648,14 @@ export class ClientServerService {
|
||||||
reply.header('X-Robots-Tag', 'noimageai');
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
reply.header('X-Robots-Tag', 'noai');
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
}
|
}
|
||||||
return await reply.view('clip', {
|
return await HtmlTemplateService.replyHtml(reply, ClipPage({
|
||||||
clip: _clip,
|
clip: _clip,
|
||||||
profile,
|
profile,
|
||||||
avatarUrl: _clip.user.avatarUrl,
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
...await this.generateCommonPugData(this.meta),
|
clientCtxJson: htmlSafeJsonStringify({
|
||||||
clientCtx: htmlSafeJsonStringify({
|
|
||||||
clip: _clip,
|
clip: _clip,
|
||||||
}),
|
}),
|
||||||
});
|
}));
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
}
|
}
|
||||||
|
|
@ -713,12 +673,11 @@ export class ClientServerService {
|
||||||
reply.header('X-Robots-Tag', 'noimageai');
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
reply.header('X-Robots-Tag', 'noai');
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
}
|
}
|
||||||
return await reply.view('gallery-post', {
|
return await HtmlTemplateService.replyHtml(reply, GalleryPostPage({
|
||||||
post: _post,
|
galleryPost: _post,
|
||||||
profile,
|
profile,
|
||||||
avatarUrl: _post.user.avatarUrl,
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
...await this.generateCommonPugData(this.meta),
|
}));
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
}
|
}
|
||||||
|
|
@ -733,10 +692,10 @@ export class ClientServerService {
|
||||||
if (channel) {
|
if (channel) {
|
||||||
const _channel = await this.channelEntityService.pack(channel);
|
const _channel = await this.channelEntityService.pack(channel);
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
return await reply.view('channel', {
|
return await HtmlTemplateService.replyHtml(reply, ChannelPage({
|
||||||
channel: _channel,
|
channel: _channel,
|
||||||
...await this.generateCommonPugData(this.meta),
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
});
|
}));
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
}
|
}
|
||||||
|
|
@ -751,10 +710,10 @@ export class ClientServerService {
|
||||||
if (game) {
|
if (game) {
|
||||||
const _game = await this.reversiGameEntityService.packDetail(game);
|
const _game = await this.reversiGameEntityService.packDetail(game);
|
||||||
reply.header('Cache-Control', 'public, max-age=3600');
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
return await reply.view('reversi-game', {
|
return await HtmlTemplateService.replyHtml(reply, ReversiGamePage({
|
||||||
game: _game,
|
reversiGame: _game,
|
||||||
...await this.generateCommonPugData(this.meta),
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
});
|
}));
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
}
|
}
|
||||||
|
|
@ -770,10 +729,10 @@ export class ClientServerService {
|
||||||
if (announcement) {
|
if (announcement) {
|
||||||
const _announcement = await this.announcementEntityService.pack(announcement);
|
const _announcement = await this.announcementEntityService.pack(announcement);
|
||||||
reply.header('Cache-Control', 'public, max-age=3600');
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
return await reply.view('announcement', {
|
return await HtmlTemplateService.replyHtml(reply, AnnouncementPage({
|
||||||
announcement: _announcement,
|
announcement: _announcement,
|
||||||
...await this.generateCommonPugData(this.meta),
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
});
|
}));
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
}
|
}
|
||||||
|
|
@ -806,13 +765,13 @@ export class ClientServerService {
|
||||||
const _user = await this.userEntityService.pack(user);
|
const _user = await this.userEntityService.pack(user);
|
||||||
|
|
||||||
reply.header('Cache-Control', 'public, max-age=3600');
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
return await reply.view('base-embed', {
|
return await HtmlTemplateService.replyHtml(reply, BaseEmbed({
|
||||||
title: this.meta.name ?? 'Misskey',
|
title: this.meta.name ?? 'Misskey',
|
||||||
...await this.generateCommonPugData(this.meta),
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
embedCtx: htmlSafeJsonStringify({
|
embedCtxJson: htmlSafeJsonStringify({
|
||||||
user: _user,
|
user: _user,
|
||||||
}),
|
}),
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => {
|
fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => {
|
||||||
|
|
@ -832,13 +791,13 @@ export class ClientServerService {
|
||||||
const _note = await this.noteEntityService.pack(note, null, { detail: true });
|
const _note = await this.noteEntityService.pack(note, null, { detail: true });
|
||||||
|
|
||||||
reply.header('Cache-Control', 'public, max-age=3600');
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
return await reply.view('base-embed', {
|
return await HtmlTemplateService.replyHtml(reply, BaseEmbed({
|
||||||
title: this.meta.name ?? 'Misskey',
|
title: this.meta.name ?? 'Misskey',
|
||||||
...await this.generateCommonPugData(this.meta),
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
embedCtx: htmlSafeJsonStringify({
|
embedCtxJson: htmlSafeJsonStringify({
|
||||||
note: _note,
|
note: _note,
|
||||||
}),
|
}),
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.get<{ Params: { clip: string; } }>('/embed/clips/:clip', async (request, reply) => {
|
fastify.get<{ Params: { clip: string; } }>('/embed/clips/:clip', async (request, reply) => {
|
||||||
|
|
@ -853,48 +812,46 @@ export class ClientServerService {
|
||||||
const _clip = await this.clipEntityService.pack(clip);
|
const _clip = await this.clipEntityService.pack(clip);
|
||||||
|
|
||||||
reply.header('Cache-Control', 'public, max-age=3600');
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
return await reply.view('base-embed', {
|
return await HtmlTemplateService.replyHtml(reply, BaseEmbed({
|
||||||
title: this.meta.name ?? 'Misskey',
|
title: this.meta.name ?? 'Misskey',
|
||||||
...await this.generateCommonPugData(this.meta),
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
embedCtx: htmlSafeJsonStringify({
|
embedCtxJson: htmlSafeJsonStringify({
|
||||||
clip: _clip,
|
clip: _clip,
|
||||||
}),
|
}),
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.get('/embed/*', async (request, reply) => {
|
fastify.get('/embed/*', async (request, reply) => {
|
||||||
reply.removeHeader('X-Frame-Options');
|
reply.removeHeader('X-Frame-Options');
|
||||||
|
|
||||||
reply.header('Cache-Control', 'public, max-age=3600');
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
return await reply.view('base-embed', {
|
return await HtmlTemplateService.replyHtml(reply, BaseEmbed({
|
||||||
title: this.meta.name ?? 'Misskey',
|
title: this.meta.name ?? 'Misskey',
|
||||||
...await this.generateCommonPugData(this.meta),
|
...await this.htmlTemplateService.getCommonData(),
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.get('/_info_card_', async (request, reply) => {
|
fastify.get('/_info_card_', async (request, reply) => {
|
||||||
reply.removeHeader('X-Frame-Options');
|
reply.removeHeader('X-Frame-Options');
|
||||||
|
|
||||||
return await reply.view('info-card', {
|
return await HtmlTemplateService.replyHtml(reply, InfoCardPage({
|
||||||
version: this.config.version,
|
version: this.config.version,
|
||||||
host: this.config.host,
|
config: this.config,
|
||||||
meta: this.meta,
|
meta: this.meta,
|
||||||
originalUsersCount: await this.usersRepository.countBy({ host: IsNull() }),
|
}));
|
||||||
originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
fastify.get('/bios', async (request, reply) => {
|
fastify.get('/bios', async (request, reply) => {
|
||||||
return await reply.view('bios', {
|
return await HtmlTemplateService.replyHtml(reply, BiosPage({
|
||||||
version: this.config.version,
|
version: this.config.version,
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.get('/cli', async (request, reply) => {
|
fastify.get('/cli', async (request, reply) => {
|
||||||
return await reply.view('cli', {
|
return await HtmlTemplateService.replyHtml(reply, CliPage({
|
||||||
version: this.config.version,
|
version: this.config.version,
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
const override = (source: string, target: string, depth = 0) =>
|
const override = (source: string, target: string, depth = 0) =>
|
||||||
|
|
@ -917,7 +874,7 @@ export class ClientServerService {
|
||||||
reply.header('Clear-Site-Data', '"*"');
|
reply.header('Clear-Site-Data', '"*"');
|
||||||
}
|
}
|
||||||
reply.header('Set-Cookie', 'http-flush-failed=1; Path=/flush; Max-Age=60');
|
reply.header('Set-Cookie', 'http-flush-failed=1; Path=/flush; Max-Age=60');
|
||||||
return await reply.view('flush');
|
return await HtmlTemplateService.replyHtml(reply, FlushPage());
|
||||||
});
|
});
|
||||||
|
|
||||||
// streamingに非WebSocketリクエストが来た場合にbase htmlをキャシュ付きで返すと、Proxy等でそのパスがキャッシュされておかしくなる
|
// streamingに非WebSocketリクエストが来た場合にbase htmlをキャシュ付きで返すと、Proxy等でそのパスがキャッシュされておかしくなる
|
||||||
|
|
@ -943,10 +900,10 @@ export class ClientServerService {
|
||||||
});
|
});
|
||||||
reply.code(500);
|
reply.code(500);
|
||||||
reply.header('Cache-Control', 'max-age=10, must-revalidate');
|
reply.header('Cache-Control', 'max-age=10, must-revalidate');
|
||||||
return await reply.view('error', {
|
return await HtmlTemplateService.replyHtml(reply, ErrorPage({
|
||||||
code: error.code,
|
code: error.code,
|
||||||
id: errId,
|
id: errId,
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { dirname } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { promises as fsp } from 'node:fs';
|
||||||
|
import { languages } from 'i18n';
|
||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { htmlSafeJsonStringify } from '@/misc/json-stringify-html-safe.js';
|
||||||
|
import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
|
||||||
|
import type { FastifyReply } from 'fastify';
|
||||||
|
import type { Config } from '@/config.js';
|
||||||
|
import type { MiMeta } from '@/models/Meta.js';
|
||||||
|
import type { CommonData } from './views/_.js';
|
||||||
|
|
||||||
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
|
const _dirname = dirname(_filename);
|
||||||
|
|
||||||
|
const frontendVitePublic = `${_dirname}/../../../../frontend/public/`;
|
||||||
|
const frontendEmbedVitePublic = `${_dirname}/../../../../frontend-embed/public/`;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class HtmlTemplateService {
|
||||||
|
private frontendBootloadersFetched = false;
|
||||||
|
public frontendBootloaderJs: string | null = null;
|
||||||
|
public frontendBootloaderCss: string | null = null;
|
||||||
|
public frontendEmbedBootloaderJs: string | null = null;
|
||||||
|
public frontendEmbedBootloaderCss: string | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.config)
|
||||||
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
|
private metaEntityService: MetaEntityService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async prepareFrontendBootloaders() {
|
||||||
|
if (this.frontendBootloadersFetched) return;
|
||||||
|
this.frontendBootloadersFetched = true;
|
||||||
|
|
||||||
|
const [bootJs, bootCss, embedBootJs, embedBootCss] = await Promise.all([
|
||||||
|
fsp.readFile(`${frontendVitePublic}loader/boot.js`, 'utf-8').catch(() => null),
|
||||||
|
fsp.readFile(`${frontendVitePublic}loader/style.css`, 'utf-8').catch(() => null),
|
||||||
|
fsp.readFile(`${frontendEmbedVitePublic}loader/boot.js`, 'utf-8').catch(() => null),
|
||||||
|
fsp.readFile(`${frontendEmbedVitePublic}loader/style.css`, 'utf-8').catch(() => null),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (bootJs != null) {
|
||||||
|
this.frontendBootloaderJs = bootJs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bootCss != null) {
|
||||||
|
this.frontendBootloaderCss = bootCss;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embedBootJs != null) {
|
||||||
|
this.frontendEmbedBootloaderJs = embedBootJs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embedBootCss != null) {
|
||||||
|
this.frontendEmbedBootloaderCss = embedBootCss;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async getCommonData(): Promise<CommonData> {
|
||||||
|
await this.prepareFrontendBootloaders();
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: this.config.version,
|
||||||
|
config: this.config,
|
||||||
|
langs: [...languages],
|
||||||
|
instanceName: this.meta.name ?? 'Misskey',
|
||||||
|
icon: this.meta.iconUrl,
|
||||||
|
appleTouchIcon: this.meta.app512IconUrl,
|
||||||
|
themeColor: this.meta.themeColor,
|
||||||
|
serverErrorImageUrl: this.meta.serverErrorImageUrl ?? 'https://xn--931a.moe/assets/error.jpg',
|
||||||
|
infoImageUrl: this.meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg',
|
||||||
|
notFoundImageUrl: this.meta.notFoundImageUrl ?? 'https://xn--931a.moe/assets/not-found.jpg',
|
||||||
|
instanceUrl: this.config.url,
|
||||||
|
metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(this.meta)),
|
||||||
|
now: Date.now(),
|
||||||
|
federationEnabled: this.meta.federation !== 'none',
|
||||||
|
frontendBootloaderJs: this.frontendBootloaderJs,
|
||||||
|
frontendBootloaderCss: this.frontendBootloaderCss,
|
||||||
|
frontendEmbedBootloaderJs: this.frontendEmbedBootloaderJs,
|
||||||
|
frontendEmbedBootloaderCss: this.frontendEmbedBootloaderCss,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async replyHtml(reply: FastifyReply, html: string | Promise<string>) {
|
||||||
|
reply.header('Content-Type', 'text/html; charset=utf-8');
|
||||||
|
const _html = await html;
|
||||||
|
return reply.send(_html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Config } from '@/config.js';
|
||||||
|
|
||||||
|
export const comment = `<!--
|
||||||
|
_____ _ _
|
||||||
|
| |_|___ ___| |_ ___ _ _
|
||||||
|
| | | | |_ -|_ -| '_| -_| | |
|
||||||
|
|_|_|_|_|___|___|_,_|___|_ |
|
||||||
|
|___|
|
||||||
|
Thank you for using Misskey!
|
||||||
|
If you are reading this message... how about joining the development?
|
||||||
|
https://github.com/misskey-dev/misskey
|
||||||
|
|
||||||
|
-->`;
|
||||||
|
|
||||||
|
export const defaultDescription = '✨🌎✨ A interplanetary communication platform ✨🚀✨';
|
||||||
|
|
||||||
|
export type MinimumCommonData = {
|
||||||
|
version: string;
|
||||||
|
config: Config;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CommonData = MinimumCommonData & {
|
||||||
|
langs: string[];
|
||||||
|
instanceName: string;
|
||||||
|
icon: string | null;
|
||||||
|
appleTouchIcon: string | null;
|
||||||
|
themeColor: string | null;
|
||||||
|
serverErrorImageUrl: string;
|
||||||
|
infoImageUrl: string;
|
||||||
|
notFoundImageUrl: string;
|
||||||
|
instanceUrl: string;
|
||||||
|
now: number;
|
||||||
|
federationEnabled: boolean;
|
||||||
|
frontendBootloaderJs: string | null;
|
||||||
|
frontendBootloaderCss: string | null;
|
||||||
|
frontendEmbedBootloaderJs: string | null;
|
||||||
|
frontendEmbedBootloaderCss: string | null;
|
||||||
|
metaJson?: string;
|
||||||
|
clientCtxJson?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CommonPropsMinimum<T = Record<string, any>> = MinimumCommonData & T;
|
||||||
|
|
||||||
|
export type CommonProps<T = Record<string, any>> = CommonData & T;
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function Splash(props: {
|
||||||
|
icon?: string | null;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div id="splash">
|
||||||
|
<img id="splashIcon" src={props.icon || '/static-assets/splash.png'} />
|
||||||
|
<div id="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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
extends ./base
|
|
||||||
|
|
||||||
block vars
|
|
||||||
- const title = announcement.title;
|
|
||||||
- const description = announcement.text.length > 100 ? announcement.text.slice(0, 100) + '…' : announcement.text;
|
|
||||||
- const url = `${config.url}/announcements/${announcement.id}`;
|
|
||||||
|
|
||||||
block title
|
|
||||||
= `${title} | ${instanceName}`
|
|
||||||
|
|
||||||
block desc
|
|
||||||
meta(name='description' content=description)
|
|
||||||
|
|
||||||
block og
|
|
||||||
meta(property='og:type' content='article')
|
|
||||||
meta(property='og:title' content= title)
|
|
||||||
meta(property='og:description' content= description)
|
|
||||||
meta(property='og:url' content= url)
|
|
||||||
if announcement.imageUrl
|
|
||||||
meta(property='og:image' content=announcement.imageUrl)
|
|
||||||
meta(property='twitter:card' content='summary_large_image')
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import { Layout } from '@/server/web/views/base.js';
|
||||||
|
|
||||||
|
export function AnnouncementPage(props: CommonProps<{
|
||||||
|
announcement: Packed<'Announcement'>;
|
||||||
|
}>) {
|
||||||
|
const description = props.announcement.text.length > 100 ? props.announcement.text.slice(0, 100) + '…' : props.announcement.text;
|
||||||
|
|
||||||
|
function ogBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:title" content={props.announcement.title} />
|
||||||
|
<meta property="og:description" content={description} />
|
||||||
|
<meta property="og:url" content={`${props.config.url}/announcements/${props.announcement.id}`} />
|
||||||
|
{props.announcement.imageUrl ? (
|
||||||
|
<>
|
||||||
|
<meta property="og:image" content={props.announcement.imageUrl} />
|
||||||
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
{...props}
|
||||||
|
title={`${props.announcement.title} | ${props.instanceName}`}
|
||||||
|
desc={description}
|
||||||
|
ogSlot={ogBlock()}
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
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')
|
|
||||||
|
|
||||||
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 = !{JSON.stringify(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
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { comment } from '@/server/web/views/_.js';
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import { Splash } from '@/server/web/views/_splash.js';
|
||||||
|
import type { PropsWithChildren, Children } from '@kitajs/html';
|
||||||
|
|
||||||
|
export function BaseEmbed(props: PropsWithChildren<CommonProps<{
|
||||||
|
title?: string;
|
||||||
|
noindex?: boolean;
|
||||||
|
desc?: string;
|
||||||
|
img?: string;
|
||||||
|
serverErrorImageUrl?: string;
|
||||||
|
infoImageUrl?: string;
|
||||||
|
notFoundImageUrl?: string;
|
||||||
|
metaJson?: string;
|
||||||
|
embedCtxJson?: string;
|
||||||
|
|
||||||
|
titleSlot?: Children;
|
||||||
|
metaSlot?: Children;
|
||||||
|
}>>) {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// 変数名をsafeで始めることでエラーをスキップ
|
||||||
|
const safeMetaJson = props.metaJson;
|
||||||
|
const safeEmbedCtxJson = props.embedCtxJson;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{'<!DOCTYPE html>'}
|
||||||
|
{comment}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="application-name" content="Misskey" />
|
||||||
|
<meta name="referer" content="origin" />
|
||||||
|
<meta name="theme-color" content={props.themeColor ?? '#86b300'} />
|
||||||
|
<meta name="theme-color-orig" content={props.themeColor ?? '#86b300'} />
|
||||||
|
<meta property="og:site_name" content={props.instanceName || 'Misskey'} />
|
||||||
|
<meta property="instance_url" content={props.instanceUrl} />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
|
||||||
|
<meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no" />
|
||||||
|
<link rel="icon" href={props.icon ?? '/favicon.ico'} />
|
||||||
|
<link rel="apple-touch-icon" href={props.appleTouchIcon ?? '/apple-touch-icon.png'} />
|
||||||
|
|
||||||
|
{!props.config.frontendEmbedManifestExists ? <script type="module" src="/embed_vite/@vite/client"></script> : null}
|
||||||
|
|
||||||
|
{props.config.frontendEmbedEntry.css != null ? props.config.frontendEmbedEntry.css.map((href) => (
|
||||||
|
<link rel="stylesheet" href={`/embed_vite/${href}`} />
|
||||||
|
)) : null}
|
||||||
|
|
||||||
|
{props.titleSlot ?? <title safe>{props.title || 'Misskey'}</title>}
|
||||||
|
|
||||||
|
{props.metaSlot}
|
||||||
|
|
||||||
|
<meta name="robots" content="noindex" />
|
||||||
|
|
||||||
|
{props.frontendEmbedBootloaderCss != null ? <style safe>{props.frontendEmbedBootloaderCss}</style> : <link rel="stylesheet" href="/embed_vite/loader/style.css" />}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const VERSION = '{props.version}';
|
||||||
|
const CLIENT_ENTRY = {JSON.stringify(props.config.frontendEmbedEntry.file)};
|
||||||
|
const LANGS = {JSON.stringify(props.langs)};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{safeMetaJson != null ? <script type="application/json" id="misskey_meta" data-generated-at={now}>{safeMetaJson}</script> : null}
|
||||||
|
{safeEmbedCtxJson != null ? <script type="application/json" id="misskey_embedCtx" data-generated-at={now}>{safeEmbedCtxJson}</script> : null}
|
||||||
|
|
||||||
|
{props.frontendEmbedBootloaderJs != null ? <script>{props.frontendEmbedBootloaderJs}</script> : <script src="/embed_vite/loader/boot.js"></script>}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<p>
|
||||||
|
JavaScriptを有効にしてください<br />
|
||||||
|
Please turn on your JavaScript
|
||||||
|
</p>
|
||||||
|
</noscript>
|
||||||
|
<Splash icon={props.icon} />
|
||||||
|
{props.children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
block vars
|
|
||||||
|
|
||||||
block loadClientEntry
|
|
||||||
- const entry = config.frontendEntry;
|
|
||||||
- const baseUrl = config.url;
|
|
||||||
|
|
||||||
doctype html
|
|
||||||
|
|
||||||
//
|
|
||||||
-
|
|
||||||
_____ _ _
|
|
||||||
| |_|___ ___| |_ ___ _ _
|
|
||||||
| | | | |_ -|_ -| '_| -_| | |
|
|
||||||
|_|_|_|_|___|___|_,_|___|_ |
|
|
||||||
|___|
|
|
||||||
Thank you for using Misskey!
|
|
||||||
If you are reading this message... how about joining the development?
|
|
||||||
https://github.com/misskey-dev/misskey
|
|
||||||
|
|
||||||
|
|
||||||
html
|
|
||||||
|
|
||||||
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, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover')
|
|
||||||
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='manifest' href='/manifest.json')
|
|
||||||
link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${baseUrl}/opensearch.xml`)
|
|
||||||
link(rel='prefetch' href=serverErrorImageUrl)
|
|
||||||
link(rel='prefetch' href=infoImageUrl)
|
|
||||||
link(rel='prefetch' href=notFoundImageUrl)
|
|
||||||
|
|
||||||
if !config.frontendManifestExists
|
|
||||||
script(type="module" src="/vite/@vite/client")
|
|
||||||
|
|
||||||
if Array.isArray(entry.css)
|
|
||||||
each href in entry.css
|
|
||||||
link(rel='stylesheet' href=`/vite/${href}`)
|
|
||||||
|
|
||||||
title
|
|
||||||
block title
|
|
||||||
= title || 'Misskey'
|
|
||||||
|
|
||||||
if noindex
|
|
||||||
meta(name='robots' content='noindex')
|
|
||||||
|
|
||||||
block desc
|
|
||||||
meta(name='description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨')
|
|
||||||
|
|
||||||
block meta
|
|
||||||
|
|
||||||
block og
|
|
||||||
meta(property='og:title' content= title || 'Misskey')
|
|
||||||
meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨')
|
|
||||||
meta(property='og:image' content= img)
|
|
||||||
meta(property='twitter:card' content='summary')
|
|
||||||
|
|
||||||
style
|
|
||||||
include ../style.css
|
|
||||||
|
|
||||||
script.
|
|
||||||
var VERSION = "#{version}";
|
|
||||||
var CLIENT_ENTRY = !{JSON.stringify(entry.file)};
|
|
||||||
|
|
||||||
script(type='application/json' id='misskey_meta' data-generated-at=now)
|
|
||||||
!= metaJson
|
|
||||||
|
|
||||||
script(type='application/json' id='misskey_clientCtx' data-generated-at=now)
|
|
||||||
!= clientCtx
|
|
||||||
|
|
||||||
script
|
|
||||||
include ../boot.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
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { comment, defaultDescription } from '@/server/web/views/_.js';
|
||||||
|
import { Splash } from '@/server/web/views/_splash.js';
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import type { PropsWithChildren, Children } from '@kitajs/html';
|
||||||
|
|
||||||
|
export function Layout(props: PropsWithChildren<CommonProps<{
|
||||||
|
title?: string;
|
||||||
|
noindex?: boolean;
|
||||||
|
desc?: string;
|
||||||
|
img?: string;
|
||||||
|
serverErrorImageUrl?: string;
|
||||||
|
infoImageUrl?: string;
|
||||||
|
notFoundImageUrl?: string;
|
||||||
|
metaJson?: string;
|
||||||
|
clientCtxJson?: string;
|
||||||
|
|
||||||
|
titleSlot?: Children;
|
||||||
|
descSlot?: Children;
|
||||||
|
metaSlot?: Children;
|
||||||
|
ogSlot?: Children;
|
||||||
|
}>>) {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// 変数名をsafeで始めることでエラーをスキップ
|
||||||
|
const safeMetaJson = props.metaJson;
|
||||||
|
const safeClientCtxJson = props.clientCtxJson;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{'<!DOCTYPE html>'}
|
||||||
|
{comment}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="application-name" content="Misskey" />
|
||||||
|
<meta name="referer" content="origin" />
|
||||||
|
<meta name="theme-color" content={props.themeColor ?? '#86b300'} />
|
||||||
|
<meta name="theme-color-orig" content={props.themeColor ?? '#86b300'} />
|
||||||
|
<meta property="og:site_name" content={props.instanceName || 'Misskey'} />
|
||||||
|
<meta property="instance_url" content={props.instanceUrl} />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
|
||||||
|
<meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no" />
|
||||||
|
<link rel="icon" href={props.icon || '/favicon.ico'} />
|
||||||
|
<link rel="apple-touch-icon" href={props.appleTouchIcon || '/apple-touch-icon.png'} />
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<link rel="search" type="application/opensearchdescription+xml" title={props.title || 'Misskey'} href={`${props.config.url}/opensearch.xml`} />
|
||||||
|
{props.serverErrorImageUrl != null ? <link rel="prefetch" as="image" href={props.serverErrorImageUrl} /> : null}
|
||||||
|
{props.infoImageUrl != null ? <link rel="prefetch" as="image" href={props.infoImageUrl} /> : null}
|
||||||
|
{props.notFoundImageUrl != null ? <link rel="prefetch" as="image" href={props.notFoundImageUrl} /> : null}
|
||||||
|
|
||||||
|
{!props.config.frontendManifestExists ? <script type="module" src="/vite/@vite/client"></script> : null}
|
||||||
|
|
||||||
|
{props.config.frontendEntry.css != null ? props.config.frontendEntry.css.map((href) => (
|
||||||
|
<link rel="stylesheet" href={`/vite/${href}`} />
|
||||||
|
)) : null}
|
||||||
|
|
||||||
|
{props.titleSlot ?? <title safe>{props.title || 'Misskey'}</title>}
|
||||||
|
|
||||||
|
{props.noindex ? <meta name="robots" content="noindex" /> : null}
|
||||||
|
|
||||||
|
{props.descSlot ?? (props.desc != null ? <meta name="description" content={props.desc || defaultDescription} /> : null)}
|
||||||
|
|
||||||
|
{props.metaSlot}
|
||||||
|
|
||||||
|
{props.ogSlot ?? (
|
||||||
|
<>
|
||||||
|
<meta property="og:title" content={props.title || 'Misskey'} />
|
||||||
|
<meta property="og:description" content={props.desc || defaultDescription} />
|
||||||
|
{props.img != null ? <meta property="og:image" content={props.img} /> : null}
|
||||||
|
<meta property="twitter:card" content="summary" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{props.frontendBootloaderCss != null ? <style safe>{props.frontendBootloaderCss}</style> : <link rel="stylesheet" href="/vite/loader/style.css" />}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const VERSION = '{props.version}';
|
||||||
|
const CLIENT_ENTRY = {JSON.stringify(props.config.frontendEntry.file)};
|
||||||
|
const LANGS = {JSON.stringify(props.langs)};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{safeMetaJson != null ? <script type="application/json" id="misskey_meta" data-generated-at={now}>{safeMetaJson}</script> : null}
|
||||||
|
{safeClientCtxJson != null ? <script type="application/json" id="misskey_clientCtx" data-generated-at={now}>{safeClientCtxJson}</script> : null}
|
||||||
|
|
||||||
|
{props.frontendBootloaderJs != null ? <script>{props.frontendBootloaderJs}</script> : <script src="/vite/loader/boot.js"></script>}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<p>
|
||||||
|
JavaScriptを有効にしてください<br />
|
||||||
|
Please turn on your JavaScript
|
||||||
|
</p>
|
||||||
|
</noscript>
|
||||||
|
<Splash icon={props.icon} />
|
||||||
|
{props.children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Layout as BasePage };
|
||||||
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
doctype html
|
|
||||||
|
|
||||||
html
|
|
||||||
|
|
||||||
head
|
|
||||||
meta(charset='utf-8')
|
|
||||||
meta(name='application-name' content='Misskey')
|
|
||||||
title Misskey Repair Tool
|
|
||||||
style
|
|
||||||
include ../bios.css
|
|
||||||
script
|
|
||||||
include ../bios.js
|
|
||||||
|
|
||||||
body
|
|
||||||
header
|
|
||||||
h1 Misskey Repair Tool #{version}
|
|
||||||
main
|
|
||||||
div.tabs
|
|
||||||
button#ls edit local storage
|
|
||||||
div#content
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function BiosPage(props: {
|
||||||
|
version: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{'<!DOCTYPE html>'}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="application-name" content="Misskey" />
|
||||||
|
<title>Misskey Repair Tool</title>
|
||||||
|
<link rel="stylesheet" href="/static-assets/misc/bios.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1 safe>Misskey Repair Tool {props.version}</h1>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="tabs">
|
||||||
|
<button id="ls">edit local storage</button>
|
||||||
|
</div>
|
||||||
|
<div id="content"></div>
|
||||||
|
</main>
|
||||||
|
<script src="/static-assets/misc/bios.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
extends ./base
|
|
||||||
|
|
||||||
block vars
|
|
||||||
- const title = channel.name;
|
|
||||||
- const url = `${config.url}/channels/${channel.id}`;
|
|
||||||
|
|
||||||
block title
|
|
||||||
= `${title} | ${instanceName}`
|
|
||||||
|
|
||||||
block desc
|
|
||||||
meta(name='description' content= channel.description)
|
|
||||||
|
|
||||||
block og
|
|
||||||
meta(property='og:type' content='article')
|
|
||||||
meta(property='og:title' content= title)
|
|
||||||
meta(property='og:description' content= channel.description)
|
|
||||||
meta(property='og:url' content= url)
|
|
||||||
meta(property='og:image' content= channel.bannerUrl)
|
|
||||||
meta(property='twitter:card' content='summary')
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import { Layout } from '@/server/web/views/base.js';
|
||||||
|
|
||||||
|
export function ChannelPage(props: CommonProps<{
|
||||||
|
channel: Packed<'Channel'>;
|
||||||
|
}>) {
|
||||||
|
|
||||||
|
function ogBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:title" content={props.channel.name} />
|
||||||
|
{props.channel.description != null ? <meta property="og:description" content={props.channel.description} /> : null}
|
||||||
|
<meta property="og:url" content={`${props.config.url}/channels/${props.channel.id}`} />
|
||||||
|
{props.channel.bannerUrl ? (
|
||||||
|
<>
|
||||||
|
<meta property="og:image" content={props.channel.bannerUrl} />
|
||||||
|
<meta property="twitter:card" content="summary" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
{...props}
|
||||||
|
title={`${props.channel.name} | ${props.instanceName}`}
|
||||||
|
desc={props.channel.description ?? undefined}
|
||||||
|
ogSlot={ogBlock()}
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
doctype html
|
|
||||||
|
|
||||||
html
|
|
||||||
|
|
||||||
head
|
|
||||||
meta(charset='utf-8')
|
|
||||||
meta(name='application-name' content='Misskey')
|
|
||||||
title Misskey Cli
|
|
||||||
style
|
|
||||||
include ../cli.css
|
|
||||||
script
|
|
||||||
include ../cli.js
|
|
||||||
|
|
||||||
body
|
|
||||||
header
|
|
||||||
h1 Misskey Cli #{version}
|
|
||||||
main
|
|
||||||
div#form
|
|
||||||
textarea#text
|
|
||||||
button#submit submit
|
|
||||||
div#tl
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function CliPage(props: {
|
||||||
|
version: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{'<!DOCTYPE html>'}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="application-name" content="Misskey" />
|
||||||
|
<title>Misskey CLI Tool</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/static-assets/misc/cli.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1 safe>Misskey CLI {props.version}</h1>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div id="form">
|
||||||
|
<textarea id="text"></textarea>
|
||||||
|
<button id="submit">Submit</button>
|
||||||
|
</div>
|
||||||
|
<div id="tl"></div>
|
||||||
|
</main>
|
||||||
|
<script src="/static-assets/misc/cli.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
extends ./base
|
|
||||||
|
|
||||||
block vars
|
|
||||||
- const user = clip.user;
|
|
||||||
- const title = clip.name;
|
|
||||||
- const url = `${config.url}/clips/${clip.id}`;
|
|
||||||
|
|
||||||
block title
|
|
||||||
= `${title} | ${instanceName}`
|
|
||||||
|
|
||||||
block desc
|
|
||||||
meta(name='description' content= clip.description)
|
|
||||||
|
|
||||||
block og
|
|
||||||
meta(property='og:type' content='article')
|
|
||||||
meta(property='og:title' content= title)
|
|
||||||
meta(property='og:description' content= clip.description)
|
|
||||||
meta(property='og:url' content= url)
|
|
||||||
meta(property='og:image' content= avatarUrl)
|
|
||||||
meta(property='twitter:card' content='summary')
|
|
||||||
|
|
||||||
block meta
|
|
||||||
if profile.noCrawle
|
|
||||||
meta(name='robots' content='noindex')
|
|
||||||
if profile.preventAiLearning
|
|
||||||
meta(name='robots' content='noimageai')
|
|
||||||
meta(name='robots' content='noai')
|
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
|
||||||
meta(name='misskey:user-id' content=user.id)
|
|
||||||
meta(name='misskey:clip-id' content=clip.id)
|
|
||||||
|
|
||||||
// todo
|
|
||||||
if user.twitter
|
|
||||||
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { MiUserProfile } from '@/models/UserProfile.js';
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import { Layout } from '@/server/web/views/base.js';
|
||||||
|
|
||||||
|
export function ClipPage(props: CommonProps<{
|
||||||
|
clip: Packed<'Clip'>;
|
||||||
|
profile: MiUserProfile;
|
||||||
|
}>) {
|
||||||
|
function ogBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:title" content={props.clip.name} />
|
||||||
|
{props.clip.description != null ? <meta property="og:description" content={props.clip.description} /> : null}
|
||||||
|
<meta property="og:url" content={`${props.config.url}/clips/${props.clip.id}`} />
|
||||||
|
{props.clip.user.avatarUrl ? (
|
||||||
|
<>
|
||||||
|
<meta property="og:image" content={props.clip.user.avatarUrl} />
|
||||||
|
<meta property="twitter:card" content="summary" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function metaBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.profile.noCrawle ? <meta name="robots" content="noindex" /> : null}
|
||||||
|
{props.profile.preventAiLearning ? (
|
||||||
|
<>
|
||||||
|
<meta name="robots" content="noimageai" />
|
||||||
|
<meta name="robots" content="noai" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<meta name="misskey:user-username" content={props.clip.user.username} />
|
||||||
|
<meta name="misskey:user-id" content={props.clip.user.id} />
|
||||||
|
<meta name="misskey:clip-id" content={props.clip.id} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
{...props}
|
||||||
|
title={`${props.clip.name} | ${props.instanceName}`}
|
||||||
|
desc={props.clip.description ?? ''}
|
||||||
|
metaSlot={metaBlock()}
|
||||||
|
ogSlot={ogBlock()}
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
doctype html
|
|
||||||
|
|
||||||
//
|
|
||||||
-
|
|
||||||
_____ _ _
|
|
||||||
| |_|___ ___| |_ ___ _ _
|
|
||||||
| | | | |_ -|_ -| '_| -_| | |
|
|
||||||
|_|_|_|_|___|___|_,_|___|_ |
|
|
||||||
|___|
|
|
||||||
Thank you for using Misskey!
|
|
||||||
If you are reading this message... how about joining the development?
|
|
||||||
https://github.com/misskey-dev/misskey
|
|
||||||
|
|
||||||
|
|
||||||
html
|
|
||||||
|
|
||||||
head
|
|
||||||
meta(charset='utf-8')
|
|
||||||
meta(name='viewport' content='width=device-width, initial-scale=1')
|
|
||||||
meta(name='application-name' content='Misskey')
|
|
||||||
meta(name='referrer' content='origin')
|
|
||||||
|
|
||||||
title
|
|
||||||
block title
|
|
||||||
= 'An error has occurred... | Misskey'
|
|
||||||
|
|
||||||
style
|
|
||||||
include ../error.css
|
|
||||||
|
|
||||||
script
|
|
||||||
include ../error.js
|
|
||||||
|
|
||||||
body
|
|
||||||
svg.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(d="M12 9v2m0 4v.01")
|
|
||||||
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")
|
|
||||||
|
|
||||||
h1(data-i18n="title") Failed to initialize Misskey
|
|
||||||
|
|
||||||
button.button-big(onclick="location.reload();")
|
|
||||||
span.button-label-big(data-i18n-reload) Reload
|
|
||||||
|
|
||||||
p(data-i18n="serverError") If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
|
|
||||||
|
|
||||||
div#errors
|
|
||||||
code.
|
|
||||||
ERROR CODE: #{code}
|
|
||||||
ERROR ID: #{id}
|
|
||||||
|
|
||||||
p
|
|
||||||
b(data-i18n="solution") The following actions may solve the problem.
|
|
||||||
|
|
||||||
p(data-i18n="solution1") Update your os and browser
|
|
||||||
p(data-i18n="solution2") Disable an adblocker
|
|
||||||
p(data-i18n="solution3") Clear your browser cache
|
|
||||||
p(data-i18n="solution4") (Tor Browser) Set dom.webaudio.enabled to true
|
|
||||||
|
|
||||||
details(style="color: #86b300;")
|
|
||||||
summary(data-i18n="otherOption") Other options
|
|
||||||
a(href="/flush")
|
|
||||||
button.button-small
|
|
||||||
span.button-label-small(data-i18n="otherOption1") Clear preferences and cache
|
|
||||||
br
|
|
||||||
a(href="/cli")
|
|
||||||
button.button-small
|
|
||||||
span.button-label-small(data-i18n="otherOption2") Start the simple client
|
|
||||||
br
|
|
||||||
a(href="/bios")
|
|
||||||
button.button-small
|
|
||||||
span.button-label-small(data-i18n="otherOption3") Start the repair tool
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { comment } from '@/server/web/views/_.js';
|
||||||
|
import type { CommonPropsMinimum } from '@/server/web/views/_.js';
|
||||||
|
|
||||||
|
export function ErrorPage(props: {
|
||||||
|
title?: string;
|
||||||
|
code: string;
|
||||||
|
id: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{'<!DOCTYPE html>'}
|
||||||
|
{comment}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="application-name" content="Misskey" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="referrer" content="origin" />
|
||||||
|
<title safe>{props.title ?? 'An error has occurred... | Misskey'}</title>
|
||||||
|
<link rel="stylesheet" href="/static-assets/misc/error.css" />
|
||||||
|
<script src="/static-assets/misc/error.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<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 d="M12 9v2m0 4v.01" />
|
||||||
|
<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" />
|
||||||
|
</svg>
|
||||||
|
<h1 data-i18n="title">Failed to initialize Misskey</h1>
|
||||||
|
|
||||||
|
<button class="button-big" onclick="location.reload();">
|
||||||
|
<span class="button-label-big" data-i18n="reload">Reload</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p data-i18n="serverError">
|
||||||
|
If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id="errors">
|
||||||
|
<code safe>
|
||||||
|
ERROR CODE: {props.code}<br />
|
||||||
|
ERROR ID: {props.id}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p><b data-i18n="solution">The following actions may solve the problem.</b></p>
|
||||||
|
|
||||||
|
<p data-i18n="solution1">Update your os and browser</p>
|
||||||
|
<p data-i18n="solution2">Disable an adblocker</p>
|
||||||
|
<p data-i18n="solution3">Clear your browser cache</p>
|
||||||
|
<p data-i18n="solution4">(Tor Browser) Set dom.webaudio.enabled to true</p>
|
||||||
|
|
||||||
|
<details style="color: #86b300;">
|
||||||
|
<summary data-i18n="otherOption">Other options</summary>
|
||||||
|
<a href="/flush">
|
||||||
|
<button class="button-small">
|
||||||
|
<span class="button-label-small" data-i18n="otherOption1">Clear preferences and cache</span>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<a href="/cli">
|
||||||
|
<button class="button-small">
|
||||||
|
<span class="button-label-small" data-i18n="otherOption2">Start the simple client</span>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<a href="/bios">
|
||||||
|
<button class="button-small">
|
||||||
|
<span class="button-label-small" data-i18n="otherOption3">Start the repair tool</span>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</details>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
extends ./base
|
|
||||||
|
|
||||||
block vars
|
|
||||||
- const user = flash.user;
|
|
||||||
- const title = flash.title;
|
|
||||||
- const url = `${config.url}/play/${flash.id}`;
|
|
||||||
|
|
||||||
block title
|
|
||||||
= `${title} | ${instanceName}`
|
|
||||||
|
|
||||||
block desc
|
|
||||||
meta(name='description' content= flash.summary)
|
|
||||||
|
|
||||||
block og
|
|
||||||
meta(property='og:type' content='article')
|
|
||||||
meta(property='og:title' content= title)
|
|
||||||
meta(property='og:description' content= flash.summary)
|
|
||||||
meta(property='og:url' content= url)
|
|
||||||
meta(property='og:image' content= avatarUrl)
|
|
||||||
meta(property='twitter:card' content='summary')
|
|
||||||
|
|
||||||
block meta
|
|
||||||
if profile.noCrawle
|
|
||||||
meta(name='robots' content='noindex')
|
|
||||||
if profile.preventAiLearning
|
|
||||||
meta(name='robots' content='noimageai')
|
|
||||||
meta(name='robots' content='noai')
|
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
|
||||||
meta(name='misskey:user-id' content=user.id)
|
|
||||||
meta(name='misskey:flash-id' content=flash.id)
|
|
||||||
|
|
||||||
// todo
|
|
||||||
if user.twitter
|
|
||||||
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { MiUserProfile } from '@/models/UserProfile.js';
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import { Layout } from '@/server/web/views/base.js';
|
||||||
|
|
||||||
|
export function FlashPage(props: CommonProps<{
|
||||||
|
flash: Packed<'Flash'>;
|
||||||
|
profile: MiUserProfile;
|
||||||
|
}>) {
|
||||||
|
function ogBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:title" content={props.flash.title} />
|
||||||
|
<meta property="og:description" content={props.flash.summary} />
|
||||||
|
<meta property="og:url" content={`${props.config.url}/play/${props.flash.id}`} />
|
||||||
|
{props.flash.user.avatarUrl ? (
|
||||||
|
<>
|
||||||
|
<meta property="og:image" content={props.flash.user.avatarUrl} />
|
||||||
|
<meta property="twitter:card" content="summary" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function metaBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.profile.noCrawle ? <meta name="robots" content="noindex" /> : null}
|
||||||
|
{props.profile.preventAiLearning ? (
|
||||||
|
<>
|
||||||
|
<meta name="robots" content="noimageai" />
|
||||||
|
<meta name="robots" content="noai" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<meta name="misskey:user-username" content={props.flash.user.username} />
|
||||||
|
<meta name="misskey:user-id" content={props.flash.user.id} />
|
||||||
|
<meta name="misskey:flash-id" content={props.flash.id} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
{...props}
|
||||||
|
title={`${props.flash.title} | ${props.instanceName}`}
|
||||||
|
desc={props.flash.summary}
|
||||||
|
metaSlot={metaBlock()}
|
||||||
|
ogSlot={ogBlock()}
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
doctype html
|
|
||||||
|
|
||||||
html
|
|
||||||
#msg
|
|
||||||
script.
|
|
||||||
const msg = document.getElementById('msg');
|
|
||||||
const successText = `\nSuccess Flush! <a href="/">Back to Misskey</a>\n成功しました。<a href="/">Misskeyを開き直してください。</a>`;
|
|
||||||
|
|
||||||
if (!document.cookie) {
|
|
||||||
message('Your site data is fully cleared by your browser.');
|
|
||||||
message(successText);
|
|
||||||
} else {
|
|
||||||
message('Your browser does not support Clear-Site-Data header. Start opportunistic flushing.');
|
|
||||||
(async function() {
|
|
||||||
try {
|
|
||||||
localStorage.clear();
|
|
||||||
message('localStorage cleared.');
|
|
||||||
|
|
||||||
const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => {
|
|
||||||
const delidb = indexedDB.deleteDatabase(name);
|
|
||||||
delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`));
|
|
||||||
delidb.onerror = e => rej(e)
|
|
||||||
}));
|
|
||||||
|
|
||||||
await Promise.all(idbPromises);
|
|
||||||
|
|
||||||
if (navigator.serviceWorker.controller) {
|
|
||||||
navigator.serviceWorker.controller.postMessage('clear');
|
|
||||||
await navigator.serviceWorker.getRegistrations()
|
|
||||||
.then(registrations => {
|
|
||||||
return Promise.all(registrations.map(registration => registration.unregister()));
|
|
||||||
})
|
|
||||||
.catch(e => { throw new Error(e) });
|
|
||||||
}
|
|
||||||
|
|
||||||
message(successText);
|
|
||||||
} catch (e) {
|
|
||||||
message(`\n${e}\n\nFlush Failed. <a href="/flush">Please retry.</a>\n失敗しました。<a href="/flush">もう一度試してみてください。</a>`);
|
|
||||||
message(`\nIf you retry more than 3 times, try manually clearing the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを手動で消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`)
|
|
||||||
|
|
||||||
console.error(e);
|
|
||||||
setTimeout(() => {
|
|
||||||
location = '/';
|
|
||||||
}, 10000)
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
function message(text) {
|
|
||||||
msg.insertAdjacentHTML('beforeend', `<p>[${(new Date()).toString()}] ${text.replace(/\n/g,'<br>')}</p>`)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function FlushPage(props?: {}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{'<!DOCTYPE html>'}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="application-name" content="Misskey" />
|
||||||
|
<title>Clear preferences and cache</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="msg"></div>
|
||||||
|
<script src="/static-assets/misc/flush.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
extends ./base
|
|
||||||
|
|
||||||
block vars
|
|
||||||
- const user = post.user;
|
|
||||||
- const title = post.title;
|
|
||||||
- const url = `${config.url}/gallery/${post.id}`;
|
|
||||||
|
|
||||||
block title
|
|
||||||
= `${title} | ${instanceName}`
|
|
||||||
|
|
||||||
block desc
|
|
||||||
meta(name='description' content= post.description)
|
|
||||||
|
|
||||||
block og
|
|
||||||
meta(property='og:type' content='article')
|
|
||||||
meta(property='og:title' content= title)
|
|
||||||
meta(property='og:description' content= post.description)
|
|
||||||
meta(property='og:url' content= url)
|
|
||||||
if post.isSensitive
|
|
||||||
meta(property='og:image' content= avatarUrl)
|
|
||||||
meta(property='twitter:card' content='summary')
|
|
||||||
else
|
|
||||||
meta(property='og:image' content= post.files[0].thumbnailUrl)
|
|
||||||
meta(property='twitter:card' content='summary_large_image')
|
|
||||||
|
|
||||||
block meta
|
|
||||||
if user.host || profile.noCrawle
|
|
||||||
meta(name='robots' content='noindex')
|
|
||||||
if profile.preventAiLearning
|
|
||||||
meta(name='robots' content='noimageai')
|
|
||||||
meta(name='robots' content='noai')
|
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
|
||||||
meta(name='misskey:user-id' content=user.id)
|
|
||||||
|
|
||||||
// todo
|
|
||||||
if user.twitter
|
|
||||||
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
|
||||||
|
|
||||||
if !user.host
|
|
||||||
link(rel='alternate' href=url type='application/activity+json')
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { MiUserProfile } from '@/models/UserProfile.js';
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import { Layout } from '@/server/web/views/base.js';
|
||||||
|
|
||||||
|
export function GalleryPostPage(props: CommonProps<{
|
||||||
|
galleryPost: Packed<'GalleryPost'>;
|
||||||
|
profile: MiUserProfile;
|
||||||
|
}>) {
|
||||||
|
function ogBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:title" content={props.galleryPost.title} />
|
||||||
|
{props.galleryPost.description != null ? <meta property="og:description" content={props.galleryPost.description} /> : null}
|
||||||
|
<meta property="og:url" content={`${props.config.url}/gallery/${props.galleryPost.id}`} />
|
||||||
|
{props.galleryPost.isSensitive && props.galleryPost.user.avatarUrl ? (
|
||||||
|
<>
|
||||||
|
<meta property="og:image" content={props.galleryPost.user.avatarUrl} />
|
||||||
|
<meta property="twitter:card" content="summary" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
{!props.galleryPost.isSensitive && props.galleryPost.files != null ? (
|
||||||
|
<>
|
||||||
|
<meta property="og:image" content={props.galleryPost.files[0]!.thumbnailUrl ?? props.galleryPost.files[0]!.url} />
|
||||||
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function metaBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.profile.noCrawle ? <meta name="robots" content="noindex" /> : null}
|
||||||
|
{props.profile.preventAiLearning ? (
|
||||||
|
<>
|
||||||
|
<meta name="robots" content="noimageai" />
|
||||||
|
<meta name="robots" content="noai" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<meta name="misskey:user-username" content={props.galleryPost.user.username} />
|
||||||
|
<meta name="misskey:user-id" content={props.galleryPost.user.id} />
|
||||||
|
<meta name="misskey:gallery-post-id" content={props.galleryPost.id} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
{...props}
|
||||||
|
title={`${props.galleryPost.title} | ${props.instanceName}`}
|
||||||
|
desc={props.galleryPost.description ?? ''}
|
||||||
|
metaSlot={metaBlock()}
|
||||||
|
ogSlot={ogBlock()}
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
doctype html
|
|
||||||
|
|
||||||
html
|
|
||||||
|
|
||||||
head
|
|
||||||
meta(charset='utf-8')
|
|
||||||
meta(name='application-name' content='Misskey')
|
|
||||||
title= meta.name || host
|
|
||||||
style.
|
|
||||||
html, body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
min-height: 100vh;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
#a {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#banner {
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#title {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 24px;
|
|
||||||
padding: 0.5em 0.8em;
|
|
||||||
color: #fff;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content {
|
|
||||||
overflow: auto;
|
|
||||||
color: #353c3e;
|
|
||||||
}
|
|
||||||
|
|
||||||
#description {
|
|
||||||
margin: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body
|
|
||||||
a#a(href=`https://${host}` target="_blank")
|
|
||||||
header#banner(style=`background-image: url(${meta.bannerUrl})`)
|
|
||||||
div#title= meta.name || host
|
|
||||||
div#content
|
|
||||||
div#description!= meta.description
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { comment, CommonPropsMinimum } from '@/server/web/views/_.js';
|
||||||
|
import type { MiMeta } from '@/models/Meta.js';
|
||||||
|
|
||||||
|
export function InfoCardPage(props: CommonPropsMinimum<{
|
||||||
|
meta: MiMeta;
|
||||||
|
}>) {
|
||||||
|
// 変数名をsafeで始めることでエラーをスキップ
|
||||||
|
const safeDescription = props.meta.description;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{'<!DOCTYPE html>'}
|
||||||
|
{comment}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="application-name" content="Misskey" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title safe>{props.meta.name ?? props.config.url}</title>
|
||||||
|
<link rel="stylesheet" href="/static-assets/misc/info-card.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a id="a" href={props.config.url} target="_blank" rel="noopener noreferrer">
|
||||||
|
<header id="banner" style={props.meta.bannerUrl != null ? `background-image: url(${props.meta.bannerUrl});` : ''}>
|
||||||
|
<div id="title" safe>{props.meta.name ?? props.config.url}</div>
|
||||||
|
</header>
|
||||||
|
</a>
|
||||||
|
<div id="content">
|
||||||
|
<div id="description">{safeDescription}</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
extends ./base
|
|
||||||
|
|
||||||
block vars
|
|
||||||
- const user = note.user;
|
|
||||||
- const title = user.name ? `${user.name} (@${user.username}${user.host ? `@${user.host}` : ''})` : `@${user.username}${user.host ? `@${user.host}` : ''}`;
|
|
||||||
- const url = `${config.url}/notes/${note.id}`;
|
|
||||||
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
|
||||||
- const images = (note.files || []).filter(file => file.type.startsWith('image/') && !file.isSensitive)
|
|
||||||
- const videos = (note.files || []).filter(file => file.type.startsWith('video/') && !file.isSensitive)
|
|
||||||
|
|
||||||
block title
|
|
||||||
= `${title} | ${instanceName}`
|
|
||||||
|
|
||||||
block desc
|
|
||||||
meta(name='description' content= summary)
|
|
||||||
|
|
||||||
block og
|
|
||||||
meta(property='og:type' content='article')
|
|
||||||
meta(property='og:title' content= title)
|
|
||||||
meta(property='og:description' content= summary)
|
|
||||||
meta(property='og:url' content= url)
|
|
||||||
if videos.length
|
|
||||||
each video in videos
|
|
||||||
meta(property='og:video:url' content= video.url)
|
|
||||||
meta(property='og:video:secure_url' content= video.url)
|
|
||||||
meta(property='og:video:type' content= video.type)
|
|
||||||
// FIXME: add width and height
|
|
||||||
// FIXME: add embed player for Twitter
|
|
||||||
if images.length
|
|
||||||
meta(property='twitter:card' content='summary_large_image')
|
|
||||||
each image in images
|
|
||||||
meta(property='og:image' content= image.url)
|
|
||||||
else
|
|
||||||
meta(property='twitter:card' content='summary')
|
|
||||||
meta(property='og:image' content= avatarUrl)
|
|
||||||
|
|
||||||
|
|
||||||
block meta
|
|
||||||
if user.host || isRenote || profile.noCrawle
|
|
||||||
meta(name='robots' content='noindex')
|
|
||||||
if profile.preventAiLearning
|
|
||||||
meta(name='robots' content='noimageai')
|
|
||||||
meta(name='robots' content='noai')
|
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
|
||||||
meta(name='misskey:user-id' content=user.id)
|
|
||||||
meta(name='misskey:note-id' content=note.id)
|
|
||||||
|
|
||||||
// todo
|
|
||||||
if user.twitter
|
|
||||||
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
|
||||||
|
|
||||||
if note.prev
|
|
||||||
link(rel='prev' href=`${config.url}/notes/${note.prev}`)
|
|
||||||
if note.next
|
|
||||||
link(rel='next' href=`${config.url}/notes/${note.next}`)
|
|
||||||
|
|
||||||
if federationEnabled
|
|
||||||
if !user.host
|
|
||||||
link(rel='alternate' href=url type='application/activity+json')
|
|
||||||
if note.uri
|
|
||||||
link(rel='alternate' href=note.uri type='application/activity+json')
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { MiUserProfile } from '@/models/UserProfile.js';
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import { Layout } from '@/server/web/views/base.js';
|
||||||
|
import { isRenotePacked } from '@/misc/is-renote.js';
|
||||||
|
import { getNoteSummary } from '@/misc/get-note-summary.js';
|
||||||
|
|
||||||
|
export function NotePage(props: CommonProps<{
|
||||||
|
note: Packed<'Note'>;
|
||||||
|
profile: MiUserProfile;
|
||||||
|
}>) {
|
||||||
|
const title = props.note.user.name ? `${props.note.user.name} (@${props.note.user.username}${props.note.user.host ? `@${props.note.user.host}` : ''})` : `@${props.note.user.username}${props.note.user.host ? `@${props.note.user.host}` : ''}`
|
||||||
|
const isRenote = isRenotePacked(props.note);
|
||||||
|
const images = (props.note.files ?? []).filter(f => f.type.startsWith('image/'));
|
||||||
|
const videos = (props.note.files ?? []).filter(f => f.type.startsWith('video/'));
|
||||||
|
const summary = getNoteSummary(props.note);
|
||||||
|
|
||||||
|
function ogBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:title" content={title} />
|
||||||
|
<meta property="og:description" content={summary} />
|
||||||
|
<meta property="og:url" content={`${props.config.url}/notes/${props.note.id}`} />
|
||||||
|
{videos.map(video => (
|
||||||
|
<>
|
||||||
|
<meta property="og:video:url" content={video.url} />
|
||||||
|
<meta property="og:video:secure_url" content={video.url} />
|
||||||
|
<meta property="og:video:type" content={video.type} />
|
||||||
|
{video.thumbnailUrl ? <meta property="og:video:image" content={video.thumbnailUrl} /> : null}
|
||||||
|
{video.properties.width != null ? <meta property="og:video:width" content={video.properties.width.toString()} /> : null}
|
||||||
|
{video.properties.height != null ? <meta property="og:video:height" content={video.properties.height.toString()} /> : null}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
{images.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
|
{images.map(image => (
|
||||||
|
<>
|
||||||
|
<meta property="og:image" content={image.url} />
|
||||||
|
{image.properties.width != null ? <meta property="og:image:width" content={image.properties.width.toString()} /> : null}
|
||||||
|
{image.properties.height != null ? <meta property="og:image:height" content={image.properties.height.toString()} /> : null}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<meta property="twitter:card" content="summary" />
|
||||||
|
<meta property="og:image" content={props.note.user.avatarUrl} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function metaBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.note.user.host != null || isRenote || props.profile.noCrawle ? <meta name="robots" content="noindex" /> : null}
|
||||||
|
{props.profile.preventAiLearning ? (
|
||||||
|
<>
|
||||||
|
<meta name="robots" content="noimageai" />
|
||||||
|
<meta name="robots" content="noai" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<meta name="misskey:user-username" content={props.note.user.username} />
|
||||||
|
<meta name="misskey:user-id" content={props.note.user.id} />
|
||||||
|
<meta name="misskey:note-id" content={props.note.id} />
|
||||||
|
|
||||||
|
{props.federationEnabled ? (
|
||||||
|
<>
|
||||||
|
{props.note.user.host == null ? <link rel="alternate" type="application/activity+json" href={`${props.config.url}/notes/${props.note.id}`} /> : null}
|
||||||
|
{props.note.uri != null ? <link rel="alternate" type="application/activity+json" href={props.note.uri} /> : null}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
{...props}
|
||||||
|
title={`${title} | ${props.instanceName}`}
|
||||||
|
desc={summary}
|
||||||
|
metaSlot={metaBlock()}
|
||||||
|
ogSlot={ogBlock()}
|
||||||
|
></Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
extends ./base
|
|
||||||
|
|
||||||
block meta
|
|
||||||
//- Should be removed by the page when it loads, so that it won't needlessly
|
|
||||||
//- stay when user navigates away via the navigation bar
|
|
||||||
//- XXX: Remove navigation bar in auth page?
|
|
||||||
meta(name='misskey:oauth:transaction-id' content=transactionId)
|
|
||||||
meta(name='misskey:oauth:client-name' content=clientName)
|
|
||||||
if clientLogo
|
|
||||||
meta(name='misskey:oauth:client-logo' content=clientLogo)
|
|
||||||
meta(name='misskey:oauth:scope' content=scope)
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import { Layout } from '@/server/web/views/base.js';
|
||||||
|
|
||||||
|
export function OAuthPage(props: CommonProps<{
|
||||||
|
transactionId: string;
|
||||||
|
clientName: string;
|
||||||
|
clientLogo?: string;
|
||||||
|
scope: string[];
|
||||||
|
}>) {
|
||||||
|
|
||||||
|
//- Should be removed by the page when it loads, so that it won't needlessly
|
||||||
|
//- stay when user navigates away via the navigation bar
|
||||||
|
//- XXX: Remove navigation bar in auth page?
|
||||||
|
function metaBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<meta name="misskey:oauth:transaction-id" content={props.transactionId} />
|
||||||
|
<meta name="misskey:oauth:client-name" content={props.clientName} />
|
||||||
|
{props.clientLogo ? <meta name="misskey:oauth:client-logo" content={props.clientLogo} /> : null}
|
||||||
|
<meta name="misskey:oauth:scope" content={props.scope.join(' ')} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
{...props}
|
||||||
|
metaSlot={metaBlock()}
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
extends ./base
|
|
||||||
|
|
||||||
block vars
|
|
||||||
- const user = page.user;
|
|
||||||
- const title = page.title;
|
|
||||||
- const url = `${config.url}/@${user.username}/pages/${page.name}`;
|
|
||||||
|
|
||||||
block title
|
|
||||||
= `${title} | ${instanceName}`
|
|
||||||
|
|
||||||
block desc
|
|
||||||
meta(name='description' content= page.summary)
|
|
||||||
|
|
||||||
block og
|
|
||||||
meta(property='og:type' content='article')
|
|
||||||
meta(property='og:title' content= title)
|
|
||||||
meta(property='og:description' content= page.summary)
|
|
||||||
meta(property='og:url' content= url)
|
|
||||||
meta(property='og:image' content= page.eyeCatchingImage ? page.eyeCatchingImage.thumbnailUrl : avatarUrl)
|
|
||||||
meta(property='twitter:card' content= page.eyeCatchingImage ? 'summary_large_image' : 'summary')
|
|
||||||
|
|
||||||
block meta
|
|
||||||
if profile.noCrawle
|
|
||||||
meta(name='robots' content='noindex')
|
|
||||||
if profile.preventAiLearning
|
|
||||||
meta(name='robots' content='noimageai')
|
|
||||||
meta(name='robots' content='noai')
|
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
|
||||||
meta(name='misskey:user-id' content=user.id)
|
|
||||||
meta(name='misskey:page-id' content=page.id)
|
|
||||||
|
|
||||||
// todo
|
|
||||||
if user.twitter
|
|
||||||
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { MiUserProfile } from '@/models/UserProfile.js';
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import { Layout } from '@/server/web/views/base.js';
|
||||||
|
|
||||||
|
export function PagePage(props: CommonProps<{
|
||||||
|
page: Packed<'Page'>;
|
||||||
|
profile: MiUserProfile;
|
||||||
|
}>) {
|
||||||
|
function ogBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:title" content={props.page.title} />
|
||||||
|
{props.page.summary != null ? <meta property="og:description" content={props.page.summary} /> : null}
|
||||||
|
<meta property="og:url" content={`${props.config.url}/pages/${props.page.id}`} />
|
||||||
|
{props.page.eyeCatchingImage != null ? (
|
||||||
|
<>
|
||||||
|
<meta property="og:image" content={props.page.eyeCatchingImage.thumbnailUrl ?? props.page.eyeCatchingImage.url} />
|
||||||
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
|
</>
|
||||||
|
) : props.page.user.avatarUrl ? (
|
||||||
|
<>
|
||||||
|
<meta property="og:image" content={props.page.user.avatarUrl} />
|
||||||
|
<meta property="twitter:card" content="summary" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function metaBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.profile.noCrawle ? <meta name="robots" content="noindex" /> : null}
|
||||||
|
{props.profile.preventAiLearning ? (
|
||||||
|
<>
|
||||||
|
<meta name="robots" content="noimageai" />
|
||||||
|
<meta name="robots" content="noai" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<meta name="misskey:user-username" content={props.page.user.username} />
|
||||||
|
<meta name="misskey:user-id" content={props.page.user.id} />
|
||||||
|
<meta name="misskey:page-id" content={props.page.id} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
{...props}
|
||||||
|
title={`${props.page.title} | ${props.instanceName}`}
|
||||||
|
desc={props.page.summary ?? ''}
|
||||||
|
metaSlot={metaBlock()}
|
||||||
|
ogSlot={ogBlock()}
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
extends ./base
|
|
||||||
|
|
||||||
block vars
|
|
||||||
- const user1 = game.user1;
|
|
||||||
- const user2 = game.user2;
|
|
||||||
- const title = `${user1.username} vs ${user2.username}`;
|
|
||||||
- const url = `${config.url}/reversi/g/${game.id}`;
|
|
||||||
|
|
||||||
block title
|
|
||||||
= `${title} | ${instanceName}`
|
|
||||||
|
|
||||||
block desc
|
|
||||||
meta(name='description' content='⚫⚪Misskey Reversi⚪⚫')
|
|
||||||
|
|
||||||
block og
|
|
||||||
meta(property='og:type' content='article')
|
|
||||||
meta(property='og:title' content= title)
|
|
||||||
meta(property='og:description' content='⚫⚪Misskey Reversi⚪⚫')
|
|
||||||
meta(property='og:url' content= url)
|
|
||||||
meta(property='twitter:card' content='summary')
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import { Layout } from '@/server/web/views/base.js';
|
||||||
|
|
||||||
|
export function ReversiGamePage(props: CommonProps<{
|
||||||
|
reversiGame: Packed<'ReversiGameDetailed'>;
|
||||||
|
}>) {
|
||||||
|
const title = `${props.reversiGame.user1.username} vs ${props.reversiGame.user2.username}`;
|
||||||
|
const description = `⚫⚪Misskey Reversi⚪⚫`;
|
||||||
|
|
||||||
|
function ogBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:title" content={title} />
|
||||||
|
<meta property="og:description" content={description} />
|
||||||
|
<meta property="og:url" content={`${props.config.url}/reversi/g/${props.reversiGame.id}`} />
|
||||||
|
<meta property="twitter:card" content="summary" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
{...props}
|
||||||
|
title={`${title} | ${props.instanceName}`}
|
||||||
|
desc={description}
|
||||||
|
ogSlot={ogBlock()}
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
extends ./base
|
|
||||||
|
|
||||||
block vars
|
|
||||||
- const title = user.name ? `${user.name} (@${user.username}${user.host ? `@${user.host}` : ''})` : `@${user.username}${user.host ? `@${user.host}` : ''}`;
|
|
||||||
- const url = `${config.url}/@${(user.host ? `${user.username}@${user.host}` : user.username)}`;
|
|
||||||
|
|
||||||
block title
|
|
||||||
= `${title} | ${instanceName}`
|
|
||||||
|
|
||||||
block desc
|
|
||||||
meta(name='description' content= profile.description)
|
|
||||||
|
|
||||||
block og
|
|
||||||
meta(property='og:type' content='blog')
|
|
||||||
meta(property='og:title' content= title)
|
|
||||||
meta(property='og:description' content= profile.description)
|
|
||||||
meta(property='og:url' content= url)
|
|
||||||
meta(property='og:image' content= avatarUrl)
|
|
||||||
meta(property='twitter:card' content='summary')
|
|
||||||
|
|
||||||
block meta
|
|
||||||
if user.host || profile.noCrawle
|
|
||||||
meta(name='robots' content='noindex')
|
|
||||||
if profile.preventAiLearning
|
|
||||||
meta(name='robots' content='noimageai')
|
|
||||||
meta(name='robots' content='noai')
|
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
|
||||||
meta(name='misskey:user-id' content=user.id)
|
|
||||||
|
|
||||||
if profile.twitter
|
|
||||||
meta(name='twitter:creator' content=`@${profile.twitter.screenName}`)
|
|
||||||
|
|
||||||
if !sub
|
|
||||||
if federationEnabled
|
|
||||||
if !user.host
|
|
||||||
link(rel='alternate' href=`${config.url}/users/${user.id}` type='application/activity+json')
|
|
||||||
if user.uri
|
|
||||||
link(rel='alternate' href=user.uri type='application/activity+json')
|
|
||||||
if profile.url
|
|
||||||
link(rel='alternate' href=profile.url type='text/html')
|
|
||||||
|
|
||||||
each m in me
|
|
||||||
link(rel='me' href=`${m}`)
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { MiUserProfile } from '@/models/UserProfile.js';
|
||||||
|
import type { CommonProps } from '@/server/web/views/_.js';
|
||||||
|
import { Layout } from '@/server/web/views/base.js';
|
||||||
|
|
||||||
|
export function UserPage(props: CommonProps<{
|
||||||
|
user: Packed<'UserDetailed'>;
|
||||||
|
profile: MiUserProfile;
|
||||||
|
sub?: string;
|
||||||
|
}>) {
|
||||||
|
const title = props.user.name ? `${props.user.name} (@${props.user.username}${props.user.host ? `@${props.user.host}` : ''})` : `@${props.user.username}${props.user.host ? `@${props.user.host}` : ''}`;
|
||||||
|
const me = props.profile.fields
|
||||||
|
? props.profile.fields
|
||||||
|
.filter(field => field.value != null && field.value.match(/^https?:/))
|
||||||
|
.map(field => field.value)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
function ogBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<meta property="og:type" content="blog" />
|
||||||
|
<meta property="og:title" content={title} />
|
||||||
|
{props.user.description != null ? <meta property="og:description" content={props.user.description} /> : null}
|
||||||
|
<meta property="og:url" content={`${props.config.url}/@${props.user.username}`} />
|
||||||
|
<meta property="og:image" content={props.user.avatarUrl} />
|
||||||
|
<meta property="twitter:card" content="summary" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function metaBlock() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.user.host != null || props.profile.noCrawle ? <meta name="robots" content="noindex" /> : null}
|
||||||
|
{props.profile.preventAiLearning ? (
|
||||||
|
<>
|
||||||
|
<meta name="robots" content="noimageai" />
|
||||||
|
<meta name="robots" content="noai" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<meta name="misskey:user-username" content={props.user.username} />
|
||||||
|
<meta name="misskey:user-id" content={props.user.id} />
|
||||||
|
|
||||||
|
{props.sub == null && props.federationEnabled ? (
|
||||||
|
<>
|
||||||
|
{props.user.host == null ? <link rel="alternate" type="application/activity+json" href={`${props.config.url}/users/${props.user.id}`} /> : null}
|
||||||
|
{props.user.uri != null ? <link rel="alternate" type="application/activity+json" href={props.user.uri} /> : null}
|
||||||
|
{props.profile.url != null ? <link rel="alternate" type="text/html" href={props.profile.url} /> : null}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{me.map((url) => (
|
||||||
|
<link rel="me" href={url} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
{...props}
|
||||||
|
title={`${props.user.name || props.user.username} (@${props.user.username}) | ${props.instanceName}`}
|
||||||
|
desc={props.user.description ?? ''}
|
||||||
|
metaSlot={metaBlock()}
|
||||||
|
ogSlot={ogBlock()}
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -50,6 +50,14 @@ services:
|
||||||
source: ../../misskey-js/package.json
|
source: ../../misskey-js/package.json
|
||||||
target: /misskey/packages/misskey-js/package.json
|
target: /misskey/packages/misskey-js/package.json
|
||||||
read_only: true
|
read_only: true
|
||||||
|
- type: bind
|
||||||
|
source: ../../i18n/built
|
||||||
|
target: /misskey/packages/i18n/built
|
||||||
|
read_only: true
|
||||||
|
- type: bind
|
||||||
|
source: ../../i18n/package.json
|
||||||
|
target: /misskey/packages/i18n/package.json
|
||||||
|
read_only: true
|
||||||
- type: bind
|
- type: bind
|
||||||
source: ../../misskey-reversi/built
|
source: ../../misskey-reversi/built
|
||||||
target: /misskey/packages/misskey-reversi/built
|
target: /misskey/packages/misskey-reversi/built
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,14 @@ services:
|
||||||
source: ../../misskey-js/package.json
|
source: ../../misskey-js/package.json
|
||||||
target: /misskey/packages/misskey-js/package.json
|
target: /misskey/packages/misskey-js/package.json
|
||||||
read_only: true
|
read_only: true
|
||||||
|
- type: bind
|
||||||
|
source: ../../i18n/built
|
||||||
|
target: /misskey/packages/i18n/built
|
||||||
|
read_only: true
|
||||||
|
- type: bind
|
||||||
|
source: ../../i18n/package.json
|
||||||
|
target: /misskey/packages/i18n/package.json
|
||||||
|
read_only: true
|
||||||
- type: bind
|
- type: bind
|
||||||
source: ../../../package.json
|
source: ../../../package.json
|
||||||
target: /misskey/package.json
|
target: /misskey/package.json
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@
|
||||||
/* Language and Environment */
|
/* Language and Environment */
|
||||||
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
"jsx": "react-jsx", /* Specify what JSX code is generated. */
|
||||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
"jsxImportSource": "@kitajs/html", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "@kitajs/html",
|
||||||
"rootDir": "../src",
|
"rootDir": "../src",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "@kitajs/html",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["../src/*"]
|
"@/*": ["../src/*"]
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,17 @@
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "@kitajs/html",
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
},
|
},
|
||||||
"outDir": "./built",
|
"outDir": "./built",
|
||||||
|
"plugins": [
|
||||||
|
{"name": "@kitajs/ts-html-plugin"}
|
||||||
|
],
|
||||||
"types": [
|
"types": [
|
||||||
"node"
|
"node"
|
||||||
],
|
],
|
||||||
|
|
@ -43,7 +48,8 @@
|
||||||
},
|
},
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"include": [
|
"include": [
|
||||||
"./src/**/*.ts"
|
"./src/**/*.ts",
|
||||||
|
"./src/**/*.tsx"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"./src/**/*.test.ts"
|
"./src/**/*.test.ts"
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,6 @@ async function writeFrontendLocalesJson(destDir: string, version: string): Promi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { locales, build, writeFrontendLocalesJson };
|
export { locales, languages, build, writeFrontendLocalesJson };
|
||||||
export type { Language, Locale, ILocale, ParameterizedString };
|
export type { Language, Locale, ILocale, ParameterizedString };
|
||||||
export default locales;
|
export default locales;
|
||||||
|
|
|
||||||
120
pnpm-lock.yaml
120
pnpm-lock.yaml
|
|
@ -77,9 +77,6 @@ importers:
|
||||||
globals:
|
globals:
|
||||||
specifier: 16.5.0
|
specifier: 16.5.0
|
||||||
version: 16.5.0
|
version: 16.5.0
|
||||||
i18n:
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:packages/i18n
|
|
||||||
ncp:
|
ncp:
|
||||||
specifier: 2.0.0
|
specifier: 2.0.0
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
|
|
@ -126,9 +123,9 @@ importers:
|
||||||
'@fastify/static':
|
'@fastify/static':
|
||||||
specifier: 8.3.0
|
specifier: 8.3.0
|
||||||
version: 8.3.0
|
version: 8.3.0
|
||||||
'@fastify/view':
|
'@kitajs/html':
|
||||||
specifier: 11.1.1
|
specifier: 4.2.11
|
||||||
version: 11.1.1
|
version: 4.2.11
|
||||||
'@misskey-dev/sharp-read-bmp':
|
'@misskey-dev/sharp-read-bmp':
|
||||||
specifier: 1.2.0
|
specifier: 1.2.0
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
|
|
@ -255,6 +252,9 @@ importers:
|
||||||
http-link-header:
|
http-link-header:
|
||||||
specifier: 1.1.3
|
specifier: 1.1.3
|
||||||
version: 1.1.3
|
version: 1.1.3
|
||||||
|
i18n:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../i18n
|
||||||
ioredis:
|
ioredis:
|
||||||
specifier: 5.8.2
|
specifier: 5.8.2
|
||||||
version: 5.8.2
|
version: 5.8.2
|
||||||
|
|
@ -345,9 +345,6 @@ importers:
|
||||||
promise-limit:
|
promise-limit:
|
||||||
specifier: 2.7.0
|
specifier: 2.7.0
|
||||||
version: 2.7.0
|
version: 2.7.0
|
||||||
pug:
|
|
||||||
specifier: 3.0.3
|
|
||||||
version: 3.0.3
|
|
||||||
qrcode:
|
qrcode:
|
||||||
specifier: 1.5.4
|
specifier: 1.5.4
|
||||||
version: 1.5.4
|
version: 1.5.4
|
||||||
|
|
@ -436,6 +433,9 @@ importers:
|
||||||
'@jest/globals':
|
'@jest/globals':
|
||||||
specifier: 29.7.0
|
specifier: 29.7.0
|
||||||
version: 29.7.0
|
version: 29.7.0
|
||||||
|
'@kitajs/ts-html-plugin':
|
||||||
|
specifier: 4.1.3
|
||||||
|
version: 4.1.3(@kitajs/html@4.2.11)(typescript@5.9.3)
|
||||||
'@nestjs/platform-express':
|
'@nestjs/platform-express':
|
||||||
specifier: 11.1.9
|
specifier: 11.1.9
|
||||||
version: 11.1.9(@nestjs/common@11.1.9(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
|
version: 11.1.9(@nestjs/common@11.1.9(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
|
||||||
|
|
@ -505,9 +505,6 @@ importers:
|
||||||
'@types/pg':
|
'@types/pg':
|
||||||
specifier: 8.15.6
|
specifier: 8.15.6
|
||||||
version: 8.15.6
|
version: 8.15.6
|
||||||
'@types/pug':
|
|
||||||
specifier: 2.0.10
|
|
||||||
version: 2.0.10
|
|
||||||
'@types/qrcode':
|
'@types/qrcode':
|
||||||
specifier: 1.5.6
|
specifier: 1.5.6
|
||||||
version: 1.5.6
|
version: 1.5.6
|
||||||
|
|
@ -592,6 +589,9 @@ importers:
|
||||||
supertest:
|
supertest:
|
||||||
specifier: 7.1.4
|
specifier: 7.1.4
|
||||||
version: 7.1.4
|
version: 7.1.4
|
||||||
|
vite:
|
||||||
|
specifier: 7.2.4
|
||||||
|
version: 7.2.4(@types/node@24.10.1)(sass@1.94.2)(terser@5.44.1)(tsx@4.20.6)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@swc/core-android-arm64':
|
'@swc/core-android-arm64':
|
||||||
specifier: 1.3.11
|
specifier: 1.3.11
|
||||||
|
|
@ -2503,9 +2503,6 @@ packages:
|
||||||
'@fastify/static@8.3.0':
|
'@fastify/static@8.3.0':
|
||||||
resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==}
|
resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==}
|
||||||
|
|
||||||
'@fastify/view@11.1.1':
|
|
||||||
resolution: {integrity: sha512-GiHqT3R2eKJgWmy0s45eELTC447a4+lTM2o+8fSWeKwBe9VToeePuHJcKtOEXPrKGSddGO0RsNayULiS3aeHeQ==}
|
|
||||||
|
|
||||||
'@file-type/xml@0.4.4':
|
'@file-type/xml@0.4.4':
|
||||||
resolution: {integrity: sha512-NhCyXoHlVZ8TqM476hyzwGJ24+D5IPSaZhmrPj7qXnEVb3q6jrFzA3mM9TBpknKSI9EuQeGTKRg2DXGUwvBBoQ==}
|
resolution: {integrity: sha512-NhCyXoHlVZ8TqM476hyzwGJ24+D5IPSaZhmrPj7qXnEVb3q6jrFzA3mM9TBpknKSI9EuQeGTKRg2DXGUwvBBoQ==}
|
||||||
|
|
||||||
|
|
@ -2864,6 +2861,17 @@ packages:
|
||||||
'@keyv/serialize@1.1.1':
|
'@keyv/serialize@1.1.1':
|
||||||
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
|
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
|
||||||
|
|
||||||
|
'@kitajs/html@4.2.11':
|
||||||
|
resolution: {integrity: sha512-gOe+zzCZKN2fPT1FUK32mHsr21ILcAOUUux/yDqQthInW8egN8RuxVp+zP3KhwWETVACkurBiKV9RWuNw+ceiw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
'@kitajs/ts-html-plugin@4.1.3':
|
||||||
|
resolution: {integrity: sha512-NlYrID5yMxfRKiO1eiiSC4MWveKe0ffoCJOZm4idNOqwimmLXr0g1NmvCcquOU2XLRrgzynxZqw6rhwR5CY5Nw==}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@kitajs/html': ^4.2.10
|
||||||
|
typescript: ^5.6.2
|
||||||
|
|
||||||
'@kurkle/color@0.3.4':
|
'@kurkle/color@0.3.4':
|
||||||
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
||||||
|
|
||||||
|
|
@ -4812,9 +4820,6 @@ packages:
|
||||||
'@types/pg@8.15.6':
|
'@types/pg@8.15.6':
|
||||||
resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==}
|
resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==}
|
||||||
|
|
||||||
'@types/pug@2.0.10':
|
|
||||||
resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
|
|
||||||
|
|
||||||
'@types/punycode@2.1.4':
|
'@types/punycode@2.1.4':
|
||||||
resolution: {integrity: sha512-trzh6NzBnq8yw5e35f8xe8VTYjqM3NE7bohBtvDVf/dtUer3zYTLK1Ka3DG3p7bdtoaOHZucma6FfVKlQ134pQ==}
|
resolution: {integrity: sha512-trzh6NzBnq8yw5e35f8xe8VTYjqM3NE7bohBtvDVf/dtUer3zYTLK1Ka3DG3p7bdtoaOHZucma6FfVKlQ134pQ==}
|
||||||
|
|
||||||
|
|
@ -5915,6 +5920,10 @@ packages:
|
||||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
cliui@9.0.1:
|
||||||
|
resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
cluster-key-slot@1.1.2:
|
cluster-key-slot@1.1.2:
|
||||||
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
@ -6451,6 +6460,9 @@ packages:
|
||||||
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
|
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
emoji-regex@10.6.0:
|
||||||
|
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
|
||||||
|
|
||||||
emoji-regex@8.0.0:
|
emoji-regex@8.0.0:
|
||||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||||
|
|
||||||
|
|
@ -7060,6 +7072,10 @@ packages:
|
||||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||||
engines: {node: 6.* || 8.* || >= 10.*}
|
engines: {node: 6.* || 8.* || >= 10.*}
|
||||||
|
|
||||||
|
get-east-asian-width@1.4.0:
|
||||||
|
resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
get-intrinsic@1.3.0:
|
get-intrinsic@1.3.0:
|
||||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -10151,6 +10167,10 @@ packages:
|
||||||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
string-width@7.2.0:
|
||||||
|
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
string.prototype.trim@1.2.10:
|
string.prototype.trim@1.2.10:
|
||||||
resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
|
resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -11108,6 +11128,10 @@ packages:
|
||||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
wrap-ansi@9.0.2:
|
||||||
|
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
wrappy@1.0.2:
|
wrappy@1.0.2:
|
||||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
|
||||||
|
|
@ -11189,6 +11213,10 @@ packages:
|
||||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
yargs-parser@22.0.0:
|
||||||
|
resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
|
||||||
|
|
||||||
yargs@15.4.1:
|
yargs@15.4.1:
|
||||||
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
@ -11201,6 +11229,10 @@ packages:
|
||||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
yargs@18.0.0:
|
||||||
|
resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
|
||||||
|
|
||||||
yauzl@2.10.0:
|
yauzl@2.10.0:
|
||||||
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
|
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
|
||||||
|
|
||||||
|
|
@ -12489,11 +12521,6 @@ snapshots:
|
||||||
fastq: 1.19.1
|
fastq: 1.19.1
|
||||||
glob: 11.1.0
|
glob: 11.1.0
|
||||||
|
|
||||||
'@fastify/view@11.1.1':
|
|
||||||
dependencies:
|
|
||||||
fastify-plugin: 5.1.0
|
|
||||||
toad-cache: 3.7.0
|
|
||||||
|
|
||||||
'@file-type/xml@0.4.4':
|
'@file-type/xml@0.4.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
sax: 1.4.3
|
sax: 1.4.3
|
||||||
|
|
@ -12904,6 +12931,18 @@ snapshots:
|
||||||
|
|
||||||
'@keyv/serialize@1.1.1': {}
|
'@keyv/serialize@1.1.1': {}
|
||||||
|
|
||||||
|
'@kitajs/html@4.2.11':
|
||||||
|
dependencies:
|
||||||
|
csstype: 3.2.3
|
||||||
|
|
||||||
|
'@kitajs/ts-html-plugin@4.1.3(@kitajs/html@4.2.11)(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@kitajs/html': 4.2.11
|
||||||
|
chalk: 5.6.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
typescript: 5.9.3
|
||||||
|
yargs: 18.0.0
|
||||||
|
|
||||||
'@kurkle/color@0.3.4': {}
|
'@kurkle/color@0.3.4': {}
|
||||||
|
|
||||||
'@levischuck/tiny-cbor@0.2.11': {}
|
'@levischuck/tiny-cbor@0.2.11': {}
|
||||||
|
|
@ -15177,8 +15216,6 @@ snapshots:
|
||||||
pg-protocol: 1.10.3
|
pg-protocol: 1.10.3
|
||||||
pg-types: 2.2.0
|
pg-types: 2.2.0
|
||||||
|
|
||||||
'@types/pug@2.0.10': {}
|
|
||||||
|
|
||||||
'@types/punycode@2.1.4': {}
|
'@types/punycode@2.1.4': {}
|
||||||
|
|
||||||
'@types/qrcode@1.5.6':
|
'@types/qrcode@1.5.6':
|
||||||
|
|
@ -16555,6 +16592,12 @@ snapshots:
|
||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
wrap-ansi: 7.0.0
|
wrap-ansi: 7.0.0
|
||||||
|
|
||||||
|
cliui@9.0.1:
|
||||||
|
dependencies:
|
||||||
|
string-width: 7.2.0
|
||||||
|
strip-ansi: 7.1.2
|
||||||
|
wrap-ansi: 9.0.2
|
||||||
|
|
||||||
cluster-key-slot@1.1.2: {}
|
cluster-key-slot@1.1.2: {}
|
||||||
|
|
||||||
co@4.6.0: {}
|
co@4.6.0: {}
|
||||||
|
|
@ -17140,6 +17183,8 @@ snapshots:
|
||||||
|
|
||||||
emittery@0.13.1: {}
|
emittery@0.13.1: {}
|
||||||
|
|
||||||
|
emoji-regex@10.6.0: {}
|
||||||
|
|
||||||
emoji-regex@8.0.0: {}
|
emoji-regex@8.0.0: {}
|
||||||
|
|
||||||
emoji-regex@9.2.2: {}
|
emoji-regex@9.2.2: {}
|
||||||
|
|
@ -18025,6 +18070,8 @@ snapshots:
|
||||||
|
|
||||||
get-caller-file@2.0.5: {}
|
get-caller-file@2.0.5: {}
|
||||||
|
|
||||||
|
get-east-asian-width@1.4.0: {}
|
||||||
|
|
||||||
get-intrinsic@1.3.0:
|
get-intrinsic@1.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind-apply-helpers: 1.0.2
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
|
@ -21684,6 +21731,12 @@ snapshots:
|
||||||
emoji-regex: 9.2.2
|
emoji-regex: 9.2.2
|
||||||
strip-ansi: 7.1.2
|
strip-ansi: 7.1.2
|
||||||
|
|
||||||
|
string-width@7.2.0:
|
||||||
|
dependencies:
|
||||||
|
emoji-regex: 10.6.0
|
||||||
|
get-east-asian-width: 1.4.0
|
||||||
|
strip-ansi: 7.1.2
|
||||||
|
|
||||||
string.prototype.trim@1.2.10:
|
string.prototype.trim@1.2.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
|
|
@ -22634,6 +22687,12 @@ snapshots:
|
||||||
string-width: 5.1.2
|
string-width: 5.1.2
|
||||||
strip-ansi: 7.1.2
|
strip-ansi: 7.1.2
|
||||||
|
|
||||||
|
wrap-ansi@9.0.2:
|
||||||
|
dependencies:
|
||||||
|
ansi-styles: 6.2.3
|
||||||
|
string-width: 7.2.0
|
||||||
|
strip-ansi: 7.1.2
|
||||||
|
|
||||||
wrappy@1.0.2: {}
|
wrappy@1.0.2: {}
|
||||||
|
|
||||||
write-file-atomic@4.0.2:
|
write-file-atomic@4.0.2:
|
||||||
|
|
@ -22690,6 +22749,8 @@ snapshots:
|
||||||
|
|
||||||
yargs-parser@21.1.1: {}
|
yargs-parser@21.1.1: {}
|
||||||
|
|
||||||
|
yargs-parser@22.0.0: {}
|
||||||
|
|
||||||
yargs@15.4.1:
|
yargs@15.4.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui: 6.0.0
|
cliui: 6.0.0
|
||||||
|
|
@ -22724,6 +22785,15 @@ snapshots:
|
||||||
y18n: 5.0.8
|
y18n: 5.0.8
|
||||||
yargs-parser: 21.1.1
|
yargs-parser: 21.1.1
|
||||||
|
|
||||||
|
yargs@18.0.0:
|
||||||
|
dependencies:
|
||||||
|
cliui: 9.0.1
|
||||||
|
escalade: 3.2.0
|
||||||
|
get-caller-file: 2.0.5
|
||||||
|
string-width: 7.2.0
|
||||||
|
y18n: 5.0.8
|
||||||
|
yargs-parser: 22.0.0
|
||||||
|
|
||||||
yauzl@2.10.0:
|
yauzl@2.10.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer-crc32: 0.2.13
|
buffer-crc32: 0.2.13
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,7 @@
|
||||||
import * as fs from 'node:fs/promises';
|
import * as fs from 'node:fs/promises';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import cssnano from 'cssnano';
|
|
||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
import postcss from 'postcss';
|
|
||||||
import * as terser from 'terser';
|
|
||||||
|
|
||||||
import { locales } from 'i18n';
|
|
||||||
import buildTarball from './tarball.mjs';
|
import buildTarball from './tarball.mjs';
|
||||||
|
|
||||||
const configDir = fileURLToPath(new URL('../.config', import.meta.url));
|
const configDir = fileURLToPath(new URL('../.config', import.meta.url));
|
||||||
|
|
@ -29,49 +24,9 @@ async function copyFrontendFonts() {
|
||||||
await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true });
|
await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyBackendViews() {
|
|
||||||
await fs.cp('./packages/backend/src/server/web/views', './packages/backend/built/server/web/views', { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildBackendScript() {
|
|
||||||
await fs.mkdir('./packages/backend/built/server/web', { recursive: true });
|
|
||||||
|
|
||||||
for (const file of [
|
|
||||||
'./packages/backend/src/server/web/boot.js',
|
|
||||||
'./packages/backend/src/server/web/boot.embed.js',
|
|
||||||
'./packages/backend/src/server/web/bios.js',
|
|
||||||
'./packages/backend/src/server/web/cli.js',
|
|
||||||
'./packages/backend/src/server/web/error.js',
|
|
||||||
]) {
|
|
||||||
let source = await fs.readFile(file, { encoding: 'utf-8' });
|
|
||||||
source = source.replaceAll('LANGS', JSON.stringify(Object.keys(locales)));
|
|
||||||
const { code } = await terser.minify(source, { toplevel: true });
|
|
||||||
await fs.writeFile(`./packages/backend/built/server/web/${path.basename(file)}`, code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildBackendStyle() {
|
|
||||||
await fs.mkdir('./packages/backend/built/server/web', { recursive: true });
|
|
||||||
|
|
||||||
for (const file of [
|
|
||||||
'./packages/backend/src/server/web/style.css',
|
|
||||||
'./packages/backend/src/server/web/style.embed.css',
|
|
||||||
'./packages/backend/src/server/web/bios.css',
|
|
||||||
'./packages/backend/src/server/web/cli.css',
|
|
||||||
'./packages/backend/src/server/web/error.css'
|
|
||||||
]) {
|
|
||||||
const source = await fs.readFile(file, { encoding: 'utf-8' });
|
|
||||||
const { css } = await postcss([cssnano({ zindex: false })]).process(source, { from: undefined });
|
|
||||||
await fs.writeFile(`./packages/backend/built/server/web/${path.basename(file)}`, css);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function build() {
|
async function build() {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
copyFrontendFonts(),
|
copyFrontendFonts(),
|
||||||
copyBackendViews(),
|
|
||||||
buildBackendScript(),
|
|
||||||
buildBackendStyle(),
|
|
||||||
loadConfig().then(config => config?.publishTarballInsteadOfProvideRepositoryUrl && buildTarball()),
|
loadConfig().then(config => config?.publishTarballInsteadOfProvideRepositoryUrl && buildTarball()),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue