From fe01a5a28f34c873019ae3c34086acd6bd791a1d Mon Sep 17 00:00:00 2001
From: おさむのひと <46447427+samunohito@users.noreply.github.com>
Date: Sun, 30 Nov 2025 13:27:44 +0900
Subject: refactor: localesをworkspace管理下のパッケージに (#16895)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* refactor: localesをworkspace管理下のパッケージに
* fix copilot review
* move
* move
* rename
* fix ci
* revert unwanted indent changes
* fix
* fix
* fix
* fix
* 間違えてコミットしていたのを戻す
* 不要
* 追加漏れ
* ymlの場所だけ戻す
* localesの位置を戻したのでこの差分は不要
* 内容的にlocalesにある方が正しい
* i18nパッケージ用のREADME.mdを用意
* fix locale.yml
* fix locale.yml
---------
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
---
scripts/build-assets.mjs | 101 +++++++++++++++++------------------------------
scripts/clean-all.js | 3 ++
scripts/clean.js | 1 +
scripts/dev.mjs | 18 +++++++++
4 files changed, 58 insertions(+), 65 deletions(-)
(limited to 'scripts')
diff --git a/scripts/build-assets.mjs b/scripts/build-assets.mjs
index e610a72380..34883e3513 100644
--- a/scripts/build-assets.mjs
+++ b/scripts/build-assets.mjs
@@ -11,9 +11,7 @@ import * as yaml from 'js-yaml';
import postcss from 'postcss';
import * as terser from 'terser';
-import { build as buildLocales } from '../locales/index.js';
-import generateDTS from '../locales/generateDTS.js';
-import meta from '../package.json' with { type: "json" };
+import { locales } from 'i18n';
import buildTarball from './tarball.mjs';
const configDir = fileURLToPath(new URL('../.config', import.meta.url));
@@ -23,86 +21,59 @@ const configPath = process.env.MISSKEY_CONFIG_YML
? path.resolve(configDir, 'test.yml')
: path.resolve(configDir, 'default.yml');
-let locales = buildLocales();
-
async function loadConfig() {
return fs.readFile(configPath, 'utf-8').then(data => yaml.load(data)).catch(() => null);
}
async function copyFrontendFonts() {
- await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true });
-}
-
-async function copyFrontendLocales() {
- generateDTS();
-
- await fs.mkdir('./built/_frontend_dist_/locales', { recursive: true });
-
- const v = { '_version_': meta.version };
-
- for (const [lang, locale] of Object.entries(locales)) {
- await fs.writeFile(`./built/_frontend_dist_/locales/${lang}.${meta.version}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8');
- }
+ 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 });
+ 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);
- }
+ 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);
- }
+ 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() {
- await Promise.all([
- copyFrontendFonts(),
- copyFrontendLocales(),
- copyBackendViews(),
- buildBackendScript(),
- buildBackendStyle(),
+ await Promise.all([
+ copyFrontendFonts(),
+ copyBackendViews(),
+ buildBackendScript(),
+ buildBackendStyle(),
loadConfig().then(config => config?.publishTarballInsteadOfProvideRepositoryUrl && buildTarball()),
- ]);
+ ]);
}
await build();
-
-if (process.argv.includes('--watch')) {
- const watcher = fs.watch('./locales');
- for await (const event of watcher) {
- const filename = event.filename?.replaceAll('\\', '/');
- if (/^[a-z]+-[A-Z]+\.yml/.test(filename)) {
- console.log(`update ${filename} ...`)
- locales = buildLocales();
- await copyFrontendLocales()
- }
- }
-}
diff --git a/scripts/clean-all.js b/scripts/clean-all.js
index 5a8f9eba23..839ea3ba1c 100644
--- a/scripts/clean-all.js
+++ b/scripts/clean-all.js
@@ -24,6 +24,9 @@ const fs = require('fs');
fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true });
+ fs.rmSync(__dirname + '/../packages/i18n/built', { recursive: true, force: true });
+ fs.rmSync(__dirname + '/../packages/i18n/node_modules', { recursive: true, force: true });
+
fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/misskey-js/node_modules', { recursive: true, force: true });
diff --git a/scripts/clean.js b/scripts/clean.js
index 69a8df76af..5cce8bacab 100644
--- a/scripts/clean.js
+++ b/scripts/clean.js
@@ -11,6 +11,7 @@ const fs = require('fs');
fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/frontend-embed/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/icons-subsetter/built', { recursive: true, force: true });
+ fs.rmSync(__dirname + '/../packages/i18n/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true });
diff --git a/scripts/dev.mjs b/scripts/dev.mjs
index e500510b9e..b54004132a 100644
--- a/scripts/dev.mjs
+++ b/scripts/dev.mjs
@@ -16,6 +16,13 @@ await execa('pnpm', ['clean'], {
stderr: process.stderr,
});
+// アセットのビルドで依存しているので一番最初に必要
+await execa('pnpm', ['--filter', 'i18n', 'build'], {
+ cwd: _dirname + '/../',
+ stdout: process.stdout,
+ stderr: process.stderr,
+});
+
await Promise.all([
execa('pnpm', ['build-pre'], {
cwd: _dirname + '/../',
@@ -38,6 +45,11 @@ await Promise.all([
stdout: process.stdout,
stderr: process.stderr,
}),
+ execa('pnpm', ['--filter', 'misskey-js', 'build'], {
+ cwd: _dirname + '/../',
+ stdout: process.stdout,
+ stderr: process.stderr,
+ }),
]);
execa('pnpm', ['build-pre', '--watch'], {
@@ -88,6 +100,12 @@ execa('pnpm', ['--filter', 'misskey-js', 'watch', '--no-clean'], {
stderr: process.stderr,
});
+execa('pnpm', ['--filter', 'i18n', 'watch', '--no-clean'], {
+ cwd: _dirname + '/../',
+ stdout: process.stdout,
+ stderr: process.stderr,
+});
+
execa('pnpm', ['--filter', 'misskey-reversi', 'watch', '--no-clean'], {
cwd: _dirname + '/../',
stdout: process.stdout,
--
cgit v1.2.3-freya
From 768e1dd0168e0c30aea59c41df3d383ef8183c00 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 30 Nov 2025 14:16:57 +0900
Subject: chore(deps): update [tools] update dependencies [ci skip] (#16903)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
scripts/changelog-checker/package-lock.json | 118 ++++++++++++++--------------
scripts/changelog-checker/package.json | 8 +-
2 files changed, 65 insertions(+), 61 deletions(-)
(limited to 'scripts')
diff --git a/scripts/changelog-checker/package-lock.json b/scripts/changelog-checker/package-lock.json
index f1d81e447b..3bf65e528a 100644
--- a/scripts/changelog-checker/package-lock.json
+++ b/scripts/changelog-checker/package-lock.json
@@ -9,16 +9,16 @@
"version": "1.0.0",
"devDependencies": {
"@types/mdast": "4.0.4",
- "@types/node": "24.9.1",
- "@vitest/coverage-v8": "4.0.10",
+ "@types/node": "24.10.1",
+ "@vitest/coverage-v8": "4.0.13",
"mdast-util-to-string": "4.0.0",
"remark": "15.0.1",
"remark-parse": "11.0.0",
"typescript": "5.9.3",
"unified": "11.0.5",
- "vite": "7.2.2",
+ "vite": "7.2.4",
"vite-node": "5.2.0",
- "vitest": "4.0.10"
+ "vitest": "4.0.13"
}
},
"node_modules/@babel/helper-string-parser": {
@@ -898,9 +898,9 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "24.9.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
- "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
+ "version": "24.10.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
+ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -915,14 +915,14 @@
"dev": true
},
"node_modules/@vitest/coverage-v8": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.10.tgz",
- "integrity": "sha512-g+brmtoKa/sAeIohNJnnWhnHtU6GuqqVOSQ4SxDIPcgZWZyhJs5RmF5LpqXs8Kq64lANP+vnbn5JLzhLj/G56g==",
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.13.tgz",
+ "integrity": "sha512-w77N6bmtJ3CFnL/YHiYotwW/JI3oDlR3K38WEIqegRfdMSScaYxwYKB/0jSNpOTZzUjQkG8HHEz4sdWQMWpQ5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
- "@vitest/utils": "4.0.10",
+ "@vitest/utils": "4.0.13",
"ast-v8-to-istanbul": "^0.3.8",
"debug": "^4.4.3",
"istanbul-lib-coverage": "^3.2.2",
@@ -937,8 +937,8 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "4.0.10",
- "vitest": "4.0.10"
+ "@vitest/browser": "4.0.13",
+ "vitest": "4.0.13"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -947,16 +947,16 @@
}
},
"node_modules/@vitest/expect": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.10.tgz",
- "integrity": "sha512-3QkTX/lK39FBNwARCQRSQr0TP9+ywSdxSX+LgbJ2M1WmveXP72anTbnp2yl5fH+dU6SUmBzNMrDHs80G8G2DZg==",
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.13.tgz",
+ "integrity": "sha512-zYtcnNIBm6yS7Gpr7nFTmq8ncowlMdOJkWLqYvhr/zweY6tFbDkDi8BPPOeHxEtK1rSI69H7Fd4+1sqvEGli6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@types/chai": "^5.2.2",
- "@vitest/spy": "4.0.10",
- "@vitest/utils": "4.0.10",
+ "@vitest/spy": "4.0.13",
+ "@vitest/utils": "4.0.13",
"chai": "^6.2.1",
"tinyrainbow": "^3.0.3"
},
@@ -965,13 +965,13 @@
}
},
"node_modules/@vitest/mocker": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.10.tgz",
- "integrity": "sha512-e2OfdexYkjkg8Hh3L9NVEfbwGXq5IZbDovkf30qW2tOh7Rh9sVtmSr2ztEXOFbymNxS4qjzLXUQIvATvN4B+lg==",
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.13.tgz",
+ "integrity": "sha512-eNCwzrI5djoauklwP1fuslHBjrbR8rqIVbvNlAnkq1OTa6XT+lX68mrtPirNM9TnR69XUPt4puBCx2Wexseylg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "4.0.10",
+ "@vitest/spy": "4.0.13",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -992,9 +992,9 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.10.tgz",
- "integrity": "sha512-99EQbpa/zuDnvVjthwz5bH9o8iPefoQZ63WV8+bsRJZNw3qQSvSltfut8yu1Jc9mqOYi7pEbsKxYTi/rjaq6PA==",
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.13.tgz",
+ "integrity": "sha512-ooqfze8URWbI2ozOeLDMh8YZxWDpGXoeY3VOgcDnsUxN0jPyPWSUvjPQWqDGCBks+opWlN1E4oP1UYl3C/2EQA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1005,13 +1005,13 @@
}
},
"node_modules/@vitest/runner": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.10.tgz",
- "integrity": "sha512-EXU2iSkKvNwtlL8L8doCpkyclw0mc/t4t9SeOnfOFPyqLmQwuceMPA4zJBa6jw0MKsZYbw7kAn+gl7HxrlB8UQ==",
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.13.tgz",
+ "integrity": "sha512-9IKlAru58wcVaWy7hz6qWPb2QzJTKt+IOVKjAx5vb5rzEFPTL6H4/R9BMvjZ2ppkxKgTrFONEJFtzvnyEpiT+A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "4.0.10",
+ "@vitest/utils": "4.0.13",
"pathe": "^2.0.3"
},
"funding": {
@@ -1019,13 +1019,13 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.10.tgz",
- "integrity": "sha512-2N4X2ZZl7kZw0qeGdQ41H0KND96L3qX1RgwuCfy6oUsF2ISGD/HpSbmms+CkIOsQmg2kulwfhJ4CI0asnZlvkg==",
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.13.tgz",
+ "integrity": "sha512-hb7Usvyika1huG6G6l191qu1urNPsq1iFc2hmdzQY3F5/rTgqQnwwplyf8zoYHkpt7H6rw5UfIw6i/3qf9oSxQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.0.10",
+ "@vitest/pretty-format": "4.0.13",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -1034,9 +1034,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.10.tgz",
- "integrity": "sha512-AsY6sVS8OLb96GV5RoG8B6I35GAbNrC49AO+jNRF9YVGb/g9t+hzNm1H6kD0NDp8tt7VJLs6hb7YMkDXqu03iw==",
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.13.tgz",
+ "integrity": "sha512-hSu+m4se0lDV5yVIcNWqjuncrmBgwaXa2utFLIrBkQCQkt+pSwyZTPFQAZiiF/63j8jYa8uAeUZ3RSfcdWaYWw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1044,13 +1044,13 @@
}
},
"node_modules/@vitest/utils": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.10.tgz",
- "integrity": "sha512-kOuqWnEwZNtQxMKg3WmPK1vmhZu9WcoX69iwWjVz+jvKTsF1emzsv3eoPcDr6ykA3qP2bsCQE7CwqfNtAVzsmg==",
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.13.tgz",
+ "integrity": "sha512-ydozWyQ4LZuu8rLp47xFUWis5VOKMdHjXCWhs1LuJsTNKww+pTHQNK4e0assIB9K80TxFyskENL6vCu3j34EYA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.0.10",
+ "@vitest/pretty-format": "4.0.13",
"tinyrainbow": "^3.0.3"
},
"funding": {
@@ -2341,9 +2341,9 @@
}
},
"node_modules/vite": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
- "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
+ "version": "7.2.4",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz",
+ "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2439,20 +2439,20 @@
}
},
"node_modules/vitest": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.10.tgz",
- "integrity": "sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==",
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.13.tgz",
+ "integrity": "sha512-QSD4I0fN6uZQfftryIXuqvqgBxTvJ3ZNkF6RWECd82YGAYAfhcppBLFXzXJHQAAhVFyYEuFTrq6h0hQqjB7jIQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "@vitest/expect": "4.0.10",
- "@vitest/mocker": "4.0.10",
- "@vitest/pretty-format": "4.0.10",
- "@vitest/runner": "4.0.10",
- "@vitest/snapshot": "4.0.10",
- "@vitest/spy": "4.0.10",
- "@vitest/utils": "4.0.10",
+ "@vitest/expect": "4.0.13",
+ "@vitest/mocker": "4.0.13",
+ "@vitest/pretty-format": "4.0.13",
+ "@vitest/runner": "4.0.13",
+ "@vitest/snapshot": "4.0.13",
+ "@vitest/spy": "4.0.13",
+ "@vitest/utils": "4.0.13",
"debug": "^4.4.3",
"es-module-lexer": "^1.7.0",
"expect-type": "^1.2.2",
@@ -2478,12 +2478,13 @@
},
"peerDependencies": {
"@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
"@types/debug": "^4.1.12",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.0.10",
- "@vitest/browser-preview": "4.0.10",
- "@vitest/browser-webdriverio": "4.0.10",
- "@vitest/ui": "4.0.10",
+ "@vitest/browser-playwright": "4.0.13",
+ "@vitest/browser-preview": "4.0.13",
+ "@vitest/browser-webdriverio": "4.0.13",
+ "@vitest/ui": "4.0.13",
"happy-dom": "*",
"jsdom": "*"
},
@@ -2491,6 +2492,9 @@
"@edge-runtime/vm": {
"optional": true
},
+ "@opentelemetry/api": {
+ "optional": true
+ },
"@types/debug": {
"optional": true
},
diff --git a/scripts/changelog-checker/package.json b/scripts/changelog-checker/package.json
index cbce64611e..8cc94d0532 100644
--- a/scripts/changelog-checker/package.json
+++ b/scripts/changelog-checker/package.json
@@ -10,15 +10,15 @@
},
"devDependencies": {
"@types/mdast": "4.0.4",
- "@types/node": "24.9.1",
- "@vitest/coverage-v8": "4.0.10",
+ "@types/node": "24.10.1",
+ "@vitest/coverage-v8": "4.0.13",
"mdast-util-to-string": "4.0.0",
"remark": "15.0.1",
"remark-parse": "11.0.0",
"typescript": "5.9.3",
"unified": "11.0.5",
- "vite": "7.2.2",
+ "vite": "7.2.4",
"vite-node": "5.2.0",
- "vitest": "4.0.10"
+ "vitest": "4.0.13"
}
}
--
cgit v1.2.3-freya
From f222d7e24d3d134a078868c89363cacde906ccdc Mon Sep 17 00:00:00 2001
From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Mon, 1 Dec 2025 18:36:55 +0900
Subject: enhance(backend): pugをやめ、JSXベースのテンプレートに変更 (#16908)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 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 14313468d34f49c363eef4d0a932e9fc0d9a37fb.
* 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>
---
.vscode/settings.json | 1 +
package.json | 1 -
packages/backend/.swcrc | 7 +-
packages/backend/assets/misc/bios.css | 46 +++
packages/backend/assets/misc/bios.js | 92 ++++++
packages/backend/assets/misc/cli.css | 25 ++
packages/backend/assets/misc/cli.js | 63 ++++
packages/backend/assets/misc/error.css | 111 +++++++
packages/backend/assets/misc/error.js | 40 +++
packages/backend/assets/misc/flush.js | 46 +++
packages/backend/assets/misc/info-card.css | 35 +++
packages/backend/jest.config.cjs | 2 +-
packages/backend/jsconfig.json | 13 -
packages/backend/package.json | 9 +-
packages/backend/scripts/dev.mjs | 2 +-
packages/backend/src/config.ts | 5 +-
.../backend/src/misc/json-stringify-html-safe.ts | 18 ++
packages/backend/src/server/ServerModule.ts | 2 +
.../src/server/oauth/OAuth2ProviderService.ts | 23 +-
.../backend/src/server/web/ClientServerService.ts | 215 ++++++-------
.../backend/src/server/web/HtmlTemplateService.ts | 105 +++++++
packages/backend/src/server/web/bios.css | 46 ---
packages/backend/src/server/web/bios.js | 92 ------
packages/backend/src/server/web/boot.embed.js | 208 -------------
packages/backend/src/server/web/boot.js | 336 ---------------------
packages/backend/src/server/web/cli.css | 25 --
packages/backend/src/server/web/cli.js | 63 ----
packages/backend/src/server/web/error.css | 111 -------
packages/backend/src/server/web/error.js | 40 ---
packages/backend/src/server/web/style.css | 78 -----
packages/backend/src/server/web/style.embed.css | 100 ------
packages/backend/src/server/web/views/_.ts | 49 +++
packages/backend/src/server/web/views/_splash.tsx | 26 ++
.../backend/src/server/web/views/announcement.pug | 21 --
.../backend/src/server/web/views/announcement.tsx | 41 +++
.../backend/src/server/web/views/base-embed.pug | 71 -----
.../backend/src/server/web/views/base-embed.tsx | 88 ++++++
packages/backend/src/server/web/views/base.pug | 100 ------
packages/backend/src/server/web/views/base.tsx | 108 +++++++
packages/backend/src/server/web/views/bios.pug | 20 --
packages/backend/src/server/web/views/bios.tsx | 35 +++
packages/backend/src/server/web/views/channel.pug | 19 --
packages/backend/src/server/web/views/channel.tsx | 40 +++
packages/backend/src/server/web/views/cli.pug | 21 --
packages/backend/src/server/web/views/cli.tsx | 37 +++
packages/backend/src/server/web/views/clip.pug | 35 ---
packages/backend/src/server/web/views/clip.tsx | 59 ++++
packages/backend/src/server/web/views/error.pug | 71 -----
packages/backend/src/server/web/views/error.tsx | 89 ++++++
packages/backend/src/server/web/views/flash.pug | 35 ---
packages/backend/src/server/web/views/flash.tsx | 59 ++++
packages/backend/src/server/web/views/flush.pug | 51 ----
packages/backend/src/server/web/views/flush.tsx | 23 ++
.../backend/src/server/web/views/gallery-post.pug | 41 ---
.../backend/src/server/web/views/gallery-post.tsx | 65 ++++
.../backend/src/server/web/views/info-card.pug | 50 ---
.../backend/src/server/web/views/info-card.tsx | 40 +++
packages/backend/src/server/web/views/note.pug | 62 ----
packages/backend/src/server/web/views/note.tsx | 94 ++++++
packages/backend/src/server/web/views/oauth.pug | 11 -
packages/backend/src/server/web/views/oauth.tsx | 37 +++
packages/backend/src/server/web/views/page.pug | 35 ---
packages/backend/src/server/web/views/page.tsx | 64 ++++
.../backend/src/server/web/views/reversi-game.pug | 20 --
.../backend/src/server/web/views/reversi-game.tsx | 37 +++
packages/backend/src/server/web/views/user.pug | 44 ---
packages/backend/src/server/web/views/user.tsx | 74 +++++
packages/backend/test-federation/compose.tpl.yml | 8 +
packages/backend/test-federation/compose.yml | 8 +
packages/backend/test-federation/tsconfig.json | 4 +-
packages/backend/test-server/tsconfig.json | 2 +
packages/backend/test/tsconfig.json | 2 +
packages/backend/tsconfig.json | 8 +-
packages/frontend-embed/public/loader/boot.js | 208 +++++++++++++
packages/frontend-embed/public/loader/style.css | 100 ++++++
packages/frontend/public/loader/boot.js | 336 +++++++++++++++++++++
packages/frontend/public/loader/style.css | 78 +++++
packages/i18n/src/index.ts | 2 +-
pnpm-lock.yaml | 120 ++++++--
scripts/build-assets.mjs | 45 ---
80 files changed, 2606 insertions(+), 2047 deletions(-)
create mode 100644 packages/backend/assets/misc/bios.css
create mode 100644 packages/backend/assets/misc/bios.js
create mode 100644 packages/backend/assets/misc/cli.css
create mode 100644 packages/backend/assets/misc/cli.js
create mode 100644 packages/backend/assets/misc/error.css
create mode 100644 packages/backend/assets/misc/error.js
create mode 100644 packages/backend/assets/misc/flush.js
create mode 100644 packages/backend/assets/misc/info-card.css
delete mode 100644 packages/backend/jsconfig.json
create mode 100644 packages/backend/src/misc/json-stringify-html-safe.ts
create mode 100644 packages/backend/src/server/web/HtmlTemplateService.ts
delete mode 100644 packages/backend/src/server/web/bios.css
delete mode 100644 packages/backend/src/server/web/bios.js
delete mode 100644 packages/backend/src/server/web/boot.embed.js
delete mode 100644 packages/backend/src/server/web/boot.js
delete mode 100644 packages/backend/src/server/web/cli.css
delete mode 100644 packages/backend/src/server/web/cli.js
delete mode 100644 packages/backend/src/server/web/error.css
delete mode 100644 packages/backend/src/server/web/error.js
delete mode 100644 packages/backend/src/server/web/style.css
delete mode 100644 packages/backend/src/server/web/style.embed.css
create mode 100644 packages/backend/src/server/web/views/_.ts
create mode 100644 packages/backend/src/server/web/views/_splash.tsx
delete mode 100644 packages/backend/src/server/web/views/announcement.pug
create mode 100644 packages/backend/src/server/web/views/announcement.tsx
delete mode 100644 packages/backend/src/server/web/views/base-embed.pug
create mode 100644 packages/backend/src/server/web/views/base-embed.tsx
delete mode 100644 packages/backend/src/server/web/views/base.pug
create mode 100644 packages/backend/src/server/web/views/base.tsx
delete mode 100644 packages/backend/src/server/web/views/bios.pug
create mode 100644 packages/backend/src/server/web/views/bios.tsx
delete mode 100644 packages/backend/src/server/web/views/channel.pug
create mode 100644 packages/backend/src/server/web/views/channel.tsx
delete mode 100644 packages/backend/src/server/web/views/cli.pug
create mode 100644 packages/backend/src/server/web/views/cli.tsx
delete mode 100644 packages/backend/src/server/web/views/clip.pug
create mode 100644 packages/backend/src/server/web/views/clip.tsx
delete mode 100644 packages/backend/src/server/web/views/error.pug
create mode 100644 packages/backend/src/server/web/views/error.tsx
delete mode 100644 packages/backend/src/server/web/views/flash.pug
create mode 100644 packages/backend/src/server/web/views/flash.tsx
delete mode 100644 packages/backend/src/server/web/views/flush.pug
create mode 100644 packages/backend/src/server/web/views/flush.tsx
delete mode 100644 packages/backend/src/server/web/views/gallery-post.pug
create mode 100644 packages/backend/src/server/web/views/gallery-post.tsx
delete mode 100644 packages/backend/src/server/web/views/info-card.pug
create mode 100644 packages/backend/src/server/web/views/info-card.tsx
delete mode 100644 packages/backend/src/server/web/views/note.pug
create mode 100644 packages/backend/src/server/web/views/note.tsx
delete mode 100644 packages/backend/src/server/web/views/oauth.pug
create mode 100644 packages/backend/src/server/web/views/oauth.tsx
delete mode 100644 packages/backend/src/server/web/views/page.pug
create mode 100644 packages/backend/src/server/web/views/page.tsx
delete mode 100644 packages/backend/src/server/web/views/reversi-game.pug
create mode 100644 packages/backend/src/server/web/views/reversi-game.tsx
delete mode 100644 packages/backend/src/server/web/views/user.pug
create mode 100644 packages/backend/src/server/web/views/user.tsx
create mode 100644 packages/frontend-embed/public/loader/boot.js
create mode 100644 packages/frontend-embed/public/loader/style.css
create mode 100644 packages/frontend/public/loader/boot.js
create mode 100644 packages/frontend/public/loader/style.css
(limited to 'scripts')
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 5f36a32af4..2d11d24db2 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,6 +3,7 @@
"**/node_modules": true
},
"typescript.tsdk": "node_modules/typescript/lib",
+ "typescript.enablePromptUseWorkspaceTsdk": true,
"files.associations": {
"*.test.ts": "typescript"
},
diff --git a/package.json b/package.json
index 01d3037826..c7873f6380 100644
--- a/package.json
+++ b/package.json
@@ -70,7 +70,6 @@
},
"devDependencies": {
"@eslint/js": "9.39.1",
- "i18n": "workspace:*",
"@misskey-dev/eslint-plugin": "2.2.0",
"@types/js-yaml": "4.0.9",
"@types/node": "24.10.1",
diff --git a/packages/backend/.swcrc b/packages/backend/.swcrc
index f4bf7a4d2a..7e1767a67a 100644
--- a/packages/backend/.swcrc
+++ b/packages/backend/.swcrc
@@ -3,12 +3,17 @@
"jsc": {
"parser": {
"syntax": "typescript",
+ "jsx": true,
"dynamicImport": true,
"decorators": true
},
"transform": {
"legacyDecorator": true,
- "decoratorMetadata": true
+ "decoratorMetadata": true,
+ "react": {
+ "runtime": "automatic",
+ "importSource": "@kitajs/html"
+ }
},
"experimental": {
"keepImportAssertions": true
diff --git a/packages/backend/assets/misc/bios.css b/packages/backend/assets/misc/bios.css
new file mode 100644
index 0000000000..91d1af10b4
--- /dev/null
+++ b/packages/backend/assets/misc/bios.css
@@ -0,0 +1,46 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+* {
+ font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
+}
+
+html {
+ background: #ffb4e1;
+}
+
+main {
+ background: #dedede;
+}
+main > .tabs {
+ padding: 16px;
+ border-bottom: solid 4px #c3c3c3;
+}
+
+#lsEditor > .adder {
+ margin: 16px;
+ padding: 16px;
+ border: solid 2px #c3c3c3;
+}
+#lsEditor > .adder > textarea {
+ display: block;
+ width: 100%;
+ min-height: 5em;
+ box-sizing: border-box;
+}
+#lsEditor > .record {
+ padding: 16px;
+ border-bottom: solid 1px #c3c3c3;
+}
+#lsEditor > .record > header {
+ font-weight: bold;
+}
+#lsEditor > .record > textarea {
+ display: block;
+ width: 100%;
+ min-height: 5em;
+ box-sizing: border-box;
+}
diff --git a/packages/backend/assets/misc/bios.js b/packages/backend/assets/misc/bios.js
new file mode 100644
index 0000000000..9ff5dca72a
--- /dev/null
+++ b/packages/backend/assets/misc/bios.js
@@ -0,0 +1,92 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+'use strict';
+
+window.onload = async () => {
+ const account = JSON.parse(localStorage.getItem('account'));
+ const i = account.token;
+
+ const api = (endpoint, data = {}) => {
+ const promise = new Promise((resolve, reject) => {
+ // Append a credential
+ if (i) data.i = i;
+
+ // Send request
+ window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
+ method: 'POST',
+ body: JSON.stringify(data),
+ credentials: 'omit',
+ cache: 'no-cache'
+ }).then(async (res) => {
+ const body = res.status === 204 ? null : await res.json();
+
+ if (res.status === 200) {
+ resolve(body);
+ } else if (res.status === 204) {
+ resolve();
+ } else {
+ reject(body.error);
+ }
+ }).catch(reject);
+ });
+
+ return promise;
+ };
+
+ const content = document.getElementById('content');
+
+ document.getElementById('ls').addEventListener('click', () => {
+ content.innerHTML = '';
+
+ const lsEditor = document.createElement('div');
+ lsEditor.id = 'lsEditor';
+
+ const adder = document.createElement('div');
+ adder.classList.add('adder');
+ const addKeyInput = document.createElement('input');
+ const addValueTextarea = document.createElement('textarea');
+ const addButton = document.createElement('button');
+ addButton.textContent = 'add';
+ addButton.addEventListener('click', () => {
+ localStorage.setItem(addKeyInput.value, addValueTextarea.value);
+ location.reload();
+ });
+
+ adder.appendChild(addKeyInput);
+ adder.appendChild(addValueTextarea);
+ adder.appendChild(addButton);
+ lsEditor.appendChild(adder);
+
+ for (let i = 0; i < localStorage.length; i++) {
+ const k = localStorage.key(i);
+ const record = document.createElement('div');
+ record.classList.add('record');
+ const header = document.createElement('header');
+ header.textContent = k;
+ const textarea = document.createElement('textarea');
+ textarea.textContent = localStorage.getItem(k);
+ const saveButton = document.createElement('button');
+ saveButton.textContent = 'save';
+ saveButton.addEventListener('click', () => {
+ localStorage.setItem(k, textarea.value);
+ location.reload();
+ });
+ const removeButton = document.createElement('button');
+ removeButton.textContent = 'remove';
+ removeButton.addEventListener('click', () => {
+ localStorage.removeItem(k);
+ location.reload();
+ });
+ record.appendChild(header);
+ record.appendChild(textarea);
+ record.appendChild(saveButton);
+ record.appendChild(removeButton);
+ lsEditor.appendChild(record);
+ }
+
+ content.appendChild(lsEditor);
+ });
+};
diff --git a/packages/backend/assets/misc/cli.css b/packages/backend/assets/misc/cli.css
new file mode 100644
index 0000000000..4e6136d59c
--- /dev/null
+++ b/packages/backend/assets/misc/cli.css
@@ -0,0 +1,25 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+* {
+ font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
+}
+
+html {
+ background: #ffb4e1;
+}
+
+main {
+ background: #dedede;
+}
+
+#tl > div {
+ padding: 16px;
+ border-bottom: solid 1px #c3c3c3;
+}
+#tl > div > header {
+ font-weight: bold;
+}
diff --git a/packages/backend/assets/misc/cli.js b/packages/backend/assets/misc/cli.js
new file mode 100644
index 0000000000..30ee77f4d9
--- /dev/null
+++ b/packages/backend/assets/misc/cli.js
@@ -0,0 +1,63 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+'use strict';
+
+window.onload = async () => {
+ const account = JSON.parse(localStorage.getItem('account'));
+ const i = account.token;
+
+ const api = (endpoint, data = {}) => {
+ const promise = new Promise((resolve, reject) => {
+ // Append a credential
+ if (i) data.i = i;
+
+ // Send request
+ fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ method: 'POST',
+ body: JSON.stringify(data),
+ credentials: 'omit',
+ cache: 'no-cache'
+ }).then(async (res) => {
+ const body = res.status === 204 ? null : await res.json();
+
+ if (res.status === 200) {
+ resolve(body);
+ } else if (res.status === 204) {
+ resolve();
+ } else {
+ reject(body.error);
+ }
+ }).catch(reject);
+ });
+
+ return promise;
+ };
+
+ document.getElementById('submit').addEventListener('click', () => {
+ api('notes/create', {
+ text: document.getElementById('text').value
+ }).then(() => {
+ location.reload();
+ });
+ });
+
+ api('notes/timeline').then(notes => {
+ const tl = document.getElementById('tl');
+ for (const note of notes) {
+ const el = document.createElement('div');
+ const name = document.createElement('header');
+ name.textContent = `${note.user.name} @${note.user.username}`;
+ const text = document.createElement('div');
+ text.textContent = `${note.text}`;
+ el.appendChild(name);
+ el.appendChild(text);
+ tl.appendChild(el);
+ }
+ });
+};
diff --git a/packages/backend/assets/misc/error.css b/packages/backend/assets/misc/error.css
new file mode 100644
index 0000000000..803bd1b4b5
--- /dev/null
+++ b/packages/backend/assets/misc/error.css
@@ -0,0 +1,111 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+* {
+ font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
+}
+
+#misskey_app,
+#splash {
+ display: none !important;
+}
+
+body,
+html {
+ background-color: #222;
+ color: #dfddcc;
+ justify-content: center;
+ margin: auto;
+ padding: 10px;
+ text-align: center;
+}
+
+button {
+ border-radius: 999px;
+ padding: 0px 12px 0px 12px;
+ border: none;
+ cursor: pointer;
+ margin-bottom: 12px;
+}
+
+.button-big {
+ background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
+ line-height: 50px;
+}
+
+.button-big:hover {
+ background: rgb(153, 204, 0);
+}
+
+.button-small {
+ background: #444;
+ line-height: 40px;
+}
+
+.button-small:hover {
+ background: #555;
+}
+
+.button-label-big {
+ color: #222;
+ font-weight: bold;
+ font-size: 1.2em;
+ padding: 12px;
+}
+
+.button-label-small {
+ color: rgb(153, 204, 0);
+ font-size: 16px;
+ padding: 12px;
+}
+
+a {
+ color: rgb(134, 179, 0);
+ text-decoration: none;
+}
+
+p,
+li {
+ font-size: 16px;
+}
+
+.icon-warning {
+ color: #dec340;
+ height: 4rem;
+ padding-top: 2rem;
+}
+
+h1 {
+ font-size: 1.5em;
+ margin: 1em;
+}
+
+code {
+ display: block;
+ font-family: Fira, FiraCode, monospace;
+ background: #333;
+ padding: 0.5rem 1rem;
+ max-width: 40rem;
+ border-radius: 10px;
+ justify-content: center;
+ margin: auto;
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+#errorInfo summary {
+ cursor: pointer;
+}
+
+#errorInfo summary>* {
+ display: inline;
+}
+
+@media screen and (max-width: 500px) {
+ #errorInfo {
+ width: 50%;
+ }
+}
diff --git a/packages/backend/assets/misc/error.js b/packages/backend/assets/misc/error.js
new file mode 100644
index 0000000000..4838dd6ef3
--- /dev/null
+++ b/packages/backend/assets/misc/error.js
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+'use strict';
+
+(() => {
+ document.addEventListener('DOMContentLoaded', () => {
+ const locale = JSON.parse(localStorage.getItem('locale') || '{}');
+
+ const messages = Object.assign({
+ title: 'Failed to initialize Misskey',
+ serverError: 'If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.',
+ solution: 'The following actions may solve the problem.',
+ solution1: 'Update your os and browser',
+ solution2: 'Disable an adblocker',
+ solution3: 'Clear the browser cache',
+ solution4: '(Tor Browser) Set dom.webaudio.enabled to true',
+ otherOption: 'Other options',
+ otherOption1: 'Clear preferences and cache',
+ otherOption2: 'Start the simple client',
+ otherOption3: 'Start the repair tool',
+ }, locale?._bootErrors || {});
+ const reload = locale?.reload || 'Reload';
+
+ const reloadEls = document.querySelectorAll('[data-i18n-reload]');
+ for (const el of reloadEls) {
+ el.textContent = reload;
+ }
+
+ const i18nEls = document.querySelectorAll('[data-i18n]');
+ for (const el of i18nEls) {
+ const key = el.dataset.i18n;
+ if (key && messages[key]) {
+ el.textContent = messages[key];
+ }
+ }
+ });
+})();
diff --git a/packages/backend/assets/misc/flush.js b/packages/backend/assets/misc/flush.js
new file mode 100644
index 0000000000..991b8ea808
--- /dev/null
+++ b/packages/backend/assets/misc/flush.js
@@ -0,0 +1,46 @@
+(async () => {
+ const msg = document.getElementById('msg');
+ const successText = `\nSuccess Flush! Back to Misskey\n成功しました。Misskeyを開き直してください。`;
+
+ 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. Please retry.\n失敗しました。もう一度試してみてください。`);
+ 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', `
[${(new Date()).toString()}] ${text.replace(/\n/g,'
')}
`)
+ }
+})();
diff --git a/packages/backend/assets/misc/info-card.css b/packages/backend/assets/misc/info-card.css
new file mode 100644
index 0000000000..3e27223cc5
--- /dev/null
+++ b/packages/backend/assets/misc/info-card.css
@@ -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;
+}
diff --git a/packages/backend/jest.config.cjs b/packages/backend/jest.config.cjs
index 5a4aa4e15a..22ffbbee5c 100644
--- a/packages/backend/jest.config.cjs
+++ b/packages/backend/jest.config.cjs
@@ -205,7 +205,7 @@ module.exports = {
// Whether to use watchman for file crawling
// watchman: true,
- extensionsToTreatAsEsm: ['.ts'],
+ extensionsToTreatAsEsm: ['.ts', '.tsx'],
testTimeout: 60000,
diff --git a/packages/backend/jsconfig.json b/packages/backend/jsconfig.json
deleted file mode 100644
index 1230aadd12..0000000000
--- a/packages/backend/jsconfig.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "compilerOptions": {
- "target": "es6",
- "module": "commonjs",
- "allowSyntheticDefaultImports": true
- },
- "exclude": [
- "node_modules",
- "jspm_packages",
- "tmp",
- "temp"
- ]
-}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 545779fd7a..dad664d0ca 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -80,7 +80,7 @@
"@fastify/http-proxy": "11.3.0",
"@fastify/multipart": "9.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/summaly": "5.2.5",
"@napi-rs/canvas": "0.1.82",
@@ -123,6 +123,7 @@
"got": "14.6.4",
"hpagent": "1.2.0",
"http-link-header": "1.1.3",
+ "i18n": "workspace:*",
"ioredis": "5.8.2",
"ip-cidr": "4.0.2",
"ipaddr.js": "2.2.0",
@@ -153,7 +154,6 @@
"pkce-challenge": "5.0.0",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
- "pug": "3.0.3",
"qrcode": "1.5.4",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
@@ -185,6 +185,7 @@
},
"devDependencies": {
"@jest/globals": "29.7.0",
+ "@kitajs/ts-html-plugin": "4.1.3",
"@nestjs/platform-express": "11.1.9",
"@sentry/vue": "10.26.0",
"@simplewebauthn/types": "12.0.0",
@@ -208,7 +209,6 @@
"@types/oauth2orize": "1.11.5",
"@types/oauth2orize-pkce": "0.1.2",
"@types/pg": "8.15.6",
- "@types/pug": "2.0.10",
"@types/qrcode": "1.5.6",
"@types/random-seed": "0.3.5",
"@types/ratelimiter": "3.4.6",
@@ -236,6 +236,7 @@
"nodemon": "3.1.11",
"pid-port": "2.0.0",
"simple-oauth2": "5.1.0",
- "supertest": "7.1.4"
+ "supertest": "7.1.4",
+ "vite": "7.2.4"
}
}
diff --git a/packages/backend/scripts/dev.mjs b/packages/backend/scripts/dev.mjs
index 023eb7eae6..db96eaf976 100644
--- a/packages/backend/scripts/dev.mjs
+++ b/packages/backend/scripts/dev.mjs
@@ -42,7 +42,7 @@ async function killProc() {
'./node_modules/nodemon/bin/nodemon.js',
[
'-w', 'src',
- '-e', 'ts,js,mjs,cjs,json,pug',
+ '-e', 'ts,js,mjs,cjs,tsx,json,pug',
'--exec', 'pnpm', 'run', 'build',
],
{
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index fdf6fe18e2..fc83899eb7 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -11,6 +11,7 @@ import { type FastifyServerOptions } from 'fastify';
import type * as Sentry from '@sentry/node';
import type * as SentryVue from '@sentry/vue';
import type { RedisOptions } from 'ioredis';
+import type { ManifestChunk } from 'vite';
type RedisOptionsSource = Partial & {
host: string;
@@ -187,9 +188,9 @@ export type Config = {
authUrl: string;
driveUrl: string;
userAgent: string;
- frontendEntry: { file: string | null };
+ frontendEntry: ManifestChunk;
frontendManifestExists: boolean;
- frontendEmbedEntry: { file: string | null };
+ frontendEmbedEntry: ManifestChunk;
frontendEmbedManifestExists: boolean;
mediaProxy: string;
externalMediaProxyEnabled: boolean;
diff --git a/packages/backend/src/misc/json-stringify-html-safe.ts b/packages/backend/src/misc/json-stringify-html-safe.ts
new file mode 100644
index 0000000000..aac12d57db
--- /dev/null
+++ b/packages/backend/src/misc/json-stringify-html-safe.ts
@@ -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;
+
+const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
+
+export function htmlSafeJsonStringify(obj: any): string {
+ return JSON.stringify(obj).replace(ESCAPE_REGEX, x => ESCAPE_LOOKUP[x]);
+}
diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts
index 0223650329..111421472d 100644
--- a/packages/backend/src/server/ServerModule.ts
+++ b/packages/backend/src/server/ServerModule.ts
@@ -25,6 +25,7 @@ import { SignupApiService } from './api/SignupApiService.js';
import { StreamingApiServerService } from './api/StreamingApiServerService.js';
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
import { ClientServerService } from './web/ClientServerService.js';
+import { HtmlTemplateService } from './web/HtmlTemplateService.js';
import { FeedService } from './web/FeedService.js';
import { UrlPreviewService } from './web/UrlPreviewService.js';
import { ClientLoggerService } from './web/ClientLoggerService.js';
@@ -58,6 +59,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
providers: [
ClientServerService,
ClientLoggerService,
+ HtmlTemplateService,
FeedService,
HealthServerService,
UrlPreviewService,
diff --git a/packages/backend/src/server/oauth/OAuth2ProviderService.ts b/packages/backend/src/server/oauth/OAuth2ProviderService.ts
index 1c15f35399..d2391c43ab 100644
--- a/packages/backend/src/server/oauth/OAuth2ProviderService.ts
+++ b/packages/backend/src/server/oauth/OAuth2ProviderService.ts
@@ -12,8 +12,6 @@ import ipaddr from 'ipaddr.js';
import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req, MiddlewareRequest } from 'oauth2orize';
import oauth2Pkce from 'oauth2orize-pkce';
import fastifyCors from '@fastify/cors';
-import fastifyView from '@fastify/view';
-import pug from 'pug';
import bodyParser from 'body-parser';
import fastifyExpress from '@fastify/express';
import { verifyChallenge } from 'pkce-challenge';
@@ -31,6 +29,8 @@ import { MemoryKVCache } from '@/misc/cache.js';
import { LoggerService } from '@/core/LoggerService.js';
import Logger from '@/logger.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 { FastifyInstance } from 'fastify';
@@ -273,6 +273,7 @@ export class OAuth2ProviderService {
private usersRepository: UsersRepository,
private cacheService: CacheService,
loggerService: LoggerService,
+ private htmlTemplateService: HtmlTemplateService,
) {
this.#logger = loggerService.getLogger('oauth');
@@ -406,24 +407,16 @@ export class OAuth2ProviderService {
this.#logger.info(`Rendering authorization page for "${oauth2.client.name}"`);
reply.header('Cache-Control', 'no-store');
- return await reply.view('oauth', {
+ return await HtmlTemplateService.replyHtml(reply, OAuthPage({
+ ...await this.htmlTemplateService.getCommonData(),
transactionId: oauth2.transactionID,
clientName: oauth2.client.name,
- clientLogo: oauth2.client.logo,
- scope: oauth2.req.scope.join(' '),
- });
+ clientLogo: oauth2.client.logo ?? undefined,
+ scope: oauth2.req.scope,
+ }));
});
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);
fastify.use('/authorize', this.#server.authorize(((areq, done) => {
(async (): Promise> => {
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index fef6a27087..bcea935409 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -9,20 +9,16 @@ import { fileURLToPath } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import sharp from 'sharp';
-import pug from 'pug';
import { In, IsNull } from 'typeorm';
import fastifyStatic from '@fastify/static';
-import fastifyView from '@fastify/view';
import fastifyProxy from '@fastify/http-proxy';
import vary from 'vary';
import type { Config } from '@/config.js';
-import { getNoteSummary } from '@/misc/get-note-summary.js';
import { DI } from '@/di-symbols.js';
import * as Acct from '@/misc/acct.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { PageEntityService } from '@/core/entities/PageEntityService.js';
-import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
@@ -41,14 +37,33 @@ import type {
} from '@/models/_.js';
import type Logger from '@/logger.js';
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
+import { htmlSafeJsonStringify } from '@/misc/json-stringify-html-safe.js';
import { bindThis } from '@/decorators.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
-import { RoleService } from '@/core/RoleService.js';
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
import { FeedService } from './FeedService.js';
import { UrlPreviewService } from './UrlPreviewService.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';
const _filename = fileURLToPath(import.meta.url);
@@ -62,20 +77,6 @@ const frontendViteOut = `${_dirname}/../../../../../built/_frontend_vite_/`;
const frontendEmbedViteOut = `${_dirname}/../../../../../built/_frontend_embed_vite_/`;
const tarball = `${_dirname}/../../../../../built/tarball/`;
-const ESCAPE_LOOKUP = {
- '&': '\\u0026',
- '>': '\\u003e',
- '<': '\\u003c',
- '\u2028': '\\u2028',
- '\u2029': '\\u2029',
-} as Record;
-
-const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
-
-function htmlSafeJsonStringify(obj: any): string {
- return JSON.stringify(obj).replace(ESCAPE_REGEX, x => ESCAPE_LOOKUP[x]);
-}
-
@Injectable()
export class ClientServerService {
private logger: Logger;
@@ -121,7 +122,6 @@ export class ClientServerService {
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
private pageEntityService: PageEntityService,
- private metaEntityService: MetaEntityService,
private galleryPostEntityService: GalleryPostEntityService,
private clipEntityService: ClipEntityService,
private channelEntityService: ChannelEntityService,
@@ -129,7 +129,7 @@ export class ClientServerService {
private announcementEntityService: AnnouncementEntityService,
private urlPreviewService: UrlPreviewService,
private feedService: FeedService,
- private roleService: RoleService,
+ private htmlTemplateService: HtmlTemplateService,
private clientLoggerService: ClientLoggerService,
) {
//this.createServer = this.createServer.bind(this);
@@ -195,38 +195,10 @@ export class ClientServerService {
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
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
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) => {
// クリックジャッキング防止のためiFrameの中に入れられないようにする
reply.header('X-Frame-Options', 'DENY');
@@ -427,16 +399,15 @@ export class ClientServerService {
//#endregion
- const renderBase = async (reply: FastifyReply, data: { [key: string]: any } = {}) => {
+ const renderBase = async (reply: FastifyReply, data: Partial[0]> = {}) => {
reply.header('Cache-Control', 'public, max-age=30');
- return await reply.view('base', {
- img: this.meta.bannerUrl,
- url: this.config.url,
+ return await HtmlTemplateService.replyHtml(reply, BasePage({
+ img: this.meta.bannerUrl ?? undefined,
title: this.meta.name ?? 'Misskey',
- desc: this.meta.description,
- ...await this.generateCommonPugData(this.meta),
+ desc: this.meta.description ?? undefined,
+ ...await this.htmlTemplateService.getCommonData(),
...data,
- });
+ }));
};
// URL preview endpoint
@@ -518,11 +489,6 @@ export class ClientServerService {
)
) {
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');
if (profile.preventAiLearning) {
@@ -535,15 +501,15 @@ export class ClientServerService {
userProfile: profile,
});
- return await reply.view('user', {
- user, profile, me,
- avatarUrl: _user.avatarUrl,
+ return await HtmlTemplateService.replyHtml(reply, UserPage({
+ user: _user,
+ profile,
sub: request.params.sub,
- ...await this.generateCommonPugData(this.meta),
- clientCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ clientCtxJson: htmlSafeJsonStringify({
user: _user,
}),
- });
+ }));
} else {
// リモートユーザーなので
// モデレータがAPI経由で参照可能にするために404にはしない
@@ -594,17 +560,14 @@ export class ClientServerService {
reply.header('X-Robots-Tag', 'noimageai');
reply.header('X-Robots-Tag', 'noai');
}
- return await reply.view('note', {
+ return await HtmlTemplateService.replyHtml(reply, NotePage({
note: _note,
profile,
- avatarUrl: _note.user.avatarUrl,
- // TODO: Let locale changeable by instance setting
- summary: getNoteSummary(_note),
- ...await this.generateCommonPugData(this.meta),
- clientCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ clientCtxJson: htmlSafeJsonStringify({
note: _note,
}),
- });
+ }));
} else {
return await renderBase(reply);
}
@@ -637,12 +600,11 @@ export class ClientServerService {
reply.header('X-Robots-Tag', 'noimageai');
reply.header('X-Robots-Tag', 'noai');
}
- return await reply.view('page', {
+ return await HtmlTemplateService.replyHtml(reply, PagePage({
page: _page,
profile,
- avatarUrl: _page.user.avatarUrl,
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -662,12 +624,11 @@ export class ClientServerService {
reply.header('X-Robots-Tag', 'noimageai');
reply.header('X-Robots-Tag', 'noai');
}
- return await reply.view('flash', {
+ return await HtmlTemplateService.replyHtml(reply, FlashPage({
flash: _flash,
profile,
- avatarUrl: _flash.user.avatarUrl,
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -687,15 +648,14 @@ export class ClientServerService {
reply.header('X-Robots-Tag', 'noimageai');
reply.header('X-Robots-Tag', 'noai');
}
- return await reply.view('clip', {
+ return await HtmlTemplateService.replyHtml(reply, ClipPage({
clip: _clip,
profile,
- avatarUrl: _clip.user.avatarUrl,
- ...await this.generateCommonPugData(this.meta),
- clientCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ clientCtxJson: htmlSafeJsonStringify({
clip: _clip,
}),
- });
+ }));
} else {
return await renderBase(reply);
}
@@ -713,12 +673,11 @@ export class ClientServerService {
reply.header('X-Robots-Tag', 'noimageai');
reply.header('X-Robots-Tag', 'noai');
}
- return await reply.view('gallery-post', {
- post: _post,
+ return await HtmlTemplateService.replyHtml(reply, GalleryPostPage({
+ galleryPost: _post,
profile,
- avatarUrl: _post.user.avatarUrl,
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -733,10 +692,10 @@ export class ClientServerService {
if (channel) {
const _channel = await this.channelEntityService.pack(channel);
reply.header('Cache-Control', 'public, max-age=15');
- return await reply.view('channel', {
+ return await HtmlTemplateService.replyHtml(reply, ChannelPage({
channel: _channel,
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -751,10 +710,10 @@ export class ClientServerService {
if (game) {
const _game = await this.reversiGameEntityService.packDetail(game);
reply.header('Cache-Control', 'public, max-age=3600');
- return await reply.view('reversi-game', {
- game: _game,
- ...await this.generateCommonPugData(this.meta),
- });
+ return await HtmlTemplateService.replyHtml(reply, ReversiGamePage({
+ reversiGame: _game,
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -770,10 +729,10 @@ export class ClientServerService {
if (announcement) {
const _announcement = await this.announcementEntityService.pack(announcement);
reply.header('Cache-Control', 'public, max-age=3600');
- return await reply.view('announcement', {
+ return await HtmlTemplateService.replyHtml(reply, AnnouncementPage({
announcement: _announcement,
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -806,13 +765,13 @@ export class ClientServerService {
const _user = await this.userEntityService.pack(user);
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',
- ...await this.generateCommonPugData(this.meta),
- embedCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ embedCtxJson: htmlSafeJsonStringify({
user: _user,
}),
- });
+ }));
});
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 });
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',
- ...await this.generateCommonPugData(this.meta),
- embedCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ embedCtxJson: htmlSafeJsonStringify({
note: _note,
}),
- });
+ }));
});
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);
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',
- ...await this.generateCommonPugData(this.meta),
- embedCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ embedCtxJson: htmlSafeJsonStringify({
clip: _clip,
}),
- });
+ }));
});
fastify.get('/embed/*', async (request, reply) => {
reply.removeHeader('X-Frame-Options');
reply.header('Cache-Control', 'public, max-age=3600');
- return await reply.view('base-embed', {
+ return await HtmlTemplateService.replyHtml(reply, BaseEmbed({
title: this.meta.name ?? 'Misskey',
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
});
fastify.get('/_info_card_', async (request, reply) => {
reply.removeHeader('X-Frame-Options');
- return await reply.view('info-card', {
+ return await HtmlTemplateService.replyHtml(reply, InfoCardPage({
version: this.config.version,
- host: this.config.host,
+ config: this.config,
meta: this.meta,
- originalUsersCount: await this.usersRepository.countBy({ host: IsNull() }),
- originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }),
- });
+ }));
});
//#endregion
fastify.get('/bios', async (request, reply) => {
- return await reply.view('bios', {
+ return await HtmlTemplateService.replyHtml(reply, BiosPage({
version: this.config.version,
- });
+ }));
});
fastify.get('/cli', async (request, reply) => {
- return await reply.view('cli', {
+ return await HtmlTemplateService.replyHtml(reply, CliPage({
version: this.config.version,
- });
+ }));
});
const override = (source: string, target: string, depth = 0) =>
@@ -917,7 +874,7 @@ export class ClientServerService {
reply.header('Clear-Site-Data', '"*"');
}
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等でそのパスがキャッシュされておかしくなる
@@ -943,10 +900,10 @@ export class ClientServerService {
});
reply.code(500);
reply.header('Cache-Control', 'max-age=10, must-revalidate');
- return await reply.view('error', {
+ return await HtmlTemplateService.replyHtml(reply, ErrorPage({
code: error.code,
id: errId,
- });
+ }));
});
done();
diff --git a/packages/backend/src/server/web/HtmlTemplateService.ts b/packages/backend/src/server/web/HtmlTemplateService.ts
new file mode 100644
index 0000000000..80c767d886
--- /dev/null
+++ b/packages/backend/src/server/web/HtmlTemplateService.ts
@@ -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 {
+ 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) {
+ reply.header('Content-Type', 'text/html; charset=utf-8');
+ const _html = await html;
+ return reply.send(_html);
+ }
+}
diff --git a/packages/backend/src/server/web/bios.css b/packages/backend/src/server/web/bios.css
deleted file mode 100644
index 91d1af10b4..0000000000
--- a/packages/backend/src/server/web/bios.css
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-* {
- font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
-}
-
-html {
- background: #ffb4e1;
-}
-
-main {
- background: #dedede;
-}
-main > .tabs {
- padding: 16px;
- border-bottom: solid 4px #c3c3c3;
-}
-
-#lsEditor > .adder {
- margin: 16px;
- padding: 16px;
- border: solid 2px #c3c3c3;
-}
-#lsEditor > .adder > textarea {
- display: block;
- width: 100%;
- min-height: 5em;
- box-sizing: border-box;
-}
-#lsEditor > .record {
- padding: 16px;
- border-bottom: solid 1px #c3c3c3;
-}
-#lsEditor > .record > header {
- font-weight: bold;
-}
-#lsEditor > .record > textarea {
- display: block;
- width: 100%;
- min-height: 5em;
- box-sizing: border-box;
-}
diff --git a/packages/backend/src/server/web/bios.js b/packages/backend/src/server/web/bios.js
deleted file mode 100644
index 9ff5dca72a..0000000000
--- a/packages/backend/src/server/web/bios.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-'use strict';
-
-window.onload = async () => {
- const account = JSON.parse(localStorage.getItem('account'));
- const i = account.token;
-
- const api = (endpoint, data = {}) => {
- const promise = new Promise((resolve, reject) => {
- // Append a credential
- if (i) data.i = i;
-
- // Send request
- window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
- method: 'POST',
- body: JSON.stringify(data),
- credentials: 'omit',
- cache: 'no-cache'
- }).then(async (res) => {
- const body = res.status === 204 ? null : await res.json();
-
- if (res.status === 200) {
- resolve(body);
- } else if (res.status === 204) {
- resolve();
- } else {
- reject(body.error);
- }
- }).catch(reject);
- });
-
- return promise;
- };
-
- const content = document.getElementById('content');
-
- document.getElementById('ls').addEventListener('click', () => {
- content.innerHTML = '';
-
- const lsEditor = document.createElement('div');
- lsEditor.id = 'lsEditor';
-
- const adder = document.createElement('div');
- adder.classList.add('adder');
- const addKeyInput = document.createElement('input');
- const addValueTextarea = document.createElement('textarea');
- const addButton = document.createElement('button');
- addButton.textContent = 'add';
- addButton.addEventListener('click', () => {
- localStorage.setItem(addKeyInput.value, addValueTextarea.value);
- location.reload();
- });
-
- adder.appendChild(addKeyInput);
- adder.appendChild(addValueTextarea);
- adder.appendChild(addButton);
- lsEditor.appendChild(adder);
-
- for (let i = 0; i < localStorage.length; i++) {
- const k = localStorage.key(i);
- const record = document.createElement('div');
- record.classList.add('record');
- const header = document.createElement('header');
- header.textContent = k;
- const textarea = document.createElement('textarea');
- textarea.textContent = localStorage.getItem(k);
- const saveButton = document.createElement('button');
- saveButton.textContent = 'save';
- saveButton.addEventListener('click', () => {
- localStorage.setItem(k, textarea.value);
- location.reload();
- });
- const removeButton = document.createElement('button');
- removeButton.textContent = 'remove';
- removeButton.addEventListener('click', () => {
- localStorage.removeItem(k);
- location.reload();
- });
- record.appendChild(header);
- record.appendChild(textarea);
- record.appendChild(saveButton);
- record.appendChild(removeButton);
- lsEditor.appendChild(record);
- }
-
- content.appendChild(lsEditor);
- });
-};
diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/backend/src/server/web/boot.embed.js
deleted file mode 100644
index ba6366b3db..0000000000
--- a/packages/backend/src/server/web/boot.embed.js
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-'use strict';
-
-// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
-(async () => {
- window.onerror = (e) => {
- console.error(e);
- renderError('SOMETHING_HAPPENED');
- };
- window.onunhandledrejection = (e) => {
- console.error(e);
- renderError('SOMETHING_HAPPENED_IN_PROMISE');
- };
-
- let forceError = localStorage.getItem('forceError');
- if (forceError != null) {
- renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.');
- return;
- }
-
- // パラメータに応じてsplashのスタイルを変更
- const params = new URLSearchParams(location.search);
- if (params.has('rounded') && params.get('rounded') === 'false') {
- document.documentElement.classList.add('norounded');
- }
- if (params.has('border') && params.get('border') === 'false') {
- document.documentElement.classList.add('noborder');
- }
-
- //#region Detect language & fetch translations
- const supportedLangs = LANGS;
- /** @type { string } */
- let lang = localStorage.getItem('lang');
- if (lang == null || !supportedLangs.includes(lang)) {
- if (supportedLangs.includes(navigator.language)) {
- lang = navigator.language;
- } else {
- lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
-
- // Fallback
- if (lang == null) lang = 'en-US';
- }
- }
-
- // for https://github.com/misskey-dev/misskey/issues/10202
- if (lang == null || lang.toString == null || lang.toString() === 'null') {
- console.error('invalid lang value detected!!!', typeof lang, lang);
- lang = 'en-US';
- }
- //#endregion
-
- //#region Script
- async function importAppScript() {
- await import(CLIENT_ENTRY ? `/embed_vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/embed_vite/src/boot.ts')
- .catch(async e => {
- console.error(e);
- renderError('APP_IMPORT');
- });
- }
-
- // タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある
- if (document.readyState !== 'loading') {
- importAppScript();
- } else {
- window.addEventListener('DOMContentLoaded', () => {
- importAppScript();
- });
- }
- //#endregion
-
- async function addStyle(styleText) {
- let css = document.createElement('style');
- css.appendChild(document.createTextNode(styleText));
- document.head.appendChild(css);
- }
-
- async function renderError(code) {
- // Cannot set property 'innerHTML' of null を回避
- if (document.readyState === 'loading') {
- await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
- }
-
- let messages = null;
- const bootloaderLocales = localStorage.getItem('bootloaderLocales');
- if (bootloaderLocales) {
- messages = JSON.parse(bootloaderLocales);
- }
- if (!messages) {
- // older version of misskey does not store bootloaderLocales, stores locale as a whole
- const legacyLocale = localStorage.getItem('locale');
- if (legacyLocale) {
- const parsed = JSON.parse(legacyLocale);
- messages = {
- ...(parsed._bootErrors ?? {}),
- reload: parsed.reload,
- };
- }
- }
- if (!messages) messages = {};
-
- const title = messages?.title || 'Failed to initialize Misskey';
- const reload = messages?.reload || 'Reload';
-
- document.body.innerHTML = `
- ${title}
- Error Code: ${code}
- `;
- addStyle(`
- #misskey_app,
- #splash {
- display: none !important;
- }
-
- html,
- body {
- margin: 0;
- }
-
- body {
- position: relative;
- color: #dee7e4;
- font-family: Hiragino Maru Gothic Pro, BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
- line-height: 1.35;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- min-height: 100vh;
- margin: 0;
- padding: 24px;
- box-sizing: border-box;
- overflow: hidden;
-
- border-radius: var(--radius, 12px);
- border: 1px solid rgba(231, 255, 251, 0.14);
- }
-
- body::before {
- content: '';
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: #192320;
- border-radius: var(--radius, 12px);
- z-index: -1;
- }
-
- html.embed.norounded body,
- html.embed.norounded body::before {
- border-radius: 0;
- }
-
- html.embed.noborder body {
- border: none;
- }
-
- .icon {
- max-width: 60px;
- width: 100%;
- height: auto;
- margin-bottom: 20px;
- color: #dec340;
- }
-
- .message {
- text-align: center;
- font-size: 20px;
- font-weight: 700;
- margin-bottom: 20px;
- }
-
- .submessage {
- text-align: center;
- font-size: 90%;
- margin-bottom: 7.5px;
- }
-
- .submessage:last-of-type {
- margin-bottom: 20px;
- }
-
- button {
- padding: 7px 14px;
- min-width: 100px;
- font-weight: 700;
- font-family: Hiragino Maru Gothic Pro, BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
- line-height: 1.35;
- border-radius: 99rem;
- background-color: #b4e900;
- color: #192320;
- border: none;
- cursor: pointer;
- -webkit-tap-highlight-color: transparent;
- }
-
- button:hover {
- background-color: #c6ff03;
- }`);
- }
-})();
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
deleted file mode 100644
index ab4b158287..0000000000
--- a/packages/backend/src/server/web/boot.js
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-'use strict';
-
-// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
-(async () => {
- window.onerror = (e) => {
- console.error(e);
- renderError('SOMETHING_HAPPENED', e);
- };
- window.onunhandledrejection = (e) => {
- console.error(e);
- renderError('SOMETHING_HAPPENED_IN_PROMISE', e.reason || e);
- };
-
- let forceError = localStorage.getItem('forceError');
- if (forceError != null) {
- renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.');
- return;
- }
-
- //#region Detect language
- const supportedLangs = LANGS;
- /** @type { string } */
- let lang = localStorage.getItem('lang');
- if (lang == null || !supportedLangs.includes(lang)) {
- if (supportedLangs.includes(navigator.language)) {
- lang = navigator.language;
- } else {
- lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
-
- // Fallback
- if (lang == null) lang = 'en-US';
- }
- }
-
- // for https://github.com/misskey-dev/misskey/issues/10202
- if (lang == null || lang.toString == null || lang.toString() === 'null') {
- console.error('invalid lang value detected!!!', typeof lang, lang);
- lang = 'en-US';
- }
- //#endregion
-
- //#region Script
- async function importAppScript() {
- await import(CLIENT_ENTRY ? `/vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/vite/src/_boot_.ts')
- .catch(async e => {
- console.error(e);
- renderError('APP_IMPORT', e);
- });
- }
-
- // タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある
- if (document.readyState !== 'loading') {
- importAppScript();
- } else {
- window.addEventListener('DOMContentLoaded', () => {
- importAppScript();
- });
- }
- //#endregion
-
- let isSafeMode = (localStorage.getItem('isSafeMode') === 'true');
-
- if (!isSafeMode) {
- const urlParams = new URLSearchParams(window.location.search);
-
- if (urlParams.has('safemode') && urlParams.get('safemode') === 'true') {
- localStorage.setItem('isSafeMode', 'true');
- isSafeMode = true;
- }
- }
-
- //#region Theme
- if (!isSafeMode) {
- const theme = localStorage.getItem('theme');
- if (theme) {
- for (const [k, v] of Object.entries(JSON.parse(theme))) {
- document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
-
- // HTMLの theme-color 適用
- if (k === 'htmlThemeColor') {
- for (const tag of document.head.children) {
- if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
- tag.setAttribute('content', v);
- break;
- }
- }
- }
- }
- }
- }
-
- const colorScheme = localStorage.getItem('colorScheme');
- if (colorScheme) {
- document.documentElement.style.setProperty('color-scheme', colorScheme);
- }
- //#endregion
-
- const fontSize = localStorage.getItem('fontSize');
- if (fontSize) {
- document.documentElement.classList.add('f-' + fontSize);
- }
-
- const useSystemFont = localStorage.getItem('useSystemFont');
- if (useSystemFont) {
- document.documentElement.classList.add('useSystemFont');
- }
-
- if (!isSafeMode) {
- const customCss = localStorage.getItem('customCss');
- if (customCss && customCss.length > 0) {
- const style = document.createElement('style');
- style.innerHTML = customCss;
- document.head.appendChild(style);
- }
- }
-
- async function addStyle(styleText) {
- let css = document.createElement('style');
- css.appendChild(document.createTextNode(styleText));
- document.head.appendChild(css);
- }
-
- async function renderError(code, details) {
- // Cannot set property 'innerHTML' of null を回避
- if (document.readyState === 'loading') {
- await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
- }
-
- let messages = null;
- const bootloaderLocales = localStorage.getItem('bootloaderLocales');
- if (bootloaderLocales) {
- messages = JSON.parse(bootloaderLocales);
- }
- if (!messages) {
- // older version of misskey does not store bootloaderLocales, stores locale as a whole
- const legacyLocale = localStorage.getItem('locale');
- if (legacyLocale) {
- const parsed = JSON.parse(legacyLocale);
- messages = {
- ...(parsed._bootErrors ?? {}),
- reload: parsed.reload,
- };
- }
- }
- if (!messages) messages = {};
-
- messages = Object.assign({
- title: 'Failed to initialize Misskey',
- solution: 'The following actions may solve the problem.',
- solution1: 'Update your os and browser',
- solution2: 'Disable an adblocker',
- solution3: 'Clear the browser cache',
- solution4: '(Tor Browser) Set dom.webaudio.enabled to true',
- otherOption: 'Other options',
- otherOption1: 'Clear preferences and cache',
- otherOption2: 'Start the simple client',
- otherOption3: 'Start the repair tool',
- otherOption4: 'Start Misskey in safe mode',
- reload: 'Reload',
- }, messages);
-
- const safeModeUrl = new URL(window.location.href);
- safeModeUrl.searchParams.set('safemode', 'true');
-
- let errorsElement = document.getElementById('errors');
-
- if (!errorsElement) {
- document.body.innerHTML = `
-
- ${messages.title}
-
- ${messages.solution}
- ${messages.solution1}
- ${messages.solution2}
- ${messages.solution3}
- ${messages.solution4}
-
- ${messages.otherOption}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `;
- errorsElement = document.getElementById('errors');
- }
- const detailsElement = document.createElement('details');
- detailsElement.id = 'errorInfo';
- detailsElement.innerHTML = `
-
-
- ERROR CODE: ${code}
-
- ${details.toString()} ${JSON.stringify(details)}`;
- errorsElement.appendChild(detailsElement);
- addStyle(`
- * {
- font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
- }
-
- #misskey_app,
- #splash {
- display: none !important;
- }
-
- body,
- html {
- background-color: #222;
- color: #dfddcc;
- justify-content: center;
- margin: auto;
- padding: 10px;
- text-align: center;
- }
-
- button {
- border-radius: 999px;
- padding: 0px 12px 0px 12px;
- border: none;
- cursor: pointer;
- margin-bottom: 12px;
- }
-
- .button-big {
- background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
- line-height: 50px;
- }
-
- .button-big:hover {
- background: rgb(153, 204, 0);
- }
-
- .button-small {
- background: #444;
- line-height: 40px;
- }
-
- .button-small:hover {
- background: #555;
- }
-
- .button-label-big {
- color: #222;
- font-weight: bold;
- font-size: 1.2em;
- padding: 12px;
- }
-
- .button-label-small {
- color: rgb(153, 204, 0);
- font-size: 16px;
- padding: 12px;
- }
-
- a {
- color: rgb(134, 179, 0);
- text-decoration: none;
- }
-
- p,
- li {
- font-size: 16px;
- }
-
- .icon-warning {
- color: #dec340;
- height: 4rem;
- padding-top: 2rem;
- }
-
- h1 {
- font-size: 1.5em;
- margin: 1em;
- }
-
- code {
- font-family: Fira, FiraCode, monospace;
- }
-
- #errorInfo {
- background: #333;
- margin-bottom: 2rem;
- padding: 0.5rem 1rem;
- width: 40rem;
- border-radius: 10px;
- justify-content: center;
- margin: auto;
- }
-
- #errorInfo summary {
- cursor: pointer;
- }
-
- #errorInfo summary > * {
- display: inline;
- }
-
- @media screen and (max-width: 500px) {
- #errorInfo {
- width: 50%;
- }
- }`);
- }
-})();
diff --git a/packages/backend/src/server/web/cli.css b/packages/backend/src/server/web/cli.css
deleted file mode 100644
index 4e6136d59c..0000000000
--- a/packages/backend/src/server/web/cli.css
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-* {
- font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
-}
-
-html {
- background: #ffb4e1;
-}
-
-main {
- background: #dedede;
-}
-
-#tl > div {
- padding: 16px;
- border-bottom: solid 1px #c3c3c3;
-}
-#tl > div > header {
- font-weight: bold;
-}
diff --git a/packages/backend/src/server/web/cli.js b/packages/backend/src/server/web/cli.js
deleted file mode 100644
index 30ee77f4d9..0000000000
--- a/packages/backend/src/server/web/cli.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-'use strict';
-
-window.onload = async () => {
- const account = JSON.parse(localStorage.getItem('account'));
- const i = account.token;
-
- const api = (endpoint, data = {}) => {
- const promise = new Promise((resolve, reject) => {
- // Append a credential
- if (i) data.i = i;
-
- // Send request
- fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
- headers: {
- 'Content-Type': 'application/json'
- },
- method: 'POST',
- body: JSON.stringify(data),
- credentials: 'omit',
- cache: 'no-cache'
- }).then(async (res) => {
- const body = res.status === 204 ? null : await res.json();
-
- if (res.status === 200) {
- resolve(body);
- } else if (res.status === 204) {
- resolve();
- } else {
- reject(body.error);
- }
- }).catch(reject);
- });
-
- return promise;
- };
-
- document.getElementById('submit').addEventListener('click', () => {
- api('notes/create', {
- text: document.getElementById('text').value
- }).then(() => {
- location.reload();
- });
- });
-
- api('notes/timeline').then(notes => {
- const tl = document.getElementById('tl');
- for (const note of notes) {
- const el = document.createElement('div');
- const name = document.createElement('header');
- name.textContent = `${note.user.name} @${note.user.username}`;
- const text = document.createElement('div');
- text.textContent = `${note.text}`;
- el.appendChild(name);
- el.appendChild(text);
- tl.appendChild(el);
- }
- });
-};
diff --git a/packages/backend/src/server/web/error.css b/packages/backend/src/server/web/error.css
deleted file mode 100644
index 803bd1b4b5..0000000000
--- a/packages/backend/src/server/web/error.css
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-* {
- font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
-}
-
-#misskey_app,
-#splash {
- display: none !important;
-}
-
-body,
-html {
- background-color: #222;
- color: #dfddcc;
- justify-content: center;
- margin: auto;
- padding: 10px;
- text-align: center;
-}
-
-button {
- border-radius: 999px;
- padding: 0px 12px 0px 12px;
- border: none;
- cursor: pointer;
- margin-bottom: 12px;
-}
-
-.button-big {
- background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
- line-height: 50px;
-}
-
-.button-big:hover {
- background: rgb(153, 204, 0);
-}
-
-.button-small {
- background: #444;
- line-height: 40px;
-}
-
-.button-small:hover {
- background: #555;
-}
-
-.button-label-big {
- color: #222;
- font-weight: bold;
- font-size: 1.2em;
- padding: 12px;
-}
-
-.button-label-small {
- color: rgb(153, 204, 0);
- font-size: 16px;
- padding: 12px;
-}
-
-a {
- color: rgb(134, 179, 0);
- text-decoration: none;
-}
-
-p,
-li {
- font-size: 16px;
-}
-
-.icon-warning {
- color: #dec340;
- height: 4rem;
- padding-top: 2rem;
-}
-
-h1 {
- font-size: 1.5em;
- margin: 1em;
-}
-
-code {
- display: block;
- font-family: Fira, FiraCode, monospace;
- background: #333;
- padding: 0.5rem 1rem;
- max-width: 40rem;
- border-radius: 10px;
- justify-content: center;
- margin: auto;
- white-space: pre-wrap;
- word-break: break-word;
-}
-
-#errorInfo summary {
- cursor: pointer;
-}
-
-#errorInfo summary>* {
- display: inline;
-}
-
-@media screen and (max-width: 500px) {
- #errorInfo {
- width: 50%;
- }
-}
diff --git a/packages/backend/src/server/web/error.js b/packages/backend/src/server/web/error.js
deleted file mode 100644
index 4838dd6ef3..0000000000
--- a/packages/backend/src/server/web/error.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-'use strict';
-
-(() => {
- document.addEventListener('DOMContentLoaded', () => {
- const locale = JSON.parse(localStorage.getItem('locale') || '{}');
-
- const messages = Object.assign({
- title: 'Failed to initialize Misskey',
- serverError: 'If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.',
- solution: 'The following actions may solve the problem.',
- solution1: 'Update your os and browser',
- solution2: 'Disable an adblocker',
- solution3: 'Clear the browser cache',
- solution4: '(Tor Browser) Set dom.webaudio.enabled to true',
- otherOption: 'Other options',
- otherOption1: 'Clear preferences and cache',
- otherOption2: 'Start the simple client',
- otherOption3: 'Start the repair tool',
- }, locale?._bootErrors || {});
- const reload = locale?.reload || 'Reload';
-
- const reloadEls = document.querySelectorAll('[data-i18n-reload]');
- for (const el of reloadEls) {
- el.textContent = reload;
- }
-
- const i18nEls = document.querySelectorAll('[data-i18n]');
- for (const el of i18nEls) {
- const key = el.dataset.i18n;
- if (key && messages[key]) {
- el.textContent = messages[key];
- }
- }
- });
-})();
diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css
deleted file mode 100644
index 8e63a2ea66..0000000000
--- a/packages/backend/src/server/web/style.css
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-html {
- background-color: var(--MI_THEME-bg);
- color: var(--MI_THEME-fg);
-}
-
-#splash {
- position: fixed;
- z-index: 10000;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- cursor: wait;
- background-color: var(--MI_THEME-bg);
- opacity: 1;
- transition: opacity 0.5s ease;
-}
-
-#splashIcon {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- margin: auto;
- width: 64px;
- height: 64px;
- border-radius: 10px;
- pointer-events: none;
-}
-
-#splashSpinner {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- margin: auto;
- display: inline-block;
- width: 28px;
- height: 28px;
- transform: translateY(70px);
- color: var(--MI_THEME-accent);
-}
-
-#splashSpinner > .spinner {
- position: absolute;
- top: 0;
- left: 0;
- width: 28px;
- height: 28px;
- fill-rule: evenodd;
- clip-rule: evenodd;
- stroke-linecap: round;
- stroke-linejoin: round;
- stroke-miterlimit: 1.5;
-}
-#splashSpinner > .spinner.bg {
- opacity: 0.275;
-}
-#splashSpinner > .spinner.fg {
- animation: splashSpinner 0.5s linear infinite;
-}
-
-@keyframes splashSpinner {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
-}
diff --git a/packages/backend/src/server/web/style.embed.css b/packages/backend/src/server/web/style.embed.css
deleted file mode 100644
index 0911d562bf..0000000000
--- a/packages/backend/src/server/web/style.embed.css
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-html {
- background-color: var(--MI_THEME-bg);
- color: var(--MI_THEME-fg);
-}
-
-html.embed {
- box-sizing: border-box;
- background-color: transparent;
- color-scheme: light dark;
- max-width: 500px;
-}
-
-#splash {
- position: fixed;
- z-index: 10000;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- cursor: wait;
- background-color: var(--MI_THEME-bg);
- opacity: 1;
- transition: opacity 0.5s ease;
-}
-
-html.embed #splash {
- box-sizing: border-box;
- min-height: 300px;
- border-radius: var(--radius, 12px);
- border: 1px solid var(--MI_THEME-divider, #e8e8e8);
-}
-
-html.embed.norounded #splash {
- border-radius: 0;
-}
-
-html.embed.noborder #splash {
- border: none;
-}
-
-#splashIcon {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- margin: auto;
- width: 64px;
- height: 64px;
- border-radius: 10px;
- pointer-events: none;
-}
-
-#splashSpinner {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- margin: auto;
- display: inline-block;
- width: 28px;
- height: 28px;
- transform: translateY(70px);
- color: var(--MI_THEME-accent);
-}
-
-#splashSpinner > .spinner {
- position: absolute;
- top: 0;
- left: 0;
- width: 28px;
- height: 28px;
- fill-rule: evenodd;
- clip-rule: evenodd;
- stroke-linecap: round;
- stroke-linejoin: round;
- stroke-miterlimit: 1.5;
-}
-#splashSpinner > .spinner.bg {
- opacity: 0.275;
-}
-#splashSpinner > .spinner.fg {
- animation: splashSpinner 0.5s linear infinite;
-}
-
-@keyframes splashSpinner {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
-}
diff --git a/packages/backend/src/server/web/views/_.ts b/packages/backend/src/server/web/views/_.ts
new file mode 100644
index 0000000000..ac7418f362
--- /dev/null
+++ b/packages/backend/src/server/web/views/_.ts
@@ -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 = ``;
+
+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> = MinimumCommonData & T;
+
+export type CommonProps> = CommonData & T;
diff --git a/packages/backend/src/server/web/views/_splash.tsx b/packages/backend/src/server/web/views/_splash.tsx
new file mode 100644
index 0000000000..ea79b8d61d
--- /dev/null
+++ b/packages/backend/src/server/web/views/_splash.tsx
@@ -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 (
+
+

+
+
+ );
+}
diff --git a/packages/backend/src/server/web/views/announcement.pug b/packages/backend/src/server/web/views/announcement.pug
deleted file mode 100644
index 7a4052e8a4..0000000000
--- a/packages/backend/src/server/web/views/announcement.pug
+++ /dev/null
@@ -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')
diff --git a/packages/backend/src/server/web/views/announcement.tsx b/packages/backend/src/server/web/views/announcement.tsx
new file mode 100644
index 0000000000..bc1c808177
--- /dev/null
+++ b/packages/backend/src/server/web/views/announcement.tsx
@@ -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 (
+ <>
+
+
+
+
+ {props.announcement.imageUrl ? (
+ <>
+
+
+ >
+ ) : null}
+ >
+ );
+ }
+
+ return (
+
+
+ );
+}
diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug
deleted file mode 100644
index 29de86b8b6..0000000000
--- a/packages/backend/src/server/web/views/base-embed.pug
+++ /dev/null
@@ -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
-
-
- block content
diff --git a/packages/backend/src/server/web/views/base-embed.tsx b/packages/backend/src/server/web/views/base-embed.tsx
new file mode 100644
index 0000000000..011b66592e
--- /dev/null
+++ b/packages/backend/src/server/web/views/base-embed.tsx
@@ -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>) {
+ const now = Date.now();
+
+ // 変数名をsafeで始めることでエラーをスキップ
+ const safeMetaJson = props.metaJson;
+ const safeEmbedCtxJson = props.embedCtxJson;
+
+ return (
+ <>
+ {''}
+ {comment}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!props.config.frontendEmbedManifestExists ? : null}
+
+ {props.config.frontendEmbedEntry.css != null ? props.config.frontendEmbedEntry.css.map((href) => (
+
+ )) : null}
+
+ {props.titleSlot ?? {props.title || 'Misskey'}}
+
+ {props.metaSlot}
+
+
+
+ {props.frontendEmbedBootloaderCss != null ? : }
+
+
+
+ {safeMetaJson != null ? : null}
+ {safeEmbedCtxJson != null ? : null}
+
+ {props.frontendEmbedBootloaderJs != null ? : }
+
+
+
+
+ {props.children}
+
+
+ >
+ );
+}
+
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
deleted file mode 100644
index 46b365a9c7..0000000000
--- a/packages/backend/src/server/web/views/base.pug
+++ /dev/null
@@ -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
-
-
- block content
diff --git a/packages/backend/src/server/web/views/base.tsx b/packages/backend/src/server/web/views/base.tsx
new file mode 100644
index 0000000000..6fa3395fb8
--- /dev/null
+++ b/packages/backend/src/server/web/views/base.tsx
@@ -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>) {
+ const now = Date.now();
+
+ // 変数名をsafeで始めることでエラーをスキップ
+ const safeMetaJson = props.metaJson;
+ const safeClientCtxJson = props.clientCtxJson;
+
+ return (
+ <>
+ {''}
+ {comment}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {props.serverErrorImageUrl != null ? : null}
+ {props.infoImageUrl != null ? : null}
+ {props.notFoundImageUrl != null ? : null}
+
+ {!props.config.frontendManifestExists ? : null}
+
+ {props.config.frontendEntry.css != null ? props.config.frontendEntry.css.map((href) => (
+
+ )) : null}
+
+ {props.titleSlot ?? {props.title || 'Misskey'}}
+
+ {props.noindex ? : null}
+
+ {props.descSlot ?? (props.desc != null ? : null)}
+
+ {props.metaSlot}
+
+ {props.ogSlot ?? (
+ <>
+
+
+ {props.img != null ? : null}
+
+ >
+ )}
+
+ {props.frontendBootloaderCss != null ? : }
+
+
+
+ {safeMetaJson != null ? : null}
+ {safeClientCtxJson != null ? : null}
+
+ {props.frontendBootloaderJs != null ? : }
+
+
+
+
+ {props.children}
+
+
+ >
+ );
+}
+
+export { Layout as BasePage };
+
diff --git a/packages/backend/src/server/web/views/bios.pug b/packages/backend/src/server/web/views/bios.pug
deleted file mode 100644
index 39a151a29b..0000000000
--- a/packages/backend/src/server/web/views/bios.pug
+++ /dev/null
@@ -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
diff --git a/packages/backend/src/server/web/views/bios.tsx b/packages/backend/src/server/web/views/bios.tsx
new file mode 100644
index 0000000000..9010de8d75
--- /dev/null
+++ b/packages/backend/src/server/web/views/bios.tsx
@@ -0,0 +1,35 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export function BiosPage(props: {
+ version: string;
+}) {
+ return (
+ <>
+ {''}
+
+
+
+
+ Misskey Repair Tool
+
+
+
+
+
+ Misskey Repair Tool {props.version}
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/packages/backend/src/server/web/views/channel.pug b/packages/backend/src/server/web/views/channel.pug
deleted file mode 100644
index c514025e0b..0000000000
--- a/packages/backend/src/server/web/views/channel.pug
+++ /dev/null
@@ -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')
diff --git a/packages/backend/src/server/web/views/channel.tsx b/packages/backend/src/server/web/views/channel.tsx
new file mode 100644
index 0000000000..7d8123ea85
--- /dev/null
+++ b/packages/backend/src/server/web/views/channel.tsx
@@ -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 (
+ <>
+
+
+ {props.channel.description != null ? : null}
+
+ {props.channel.bannerUrl ? (
+ <>
+
+
+ >
+ ) : null}
+ >
+ );
+ }
+
+ return (
+
+
+ );
+}
diff --git a/packages/backend/src/server/web/views/cli.pug b/packages/backend/src/server/web/views/cli.pug
deleted file mode 100644
index d2cf7c4335..0000000000
--- a/packages/backend/src/server/web/views/cli.pug
+++ /dev/null
@@ -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
diff --git a/packages/backend/src/server/web/views/cli.tsx b/packages/backend/src/server/web/views/cli.tsx
new file mode 100644
index 0000000000..009d982b35
--- /dev/null
+++ b/packages/backend/src/server/web/views/cli.tsx
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export function CliPage(props: {
+ version: string;
+}) {
+ return (
+ <>
+ {''}
+
+
+
+
+ Misskey CLI Tool
+
+
+
+
+
+
+ Misskey CLI {props.version}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/packages/backend/src/server/web/views/clip.pug b/packages/backend/src/server/web/views/clip.pug
deleted file mode 100644
index 5a0018803a..0000000000
--- a/packages/backend/src/server/web/views/clip.pug
+++ /dev/null
@@ -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}`)
diff --git a/packages/backend/src/server/web/views/clip.tsx b/packages/backend/src/server/web/views/clip.tsx
new file mode 100644
index 0000000000..c3cc505e35
--- /dev/null
+++ b/packages/backend/src/server/web/views/clip.tsx
@@ -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 (
+ <>
+
+
+ {props.clip.description != null ? : null}
+
+ {props.clip.user.avatarUrl ? (
+ <>
+
+
+ >
+ ) : null}
+ >
+ );
+ }
+
+ function metaBlock() {
+ return (
+ <>
+ {props.profile.noCrawle ? : null}
+ {props.profile.preventAiLearning ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+
+ >
+ );
+ }
+
+ return (
+
+
+ );
+}
diff --git a/packages/backend/src/server/web/views/error.pug b/packages/backend/src/server/web/views/error.pug
deleted file mode 100644
index 6a78d1878c..0000000000
--- a/packages/backend/src/server/web/views/error.pug
+++ /dev/null
@@ -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
diff --git a/packages/backend/src/server/web/views/error.tsx b/packages/backend/src/server/web/views/error.tsx
new file mode 100644
index 0000000000..9d0e60aa30
--- /dev/null
+++ b/packages/backend/src/server/web/views/error.tsx
@@ -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 (
+ <>
+ {''}
+ {comment}
+
+
+
+
+
+
+ {props.title ?? 'An error has occurred... | Misskey'}
+
+
+
+
+
+ Failed to initialize Misskey
+
+
+
+
+ If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
+
+
+
+
+ ERROR CODE: {props.code}
+ ERROR ID: {props.id}
+
+
+
+ The following actions may solve the problem.
+
+ Update your os and browser
+ Disable an adblocker
+ Clear your browser cache
+ (Tor Browser) Set dom.webaudio.enabled to true
+
+
+ Other options
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/packages/backend/src/server/web/views/flash.pug b/packages/backend/src/server/web/views/flash.pug
deleted file mode 100644
index 1549aa7906..0000000000
--- a/packages/backend/src/server/web/views/flash.pug
+++ /dev/null
@@ -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}`)
diff --git a/packages/backend/src/server/web/views/flash.tsx b/packages/backend/src/server/web/views/flash.tsx
new file mode 100644
index 0000000000..25a6b2c0ae
--- /dev/null
+++ b/packages/backend/src/server/web/views/flash.tsx
@@ -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 (
+ <>
+
+
+
+
+ {props.flash.user.avatarUrl ? (
+ <>
+
+
+ >
+ ) : null}
+ >
+ );
+ }
+
+ function metaBlock() {
+ return (
+ <>
+ {props.profile.noCrawle ? : null}
+ {props.profile.preventAiLearning ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+
+ >
+ );
+ }
+
+ return (
+
+
+ );
+}
diff --git a/packages/backend/src/server/web/views/flush.pug b/packages/backend/src/server/web/views/flush.pug
deleted file mode 100644
index 7884495d08..0000000000
--- a/packages/backend/src/server/web/views/flush.pug
+++ /dev/null
@@ -1,51 +0,0 @@
-doctype html
-
-html
- #msg
- script.
- const msg = document.getElementById('msg');
- const successText = `\nSuccess Flush! Back to Misskey\n成功しました。Misskeyを開き直してください。`;
-
- 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. Please retry.\n失敗しました。もう一度試してみてください。`);
- 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', `[${(new Date()).toString()}] ${text.replace(/\n/g,'
')}
`)
- }
diff --git a/packages/backend/src/server/web/views/flush.tsx b/packages/backend/src/server/web/views/flush.tsx
new file mode 100644
index 0000000000..f3fdc8fcb0
--- /dev/null
+++ b/packages/backend/src/server/web/views/flush.tsx
@@ -0,0 +1,23 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export function FlushPage(props?: {}) {
+ return (
+ <>
+ {''}
+
+
+
+
+ Clear preferences and cache
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/packages/backend/src/server/web/views/gallery-post.pug b/packages/backend/src/server/web/views/gallery-post.pug
deleted file mode 100644
index 9ae25d9ac8..0000000000
--- a/packages/backend/src/server/web/views/gallery-post.pug
+++ /dev/null
@@ -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')
diff --git a/packages/backend/src/server/web/views/gallery-post.tsx b/packages/backend/src/server/web/views/gallery-post.tsx
new file mode 100644
index 0000000000..2bec2de930
--- /dev/null
+++ b/packages/backend/src/server/web/views/gallery-post.tsx
@@ -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 (
+ <>
+
+
+ {props.galleryPost.description != null ? : null}
+
+ {props.galleryPost.isSensitive && props.galleryPost.user.avatarUrl ? (
+ <>
+
+
+ >
+ ) : null}
+ {!props.galleryPost.isSensitive && props.galleryPost.files != null ? (
+ <>
+
+
+ >
+ ) : null}
+ >
+ );
+ }
+
+ function metaBlock() {
+ return (
+ <>
+ {props.profile.noCrawle ? : null}
+ {props.profile.preventAiLearning ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+
+ >
+ );
+ }
+
+ return (
+
+
+ );
+}
diff --git a/packages/backend/src/server/web/views/info-card.pug b/packages/backend/src/server/web/views/info-card.pug
deleted file mode 100644
index 2a4954ec8b..0000000000
--- a/packages/backend/src/server/web/views/info-card.pug
+++ /dev/null
@@ -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
diff --git a/packages/backend/src/server/web/views/info-card.tsx b/packages/backend/src/server/web/views/info-card.tsx
new file mode 100644
index 0000000000..27be4c69e8
--- /dev/null
+++ b/packages/backend/src/server/web/views/info-card.tsx
@@ -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 (
+ <>
+ {''}
+ {comment}
+
+
+
+
+
+ {props.meta.name ?? props.config.url}
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug
deleted file mode 100644
index ea1993aed0..0000000000
--- a/packages/backend/src/server/web/views/note.pug
+++ /dev/null
@@ -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')
diff --git a/packages/backend/src/server/web/views/note.tsx b/packages/backend/src/server/web/views/note.tsx
new file mode 100644
index 0000000000..803c3d2537
--- /dev/null
+++ b/packages/backend/src/server/web/views/note.tsx
@@ -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 (
+ <>
+
+
+
+
+ {videos.map(video => (
+ <>
+
+
+
+ {video.thumbnailUrl ? : null}
+ {video.properties.width != null ? : null}
+ {video.properties.height != null ? : null}
+ >
+ ))}
+ {images.length > 0 ? (
+ <>
+
+ {images.map(image => (
+ <>
+
+ {image.properties.width != null ? : null}
+ {image.properties.height != null ? : null}
+ >
+ ))}
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+ >
+ );
+ }
+
+ function metaBlock() {
+ return (
+ <>
+ {props.note.user.host != null || isRenote || props.profile.noCrawle ? : null}
+ {props.profile.preventAiLearning ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+
+
+ {props.federationEnabled ? (
+ <>
+ {props.note.user.host == null ? : null}
+ {props.note.uri != null ? : null}
+ >
+ ) : null}
+ >
+ );
+ }
+
+ return (
+
+ )
+}
diff --git a/packages/backend/src/server/web/views/oauth.pug b/packages/backend/src/server/web/views/oauth.pug
deleted file mode 100644
index 4195ccc3a3..0000000000
--- a/packages/backend/src/server/web/views/oauth.pug
+++ /dev/null
@@ -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)
diff --git a/packages/backend/src/server/web/views/oauth.tsx b/packages/backend/src/server/web/views/oauth.tsx
new file mode 100644
index 0000000000..d12b0d15fd
--- /dev/null
+++ b/packages/backend/src/server/web/views/oauth.tsx
@@ -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 (
+ <>
+
+
+ {props.clientLogo ? : null}
+
+ >
+ );
+ }
+
+ return (
+
+
+ );
+}
diff --git a/packages/backend/src/server/web/views/page.pug b/packages/backend/src/server/web/views/page.pug
deleted file mode 100644
index 03c50eca8a..0000000000
--- a/packages/backend/src/server/web/views/page.pug
+++ /dev/null
@@ -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}`)
diff --git a/packages/backend/src/server/web/views/page.tsx b/packages/backend/src/server/web/views/page.tsx
new file mode 100644
index 0000000000..d0484612df
--- /dev/null
+++ b/packages/backend/src/server/web/views/page.tsx
@@ -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 (
+ <>
+
+
+ {props.page.summary != null ? : null}
+
+ {props.page.eyeCatchingImage != null ? (
+ <>
+
+
+ >
+ ) : props.page.user.avatarUrl ? (
+ <>
+
+
+ >
+ ) : null}
+ >
+ );
+ }
+
+ function metaBlock() {
+ return (
+ <>
+ {props.profile.noCrawle ? : null}
+ {props.profile.preventAiLearning ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+
+ >
+ );
+ }
+
+ return (
+
+
+ );
+}
diff --git a/packages/backend/src/server/web/views/reversi-game.pug b/packages/backend/src/server/web/views/reversi-game.pug
deleted file mode 100644
index 0b5ffb2bb0..0000000000
--- a/packages/backend/src/server/web/views/reversi-game.pug
+++ /dev/null
@@ -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')
diff --git a/packages/backend/src/server/web/views/reversi-game.tsx b/packages/backend/src/server/web/views/reversi-game.tsx
new file mode 100644
index 0000000000..22609311fd
--- /dev/null
+++ b/packages/backend/src/server/web/views/reversi-game.tsx
@@ -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 (
+ <>
+
+
+
+
+
+ >
+ );
+ }
+
+ return (
+
+
+ );
+}
diff --git a/packages/backend/src/server/web/views/user.pug b/packages/backend/src/server/web/views/user.pug
deleted file mode 100644
index b9f740f5b6..0000000000
--- a/packages/backend/src/server/web/views/user.pug
+++ /dev/null
@@ -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}`)
diff --git a/packages/backend/src/server/web/views/user.tsx b/packages/backend/src/server/web/views/user.tsx
new file mode 100644
index 0000000000..76c2633ab9
--- /dev/null
+++ b/packages/backend/src/server/web/views/user.tsx
@@ -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 (
+ <>
+
+
+ {props.user.description != null ? : null}
+
+
+
+ >
+ );
+ }
+
+ function metaBlock() {
+ return (
+ <>
+ {props.user.host != null || props.profile.noCrawle ? : null}
+ {props.profile.preventAiLearning ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+
+ {props.sub == null && props.federationEnabled ? (
+ <>
+ {props.user.host == null ? : null}
+ {props.user.uri != null ? : null}
+ {props.profile.url != null ? : null}
+ >
+ ) : null}
+
+ {me.map((url) => (
+
+ ))}
+ >
+ );
+ }
+
+ return (
+
+
+ );
+}
diff --git a/packages/backend/test-federation/compose.tpl.yml b/packages/backend/test-federation/compose.tpl.yml
index 92b986736d..97068171d3 100644
--- a/packages/backend/test-federation/compose.tpl.yml
+++ b/packages/backend/test-federation/compose.tpl.yml
@@ -50,6 +50,14 @@ services:
source: ../../misskey-js/package.json
target: /misskey/packages/misskey-js/package.json
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
source: ../../misskey-reversi/built
target: /misskey/packages/misskey-reversi/built
diff --git a/packages/backend/test-federation/compose.yml b/packages/backend/test-federation/compose.yml
index 330cc33854..e9ac63e4f4 100644
--- a/packages/backend/test-federation/compose.yml
+++ b/packages/backend/test-federation/compose.yml
@@ -62,6 +62,14 @@ services:
source: ../../misskey-js/package.json
target: /misskey/packages/misskey-js/package.json
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
source: ../../../package.json
target: /misskey/package.json
diff --git a/packages/backend/test-federation/tsconfig.json b/packages/backend/test-federation/tsconfig.json
index 3a1cb3b9f3..8e74a62e81 100644
--- a/packages/backend/test-federation/tsconfig.json
+++ b/packages/backend/test-federation/tsconfig.json
@@ -13,12 +13,12 @@
/* Language and Environment */
"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. */
- // "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. */
// "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'. */
// "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. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
diff --git a/packages/backend/test-server/tsconfig.json b/packages/backend/test-server/tsconfig.json
index 10313699c2..7ed7c10ed7 100644
--- a/packages/backend/test-server/tsconfig.json
+++ b/packages/backend/test-server/tsconfig.json
@@ -23,6 +23,8 @@
"emitDecoratorMetadata": true,
"resolveJsonModule": true,
"isolatedModules": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "@kitajs/html",
"rootDir": "../src",
"baseUrl": "./",
"paths": {
diff --git a/packages/backend/test/tsconfig.json b/packages/backend/test/tsconfig.json
index 2b562acda8..c6754c4802 100644
--- a/packages/backend/test/tsconfig.json
+++ b/packages/backend/test/tsconfig.json
@@ -23,6 +23,8 @@
"emitDecoratorMetadata": true,
"resolveJsonModule": true,
"isolatedModules": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "@kitajs/html",
"baseUrl": "./",
"paths": {
"@/*": ["../src/*"]
diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json
index 2b15a5cc7a..25584e475d 100644
--- a/packages/backend/tsconfig.json
+++ b/packages/backend/tsconfig.json
@@ -23,12 +23,17 @@
"emitDecoratorMetadata": true,
"resolveJsonModule": true,
"isolatedModules": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "@kitajs/html",
"rootDir": "./src",
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
},
"outDir": "./built",
+ "plugins": [
+ {"name": "@kitajs/ts-html-plugin"}
+ ],
"types": [
"node"
],
@@ -43,7 +48,8 @@
},
"compileOnSave": false,
"include": [
- "./src/**/*.ts"
+ "./src/**/*.ts",
+ "./src/**/*.tsx"
],
"exclude": [
"./src/**/*.test.ts"
diff --git a/packages/frontend-embed/public/loader/boot.js b/packages/frontend-embed/public/loader/boot.js
new file mode 100644
index 0000000000..ba6366b3db
--- /dev/null
+++ b/packages/frontend-embed/public/loader/boot.js
@@ -0,0 +1,208 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+'use strict';
+
+// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
+(async () => {
+ window.onerror = (e) => {
+ console.error(e);
+ renderError('SOMETHING_HAPPENED');
+ };
+ window.onunhandledrejection = (e) => {
+ console.error(e);
+ renderError('SOMETHING_HAPPENED_IN_PROMISE');
+ };
+
+ let forceError = localStorage.getItem('forceError');
+ if (forceError != null) {
+ renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.');
+ return;
+ }
+
+ // パラメータに応じてsplashのスタイルを変更
+ const params = new URLSearchParams(location.search);
+ if (params.has('rounded') && params.get('rounded') === 'false') {
+ document.documentElement.classList.add('norounded');
+ }
+ if (params.has('border') && params.get('border') === 'false') {
+ document.documentElement.classList.add('noborder');
+ }
+
+ //#region Detect language & fetch translations
+ const supportedLangs = LANGS;
+ /** @type { string } */
+ let lang = localStorage.getItem('lang');
+ if (lang == null || !supportedLangs.includes(lang)) {
+ if (supportedLangs.includes(navigator.language)) {
+ lang = navigator.language;
+ } else {
+ lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
+
+ // Fallback
+ if (lang == null) lang = 'en-US';
+ }
+ }
+
+ // for https://github.com/misskey-dev/misskey/issues/10202
+ if (lang == null || lang.toString == null || lang.toString() === 'null') {
+ console.error('invalid lang value detected!!!', typeof lang, lang);
+ lang = 'en-US';
+ }
+ //#endregion
+
+ //#region Script
+ async function importAppScript() {
+ await import(CLIENT_ENTRY ? `/embed_vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/embed_vite/src/boot.ts')
+ .catch(async e => {
+ console.error(e);
+ renderError('APP_IMPORT');
+ });
+ }
+
+ // タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある
+ if (document.readyState !== 'loading') {
+ importAppScript();
+ } else {
+ window.addEventListener('DOMContentLoaded', () => {
+ importAppScript();
+ });
+ }
+ //#endregion
+
+ async function addStyle(styleText) {
+ let css = document.createElement('style');
+ css.appendChild(document.createTextNode(styleText));
+ document.head.appendChild(css);
+ }
+
+ async function renderError(code) {
+ // Cannot set property 'innerHTML' of null を回避
+ if (document.readyState === 'loading') {
+ await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
+ }
+
+ let messages = null;
+ const bootloaderLocales = localStorage.getItem('bootloaderLocales');
+ if (bootloaderLocales) {
+ messages = JSON.parse(bootloaderLocales);
+ }
+ if (!messages) {
+ // older version of misskey does not store bootloaderLocales, stores locale as a whole
+ const legacyLocale = localStorage.getItem('locale');
+ if (legacyLocale) {
+ const parsed = JSON.parse(legacyLocale);
+ messages = {
+ ...(parsed._bootErrors ?? {}),
+ reload: parsed.reload,
+ };
+ }
+ }
+ if (!messages) messages = {};
+
+ const title = messages?.title || 'Failed to initialize Misskey';
+ const reload = messages?.reload || 'Reload';
+
+ document.body.innerHTML = `
+ ${title}
+ Error Code: ${code}
+ `;
+ addStyle(`
+ #misskey_app,
+ #splash {
+ display: none !important;
+ }
+
+ html,
+ body {
+ margin: 0;
+ }
+
+ body {
+ position: relative;
+ color: #dee7e4;
+ font-family: Hiragino Maru Gothic Pro, BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
+ line-height: 1.35;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ margin: 0;
+ padding: 24px;
+ box-sizing: border-box;
+ overflow: hidden;
+
+ border-radius: var(--radius, 12px);
+ border: 1px solid rgba(231, 255, 251, 0.14);
+ }
+
+ body::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: #192320;
+ border-radius: var(--radius, 12px);
+ z-index: -1;
+ }
+
+ html.embed.norounded body,
+ html.embed.norounded body::before {
+ border-radius: 0;
+ }
+
+ html.embed.noborder body {
+ border: none;
+ }
+
+ .icon {
+ max-width: 60px;
+ width: 100%;
+ height: auto;
+ margin-bottom: 20px;
+ color: #dec340;
+ }
+
+ .message {
+ text-align: center;
+ font-size: 20px;
+ font-weight: 700;
+ margin-bottom: 20px;
+ }
+
+ .submessage {
+ text-align: center;
+ font-size: 90%;
+ margin-bottom: 7.5px;
+ }
+
+ .submessage:last-of-type {
+ margin-bottom: 20px;
+ }
+
+ button {
+ padding: 7px 14px;
+ min-width: 100px;
+ font-weight: 700;
+ font-family: Hiragino Maru Gothic Pro, BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
+ line-height: 1.35;
+ border-radius: 99rem;
+ background-color: #b4e900;
+ color: #192320;
+ border: none;
+ cursor: pointer;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ button:hover {
+ background-color: #c6ff03;
+ }`);
+ }
+})();
diff --git a/packages/frontend-embed/public/loader/style.css b/packages/frontend-embed/public/loader/style.css
new file mode 100644
index 0000000000..0911d562bf
--- /dev/null
+++ b/packages/frontend-embed/public/loader/style.css
@@ -0,0 +1,100 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+html {
+ background-color: var(--MI_THEME-bg);
+ color: var(--MI_THEME-fg);
+}
+
+html.embed {
+ box-sizing: border-box;
+ background-color: transparent;
+ color-scheme: light dark;
+ max-width: 500px;
+}
+
+#splash {
+ position: fixed;
+ z-index: 10000;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ cursor: wait;
+ background-color: var(--MI_THEME-bg);
+ opacity: 1;
+ transition: opacity 0.5s ease;
+}
+
+html.embed #splash {
+ box-sizing: border-box;
+ min-height: 300px;
+ border-radius: var(--radius, 12px);
+ border: 1px solid var(--MI_THEME-divider, #e8e8e8);
+}
+
+html.embed.norounded #splash {
+ border-radius: 0;
+}
+
+html.embed.noborder #splash {
+ border: none;
+}
+
+#splashIcon {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ margin: auto;
+ width: 64px;
+ height: 64px;
+ border-radius: 10px;
+ pointer-events: none;
+}
+
+#splashSpinner {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ margin: auto;
+ display: inline-block;
+ width: 28px;
+ height: 28px;
+ transform: translateY(70px);
+ color: var(--MI_THEME-accent);
+}
+
+#splashSpinner > .spinner {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 28px;
+ height: 28px;
+ fill-rule: evenodd;
+ clip-rule: evenodd;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-miterlimit: 1.5;
+}
+#splashSpinner > .spinner.bg {
+ opacity: 0.275;
+}
+#splashSpinner > .spinner.fg {
+ animation: splashSpinner 0.5s linear infinite;
+}
+
+@keyframes splashSpinner {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/packages/frontend/public/loader/boot.js b/packages/frontend/public/loader/boot.js
new file mode 100644
index 0000000000..ab4b158287
--- /dev/null
+++ b/packages/frontend/public/loader/boot.js
@@ -0,0 +1,336 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+'use strict';
+
+// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
+(async () => {
+ window.onerror = (e) => {
+ console.error(e);
+ renderError('SOMETHING_HAPPENED', e);
+ };
+ window.onunhandledrejection = (e) => {
+ console.error(e);
+ renderError('SOMETHING_HAPPENED_IN_PROMISE', e.reason || e);
+ };
+
+ let forceError = localStorage.getItem('forceError');
+ if (forceError != null) {
+ renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.');
+ return;
+ }
+
+ //#region Detect language
+ const supportedLangs = LANGS;
+ /** @type { string } */
+ let lang = localStorage.getItem('lang');
+ if (lang == null || !supportedLangs.includes(lang)) {
+ if (supportedLangs.includes(navigator.language)) {
+ lang = navigator.language;
+ } else {
+ lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
+
+ // Fallback
+ if (lang == null) lang = 'en-US';
+ }
+ }
+
+ // for https://github.com/misskey-dev/misskey/issues/10202
+ if (lang == null || lang.toString == null || lang.toString() === 'null') {
+ console.error('invalid lang value detected!!!', typeof lang, lang);
+ lang = 'en-US';
+ }
+ //#endregion
+
+ //#region Script
+ async function importAppScript() {
+ await import(CLIENT_ENTRY ? `/vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/vite/src/_boot_.ts')
+ .catch(async e => {
+ console.error(e);
+ renderError('APP_IMPORT', e);
+ });
+ }
+
+ // タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある
+ if (document.readyState !== 'loading') {
+ importAppScript();
+ } else {
+ window.addEventListener('DOMContentLoaded', () => {
+ importAppScript();
+ });
+ }
+ //#endregion
+
+ let isSafeMode = (localStorage.getItem('isSafeMode') === 'true');
+
+ if (!isSafeMode) {
+ const urlParams = new URLSearchParams(window.location.search);
+
+ if (urlParams.has('safemode') && urlParams.get('safemode') === 'true') {
+ localStorage.setItem('isSafeMode', 'true');
+ isSafeMode = true;
+ }
+ }
+
+ //#region Theme
+ if (!isSafeMode) {
+ const theme = localStorage.getItem('theme');
+ if (theme) {
+ for (const [k, v] of Object.entries(JSON.parse(theme))) {
+ document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
+
+ // HTMLの theme-color 適用
+ if (k === 'htmlThemeColor') {
+ for (const tag of document.head.children) {
+ if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
+ tag.setAttribute('content', v);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ const colorScheme = localStorage.getItem('colorScheme');
+ if (colorScheme) {
+ document.documentElement.style.setProperty('color-scheme', colorScheme);
+ }
+ //#endregion
+
+ const fontSize = localStorage.getItem('fontSize');
+ if (fontSize) {
+ document.documentElement.classList.add('f-' + fontSize);
+ }
+
+ const useSystemFont = localStorage.getItem('useSystemFont');
+ if (useSystemFont) {
+ document.documentElement.classList.add('useSystemFont');
+ }
+
+ if (!isSafeMode) {
+ const customCss = localStorage.getItem('customCss');
+ if (customCss && customCss.length > 0) {
+ const style = document.createElement('style');
+ style.innerHTML = customCss;
+ document.head.appendChild(style);
+ }
+ }
+
+ async function addStyle(styleText) {
+ let css = document.createElement('style');
+ css.appendChild(document.createTextNode(styleText));
+ document.head.appendChild(css);
+ }
+
+ async function renderError(code, details) {
+ // Cannot set property 'innerHTML' of null を回避
+ if (document.readyState === 'loading') {
+ await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
+ }
+
+ let messages = null;
+ const bootloaderLocales = localStorage.getItem('bootloaderLocales');
+ if (bootloaderLocales) {
+ messages = JSON.parse(bootloaderLocales);
+ }
+ if (!messages) {
+ // older version of misskey does not store bootloaderLocales, stores locale as a whole
+ const legacyLocale = localStorage.getItem('locale');
+ if (legacyLocale) {
+ const parsed = JSON.parse(legacyLocale);
+ messages = {
+ ...(parsed._bootErrors ?? {}),
+ reload: parsed.reload,
+ };
+ }
+ }
+ if (!messages) messages = {};
+
+ messages = Object.assign({
+ title: 'Failed to initialize Misskey',
+ solution: 'The following actions may solve the problem.',
+ solution1: 'Update your os and browser',
+ solution2: 'Disable an adblocker',
+ solution3: 'Clear the browser cache',
+ solution4: '(Tor Browser) Set dom.webaudio.enabled to true',
+ otherOption: 'Other options',
+ otherOption1: 'Clear preferences and cache',
+ otherOption2: 'Start the simple client',
+ otherOption3: 'Start the repair tool',
+ otherOption4: 'Start Misskey in safe mode',
+ reload: 'Reload',
+ }, messages);
+
+ const safeModeUrl = new URL(window.location.href);
+ safeModeUrl.searchParams.set('safemode', 'true');
+
+ let errorsElement = document.getElementById('errors');
+
+ if (!errorsElement) {
+ document.body.innerHTML = `
+
+ ${messages.title}
+
+ ${messages.solution}
+ ${messages.solution1}
+ ${messages.solution2}
+ ${messages.solution3}
+ ${messages.solution4}
+
+ ${messages.otherOption}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+ errorsElement = document.getElementById('errors');
+ }
+ const detailsElement = document.createElement('details');
+ detailsElement.id = 'errorInfo';
+ detailsElement.innerHTML = `
+
+
+ ERROR CODE: ${code}
+
+ ${details.toString()} ${JSON.stringify(details)}`;
+ errorsElement.appendChild(detailsElement);
+ addStyle(`
+ * {
+ font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
+ }
+
+ #misskey_app,
+ #splash {
+ display: none !important;
+ }
+
+ body,
+ html {
+ background-color: #222;
+ color: #dfddcc;
+ justify-content: center;
+ margin: auto;
+ padding: 10px;
+ text-align: center;
+ }
+
+ button {
+ border-radius: 999px;
+ padding: 0px 12px 0px 12px;
+ border: none;
+ cursor: pointer;
+ margin-bottom: 12px;
+ }
+
+ .button-big {
+ background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
+ line-height: 50px;
+ }
+
+ .button-big:hover {
+ background: rgb(153, 204, 0);
+ }
+
+ .button-small {
+ background: #444;
+ line-height: 40px;
+ }
+
+ .button-small:hover {
+ background: #555;
+ }
+
+ .button-label-big {
+ color: #222;
+ font-weight: bold;
+ font-size: 1.2em;
+ padding: 12px;
+ }
+
+ .button-label-small {
+ color: rgb(153, 204, 0);
+ font-size: 16px;
+ padding: 12px;
+ }
+
+ a {
+ color: rgb(134, 179, 0);
+ text-decoration: none;
+ }
+
+ p,
+ li {
+ font-size: 16px;
+ }
+
+ .icon-warning {
+ color: #dec340;
+ height: 4rem;
+ padding-top: 2rem;
+ }
+
+ h1 {
+ font-size: 1.5em;
+ margin: 1em;
+ }
+
+ code {
+ font-family: Fira, FiraCode, monospace;
+ }
+
+ #errorInfo {
+ background: #333;
+ margin-bottom: 2rem;
+ padding: 0.5rem 1rem;
+ width: 40rem;
+ border-radius: 10px;
+ justify-content: center;
+ margin: auto;
+ }
+
+ #errorInfo summary {
+ cursor: pointer;
+ }
+
+ #errorInfo summary > * {
+ display: inline;
+ }
+
+ @media screen and (max-width: 500px) {
+ #errorInfo {
+ width: 50%;
+ }
+ }`);
+ }
+})();
diff --git a/packages/frontend/public/loader/style.css b/packages/frontend/public/loader/style.css
new file mode 100644
index 0000000000..8e63a2ea66
--- /dev/null
+++ b/packages/frontend/public/loader/style.css
@@ -0,0 +1,78 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+html {
+ background-color: var(--MI_THEME-bg);
+ color: var(--MI_THEME-fg);
+}
+
+#splash {
+ position: fixed;
+ z-index: 10000;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ cursor: wait;
+ background-color: var(--MI_THEME-bg);
+ opacity: 1;
+ transition: opacity 0.5s ease;
+}
+
+#splashIcon {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ margin: auto;
+ width: 64px;
+ height: 64px;
+ border-radius: 10px;
+ pointer-events: none;
+}
+
+#splashSpinner {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ margin: auto;
+ display: inline-block;
+ width: 28px;
+ height: 28px;
+ transform: translateY(70px);
+ color: var(--MI_THEME-accent);
+}
+
+#splashSpinner > .spinner {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 28px;
+ height: 28px;
+ fill-rule: evenodd;
+ clip-rule: evenodd;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-miterlimit: 1.5;
+}
+#splashSpinner > .spinner.bg {
+ opacity: 0.275;
+}
+#splashSpinner > .spinner.fg {
+ animation: splashSpinner 0.5s linear infinite;
+}
+
+@keyframes splashSpinner {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts
index e428267748..77a4f42012 100644
--- a/packages/i18n/src/index.ts
+++ b/packages/i18n/src/index.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 default locales;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f971d79363..28795dfa43 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -77,9 +77,6 @@ importers:
globals:
specifier: 16.5.0
version: 16.5.0
- i18n:
- specifier: workspace:*
- version: link:packages/i18n
ncp:
specifier: 2.0.0
version: 2.0.0
@@ -126,9 +123,9 @@ importers:
'@fastify/static':
specifier: 8.3.0
version: 8.3.0
- '@fastify/view':
- specifier: 11.1.1
- version: 11.1.1
+ '@kitajs/html':
+ specifier: 4.2.11
+ version: 4.2.11
'@misskey-dev/sharp-read-bmp':
specifier: 1.2.0
version: 1.2.0
@@ -255,6 +252,9 @@ importers:
http-link-header:
specifier: 1.1.3
version: 1.1.3
+ i18n:
+ specifier: workspace:*
+ version: link:../i18n
ioredis:
specifier: 5.8.2
version: 5.8.2
@@ -345,9 +345,6 @@ importers:
promise-limit:
specifier: 2.7.0
version: 2.7.0
- pug:
- specifier: 3.0.3
- version: 3.0.3
qrcode:
specifier: 1.5.4
version: 1.5.4
@@ -436,6 +433,9 @@ importers:
'@jest/globals':
specifier: 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':
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)
@@ -505,9 +505,6 @@ importers:
'@types/pg':
specifier: 8.15.6
version: 8.15.6
- '@types/pug':
- specifier: 2.0.10
- version: 2.0.10
'@types/qrcode':
specifier: 1.5.6
version: 1.5.6
@@ -592,6 +589,9 @@ importers:
supertest:
specifier: 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:
'@swc/core-android-arm64':
specifier: 1.3.11
@@ -2503,9 +2503,6 @@ packages:
'@fastify/static@8.3.0':
resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==}
- '@fastify/view@11.1.1':
- resolution: {integrity: sha512-GiHqT3R2eKJgWmy0s45eELTC447a4+lTM2o+8fSWeKwBe9VToeePuHJcKtOEXPrKGSddGO0RsNayULiS3aeHeQ==}
-
'@file-type/xml@0.4.4':
resolution: {integrity: sha512-NhCyXoHlVZ8TqM476hyzwGJ24+D5IPSaZhmrPj7qXnEVb3q6jrFzA3mM9TBpknKSI9EuQeGTKRg2DXGUwvBBoQ==}
@@ -2864,6 +2861,17 @@ packages:
'@keyv/serialize@1.1.1':
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':
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
@@ -4812,9 +4820,6 @@ packages:
'@types/pg@8.15.6':
resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==}
- '@types/pug@2.0.10':
- resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
-
'@types/punycode@2.1.4':
resolution: {integrity: sha512-trzh6NzBnq8yw5e35f8xe8VTYjqM3NE7bohBtvDVf/dtUer3zYTLK1Ka3DG3p7bdtoaOHZucma6FfVKlQ134pQ==}
@@ -5915,6 +5920,10 @@ packages:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
+ cliui@9.0.1:
+ resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==}
+ engines: {node: '>=20'}
+
cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
@@ -6451,6 +6460,9 @@ packages:
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
engines: {node: '>=12'}
+ emoji-regex@10.6.0:
+ resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -7060,6 +7072,10 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
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:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@@ -10151,6 +10167,10 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
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:
resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
engines: {node: '>= 0.4'}
@@ -11108,6 +11128,10 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
+ wrap-ansi@9.0.2:
+ resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
+ engines: {node: '>=18'}
+
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -11189,6 +11213,10 @@ packages:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
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:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
@@ -11201,6 +11229,10 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
+ yargs@18.0.0:
+ resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=23}
+
yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
@@ -12489,11 +12521,6 @@ snapshots:
fastq: 1.19.1
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':
dependencies:
sax: 1.4.3
@@ -12904,6 +12931,18 @@ snapshots:
'@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': {}
'@levischuck/tiny-cbor@0.2.11': {}
@@ -15177,8 +15216,6 @@ snapshots:
pg-protocol: 1.10.3
pg-types: 2.2.0
- '@types/pug@2.0.10': {}
-
'@types/punycode@2.1.4': {}
'@types/qrcode@1.5.6':
@@ -16555,6 +16592,12 @@ snapshots:
strip-ansi: 6.0.1
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: {}
co@4.6.0: {}
@@ -17140,6 +17183,8 @@ snapshots:
emittery@0.13.1: {}
+ emoji-regex@10.6.0: {}
+
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -18025,6 +18070,8 @@ snapshots:
get-caller-file@2.0.5: {}
+ get-east-asian-width@1.4.0: {}
+
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -21684,6 +21731,12 @@ snapshots:
emoji-regex: 9.2.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:
dependencies:
call-bind: 1.0.8
@@ -22634,6 +22687,12 @@ snapshots:
string-width: 5.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: {}
write-file-atomic@4.0.2:
@@ -22690,6 +22749,8 @@ snapshots:
yargs-parser@21.1.1: {}
+ yargs-parser@22.0.0: {}
+
yargs@15.4.1:
dependencies:
cliui: 6.0.0
@@ -22724,6 +22785,15 @@ snapshots:
y18n: 5.0.8
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:
dependencies:
buffer-crc32: 0.2.13
diff --git a/scripts/build-assets.mjs b/scripts/build-assets.mjs
index 34883e3513..0cfce02fef 100644
--- a/scripts/build-assets.mjs
+++ b/scripts/build-assets.mjs
@@ -6,12 +6,7 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
-import cssnano from 'cssnano';
import * as yaml from 'js-yaml';
-import postcss from 'postcss';
-import * as terser from 'terser';
-
-import { locales } from 'i18n';
import buildTarball from './tarball.mjs';
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 });
}
-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() {
await Promise.all([
copyFrontendFonts(),
- copyBackendViews(),
- buildBackendScript(),
- buildBackendStyle(),
loadConfig().then(config => config?.publishTarballInsteadOfProvideRepositoryUrl && buildTarball()),
]);
}
--
cgit v1.2.3-freya
From 4ee6f90ab20dec17291f9d1c5b35b1aa862ac9c1 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 5 Dec 2025 21:31:04 +0900
Subject: chore(deps): update [tools] update dependencies to v4.0.14 [ci skip]
(#16940)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
scripts/changelog-checker/package-lock.json | 106 +++++++++++++---------------
scripts/changelog-checker/package.json | 4 +-
2 files changed, 53 insertions(+), 57 deletions(-)
(limited to 'scripts')
diff --git a/scripts/changelog-checker/package-lock.json b/scripts/changelog-checker/package-lock.json
index 3bf65e528a..cb18038f52 100644
--- a/scripts/changelog-checker/package-lock.json
+++ b/scripts/changelog-checker/package-lock.json
@@ -10,7 +10,7 @@
"devDependencies": {
"@types/mdast": "4.0.4",
"@types/node": "24.10.1",
- "@vitest/coverage-v8": "4.0.13",
+ "@vitest/coverage-v8": "4.0.14",
"mdast-util-to-string": "4.0.0",
"remark": "15.0.1",
"remark-parse": "11.0.0",
@@ -18,7 +18,7 @@
"unified": "11.0.5",
"vite": "7.2.4",
"vite-node": "5.2.0",
- "vitest": "4.0.13"
+ "vitest": "4.0.14"
}
},
"node_modules/@babel/helper-string-parser": {
@@ -915,21 +915,21 @@
"dev": true
},
"node_modules/@vitest/coverage-v8": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.13.tgz",
- "integrity": "sha512-w77N6bmtJ3CFnL/YHiYotwW/JI3oDlR3K38WEIqegRfdMSScaYxwYKB/0jSNpOTZzUjQkG8HHEz4sdWQMWpQ5g==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.14.tgz",
+ "integrity": "sha512-EYHLqN/BY6b47qHH7gtMxAg++saoGmsjWmAq9MlXxAz4M0NcHh9iOyKhBZyU4yxZqOd8Xnqp80/5saeitz4Cng==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
- "@vitest/utils": "4.0.13",
+ "@vitest/utils": "4.0.14",
"ast-v8-to-istanbul": "^0.3.8",
- "debug": "^4.4.3",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^5.0.6",
"istanbul-reports": "^3.2.0",
"magicast": "^0.5.1",
+ "obug": "^2.1.1",
"std-env": "^3.10.0",
"tinyrainbow": "^3.0.3"
},
@@ -937,8 +937,8 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "4.0.13",
- "vitest": "4.0.13"
+ "@vitest/browser": "4.0.14",
+ "vitest": "4.0.14"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -947,16 +947,16 @@
}
},
"node_modules/@vitest/expect": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.13.tgz",
- "integrity": "sha512-zYtcnNIBm6yS7Gpr7nFTmq8ncowlMdOJkWLqYvhr/zweY6tFbDkDi8BPPOeHxEtK1rSI69H7Fd4+1sqvEGli6w==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.14.tgz",
+ "integrity": "sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@types/chai": "^5.2.2",
- "@vitest/spy": "4.0.13",
- "@vitest/utils": "4.0.13",
+ "@vitest/spy": "4.0.14",
+ "@vitest/utils": "4.0.14",
"chai": "^6.2.1",
"tinyrainbow": "^3.0.3"
},
@@ -965,13 +965,13 @@
}
},
"node_modules/@vitest/mocker": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.13.tgz",
- "integrity": "sha512-eNCwzrI5djoauklwP1fuslHBjrbR8rqIVbvNlAnkq1OTa6XT+lX68mrtPirNM9TnR69XUPt4puBCx2Wexseylg==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.14.tgz",
+ "integrity": "sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "4.0.13",
+ "@vitest/spy": "4.0.14",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -992,9 +992,9 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.13.tgz",
- "integrity": "sha512-ooqfze8URWbI2ozOeLDMh8YZxWDpGXoeY3VOgcDnsUxN0jPyPWSUvjPQWqDGCBks+opWlN1E4oP1UYl3C/2EQA==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz",
+ "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1005,13 +1005,13 @@
}
},
"node_modules/@vitest/runner": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.13.tgz",
- "integrity": "sha512-9IKlAru58wcVaWy7hz6qWPb2QzJTKt+IOVKjAx5vb5rzEFPTL6H4/R9BMvjZ2ppkxKgTrFONEJFtzvnyEpiT+A==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.14.tgz",
+ "integrity": "sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "4.0.13",
+ "@vitest/utils": "4.0.14",
"pathe": "^2.0.3"
},
"funding": {
@@ -1019,13 +1019,13 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.13.tgz",
- "integrity": "sha512-hb7Usvyika1huG6G6l191qu1urNPsq1iFc2hmdzQY3F5/rTgqQnwwplyf8zoYHkpt7H6rw5UfIw6i/3qf9oSxQ==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.14.tgz",
+ "integrity": "sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.0.13",
+ "@vitest/pretty-format": "4.0.14",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -1034,9 +1034,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.13.tgz",
- "integrity": "sha512-hSu+m4se0lDV5yVIcNWqjuncrmBgwaXa2utFLIrBkQCQkt+pSwyZTPFQAZiiF/63j8jYa8uAeUZ3RSfcdWaYWw==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.14.tgz",
+ "integrity": "sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1044,13 +1044,13 @@
}
},
"node_modules/@vitest/utils": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.13.tgz",
- "integrity": "sha512-ydozWyQ4LZuu8rLp47xFUWis5VOKMdHjXCWhs1LuJsTNKww+pTHQNK4e0assIB9K80TxFyskENL6vCu3j34EYA==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz",
+ "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.0.13",
+ "@vitest/pretty-format": "4.0.14",
"tinyrainbow": "^3.0.3"
},
"funding": {
@@ -2439,24 +2439,24 @@
}
},
"node_modules/vitest": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.13.tgz",
- "integrity": "sha512-QSD4I0fN6uZQfftryIXuqvqgBxTvJ3ZNkF6RWECd82YGAYAfhcppBLFXzXJHQAAhVFyYEuFTrq6h0hQqjB7jIQ==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.14.tgz",
+ "integrity": "sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "@vitest/expect": "4.0.13",
- "@vitest/mocker": "4.0.13",
- "@vitest/pretty-format": "4.0.13",
- "@vitest/runner": "4.0.13",
- "@vitest/snapshot": "4.0.13",
- "@vitest/spy": "4.0.13",
- "@vitest/utils": "4.0.13",
- "debug": "^4.4.3",
+ "@vitest/expect": "4.0.14",
+ "@vitest/mocker": "4.0.14",
+ "@vitest/pretty-format": "4.0.14",
+ "@vitest/runner": "4.0.14",
+ "@vitest/snapshot": "4.0.14",
+ "@vitest/spy": "4.0.14",
+ "@vitest/utils": "4.0.14",
"es-module-lexer": "^1.7.0",
"expect-type": "^1.2.2",
"magic-string": "^0.30.21",
+ "obug": "^2.1.1",
"pathe": "^2.0.3",
"picomatch": "^4.0.3",
"std-env": "^3.10.0",
@@ -2479,12 +2479,11 @@
"peerDependencies": {
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
- "@types/debug": "^4.1.12",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.0.13",
- "@vitest/browser-preview": "4.0.13",
- "@vitest/browser-webdriverio": "4.0.13",
- "@vitest/ui": "4.0.13",
+ "@vitest/browser-playwright": "4.0.14",
+ "@vitest/browser-preview": "4.0.14",
+ "@vitest/browser-webdriverio": "4.0.14",
+ "@vitest/ui": "4.0.14",
"happy-dom": "*",
"jsdom": "*"
},
@@ -2495,9 +2494,6 @@
"@opentelemetry/api": {
"optional": true
},
- "@types/debug": {
- "optional": true
- },
"@types/node": {
"optional": true
},
diff --git a/scripts/changelog-checker/package.json b/scripts/changelog-checker/package.json
index 8cc94d0532..3ca43d9001 100644
--- a/scripts/changelog-checker/package.json
+++ b/scripts/changelog-checker/package.json
@@ -11,7 +11,7 @@
"devDependencies": {
"@types/mdast": "4.0.4",
"@types/node": "24.10.1",
- "@vitest/coverage-v8": "4.0.13",
+ "@vitest/coverage-v8": "4.0.14",
"mdast-util-to-string": "4.0.0",
"remark": "15.0.1",
"remark-parse": "11.0.0",
@@ -19,6 +19,6 @@
"unified": "11.0.5",
"vite": "7.2.4",
"vite-node": "5.2.0",
- "vitest": "4.0.13"
+ "vitest": "4.0.14"
}
}
--
cgit v1.2.3-freya