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} + + + + + + +
+
{safeDescription}
+
+ + + + ); +} 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