From 618e2ba1d22f57efca25e5a4b2c1f0bea0e5ea21 Mon Sep 17 00:00:00 2001 From: zyoshoka <107108195+zyoshoka@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:40:37 +0900 Subject: [PATCH 001/134] =?UTF-8?q?fix(backend):=20`drive/files/update`?= =?UTF-8?q?=E3=81=AB=E3=81=8A=E3=81=91=E3=82=8B=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E5=90=8D=E3=81=AE=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=8C=E6=A9=9F=E8=83=BD=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#12923)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): `drive/files/update`におけるファイル名のバリデーションが機能していない問題を修正 * Update CHANGELOG.md * refactor: `!== undefined` -> `!= null` * add test --- CHANGELOG.md | 1 + packages/backend/src/core/DriveService.ts | 2 +- packages/backend/test/e2e/endpoints.ts | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 474fcad674..7adeebb479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました - Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916) - Enhance: クリップをエクスポートできるように +- Fix: `drive/files/update`でファイル名のバリデーションが機能していない問題を修正 ## 2023.12.2 diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 04f0e38e6f..075bc9d7f7 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -655,7 +655,7 @@ export class DriveService { public async updateFile(file: MiDriveFile, values: Partial, updater: MiUser) { const alwaysMarkNsfw = (await this.roleService.getUserPolicies(file.userId)).alwaysMarkNsfw; - if (values.name && !this.driveFileEntityService.validateFileName(file.name)) { + if (values.name != null && !this.driveFileEntityService.validateFileName(values.name)) { throw new DriveService.InvalidFileNameError(); } diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index bfd0b0272d..d75549c816 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -710,6 +710,18 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 400); }); + test('不正なファイル名で怒られる', async () => { + const file = (await uploadFile(alice)).body; + const newName = ''; + + const res = await api('/drive/files/update', { + fileId: file.id, + name: newName, + }, alice); + + assert.strictEqual(res.status, 400); + }); + test('間違ったIDで怒られる', async () => { const res = await api('/drive/files/update', { fileId: 'kyoppie', From 35ec41fc1eddd7ebf5552e6f0bceebfbfa077a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:43:52 +0900 Subject: [PATCH 002/134] =?UTF-8?q?enhance(backend):=20=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=81=AE=E9=AB=98=E9=80=9F=E5=8C=96=20(#12939)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(backend): テストの高速化 * add ls * 自動的にマージされるようなので不要 * 起動方法を揃える * fix test --- .github/workflows/test-backend.yml | 56 ++++++- .gitignore | 1 + packages/backend/jest.config.cjs | 1 - packages/backend/jest.config.e2e.cjs | 15 ++ packages/backend/jest.config.unit.cjs | 14 ++ packages/backend/package.json | 12 +- packages/backend/test-server/.eslintrc.cjs | 32 ++++ packages/backend/test-server/.swcrc | 23 +++ packages/backend/test-server/entry.ts | 80 +++++++++ packages/backend/test-server/tsconfig.json | 52 ++++++ packages/backend/test/e2e/2fa.ts | 9 +- packages/backend/test/e2e/antennas.ts | 26 +-- packages/backend/test/e2e/api-visibility.ts | 13 +- packages/backend/test/e2e/api.ts | 19 ++- packages/backend/test/e2e/block.ts | 10 +- packages/backend/test/e2e/clips.ts | 19 +-- packages/backend/test/e2e/endpoints.ts | 10 +- packages/backend/test/e2e/exports.ts | 9 +- packages/backend/test/e2e/fetch-resource.ts | 10 +- packages/backend/test/e2e/ff-visibility.ts | 10 +- packages/backend/test/e2e/move.ts | 12 +- packages/backend/test/e2e/mute.ts | 10 +- packages/backend/test/e2e/nodeinfo.ts | 13 +- packages/backend/test/e2e/note.ts | 9 +- packages/backend/test/e2e/oauth.ts | 20 ++- packages/backend/test/e2e/renote-mute.ts | 10 +- packages/backend/test/e2e/streaming.ts | 9 +- packages/backend/test/e2e/thread-mute.ts | 10 +- packages/backend/test/e2e/timelines.ts | 28 +--- packages/backend/test/e2e/user-notes.ts | 10 +- packages/backend/test/e2e/users.ts | 24 +-- packages/backend/test/e2e/well-known.ts | 10 +- packages/backend/test/jest.setup.ts | 8 + packages/backend/test/misc/mock-resolver.ts | 8 +- .../backend/test/unit/AnnouncementService.ts | 8 +- packages/backend/test/unit/DriveService.ts | 8 +- .../test/unit/FetchInstanceMetadataService.ts | 3 +- packages/backend/test/unit/FileInfoService.ts | 2 +- packages/backend/test/unit/MetaService.ts | 4 +- packages/backend/test/unit/RoleService.ts | 2 +- packages/backend/test/unit/S3Service.ts | 8 +- packages/backend/test/unit/misc/id.ts | 4 +- packages/backend/test/unit/misc/others.ts | 2 +- packages/backend/test/utils.ts | 49 +++++- pnpm-lock.yaml | 154 +++++++++++++++--- 45 files changed, 563 insertions(+), 283 deletions(-) create mode 100644 packages/backend/jest.config.e2e.cjs create mode 100644 packages/backend/jest.config.unit.cjs create mode 100644 packages/backend/test-server/.eslintrc.cjs create mode 100644 packages/backend/test-server/.swcrc create mode 100644 packages/backend/test-server/entry.ts create mode 100644 packages/backend/test-server/tsconfig.json create mode 100644 packages/backend/test/jest.setup.ts diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 9681cbec59..3b49173f45 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -8,7 +8,7 @@ on: pull_request: jobs: - jest: + unit: runs-on: ubuntu-latest strategy: @@ -51,9 +51,59 @@ jobs: - name: Build run: pnpm build - name: Test - run: pnpm jest-and-coverage - - name: Upload Coverage + run: pnpm --filter backend test-and-coverage + - name: Upload to Codecov uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./packages/backend/coverage/coverage-final.json + + e2e: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.10.0] + + services: + postgres: + image: postgres:15 + ports: + - 54312:5432 + env: + POSTGRES_DB: test-misskey + POSTGRES_HOST_AUTH_METHOD: trust + redis: + image: redis:7 + ports: + - 56312:6379 + + steps: + - uses: actions/checkout@v4.1.1 + with: + submodules: true + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + run_install: false + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4.0.1 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + - run: corepack enable + - run: pnpm i --frozen-lockfile + - name: Check pnpm-lock.yaml + run: git diff --exit-code pnpm-lock.yaml + - name: Copy Configure + run: cp .github/misskey/test.yml .config + - name: Build + run: pnpm build + - name: Test + run: pnpm --filter backend test-and-coverage:e2e + - name: Upload to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./packages/backend/coverage/coverage-final.json diff --git a/.gitignore b/.gitignore index a66e527db0..d7b486b977 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ docker-compose.yml # misskey /build built +built-test /data /.cache-loader /db diff --git a/packages/backend/jest.config.cjs b/packages/backend/jest.config.cjs index 97d777c862..5a4aa4e15a 100644 --- a/packages/backend/jest.config.cjs +++ b/packages/backend/jest.config.cjs @@ -160,7 +160,6 @@ module.exports = { testMatch: [ "/test/unit/**/*.ts", "/src/**/*.test.ts", - "/test/e2e/**/*.ts", ], // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped diff --git a/packages/backend/jest.config.e2e.cjs b/packages/backend/jest.config.e2e.cjs new file mode 100644 index 0000000000..4502da47df --- /dev/null +++ b/packages/backend/jest.config.e2e.cjs @@ -0,0 +1,15 @@ +/* +* For a detailed explanation regarding each configuration property and type check, visit: +* https://jestjs.io/docs/en/configuration.html +*/ + +const base = require('./jest.config.cjs') + +module.exports = { + ...base, + globalSetup: "/built-test/entry.js", + setupFilesAfterEnv: ["/test/jest.setup.ts"], + testMatch: [ + "/test/e2e/**/*.ts", + ], +}; diff --git a/packages/backend/jest.config.unit.cjs b/packages/backend/jest.config.unit.cjs new file mode 100644 index 0000000000..aa5992936b --- /dev/null +++ b/packages/backend/jest.config.unit.cjs @@ -0,0 +1,14 @@ +/* +* For a detailed explanation regarding each configuration property and type check, visit: +* https://jestjs.io/docs/en/configuration.html +*/ + +const base = require('./jest.config.cjs') + +module.exports = { + ...base, + testMatch: [ + "/test/unit/**/*.ts", + "/src/**/*.test.ts", + ], +}; diff --git a/packages/backend/package.json b/packages/backend/package.json index 7b9654c207..5ab476295c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -13,6 +13,7 @@ "revert": "pnpm typeorm migration:revert -d ormconfig.js", "check:connect": "node ./check_connect.js", "build": "swc src -d built -D", + "build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc", "watch:swc": "swc src -d built -D -w", "build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json", "watch": "node watch.mjs", @@ -21,11 +22,15 @@ "typecheck": "tsc --noEmit", "eslint": "eslint --quiet \"src/**/*.ts\"", "lint": "pnpm typecheck && pnpm eslint", - "jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit", - "jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit", + "jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs", + "jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs", + "jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs", + "jest-and-coverage:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.e2e.cjs", "jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache", "test": "pnpm jest", + "test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e", "test-and-coverage": "pnpm jest-and-coverage", + "test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e", "generate-api-json": "node ./generate_api_json.js" }, "optionalDependencies": { @@ -178,6 +183,7 @@ "devDependencies": { "@jest/globals": "29.7.0", "@misskey-dev/eslint-plugin": "^1.0.0", + "@nestjs/platform-express": "^10.3.0", "@simplewebauthn/typescript-types": "8.3.4", "@swc/jest": "0.2.29", "@types/accepts": "1.3.7", @@ -226,9 +232,11 @@ "eslint": "8.56.0", "eslint-plugin-import": "2.29.1", "execa": "8.0.1", + "fkill": "^9.0.0", "jest": "29.7.0", "jest-mock": "29.7.0", "nodemon": "3.0.2", + "pid-port": "^1.0.0", "simple-oauth2": "5.0.0" } } diff --git a/packages/backend/test-server/.eslintrc.cjs b/packages/backend/test-server/.eslintrc.cjs new file mode 100644 index 0000000000..c261741a36 --- /dev/null +++ b/packages/backend/test-server/.eslintrc.cjs @@ -0,0 +1,32 @@ +module.exports = { + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + extends: [ + '../../shared/.eslintrc.js', + ], + rules: { + 'import/order': ['warn', { + 'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'], + 'pathGroups': [ + { + 'pattern': '@/**', + 'group': 'external', + 'position': 'after' + } + ], + }], + 'no-restricted-globals': [ + 'error', + { + 'name': '__dirname', + 'message': 'Not in ESModule. Use `import.meta.url` instead.' + }, + { + 'name': '__filename', + 'message': 'Not in ESModule. Use `import.meta.url` instead.' + } + ] + }, +}; diff --git a/packages/backend/test-server/.swcrc b/packages/backend/test-server/.swcrc new file mode 100644 index 0000000000..e3d6935169 --- /dev/null +++ b/packages/backend/test-server/.swcrc @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/swcrc", + "jsc": { + "parser": { + "syntax": "typescript", + "dynamicImport": true, + "decorators": true + }, + "transform": { + "legacyDecorator": true, + "decoratorMetadata": true + }, + "experimental": { + "keepImportAssertions": true + }, + "baseUrl": "../built", + "paths": { + "@/*": ["*"] + }, + "target": "es2022" + }, + "minify": false +} diff --git a/packages/backend/test-server/entry.ts b/packages/backend/test-server/entry.ts new file mode 100644 index 0000000000..866a7e1f5b --- /dev/null +++ b/packages/backend/test-server/entry.ts @@ -0,0 +1,80 @@ +import { portToPid } from 'pid-port'; +import fkill from 'fkill'; +import Fastify from 'fastify'; +import { NestFactory } from '@nestjs/core'; +import { MainModule } from '@/MainModule.js'; +import { ServerService } from '@/server/ServerService.js'; +import { loadConfig } from '@/config.js'; +import { NestLogger } from '@/NestLogger.js'; + +const config = loadConfig(); +const originEnv = JSON.stringify(process.env); + +process.env.NODE_ENV = 'test'; + +/** + * テスト用のサーバインスタンスを起動する + */ +async function launch() { + await killTestServer(); + + console.log('starting application...'); + + const app = await NestFactory.createApplicationContext(MainModule, { + logger: new NestLogger(), + }); + const serverService = app.get(ServerService); + await serverService.launch(); + + await startControllerEndpoints(); + + // ジョブキューは必要な時にテストコード側で起動する + // ジョブキューが動くとテスト結果の確認に支障が出ることがあるので意図的に動かさないでいる + + console.log('application initialized.'); +} + +/** + * 既に重複したポートで待ち受けしているサーバがある場合はkillする + */ +async function killTestServer() { + // + try { + const pid = await portToPid(config.port); + if (pid) { + await fkill(pid, { force: true }); + } + } catch { + // NOP; + } +} + +/** + * 別プロセスに切り離してしまったが故に出来なくなった環境変数の書き換え等を実現するためのエンドポイントを作る + * @param port + */ +async function startControllerEndpoints(port = config.port + 1000) { + const fastify = Fastify(); + + fastify.post<{ Body: { key?: string, value?: string } }>('/env', async (req, res) => { + console.log(req.body); + const key = req.body['key']; + if (!key) { + res.code(400).send({ success: false }); + return; + } + + process.env[key] = req.body['value']; + + res.code(200).send({ success: true }); + }); + + fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => { + process.env = JSON.parse(originEnv); + res.code(200).send({ success: true }); + }); + + await fastify.listen({ port: port, host: 'localhost' }); +} + +export default launch; diff --git a/packages/backend/test-server/tsconfig.json b/packages/backend/test-server/tsconfig.json new file mode 100644 index 0000000000..10313699c2 --- /dev/null +++ b/packages/backend/test-server/tsconfig.json @@ -0,0 +1,52 @@ +{ + "compilerOptions": { + "allowJs": true, + "noEmitOnError": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noUnusedParameters": false, + "noUnusedLocals": false, + "noFallthroughCasesInSwitch": true, + "declaration": false, + "sourceMap": true, + "target": "ES2022", + "module": "nodenext", + "moduleResolution": "nodenext", + "allowSyntheticDefaultImports": true, + "removeComments": false, + "noLib": false, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": false, + "skipLibCheck": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "resolveJsonModule": true, + "isolatedModules": true, + "rootDir": "../src", + "baseUrl": "./", + "paths": { + "@/*": ["../src/*"] + }, + "outDir": "../built-test", + "types": [ + "node" + ], + "typeRoots": [ + "../src/@types", + "../node_modules/@types", + "../node_modules" + ], + "lib": [ + "esnext" + ] + }, + "compileOnSave": false, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ], + "exclude": [ + "../src/**/*.test.ts" + ] +} diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts index dfed8b2fce..165a1055c9 100644 --- a/packages/backend/test/e2e/2fa.ts +++ b/packages/backend/test/e2e/2fa.ts @@ -10,7 +10,7 @@ import * as crypto from 'node:crypto'; import cbor from 'cbor'; import * as OTPAuth from 'otpauth'; import { loadConfig } from '@/config.js'; -import { api, signup, startServer } from '../utils.js'; +import { api, signup } from '../utils.js'; import type { AuthenticationResponseJSON, AuthenticatorAssertionResponseJSON, @@ -19,11 +19,9 @@ import type { PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON, } from '@simplewebauthn/typescript-types'; -import type { INestApplicationContext } from '@nestjs/common'; import type * as misskey from 'misskey-js'; describe('2要素認証', () => { - let app: INestApplicationContext; let alice: misskey.entities.SignupResponse; const config = loadConfig(); @@ -185,14 +183,9 @@ describe('2要素認証', () => { }; beforeAll(async () => { - app = await startServer(); alice = await signup({ username, password }); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - test('が設定でき、OTPでログインできる。', async () => { const registerResponse = await api('/i/2fa/register', { password, diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index 9bac5122d4..e63722b246 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -6,24 +6,20 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { inspect } from 'node:util'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; import type { Packed } from '@/misc/json-schema.js'; import { - signup, - post, - userList, - page, - role, - startServer, api, - successfulApiCall, failedApiCall, - uploadFile, + post, + role, + signup, + successfulApiCall, testPaginationConsistency, + uploadFile, + userList, } from '../utils.js'; import type * as misskey from 'misskey-js'; -import type { INestApplicationContext } from '@nestjs/common'; const compareBy = (selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => { return selector(a).localeCompare(selector(b)); @@ -54,8 +50,6 @@ describe('アンテナ', () => { withReplies: false, }; - let app: INestApplicationContext; - let root: User; let alice: User; let bob: User; @@ -79,10 +73,6 @@ describe('アンテナ', () => { let userMutingAlice: User; let userMutedByAlice: User; - beforeAll(async () => { - app = await startServer(); - }, 1000 * 60 * 2); - beforeAll(async () => { root = await signup({ username: 'root' }); alice = await signup({ username: 'alice' }); @@ -136,10 +126,6 @@ describe('アンテナ', () => { await api('mute/create', { userId: userMutedByAlice.id }, alice); }, 1000 * 60 * 10); - afterAll(async () => { - await app.close(); - }); - beforeEach(async () => { // テスト間で影響し合わないように毎回全部消す。 for (const user of [alice, bob]) { diff --git a/packages/backend/test/e2e/api-visibility.ts b/packages/backend/test/e2e/api-visibility.ts index afe4f9c05a..89d8b42271 100644 --- a/packages/backend/test/e2e/api-visibility.ts +++ b/packages/backend/test/e2e/api-visibility.ts @@ -6,21 +6,10 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { signup, api, post, startServer } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, post, signup } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('API visibility', () => { - let app: INestApplicationContext; - - beforeAll(async () => { - app = await startServer(); - }, 1000 * 60 * 2); - - afterAll(async () => { - await app.close(); - }); - describe('Note visibility', () => { //#region vars /** ヒロイン */ diff --git a/packages/backend/test/e2e/api.ts b/packages/backend/test/e2e/api.ts index ad351eebbb..25d5bdb175 100644 --- a/packages/backend/test/e2e/api.ts +++ b/packages/backend/test/e2e/api.ts @@ -7,27 +7,30 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { IncomingMessage } from 'http'; -import { signup, api, startServer, successfulApiCall, failedApiCall, uploadFile, waitFire, connectStream, relativeFetch, createAppToken } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { + api, + connectStream, + createAppToken, + failedApiCall, + relativeFetch, + signup, + successfulApiCall, + uploadFile, + waitFire, +} from '../utils.js'; import type * as misskey from 'misskey-js'; describe('API', () => { - let app: INestApplicationContext; let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; let carol: misskey.entities.SignupResponse; beforeAll(async () => { - app = await startServer(); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); carol = await signup({ username: 'carol' }); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - describe('General validation', () => { test('wrong type', async () => { const res = await api('/test', { diff --git a/packages/backend/test/e2e/block.ts b/packages/backend/test/e2e/block.ts index 25ff9f11ac..1dfc87c64f 100644 --- a/packages/backend/test/e2e/block.ts +++ b/packages/backend/test/e2e/block.ts @@ -6,29 +6,21 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { signup, api, post, startServer } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, post, signup } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('Block', () => { - let app: INestApplicationContext; - // alice blocks bob let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; let carol: misskey.entities.SignupResponse; beforeAll(async () => { - app = await startServer(); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); carol = await signup({ username: 'carol' }); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - test('Block作成', async () => { const res = await api('/blocking/create', { userId: bob.id, diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts index 49092fba63..b679eea8cf 100644 --- a/packages/backend/test/e2e/clips.ts +++ b/packages/backend/test/e2e/clips.ts @@ -18,25 +18,13 @@ import { paramDef as UnfavoriteParamDef } from '@/server/api/endpoints/clips/unf import { paramDef as AddNoteParamDef } from '@/server/api/endpoints/clips/add-note.js'; import { paramDef as RemoveNoteParamDef } from '@/server/api/endpoints/clips/remove-note.js'; import { paramDef as NotesParamDef } from '@/server/api/endpoints/clips/notes.js'; -import { - signup, - post, - startServer, - api, - successfulApiCall, - failedApiCall, - ApiRequest, - hiddenNote, -} from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, ApiRequest, failedApiCall, hiddenNote, post, signup, successfulApiCall } from '../utils.js'; describe('クリップ', () => { type User = Packed<'User'>; type Note = Packed<'Note'>; type Clip = Packed<'Clip'>; - let app: INestApplicationContext; - let alice: User; let bob: User; let aliceNote: Note; @@ -145,7 +133,6 @@ describe('クリップ', () => { }; beforeAll(async () => { - app = await startServer(); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); @@ -160,10 +147,6 @@ describe('クリップ', () => { bobSpecifiedNote = await post(bob, { text: 'specified only', visibility: 'specified' }) as any; }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - afterEach(async () => { // テスト間で影響し合わないように毎回全部消す。 for (const user of [alice, bob]) { diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index d75549c816..b12b062a63 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -10,30 +10,22 @@ import * as assert from 'assert'; // https://github.com/node-fetch/node-fetch/pull/1664 import { Blob } from 'node-fetch'; import { MiUser } from '@/models/_.js'; -import { startServer, signup, post, api, uploadFile, simpleGet, initTestDb } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, initTestDb, post, signup, simpleGet, uploadFile } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('Endpoints', () => { - let app: INestApplicationContext; - let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; let carol: misskey.entities.SignupResponse; let dave: misskey.entities.SignupResponse; beforeAll(async () => { - app = await startServer(); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); carol = await signup({ username: 'carol' }); dave = await signup({ username: 'dave' }); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - describe('signup', () => { test('不正なユーザー名でアカウントが作成できない', async () => { const res = await api('signup', { diff --git a/packages/backend/test/e2e/exports.ts b/packages/backend/test/e2e/exports.ts index 9686f2b7fd..f9b59144a3 100644 --- a/packages/backend/test/e2e/exports.ts +++ b/packages/backend/test/e2e/exports.ts @@ -6,12 +6,12 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { signup, api, startServer, startJobQueue, port, post } from '../utils.js'; +import { api, port, post, signup, startJobQueue } from '../utils.js'; import type { INestApplicationContext } from '@nestjs/common'; import type * as misskey from 'misskey-js'; describe('export-clips', () => { - let app: INestApplicationContext; + let queue: INestApplicationContext; let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; @@ -33,14 +33,13 @@ describe('export-clips', () => { } beforeAll(async () => { - app = await startServer(); - await startJobQueue(); + queue = await startJobQueue(); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); }, 1000 * 60 * 2); afterAll(async () => { - await app.close(); + await queue.close(); }); beforeEach(async () => { diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts index 28affe7768..0d23b4fe67 100644 --- a/packages/backend/test/e2e/fetch-resource.ts +++ b/packages/backend/test/e2e/fetch-resource.ts @@ -6,9 +6,8 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { startServer, channel, clip, cookie, galleryPost, signup, page, play, post, simpleGet, uploadFile } from '../utils.js'; +import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js'; import type { SimpleGetResponse } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; import type * as misskey from 'misskey-js'; // Request Accept @@ -23,8 +22,6 @@ const HTML = 'text/html; charset=utf-8'; const JSON_UTF8 = 'application/json; charset=utf-8'; describe('Webリソース', () => { - let app: INestApplicationContext; - let alice: misskey.entities.SignupResponse; let aliceUploadedFile: any; let alicesPost: any; @@ -79,7 +76,6 @@ describe('Webリソース', () => { }; beforeAll(async () => { - app = await startServer(); alice = await signup({ username: 'alice' }); aliceUploadedFile = await uploadFile(alice); alicesPost = await post(alice, { @@ -96,10 +92,6 @@ describe('Webリソース', () => { bob = await signup({ username: 'bob' }); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - describe.each([ { path: '/', type: HTML }, { path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。" diff --git a/packages/backend/test/e2e/ff-visibility.ts b/packages/backend/test/e2e/ff-visibility.ts index 4d323e14e7..1fe0478a18 100644 --- a/packages/backend/test/e2e/ff-visibility.ts +++ b/packages/backend/test/e2e/ff-visibility.ts @@ -6,26 +6,18 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { signup, api, startServer, simpleGet } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, signup, simpleGet } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('FF visibility', () => { - let app: INestApplicationContext; - let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; beforeAll(async () => { - app = await startServer(); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - test('followingVisibility, followersVisibility がともに public なユーザーのフォロー/フォロワーを誰でも見れる', async () => { await api('/i/update', { followingVisibility: 'public', diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts index f7da66a27c..3937203569 100644 --- a/packages/backend/test/e2e/move.ts +++ b/packages/backend/test/e2e/move.ts @@ -3,19 +3,19 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { INestApplicationContext } from '@nestjs/common'; + process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { loadConfig } from '@/config.js'; import { MiUser, UsersRepository } from '@/models/_.js'; -import { jobQueue } from '@/boot/common.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; -import { uploadFile, signup, startServer, initTestDb, api, sleep, successfulApiCall } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { jobQueue } from '@/boot/common.js'; +import { api, initTestDb, signup, sleep, successfulApiCall, uploadFile } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('Account Move', () => { - let app: INestApplicationContext; let jq: INestApplicationContext; let url: URL; @@ -30,8 +30,8 @@ describe('Account Move', () => { let Users: UsersRepository; beforeAll(async () => { - app = await startServer(); jq = await jobQueue(); + const config = loadConfig(); url = new URL(config.url); const connection = await initTestDb(false); @@ -46,7 +46,7 @@ describe('Account Move', () => { }, 1000 * 60 * 2); afterAll(async () => { - await Promise.all([app.close(), jq.close()]); + await jq.close(); }); describe('Create Alias', () => { diff --git a/packages/backend/test/e2e/mute.ts b/packages/backend/test/e2e/mute.ts index 3b5542dfe0..5144df5ebe 100644 --- a/packages/backend/test/e2e/mute.ts +++ b/packages/backend/test/e2e/mute.ts @@ -6,29 +6,21 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { signup, api, post, react, startServer, waitFire } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, post, react, signup, waitFire } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('Mute', () => { - let app: INestApplicationContext; - // alice mutes carol let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; let carol: misskey.entities.SignupResponse; beforeAll(async () => { - app = await startServer(); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); carol = await signup({ username: 'carol' }); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - test('ミュート作成', async () => { const res = await api('/mute/create', { userId: carol.id, diff --git a/packages/backend/test/e2e/nodeinfo.ts b/packages/backend/test/e2e/nodeinfo.ts index 7eed39c5ed..934ef08507 100644 --- a/packages/backend/test/e2e/nodeinfo.ts +++ b/packages/backend/test/e2e/nodeinfo.ts @@ -6,20 +6,9 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { relativeFetch, startServer } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { relativeFetch } from '../utils.js'; describe('nodeinfo', () => { - let app: INestApplicationContext; - - beforeAll(async () => { - app = await startServer(); - }, 1000 * 60 * 2); - - afterAll(async () => { - await app.close(); - }); - test('nodeinfo 2.1', async () => { const res = await relativeFetch('nodeinfo/2.1'); assert.ok(res.ok); diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts index 8d33c63485..0f2e08e675 100644 --- a/packages/backend/test/e2e/note.ts +++ b/packages/backend/test/e2e/note.ts @@ -8,29 +8,22 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { MiNote } from '@/models/Note.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import { signup, post, uploadUrl, startServer, initTestDb, api, uploadFile } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, initTestDb, post, signup, uploadFile, uploadUrl } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('Note', () => { - let app: INestApplicationContext; let Notes: any; let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; beforeAll(async () => { - app = await startServer(); const connection = await initTestDb(true); Notes = connection.getRepository(MiNote); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - test('投稿できる', async () => { const post = { text: 'test', diff --git a/packages/backend/test/e2e/oauth.ts b/packages/backend/test/e2e/oauth.ts index 3ca1f8b542..df6ff42df9 100644 --- a/packages/backend/test/e2e/oauth.ts +++ b/packages/backend/test/e2e/oauth.ts @@ -11,13 +11,18 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { AuthorizationCode, ResourceOwnerPassword, type AuthorizationTokenConfig, ClientCredentials, ModuleOptions } from 'simple-oauth2'; +import { + AuthorizationCode, + type AuthorizationTokenConfig, + ClientCredentials, + ModuleOptions, + ResourceOwnerPassword, +} from 'simple-oauth2'; import pkceChallenge from 'pkce-challenge'; import { JSDOM } from 'jsdom'; -import Fastify, { type FastifyReply, type FastifyInstance } from 'fastify'; -import { api, port, signup, startServer } from '../utils.js'; +import Fastify, { type FastifyInstance, type FastifyReply } from 'fastify'; +import { api, port, sendEnvUpdateRequest, signup } from '../utils.js'; import type * as misskey from 'misskey-js'; -import type { INestApplicationContext } from '@nestjs/common'; const host = `http://127.0.0.1:${port}`; @@ -147,7 +152,6 @@ async function assertDirectError(response: Response, status: number, error: stri } describe('OAuth', () => { - let app: INestApplicationContext; let fastify: FastifyInstance; let alice: misskey.entities.SignupResponse; @@ -156,7 +160,6 @@ describe('OAuth', () => { let sender: (reply: FastifyReply) => void; beforeAll(async () => { - app = await startServer(); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); @@ -168,7 +171,7 @@ describe('OAuth', () => { }, 1000 * 60 * 2); beforeEach(async () => { - process.env.MISSKEY_TEST_CHECK_IP_RANGE = ''; + await sendEnvUpdateRequest({ key: 'MISSKEY_TEST_CHECK_IP_RANGE', value: '' }); sender = (reply): void => { reply.send(` @@ -180,7 +183,6 @@ describe('OAuth', () => { afterAll(async () => { await fastify.close(); - await app.close(); }); test('Full flow', async () => { @@ -881,7 +883,7 @@ describe('OAuth', () => { }); test('Disallow loopback', async () => { - process.env.MISSKEY_TEST_CHECK_IP_RANGE = '1'; + await sendEnvUpdateRequest({ key: 'MISSKEY_TEST_CHECK_IP_RANGE', value: '1' }); const client = new AuthorizationCode(clientConfig); const response = await fetch(client.authorizeURL({ diff --git a/packages/backend/test/e2e/renote-mute.ts b/packages/backend/test/e2e/renote-mute.ts index fededdff32..42cc414c3f 100644 --- a/packages/backend/test/e2e/renote-mute.ts +++ b/packages/backend/test/e2e/renote-mute.ts @@ -6,29 +6,21 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { signup, api, post, react, startServer, waitFire, sleep } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, post, signup, sleep, waitFire } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('Renote Mute', () => { - let app: INestApplicationContext; - // alice mutes carol let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; let carol: misskey.entities.SignupResponse; beforeAll(async () => { - app = await startServer(); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); carol = await signup({ username: 'carol' }); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - test('ミュート作成', async () => { const res = await api('/renote-mute/create', { userId: carol.id, diff --git a/packages/backend/test/e2e/streaming.ts b/packages/backend/test/e2e/streaming.ts index a447ba94ae..b6f584fa70 100644 --- a/packages/backend/test/e2e/streaming.ts +++ b/packages/backend/test/e2e/streaming.ts @@ -8,12 +8,10 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { WebSocket } from 'ws'; import { MiFollowing } from '@/models/Following.js'; -import { signup, api, post, startServer, initTestDb, waitFire, createAppToken, port } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, createAppToken, initTestDb, port, post, signup, waitFire } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('Streaming', () => { - let app: INestApplicationContext; let Followings: any; const follow = async (follower: any, followee: any) => { @@ -48,7 +46,6 @@ describe('Streaming', () => { let list: any; beforeAll(async () => { - app = await startServer(); const connection = await initTestDb(true); Followings = connection.getRepository(MiFollowing); @@ -95,10 +92,6 @@ describe('Streaming', () => { }, chitose); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - describe('Events', () => { test('mention event', async () => { const fired = await waitFire( diff --git a/packages/backend/test/e2e/thread-mute.ts b/packages/backend/test/e2e/thread-mute.ts index 5c68e2b150..26c30d6c4c 100644 --- a/packages/backend/test/e2e/thread-mute.ts +++ b/packages/backend/test/e2e/thread-mute.ts @@ -6,28 +6,20 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { signup, api, post, connectStream, startServer } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, connectStream, post, signup } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('Note thread mute', () => { - let app: INestApplicationContext; - let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; let carol: misskey.entities.SignupResponse; beforeAll(async () => { - app = await startServer(); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); carol = await signup({ username: 'carol' }); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - test('notes/mentions にミュートしているスレッドの投稿が含まれない', async () => { const bobNote = await post(bob, { text: '@alice @carol root note' }); const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index cb9558b416..88f89c4a6f 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -6,12 +6,8 @@ // How to run: // pnpm jest -- e2e/timelines.ts -process.env.NODE_ENV = 'test'; -process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING = 'true'; - import * as assert from 'assert'; -import { api, post, randomString, signup, sleep, startServer, uploadUrl } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, post, randomString, sendEnvUpdateRequest, signup, sleep, uploadUrl } from '../utils.js'; function genHost() { return randomString() + '.example.com'; @@ -21,16 +17,6 @@ function waitForPushToTl() { return sleep(500); } -let app: INestApplicationContext; - -beforeAll(async () => { - app = await startServer(); -}, 1000 * 60 * 2); - -afterAll(async () => { - await app.close(); -}); - describe('Timelines', () => { describe('Home TL', () => { test.concurrent('自分の visibility: followers なノートが含まれる', async () => { @@ -334,8 +320,9 @@ describe('Timelines', () => { test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); + await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); await api('/following/create', { userId: bob.id }, alice); - await sleep(1000); + const bobNote = await post(bob, { text: 'hi' }); await waitForPushToTl(); @@ -348,8 +335,9 @@ describe('Timelines', () => { test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); + await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); await api('/following/create', { userId: bob.id }, alice); - await sleep(1000); + const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); await waitForPushToTl(); @@ -762,8 +750,9 @@ describe('Timelines', () => { test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); + await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); await api('/following/create', { userId: bob.id }, alice); - await sleep(1000); + const bobNote = await post(bob, { text: 'hi' }); await waitForPushToTl(); @@ -776,8 +765,9 @@ describe('Timelines', () => { test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); + await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); await api('/following/create', { userId: bob.id }, alice); - await sleep(1000); + const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); await waitForPushToTl(); diff --git a/packages/backend/test/e2e/user-notes.ts b/packages/backend/test/e2e/user-notes.ts index 4f2e7c4cf3..07da0db369 100644 --- a/packages/backend/test/e2e/user-notes.ts +++ b/packages/backend/test/e2e/user-notes.ts @@ -6,20 +6,16 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { signup, api, post, uploadUrl, startServer } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { api, post, signup, uploadUrl } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('users/notes', () => { - let app: INestApplicationContext; - let alice: misskey.entities.SignupResponse; let jpgNote: any; let pngNote: any; let jpgPngNote: any; beforeAll(async () => { - app = await startServer(); alice = await signup({ username: 'alice' }); const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg'); const png = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.png'); @@ -34,10 +30,6 @@ describe('users/notes', () => { }); }, 1000 * 60 * 2); - afterAll(async() => { - await app.close(); - }); - test('withFiles', async () => { const res = await api('/users/notes', { userId: alice.id, diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index 9c4cbac368..bc23c009b2 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -8,20 +8,8 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { inspect } from 'node:util'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; -import type { Packed } from '@/misc/json-schema.js'; -import { - signup, - post, - page, - role, - startServer, - api, - successfulApiCall, - failedApiCall, - uploadFile, -} from '../utils.js'; +import { api, page, post, role, signup, successfulApiCall, uploadFile } from '../utils.js'; import type * as misskey from 'misskey-js'; -import type { INestApplicationContext } from '@nestjs/common'; describe('ユーザー', () => { // エンティティとしてのユーザーを主眼においたテストを記述する @@ -185,8 +173,6 @@ describe('ユーザー', () => { }); }; - let app: INestApplicationContext; - let root: User; let alice: User; let aliceNote: misskey.entities.Note; @@ -230,10 +216,6 @@ describe('ユーザー', () => { let userFollowRequesting: User; let userFollowRequested: User; - beforeAll(async () => { - app = await startServer(); - }, 1000 * 60 * 2); - beforeAll(async () => { root = await signup({ username: 'root' }); alice = await signup({ username: 'alice' }); @@ -321,10 +303,6 @@ describe('ユーザー', () => { await api('following/create', { userId: userFollowRequested.id }, userFollowRequesting); }, 1000 * 60 * 10); - afterAll(async () => { - await app.close(); - }); - beforeEach(async () => { alice = { ...alice, diff --git a/packages/backend/test/e2e/well-known.ts b/packages/backend/test/e2e/well-known.ts index 14e32e1627..0429b7c8b2 100644 --- a/packages/backend/test/e2e/well-known.ts +++ b/packages/backend/test/e2e/well-known.ts @@ -6,24 +6,16 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { host, origin, relativeFetch, signup, startServer } from '../utils.js'; -import type { INestApplicationContext } from '@nestjs/common'; +import { host, origin, relativeFetch, signup } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('.well-known', () => { - let app: INestApplicationContext; let alice: misskey.entities.User; beforeAll(async () => { - app = await startServer(); - alice = await signup({ username: 'alice' }); }, 1000 * 60 * 2); - afterAll(async () => { - await app.close(); - }); - test('nodeinfo', async () => { const res = await relativeFetch('.well-known/nodeinfo'); assert.ok(res.ok); diff --git a/packages/backend/test/jest.setup.ts b/packages/backend/test/jest.setup.ts new file mode 100644 index 0000000000..cf5b9bf24d --- /dev/null +++ b/packages/backend/test/jest.setup.ts @@ -0,0 +1,8 @@ +import { initTestDb, sendEnvResetRequest } from './utils.js'; + +beforeAll(async () => { + await Promise.all([ + initTestDb(false), + sendEnvResetRequest(), + ]); +}); diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index 7cba7a2aa8..7ee65d1ab0 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -15,7 +15,13 @@ import type { LoggerService } from '@/core/LoggerService.js'; import type { MetaService } from '@/core/MetaService.js'; import type { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; -import type { NoteReactionsRepository, NotesRepository, PollsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js'; +import type { + FollowRequestsRepository, + NoteReactionsRepository, + NotesRepository, + PollsRepository, + UsersRepository, +} from '@/models/_.js'; type MockResponse = { type: string; diff --git a/packages/backend/test/unit/AnnouncementService.ts b/packages/backend/test/unit/AnnouncementService.ts index f2aa5d35e4..f02c4e6700 100644 --- a/packages/backend/test/unit/AnnouncementService.ts +++ b/packages/backend/test/unit/AnnouncementService.ts @@ -10,7 +10,13 @@ import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; import { GlobalModule } from '@/GlobalModule.js'; import { AnnouncementService } from '@/core/AnnouncementService.js'; -import type { MiAnnouncement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, MiUser } from '@/models/_.js'; +import type { + AnnouncementReadsRepository, + AnnouncementsRepository, + MiAnnouncement, + MiUser, + UsersRepository, +} from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { genAidx } from '@/misc/id/aidx.js'; import { CacheService } from '@/core/CacheService.js'; diff --git a/packages/backend/test/unit/DriveService.ts b/packages/backend/test/unit/DriveService.ts index 7234da2e36..64397a1a4f 100644 --- a/packages/backend/test/unit/DriveService.ts +++ b/packages/backend/test/unit/DriveService.ts @@ -6,7 +6,13 @@ process.env.NODE_ENV = 'test'; import { Test } from '@nestjs/testing'; -import { DeleteObjectCommandOutput, DeleteObjectCommand, NoSuchKey, InvalidObjectState, S3Client } from '@aws-sdk/client-s3'; +import { + DeleteObjectCommand, + DeleteObjectCommandOutput, + InvalidObjectState, + NoSuchKey, + S3Client, +} from '@aws-sdk/client-s3'; import { mockClient } from 'aws-sdk-client-mock'; import { GlobalModule } from '@/GlobalModule.js'; import { DriveService } from '@/core/DriveService.js'; diff --git a/packages/backend/test/unit/FetchInstanceMetadataService.ts b/packages/backend/test/unit/FetchInstanceMetadataService.ts index 34200899d4..cddc374f9a 100644 --- a/packages/backend/test/unit/FetchInstanceMetadataService.ts +++ b/packages/backend/test/unit/FetchInstanceMetadataService.ts @@ -55,7 +55,8 @@ describe('FetchInstanceMetadataService', () => { return { fetch: jest.fn() }; } else if (token === DI.redis) { return mockRedis; - }}) + } + }) .compile(); app.enableShutdownHooks(); diff --git a/packages/backend/test/unit/FileInfoService.ts b/packages/backend/test/unit/FileInfoService.ts index de0b31488c..f3717d73cd 100644 --- a/packages/backend/test/unit/FileInfoService.ts +++ b/packages/backend/test/unit/FileInfoService.ts @@ -10,7 +10,7 @@ import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; -import { describe, beforeAll, afterAll, test } from '@jest/globals'; +import { afterAll, beforeAll, describe, test } from '@jest/globals'; import { GlobalModule } from '@/GlobalModule.js'; import { FileInfoService } from '@/core/FileInfoService.js'; //import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/test/unit/MetaService.ts b/packages/backend/test/unit/MetaService.ts index ab30f48283..c4c7f21913 100644 --- a/packages/backend/test/unit/MetaService.ts +++ b/packages/backend/test/unit/MetaService.ts @@ -6,15 +6,13 @@ process.env.NODE_ENV = 'test'; import { jest } from '@jest/globals'; -import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; import { GlobalModule } from '@/GlobalModule.js'; -import type { MetasRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; import { CoreModule } from '@/core/CoreModule.js'; -import type { DataSource } from 'typeorm'; import type { TestingModule } from '@nestjs/testing'; +import type { DataSource } from 'typeorm'; describe('MetaService', () => { let app: TestingModule; diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index 9879eb8e3e..46613c29c8 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -11,7 +11,7 @@ import { Test } from '@nestjs/testing'; import * as lolex from '@sinonjs/fake-timers'; import { GlobalModule } from '@/GlobalModule.js'; import { RoleService } from '@/core/RoleService.js'; -import type { MiRole, RolesRepository, RoleAssignmentsRepository, UsersRepository, MiUser } from '@/models/_.js'; +import type { MiRole, MiUser, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; import { genAidx } from '@/misc/id/aidx.js'; diff --git a/packages/backend/test/unit/S3Service.ts b/packages/backend/test/unit/S3Service.ts index c1eafc96b7..2ffc99380d 100644 --- a/packages/backend/test/unit/S3Service.ts +++ b/packages/backend/test/unit/S3Service.ts @@ -6,7 +6,13 @@ process.env.NODE_ENV = 'test'; import { Test } from '@nestjs/testing'; -import { UploadPartCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; +import { + CompleteMultipartUploadCommand, + CreateMultipartUploadCommand, + PutObjectCommand, + S3Client, + UploadPartCommand, +} from '@aws-sdk/client-s3'; import { mockClient } from 'aws-sdk-client-mock'; import { GlobalModule } from '@/GlobalModule.js'; import { CoreModule } from '@/core/CoreModule.js'; diff --git a/packages/backend/test/unit/misc/id.ts b/packages/backend/test/unit/misc/id.ts index 59783a9fa1..1498c075aa 100644 --- a/packages/backend/test/unit/misc/id.ts +++ b/packages/backend/test/unit/misc/id.ts @@ -4,13 +4,13 @@ */ import { ulid } from 'ulid'; -import { describe, test, expect } from '@jest/globals'; +import { describe, expect, test } from '@jest/globals'; import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js'; import { aidxRegExp, genAidx, parseAidx } from '@/misc/id/aidx.js'; import { genMeid, meidRegExp, parseMeid } from '@/misc/id/meid.js'; import { genMeidg, meidgRegExp, parseMeidg } from '@/misc/id/meidg.js'; import { genObjectId, objectIdRegExp, parseObjectId } from '@/misc/id/object-id.js'; -import { ulidRegExp, parseUlid } from '@/misc/id/ulid.js'; +import { parseUlid, ulidRegExp } from '@/misc/id/ulid.js'; describe('misc:id', () => { test('aid', () => { diff --git a/packages/backend/test/unit/misc/others.ts b/packages/backend/test/unit/misc/others.ts index b16d26d866..caa815b3df 100644 --- a/packages/backend/test/unit/misc/others.ts +++ b/packages/backend/test/unit/misc/others.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { describe, test, expect } from '@jest/globals'; +import { describe, expect, test } from '@jest/globals'; import { contentDisposition } from '@/misc/content-disposition.js'; describe('misc:content-disposition', () => { diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 7c9428d476..2b232a0a5d 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -5,7 +5,7 @@ import * as assert from 'node:assert'; import { readFile } from 'node:fs/promises'; -import { isAbsolute, basename } from 'node:path'; +import { basename, isAbsolute } from 'node:path'; import { randomUUID } from 'node:crypto'; import { inspect } from 'node:util'; import WebSocket, { ClientOptions } from 'ws'; @@ -68,7 +68,11 @@ export const failedApiCall = async (request: ApiRequest, assertion: { return res.body; }; -const request = async (path: string, params: any, me?: UserToken): Promise<{ status: number, headers: Headers, body: any }> => { +const request = async (path: string, params: any, me?: UserToken): Promise<{ + status: number, + headers: Headers, + body: any +}> => { const bodyAuth: Record = {}; const headers: Record = { 'Content-Type': 'application/json', @@ -275,7 +279,11 @@ interface UploadOptions { * Upload file * @param user User */ -export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadOptions = {}): Promise<{ status: number, headers: Headers, body: misskey.Endpoints['drive/files/create']['res'] | null }> => { +export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadOptions = {}): Promise<{ + status: number, + headers: Headers, + body: misskey.Endpoints['drive/files/create']['res'] | null +}> => { const absPath = path == null ? new URL('resources/Lenna.jpg', import.meta.url) : isAbsolute(path.toString()) @@ -426,8 +434,8 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde ]; const body = - jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() : - htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) : + jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() : + htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) : null; return { @@ -557,3 +565,34 @@ export function sleep(msec: number) { }, msec); }); } + +export async function sendEnvUpdateRequest(params: { key: string, value?: string }) { + const res = await fetch( + `http://localhost:${port + 1000}/env`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + }, + ); + + if (res.status !== 200) { + throw new Error('server env update failed.'); + } +} + +export async function sendEnvResetRequest() { + const res = await fetch( + `http://localhost:${port + 1000}/env-reset`, + { + method: 'POST', + body: JSON.stringify({}), + }, + ); + + if (res.status !== 200) { + throw new Error('server env update failed.'); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28cfe3222f..d0f74de843 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -112,10 +112,10 @@ importers: version: 10.2.10(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/core': specifier: 10.2.10 - version: 10.2.10(@nestjs/common@10.2.10)(reflect-metadata@0.1.14)(rxjs@7.8.1) + version: 10.2.10(@nestjs/common@10.2.10)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/testing': specifier: 10.2.10 - version: 10.2.10(@nestjs/common@10.2.10)(@nestjs/core@10.2.10) + version: 10.2.10(@nestjs/common@10.2.10)(@nestjs/core@10.2.10)(@nestjs/platform-express@10.3.0) '@peertube/http-signature': specifier: 1.7.0 version: 1.7.0 @@ -496,6 +496,9 @@ importers: '@misskey-dev/eslint-plugin': specifier: ^1.0.0 version: 1.0.0(@typescript-eslint/eslint-plugin@6.14.0)(@typescript-eslint/parser@6.14.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) + '@nestjs/platform-express': + specifier: ^10.3.0 + version: 10.3.0(@nestjs/common@10.2.10)(@nestjs/core@10.2.10) '@simplewebauthn/typescript-types': specifier: 8.3.4 version: 8.3.4 @@ -640,6 +643,9 @@ importers: execa: specifier: 8.0.1 version: 8.0.1 + fkill: + specifier: ^9.0.0 + version: 9.0.0 jest: specifier: 29.7.0 version: 29.7.0(@types/node@20.10.5) @@ -649,6 +655,9 @@ importers: nodemon: specifier: 3.0.2 version: 3.0.2 + pid-port: + specifier: ^1.0.0 + version: 1.0.0 simple-oauth2: specifier: 5.0.0 version: 5.0.0 @@ -4878,7 +4887,6 @@ packages: /@lukeed/csprng@1.0.1: resolution: {integrity: sha512-uSvJdwQU5nK+Vdf6zxcWAY2A8r7uqe+gePwLWzJ+fsQehq18pc0I2hJKwypZ2aLM90+Er9u1xn4iLJPZ+xlL4g==} engines: {node: '>=8'} - dev: false /@lukeed/ms@2.0.1: resolution: {integrity: sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==} @@ -5132,9 +5140,8 @@ packages: rxjs: 7.8.1 tslib: 2.6.2 uid: 2.0.2 - dev: false - /@nestjs/core@10.2.10(@nestjs/common@10.2.10)(reflect-metadata@0.1.14)(rxjs@7.8.1): + /@nestjs/core@10.2.10(@nestjs/common@10.2.10)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1): resolution: {integrity: sha512-+ckOI6BPi2ZMHikT9MCG4ctHDc4OnjhoIytrn7f2AYMMXI4bnutJhqyQKc30VDka5x3Wq6QAD57pgSP7y+JjJg==} requiresBuild: true peerDependencies: @@ -5153,6 +5160,7 @@ packages: optional: true dependencies: '@nestjs/common': 10.2.10(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/platform-express': 10.3.0(@nestjs/common@10.2.10)(@nestjs/core@10.2.10) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -5163,9 +5171,24 @@ packages: uid: 2.0.2 transitivePeerDependencies: - encoding - dev: false - /@nestjs/testing@10.2.10(@nestjs/common@10.2.10)(@nestjs/core@10.2.10): + /@nestjs/platform-express@10.3.0(@nestjs/common@10.2.10)(@nestjs/core@10.2.10): + resolution: {integrity: sha512-E4hUW48bYv8OHbP9XQg6deefmXb0pDSSuE38SdhA0mJ37zGY7C5EqqBUdlQk4ttfD+OdnbIgJ1zOokT6dd2d7A==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + dependencies: + '@nestjs/common': 10.2.10(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 10.2.10(@nestjs/common@10.2.10)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + body-parser: 1.20.2 + cors: 2.8.5 + express: 4.18.2 + multer: 1.4.4-lts.1 + tslib: 2.6.2 + transitivePeerDependencies: + - supports-color + + /@nestjs/testing@10.2.10(@nestjs/common@10.2.10)(@nestjs/core@10.2.10)(@nestjs/platform-express@10.3.0): resolution: {integrity: sha512-IVLUnPz/+fkBtPATYfqTIP+phN9yjkXejmj+JyhmcfPJZpxBmD1i9VSMqa4u54l37j0xkGPscQ0IXpbhqMYUKw==} peerDependencies: '@nestjs/common': ^10.0.0 @@ -5179,7 +5202,8 @@ packages: optional: true dependencies: '@nestjs/common': 10.2.10(reflect-metadata@0.1.14)(rxjs@7.8.1) - '@nestjs/core': 10.2.10(@nestjs/common@10.2.10)(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 10.2.10(@nestjs/common@10.2.10)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/platform-express': 10.3.0(@nestjs/common@10.2.10)(@nestjs/core@10.2.10) tslib: 2.6.2 dev: false @@ -5249,7 +5273,6 @@ packages: node-fetch: 2.7.0 transitivePeerDependencies: - encoding - dev: false /@one-ini/wasm@0.1.1: resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} @@ -9164,6 +9187,14 @@ packages: clean-stack: 2.2.0 indent-string: 4.0.0 + /aggregate-error@5.0.0: + resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} + engines: {node: '>=18'} + dependencies: + clean-stack: 5.2.0 + indent-string: 5.0.0 + dev: true + /ajv-draft-04@1.0.0(ajv@8.12.0): resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -9268,6 +9299,9 @@ packages: engines: {node: '>= 6.0.0'} dev: false + /append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + /aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} requiresBuild: true @@ -9806,7 +9840,6 @@ packages: unpipe: 1.0.0 transitivePeerDependencies: - supports-color - dev: false /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -9952,7 +9985,6 @@ packages: engines: {node: '>=10.16.0'} dependencies: streamsearch: 1.1.0 - dev: false /bytes@3.0.0: resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} @@ -10290,6 +10322,13 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + /clean-stack@5.2.0: + resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==} + engines: {node: '>=14.16'} + dependencies: + escape-string-regexp: 5.0.0 + dev: true + /cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -10534,7 +10573,6 @@ packages: inherits: 2.0.4 readable-stream: 2.3.7 typedarray: 0.0.6 - dev: true /config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} @@ -10545,7 +10583,6 @@ packages: /consola@2.15.3: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} - dev: false /console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} @@ -10611,6 +10648,13 @@ packages: /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + /cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + /crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} @@ -11636,7 +11680,6 @@ packages: /escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - dev: false /escodegen@2.1.0: resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} @@ -12058,6 +12101,21 @@ packages: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + /execa@6.1.0: + resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 3.0.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + dev: true + /execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -12252,7 +12310,6 @@ packages: /fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - dev: false /fast-uri@2.2.0: resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} @@ -12470,6 +12527,18 @@ packages: semver-regex: 4.0.5 dev: false + /fkill@9.0.0: + resolution: {integrity: sha512-MdYSsbdCaIRjzo5edthZtWmEZVMfr1qrtYZUHIdO3swCE+CoZA8S5l0s4jDsYlTa9ZiXv0pTgpzE7s4N8NeUOA==} + engines: {node: '>=18'} + dependencies: + aggregate-error: 5.0.0 + execa: 8.0.1 + pid-port: 1.0.0 + process-exists: 5.0.0 + ps-list: 8.1.1 + taskkill: 5.0.0 + dev: true + /flat-cache@3.0.4: resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -13298,6 +13367,11 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + /human-signals@3.0.1: + resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} + engines: {node: '>=12.20.0'} + dev: true + /human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -13362,6 +13436,11 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + /indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + dev: true + /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -13893,7 +13972,6 @@ packages: /iterare@1.2.1: resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} engines: {node: '>=6'} - dev: false /jackspeak@2.3.6: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} @@ -15437,6 +15515,18 @@ packages: resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} dev: true + /multer@1.4.4-lts.1: + resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} + engines: {node: '>= 6.0.0'} + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 1.6.2 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + /multi-integer-range@3.0.0: resolution: {integrity: sha512-uQzynjVJ8F7x5wjaK0g4Ybhy2TvO/pk96+YHyS5g1W4GuUEV6HMebZ8HcRwWgKIRCUT2MLbM5uCKwYcAqkS+8Q==} dev: false @@ -16230,7 +16320,6 @@ packages: /path-to-regexp@3.2.0: resolution: {integrity: sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==} - dev: false /path-to-regexp@6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} @@ -16366,6 +16455,13 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + /pid-port@1.0.0: + resolution: {integrity: sha512-LSNBeKChRPA4Xlrs6+zV588G1hSrFvANtPV5rt/5MPfSPK3V9XPWxx1d29svsrOjngT9ifLisXWCLS7DvO9ZhQ==} + engines: {node: '>=18'} + dependencies: + execa: 8.0.1 + dev: true + /pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -16922,6 +17018,13 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: false + /process-exists@5.0.0: + resolution: {integrity: sha512-6QPRh5fyHD8MaXr4GYML8K/YY0Sq5dKHGIOrAKS3cYpHQdmygFCcijIu1dVoNKAZ0TWAMoeh8KDK9dF8auBkJA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + ps-list: 8.1.1 + dev: true + /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -16993,6 +17096,11 @@ packages: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: true + /ps-list@8.1.1: + resolution: {integrity: sha512-OPS9kEJYVmiO48u/B9qneqhkMvgCxT+Tm28VCEJpheTpl8cJ0ffZRRNgS5mrQRTrX5yRTpaJ+hRDeefXYmmorQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /ps-tree@1.2.0: resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} engines: {node: '>= 0.10'} @@ -17258,7 +17366,6 @@ packages: http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 - dev: false /rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} @@ -17563,7 +17670,6 @@ packages: /reflect-metadata@0.1.14: resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} - dev: false /regenerate-unicode-properties@10.1.0: resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==} @@ -18546,7 +18652,6 @@ packages: /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - dev: false /streamx@2.15.0: resolution: {integrity: sha512-HcxY6ncGjjklGs1xsP1aR71INYcsXFJet5CU1CHqihQ2J5nOsbd4OjgjHO42w/4QNv9gZb3BueV+Vxok5pLEXg==} @@ -18855,6 +18960,13 @@ packages: mkdirp: 1.0.4 yallist: 4.0.0 + /taskkill@5.0.0: + resolution: {integrity: sha512-+HRtZ40Vc+6YfCDWCeAsixwxJgMbPY4HHuTgzPYH3JXvqHWUlsCfy+ylXlAKhFNcuLp4xVeWeFBUhDk+7KYUvQ==} + engines: {node: '>=14.16'} + dependencies: + execa: 6.1.0 + dev: true + /telejson@7.2.0: resolution: {integrity: sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==} dependencies: @@ -19288,7 +19400,6 @@ packages: /typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - dev: true /typeorm@0.3.17(ioredis@5.3.2)(pg@8.11.3): resolution: {integrity: sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==} @@ -19401,7 +19512,6 @@ packages: engines: {node: '>=8'} dependencies: '@lukeed/csprng': 1.0.1 - dev: false /ulid@2.3.0: resolution: {integrity: sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==} From 5dcd8c827b9a241cd0f1dd9ce14fa26bcbeb51b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:56:41 +0900 Subject: [PATCH 003/134] =?UTF-8?q?Update=20CHANGELOG.md=20(=E9=A0=85?= =?UTF-8?q?=E7=9B=AE=E3=81=AE=E9=A0=86=E7=95=AA=E3=81=AE=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7adeebb479..1ea6862d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,9 +21,9 @@ ### Client - Feat: 新しいゲームを追加 - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように +- Enhance: チャンネルノートのピン留めをノートのメニューからできるように - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 -- Enhance: チャンネルノートのピン留めをノートのメニューからできるよ ### Server - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました From 64de87438eec284cd3cc5a9ebef90f502f2e91c7 Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Mon, 8 Jan 2024 18:51:08 +0900 Subject: [PATCH 004/134] Update CHANGELOG.md (#12949) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ea6862d1b..04f4210913 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Enhance: チャンネルノートのピン留めをノートのメニューからできるように - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 +- Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 ### Server - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました From 0f9e3bccefa11d7a00a06bb31918b6ddb8001089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Mon, 8 Jan 2024 23:51:31 +0900 Subject: [PATCH 005/134] =?UTF-8?q?refactor(CI):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E7=AF=84=E5=9B=B2=E3=81=A8=E9=96=A2=E4=BF=82=E3=81=AA=E3=81=84?= =?UTF-8?q?Actions=E3=81=8C=E8=B5=B0=E3=82=8B=E3=81=AE=E3=82=92=E6=8A=91?= =?UTF-8?q?=E6=AD=A2=E3=81=99=E3=82=8B=20(#12918)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor?: 修正範囲と関係ないActionsが走るのを抑止する * fix * バックエンドの対象にmisskey-jsを追加&フロントエンドの対象にmisskey-jsとbackendを追加 --- .github/workflows/api-misskey-js.yml | 8 +++++++- .github/workflows/lint.yml | 12 ++++++++++++ .github/workflows/test-backend.yml | 8 ++++++++ .github/workflows/test-frontend.yml | 13 +++++++++++++ .github/workflows/test-misskey-js.yml | 4 ++++ 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index 5cffbd81bc..e52cbc33e4 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -1,6 +1,12 @@ name: API report (misskey.js) -on: [push, pull_request] +on: + push: + paths: + - packages/misskey-js/** + pull_request: + paths: + - packages/misskey-js/** jobs: report: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f3074ab0a4..23cea7d565 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,7 +5,19 @@ on: branches: - master - develop + paths: + - packages/backend/** + - packages/frontend/** + - packages/sw/** + - packages/misskey-js/** + - packages/shared/.eslintrc.js pull_request: + paths: + - packages/backend/** + - packages/frontend/** + - packages/sw/** + - packages/misskey-js/** + - packages/shared/.eslintrc.js jobs: pnpm_install: diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 3b49173f45..a6c12e2824 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -5,7 +5,15 @@ on: branches: - master - develop + paths: + - packages/backend/** + # for permissions + - packages/misskey-js/** pull_request: + paths: + - packages/backend/** + # for permissions + - packages/misskey-js/** jobs: unit: diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index 83740bf156..3fb880fac2 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -5,7 +5,20 @@ on: branches: - master - develop + paths: + - packages/frontend/** + # for permissions + - packages/misskey-js/** + # for e2e + - packages/backend/** + pull_request: + paths: + - packages/frontend/** + # for permissions + - packages/misskey-js/** + # for e2e + - packages/backend/** jobs: vitest: diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index 055152f321..10c7ccf4d3 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -6,8 +6,12 @@ name: Test (misskey.js) on: push: branches: [ develop ] + paths: + - packages/misskey-js/** pull_request: branches: [ develop ] + paths: + - packages/misskey-js/** jobs: test: From 34088ecd27c081b00f425ec2546ef0670dbb10fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Tue, 9 Jan 2024 08:34:23 +0900 Subject: [PATCH 006/134] =?UTF-8?q?feat(ci):=20api.json=E3=81=AE=E3=83=90?= =?UTF-8?q?=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=83=81?= =?UTF-8?q?=E3=82=A7=E3=83=83=E3=82=AFCI=E3=82=92=E8=BF=BD=E5=8A=A0=20(#12?= =?UTF-8?q?950)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ci): api.jsonのバリデーションチェックCIを追加 * fix name --- .github/workflows/validate-api-json.yml | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/validate-api-json.yml diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml new file mode 100644 index 0000000000..bc5ba20cb9 --- /dev/null +++ b/.github/workflows/validate-api-json.yml @@ -0,0 +1,47 @@ +name: Test (backend) + +on: + push: + branches: + - master + - develop + paths: + - packages/backend/** + pull_request: + paths: + - packages/backend/** + +jobs: + validate-api-json: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.10.0] + + steps: + - uses: actions/checkout@v4.1.1 + with: + submodules: true + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + run_install: false + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4.0.1 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + - name: Install swagger-cli + run: npm i -g swagger-cli + - run: corepack enable + - run: pnpm i --frozen-lockfile + - name: Check pnpm-lock.yaml + run: git diff --exit-code pnpm-lock.yaml + - name: Copy Configure + run: cp .config/example.yml .config/default.yml + - name: Build and generate + run: pnpm build && pnpm --filter backend generate-api-json + - name: Validation + run: swagger-cli validate ./packages/backend/built/api.json From 0d7f9308cc233cf0688364cb947a376afc656871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:25:33 +0900 Subject: [PATCH 007/134] =?UTF-8?q?enhance(frontend):=20=E3=83=90=E3=83=96?= =?UTF-8?q?=E3=83=AB=E3=82=B2=E3=83=BC=E3=83=A0=E3=81=AE=E8=AB=B8=E3=80=85?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=E3=83=BB=E6=94=B9=E8=89=AF2=20(#129?= =?UTF-8?q?48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * (fix) ゲームが正常に終了するように * (enhance) 効果音の音量を設定可能に * (add) store * (add) スクショにロゴの透かしを入れる * Update packages/frontend/src/pages/drop-and-fusion.vue Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com> * tweak * tweak * tweak * tweak * Update drop-and-fusion.vue * tweak * tweak --------- Co-authored-by: syuilo Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com> --- locales/index.d.ts | 2 + locales/ja-JP.yml | 2 + .../frontend/assets/drop-and-fusion/hold.mp3 | Bin 0 -> 26496 bytes packages/frontend/src/boot/main-boot.ts | 2 +- packages/frontend/src/components/MkNote.vue | 4 +- .../src/components/MkNoteDetailed.vue | 4 +- packages/frontend/src/components/MkRange.vue | 2 + .../components/MkReactionsViewer.reaction.vue | 4 +- .../frontend/src/components/MkTimeline.vue | 2 +- .../src/components/global/MkCustomEmoji.vue | 2 +- .../src/components/global/MkEmoji.vue | 2 +- .../frontend/src/pages/drop-and-fusion.vue | 279 ++++++++++++------ .../src/pages/settings/sounds.sound.vue | 4 +- .../src/scripts/drop-and-fusion-engine.ts | 77 ++++- packages/frontend/src/scripts/sound.ts | 44 ++- packages/frontend/src/store.ts | 7 + packages/frontend/src/ui/_common_/common.vue | 2 +- .../frontend/src/widgets/WidgetJobQueue.vue | 2 +- 18 files changed, 311 insertions(+), 130 deletions(-) create mode 100644 packages/frontend/assets/drop-and-fusion/hold.mp3 diff --git a/locales/index.d.ts b/locales/index.d.ts index 7c73caaac9..96bc9099dd 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1193,6 +1193,8 @@ export interface Locale { "addMfmFunction": string; "enableQuickAddMfmFunction": string; "bubbleGame": string; + "sfx": string; + "soundWillBePlayed": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 55ff3201f0..c28fde56cb 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1190,6 +1190,8 @@ decorate: "デコる" addMfmFunction: "装飾を追加" enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する" bubbleGame: "バブルゲーム" +sfx: "効果音" +soundWillBePlayed: "サウンドが再生されます" _announcement: forExistingUsers: "既存ユーザーのみ" diff --git a/packages/frontend/assets/drop-and-fusion/hold.mp3 b/packages/frontend/assets/drop-and-fusion/hold.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ef03e60f61f68af7b8f182db7e3148ea8ee8861f GIT binary patch literal 26496 zcmeH~c|2760>IA6*=bzLHZfy~N{tB_#$?|kVak>zq?NIzxeB*IsBDiYwU{qa8U{q;WYocB3DpEIBFJLhwJ&+q#^XN;+l77xH3 z*wcm<3-baX<()vFv1 zCo?lAr?9ZFq@=8@qN3v2vxbJ2mX^-WzP^EhfuW(Xv8k!4`T6B#5aj-?J?2z~v6edb zxrM6QKqQ_A0PR{u%zQfl)NXK?yJS>v>_Eo9+J*Rh zPiOHbs{4az7V7DE&W(D-cB5|d*Es zOA%`;k{#4$r;xp!as9!p09606jF3sm__Zr;9qap5%&a-BQW7~{cNfl8X9o>Wi>@bP z&t18-R6x+TdN7qFXU&ONUpiA_uiwLm@wk6YCg9P4f z0oZ+;cz9Wghr9tmVt@q@1B`$JkvG14?~G;g@Q4fXaYv{^B-k913t$g{9U(WSU#*9) zUfT0no?l)Xw7J>L#}|Q}`JQ1QdB)Dj$Xbei<+01-lgXtOv9W@_Nzav>$xZ~%hM4G{ zis#BQC`(%}y+aoHvU;b}7Tey@pb z6`wvIwbkHgqMq_F(}6-0+xfUH^O0wf_L(wW1!u|5ezFSxq}TpeH%m2-CpxrU$fHNc zP5n|A@ggJl)9S^V6YsMYJgX{7Nlyb}Ogg$BoNK-QE=h>ezL@p#=mR&A<%19Bn}QuR zorSi$iA=;NZ)I7l6{$o^&hT4_eNGBgF*P6YC>Kpy8{YI0@!IR#Ose37<(Q(D8FER7ahkTe;&Tv#xov-|Xs)DN4KvG@lbA!kH&f?YAa za;KP&!Wh22z9;G)69E~KD<};ge=Xo4r!LrmU3uw*Z%gjvPh&r2?C$Jdqqp>Z9N1yb z-x{1fK2b4Dj}PrpwRY`2RvpGEdw25N&3d)u?iH%o&r;lH)s;g&%nFdc;$CT{^%x7Rxvw_<YthJ9;U-pid#p}q2i`gX~rSZmjjeLh8Voh}@T zv746l;U|v5k1<=s2&BJMC&}xmyG!o1z1vsLSMksW&)jTly+?5WkHS0ZCI!mln(XL9 z=N=6$@Ah-DDQR44k}l%iyN931&Hz(9n;KZWtkd0I-*sT4AL*p|;sa7q!xn(KVN8&q ziWvZ){vrwh1n%cWGz;Lp>pz`^FCqD0cFt)rtO&ELJW^zkyvJ-IZw zpGYJMX_lwTxQiOoWtFRVyc!gZ4~r`&)^cp+rM4tu8+ktE7!d`cYL{5{io)zz1?p_H zCi?VS0d_(oCN3_fwv^^bi|kWXRRfo1t*mSWz@+@QX{N{Rtii&ka=x=?S3ZFARu7C0 z2o{;QTSU!Dv6!bmDq;7C^Pu_B6ZKMa7eWL9Jw8;^AZe?^6t#T}Rq!&$Ta$*DMkPi8 z?yMKffl>0x5?%s)`_R!WJgG>A9~)U*HGtz|)dTM2PJoYP-~j{)i($&Z^!8t}GEuPr z%e05jU~^RN%R!GkM@g>|zJ3E#<<^hOqWIjx>sVq`MkB%hl%7Naz^8?0VHHsZz%dp{ z5-WkKjRdQFy%uiwOS>!0N%#`x

Jg%9Pc~$r|Dm5Cq4ws;~@gd>{x$5r9_-`g_8J zo{M@5oz*jJd^2ha^53QnO2gMAQ|(l=3~t zQBY2s&DNcPaoTA-t3kz)<-ed67=XGJILt2T%JQ*rj}OX^yB?xsYl*TE>8aEa?I+qO z+Vp;RoX)T9cL~#QYQIud>3h>$>RW2X=gZ3+*K6Ur0j`2Ay~744vtm$wugX^yl11-0 zvTE-brF{i}v1?oD(ro9Pxmjk+i||jQDS{0i8WY~FR&M$uw7_mF%KXD--xyr`=jc(F zA4B6kEk=0q9Trqq@2B~F_ozAfCGh(Vic~JU>&1|RLTK8KFpBK7Q_HC-*Xi(qU7zBm zr#BYn?_<4v|C;No%`0u^|C|BAl~q5PtWzZJzb<@e*2cXGU~lHLk1m%HbEyRN!^H(- z?=F6E@bp|x_&_1D=W>=!pY|6p7r`5o{nv|Jp@e{?jE-pS|F!;L8 zlI{)76H@T+_XQz{;T3s z)$YTrR9!HIui*H5!QOZN`#@Tm*Ht?S$Jurr7neqcFbFQs(IhWY|B9=2Lj?zAa#t(s zto%z{^|9X~vVM%yh>VQnPW2hJlr+1V_)6S$?%i#d?PI~6TWOR#lOEG_(>#};*o|<3H=%8~d&TEB86B z@k0vsqF{=5o@2OFX5LkgYoBCANshJ*?H@Q~nkqmQL)T(;NOxo|Dd*p|>~kh7S@xck zNFF`;D7&#@;%Y>?=uReCMZ{XT*(|m{ouZL{M#u2-jSC4ryWDpeiOS@86m1(!*)lp? zwd?t3dPb}Fe8#T0rAL`c=7vjMR%SO1Q`_DgJ1@TFPWZurA0GFb4B6y#H`I338^;_4 zPtJJHsD?^n=sd>MVoPA(#(MlYM#%*#-j-X5?h0n^3Q-JxOmpDDtL!mCrQn!V?Y(HV znDhVm@?rO19UJ`sTV?>jXX6F{0734JfZF){;{M8gtIYxc-hYeoZ+HGz(UFrwUk$xZ`0mcYOKQNXD0d%Av2uOf20@4qR!`;2LcjcjDYk5V|frjNBV() x1Q;VA{lHir1kjOwARqz82uMFLmIncJq#p=KfH4Bn4~*qO03GQE0uo@1@HgbA>$(5{ literal 0 HcmV?d00001 diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 5011ce9e74..bdb145b39a 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -271,7 +271,7 @@ export async function mainBoot() { main.on('unreadAntenna', () => { updateAccount({ hasUnreadAntenna: true }); - sound.play('antenna'); + sound.playMisskeySfx('antenna'); }); main.on('readAllAnnouncements', () => { diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 3ec9c3c46a..9c4354ef5f 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -345,7 +345,7 @@ function react(viaKeyboard = false): void { pleaseLogin(); showMovedDialog(); if (appearNote.value.reactionAcceptance === 'likeOnly') { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); if (props.mock) { return; @@ -365,7 +365,7 @@ function react(viaKeyboard = false): void { } else { blur(); reactionPicker.show(reactButton.value, reaction => { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); if (props.mock) { emit('reaction', reaction); diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 6f0c0323cc..e941827d74 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -370,7 +370,7 @@ function react(viaKeyboard = false): void { pleaseLogin(); showMovedDialog(); if (appearNote.value.reactionAcceptance === 'likeOnly') { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); misskeyApi('notes/reactions/create', { noteId: appearNote.value.id, @@ -386,7 +386,7 @@ function react(viaKeyboard = false): void { } else { blur(); reactionPicker.show(reactButton.value, reaction => { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); misskeyApi('notes/reactions/create', { noteId: appearNote.value.id, diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue index 04390c6f0c..1aee1aaac3 100644 --- a/packages/frontend/src/components/MkRange.vue +++ b/packages/frontend/src/components/MkRange.vue @@ -43,6 +43,7 @@ const props = withDefaults(defineProps<{ const emit = defineEmits<{ (ev: 'update:modelValue', value: number): void; + (ev: 'dragEnded', value: number): void; }>(); const containerEl = shallowRef(); @@ -143,6 +144,7 @@ const onMousedown = (ev: MouseEvent | TouchEvent) => { // 値が変わってたら通知 if (beforeValue !== finalValue.value) { emit('update:modelValue', finalValue.value); + emit('dragEnded', finalValue.value); } }; diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 2e75f444da..5ca09fa822 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -62,7 +62,7 @@ async function toggleReaction() { if (confirm.canceled) return; if (oldReaction !== props.reaction) { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); } if (mock) { @@ -81,7 +81,7 @@ async function toggleReaction() { } }); } else { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); if (mock) { emit('reactionToggled', props.reaction, (props.count + 1)); diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 63f779dbde..8a5076ea1d 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -81,7 +81,7 @@ function prepend(note) { emit('note'); if (props.sound) { - sound.play($i && (note.userId === $i.id) ? 'noteMy' : 'note'); + sound.playMisskeySfx($i && (note.userId === $i.id) ? 'noteMy' : 'note'); } } diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index a9643d68ca..dd3fe77251 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -91,7 +91,7 @@ function onClick(ev: MouseEvent) { icon: 'ti ti-plus', action: () => { react(`:${props.name}:`); - sound.play('reaction'); + sound.playMisskeySfx('reaction'); }, }] : [])], ev.currentTarget ?? ev.target); } diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index f6b21343b6..cbdb3881c6 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -55,7 +55,7 @@ function onClick(ev: MouseEvent) { icon: 'ti ti-plus', action: () => { react(props.emoji); - sound.play('reaction'); + sound.playMisskeySfx('reaction'); }, }] : [])], ev.currentTarget ?? ev.target); } diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 0ddee55f5f..b8d3d8bf04 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -24,20 +24,31 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.start }} +

+
+
{{ i18n.ts.soundWillBePlayed }}
+ + + +
+
-
-
+
+
BUBBLE GAME
- {{ gameMode }} -
-
-
- NEXT >>> +
+
+ HOLD + +
+
-
- -
+
-
-
- - - - -
{{ comboPrev }} Chain!
-
- +
+ + + + +
{{ comboPrev }} Chain!
+
+
+ - + -
-
- -
SCORE:
-
MAX CHAIN:
-
- Restart - Share -
+
+
+
+ +
SCORE:
+
MAX CHAIN:
+
+ Restart + Share
@@ -109,15 +118,23 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - +
+ + + + + + +
-
-
-
Credit
-
BGM: @ys@misskey.design
+
+
Credit
+
+
Ai-chan illustration: @poteriri@misskey.io
+
BGM: @ys@misskey.design
+
+
@@ -150,10 +167,7 @@ import { $i } from '@/account.js'; import { DropAndFusionGame, Mono } from '@/scripts/drop-and-fusion-engine.js'; import * as sound from '@/scripts/sound.js'; import MkRange from '@/components/MkRange.vue'; - -const containerEl = shallowRef(); -const canvasEl = shallowRef(); -const dropperX = ref(0); +import MkSwitch from '@/components/MkSwitch.vue'; const NORMAL_BASE_SIZE = 30; const NORAML_MONOS: Mono[] = [{ @@ -384,10 +398,16 @@ const SQUARE_MONOS: Mono[] = [{ const GAME_WIDTH = 450; const GAME_HEIGHT = 600; -let viewScaleX = 1; -let viewScaleY = 1; +let viewScale = 1; +let game: DropAndFusionGame; +let containerElRect: DOMRect | null = null; + +const containerEl = shallowRef(); +const canvasEl = shallowRef(); +const dropperX = ref(0); const currentPick = shallowRef<{ id: string; mono: Mono } | null>(null); const stock = shallowRef<{ id: string; mono: Mono }[]>([]); +const holdingStock = shallowRef<{ id: string; mono: Mono } | null>(null); const score = ref(0); const combo = ref(0); const comboPrev = ref(0); @@ -398,20 +418,19 @@ const gameOver = ref(false); const gameStarted = ref(false); const highScore = ref(null); const showConfig = ref(false); -const bgmVolume = ref(0.1); - -let game: DropAndFusionGame; -let containerElRect: DOMRect | null = null; +const mute = ref(false); +const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume); +const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume); function onClick(ev: MouseEvent) { if (!containerElRect) return; - const x = (ev.clientX - containerElRect.left) / viewScaleX; + const x = (ev.clientX - containerElRect.left) / viewScale; game.drop(x); } function onTouchend(ev: TouchEvent) { if (!containerElRect) return; - const x = (ev.changedTouches[0].clientX - containerElRect.left) / viewScaleX; + const x = (ev.changedTouches[0].clientX - containerElRect.left) / viewScale; game.drop(x); } @@ -431,6 +450,10 @@ function moveDropper(rect: DOMRect, x: number) { dropperX.value = Math.min(rect.width * ((GAME_WIDTH - game.PLAYAREA_MARGIN) / GAME_WIDTH), Math.max(rect.width * (game.PLAYAREA_MARGIN / GAME_WIDTH), x)); } +function hold() { + game.hold(); +} + function restart() { game.dispose(); gameOver.value = false; @@ -440,6 +463,7 @@ function restart() { score.value = 0; combo.value = 0; comboPrev.value = 0; + bgmNodes?.soundSource.stop(); gameStarted.value = false; } @@ -463,6 +487,10 @@ function attachGameEvents() { stock.value = JSON.parse(JSON.stringify(value.slice(1))); }); + game.addListener('changeHolding', value => { + holdingStock.value = value; + }); + game.addListener('dropped', () => { dropReady.value = false; window.setTimeout(() => { @@ -476,8 +504,8 @@ function attachGameEvents() { if (!canvasEl.value) return; const rect = canvasEl.value.getBoundingClientRect(); - const domX = rect.left + (x * viewScaleX); - const domY = rect.top + (y * viewScaleY); + const domX = rect.left + (x * viewScale); + const domY = rect.top + (y * viewScale); os.popup(MkRippleEffect, { x: domX, y: domY }, {}, 'end'); os.popup(MkPlusOneEffect, { x: domX, y: domY, value: scoreDelta }, {}, 'end'); }); @@ -511,7 +539,7 @@ function attachGameEvents() { }); } -let bgmNodes: ReturnType = null; +let bgmNodes: ReturnType | null = null; async function start() { try { @@ -527,6 +555,7 @@ async function start() { width: GAME_WIDTH, height: GAME_HEIGHT, canvas: canvasEl.value!, + sfxVolume: mute.value ? 0 : sfxVolume.value, ...( gameMode.value === 'normal' ? { monoDefinitions: NORAML_MONOS, @@ -546,19 +575,50 @@ async function start() { } const bgmBuffer = await sound.loadAudio('/client-assets/drop-and-fusion/bgm_1.mp3'); if (!bgmBuffer) return; - bgmNodes = sound.createSourceNode(bgmBuffer, bgmVolume.value); + bgmNodes = sound.createSourceNode(bgmBuffer, { + volume: mute.value ? 0 : bgmVolume.value, + }); if (!bgmNodes) return; bgmNodes.soundSource.loop = true; bgmNodes.soundSource.start(); }); } -watch(bgmVolume, (value) => { +watch(bgmVolume, (newValue, oldValue) => { if (bgmNodes) { - bgmNodes.gainNode.gain.value = value; + bgmNodes.gainNode.gain.value = mute.value ? 0 : newValue; } }); +watch(sfxVolume, (newValue, oldValue) => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (game) { + game.setSfxVolume(mute.value ? 0 : newValue); + } +}); + +function updateSettings< + K extends keyof typeof defaultStore.state.dropAndFusion, + V extends typeof defaultStore.state.dropAndFusion[K], +>(key: K, value: V) { + const changes: { [P in K]?: V } = {}; + changes[key] = value; + defaultStore.set('dropAndFusion', { + ...defaultStore.state.dropAndFusion, + ...changes, + }); +} + +function loadImage(url: string) { + return new Promise(res => { + const img = new Image(); + img.src = url; + img.addEventListener('load', () => { + res(img); + }); + }); +} + function getGameImageDriveFile() { return new Promise(res => { const dcanvas = document.createElement('canvas'); @@ -566,13 +626,18 @@ function getGameImageDriveFile() { dcanvas.height = GAME_HEIGHT; const ctx = dcanvas.getContext('2d'); if (!ctx || !canvasEl.value) return res(null); - const dimage = new Image(); - dimage.src = '/client-assets/drop-and-fusion/frame-light.svg'; - dimage.addEventListener('load', () => { + Promise.all([ + loadImage('/client-assets/drop-and-fusion/frame-light.svg'), + loadImage('/client-assets/drop-and-fusion/logo.png'), + ]).then((images) => { + const [frame, logo] = images; ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); - ctx.drawImage(dimage, 0, 0, GAME_WIDTH, GAME_HEIGHT); + ctx.drawImage(frame, 0, 0, GAME_WIDTH, GAME_HEIGHT); ctx.drawImage(canvasEl.value!, 0, 0, GAME_WIDTH, GAME_HEIGHT); + ctx.globalAlpha = 0.7; + ctx.drawImage(logo, GAME_WIDTH * 0.55, 6, GAME_WIDTH * 0.45, GAME_WIDTH * 0.45 * (logo.height / logo.width)); + ctx.globalAlpha = 1; dcanvas.toBlob(blob => { if (!blob) return res(null); @@ -610,22 +675,22 @@ async function share() { os.post({ initialText: `#BubbleGame MODE: ${gameMode.value} -SCORE: ${score.value} (MAX CHAIN: ${maxCombo.value})})`, +SCORE: ${score.value} (MAX CHAIN: ${maxCombo.value})`, initialFiles: [file], + instant: true, }); } useInterval(() => { if (!canvasEl.value) return; const actualCanvasWidth = canvasEl.value.getBoundingClientRect().width; - const actualCanvasHeight = canvasEl.value.getBoundingClientRect().height; - viewScaleX = actualCanvasWidth / GAME_WIDTH; - viewScaleY = actualCanvasHeight / GAME_HEIGHT; + if (actualCanvasWidth === 0) return; + viewScale = actualCanvasWidth / GAME_WIDTH; containerElRect = containerEl.value?.getBoundingClientRect() ?? null; }, 1000, { immediate: false, afterMounted: true }); onDeactivated(() => { - game.dispose(); + restart(); }); definePageMetadata({ @@ -697,16 +762,52 @@ definePageMetadata({ box-shadow: 0 6px 16px #0007, 0 0 1px 1px #693410, inset 0 0 2px 1px #ce8a5c; border-radius: 10px; } + +.frameH { + display: flex; + gap: 6px; +} + .frameInner { - padding: 4px 8px; + padding: 8px; + margin-top: 8px; background: #F1E8DC; box-shadow: 0 0 2px 1px #ce8a5c, inset 0 0 1px 1px #693410; border-radius: 6px; color: #693410; + + &:first-child { + margin-top: 0; + } } -.main { +.frameDivider { + height: 0; + border: none; + border-top: 1px solid #693410; + border-bottom: 1px solid #ce8a5c; +} + +.header { position: relative; + z-index: 10; + display: grid; + grid-template-columns: 1fr; + grid-template-rows: auto auto; + gap: 8px; + + > .headerTitle { + text-align: center; + } + + @media (min-width: 500px) { + grid-template-columns: 1fr auto; + grid-template-rows: auto; + + > .headerTitle { + text-align: start; + } + } } .mainFrameImg { @@ -724,15 +825,15 @@ definePageMetadata({ position: relative; display: block; z-index: 1; - margin-top: -50px; width: 100% !important; height: auto !important; pointer-events: none; user-select: none; } -.container { +.gameContainer { position: relative; + margin-top: -20px; } .stock { @@ -755,45 +856,51 @@ definePageMetadata({ user-select: none; } -.currentMono { +.dropperContainer { position: absolute; - margin-top: 80px; + top: 0; + height: 100%; z-index: 2; - filter: drop-shadow(0 6px 16px #0007); pointer-events: none; user-select: none; + will-change: left; +} + +.currentMono { + position: absolute; + display: block; + bottom: 88%; + z-index: 2; + filter: drop-shadow(0 6px 16px #0007); } .dropper { - position: absolute; + position: relative; top: 0; width: 70px; margin-top: -10px; margin-left: -30px; z-index: 2; filter: drop-shadow(0 6px 16px #0007); - pointer-events: none; - user-select: none; } .currentMonoArrow { position: absolute; - margin-top: 100px; + width: 20px; + bottom: 80%; + left: -10px; z-index: 3; animation: currentMonoArrow 2s ease infinite; - pointer-events: none; - user-select: none; } .dropGuide { position: absolute; - top: 120px; z-index: 3; + bottom: 0; width: 3px; - height: calc(100% - 120px); + margin-left: -2px; + height: 85%; background: #f002; - pointer-events: none; - user-select: none; } .gameOverLabel { diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue index 57bafce0ac..798980b3d1 100644 --- a/packages/frontend/src/pages/settings/sounds.sound.vue +++ b/packages/frontend/src/pages/settings/sounds.sound.vue @@ -33,7 +33,7 @@ import MkRange from '@/components/MkRange.vue'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { playFile, soundsTypes, getSoundDuration } from '@/scripts/sound.js'; +import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/scripts/sound.js'; import { selectFile } from '@/scripts/select-file.js'; const props = defineProps<{ @@ -119,7 +119,7 @@ function listen() { return; } - playFile(type.value === '_driveFile_' ? { + playMisskeySfxFile(type.value === '_driveFile_' ? { type: '_driveFile_', fileId: fileId.value as string, fileUrl: fileUrl.value as string, diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index b6e735ddf2..03c52e00fe 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -20,17 +20,17 @@ export type Mono = { spriteScale: number; }; -const PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる - export class DropAndFusionGame extends EventEmitter<{ changeScore: (newScore: number) => void; changeCombo: (newCombo: number) => void; changeStock: (newStock: { id: string; mono: Mono }[]) => void; + changeHolding: (newHolding: { id: string; mono: Mono } | null) => void; dropped: () => void; fusioned: (x: number, y: number, scoreDelta: number) => void; monoAdded: (mono: Mono) => void; gameOver: () => void; }> { + private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる private COMBO_INTERVAL = 1000; public readonly DROP_INTERVAL = 500; public readonly PLAYAREA_MARGIN = 25; @@ -48,6 +48,8 @@ export class DropAndFusionGame extends EventEmitter<{ private monoTextures: Record = {}; private monoTextureUrls: Record = {}; + private sfxVolume = 1; + /** * フィールドに出ていて、かつ合体の対象となるアイテム */ @@ -58,6 +60,7 @@ export class DropAndFusionGame extends EventEmitter<{ private latestDroppedAt = 0; private latestFusionedAt = 0; private stock: { id: string; mono: Mono }[] = []; + private holding: { id: string; mono: Mono } | null = null; private _combo = 0; private get combo() { @@ -84,6 +87,7 @@ export class DropAndFusionGame extends EventEmitter<{ width: number; height: number; monoDefinitions: Mono[]; + sfxVolume?: number; }) { super(); @@ -91,10 +95,14 @@ export class DropAndFusionGame extends EventEmitter<{ this.gameHeight = opts.height; this.monoDefinitions = opts.monoDefinitions; + if (opts.sfxVolume) { + this.sfxVolume = opts.sfxVolume; + } + this.engine = Matter.Engine.create({ - constraintIterations: 2 * PHYSICS_QUALITY_FACTOR, - positionIterations: 6 * PHYSICS_QUALITY_FACTOR, - velocityIterations: 4 * PHYSICS_QUALITY_FACTOR, + constraintIterations: 2 * this.PHYSICS_QUALITY_FACTOR, + positionIterations: 6 * this.PHYSICS_QUALITY_FACTOR, + velocityIterations: 4 * this.PHYSICS_QUALITY_FACTOR, gravity: { x: 0, y: 1, @@ -183,6 +191,7 @@ export class DropAndFusionGame extends EventEmitter<{ }; if (mono.shape === 'circle') { return Matter.Bodies.circle(x, y, mono.size / 2, options); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (mono.shape === 'rectangle') { return Matter.Bodies.rectangle(x, y, mono.size, mono.size, options); } else { @@ -224,7 +233,11 @@ export class DropAndFusionGame extends EventEmitter<{ // TODO: 効果音再生はコンポーネント側の責務なので移動する const pan = ((newX / this.gameWidth) - 0.5) * 2; - sound.playUrl('/client-assets/drop-and-fusion/bubble2.mp3', 1, pan, nextMono.sfxPitch); + sound.playUrl('/client-assets/drop-and-fusion/bubble2.mp3', { + volume: this.sfxVolume, + pan, + playbackRate: nextMono.sfxPitch, + }); this.emit('monoAdded', nextMono); this.emit('fusioned', newX, newY, additionalScore); @@ -237,7 +250,7 @@ export class DropAndFusionGame extends EventEmitter<{ //} //sound.playUrl({ // type: 'syuilo/bubble2', - // volume: 1, + // volume: this.sfxVolume, //}); } } @@ -323,10 +336,14 @@ export class DropAndFusionGame extends EventEmitter<{ const energy = pairs.collision.depth; if (energy > minCollisionEnergyForSound) { // TODO: 効果音再生はコンポーネント側の責務なので移動する - const vol = (Math.min(maxCollisionEnergyForSound, energy - minCollisionEnergyForSound) / maxCollisionEnergyForSound) / 4; + const vol = ((Math.min(maxCollisionEnergyForSound, energy - minCollisionEnergyForSound) / maxCollisionEnergyForSound) / 4) * this.sfxVolume; const pan = ((((bodyA.position.x + bodyB.position.x) / 2) / this.gameWidth) - 0.5) * 2; const pitch = soundPitchMin + ((soundPitchMax - soundPitchMin) * (1 - (Math.min(10, energy) / 10))); - sound.playUrl('/client-assets/drop-and-fusion/poi1.mp3', vol, pan, pitch); + sound.playUrl('/client-assets/drop-and-fusion/poi1.mp3', { + volume: vol, + pan, + playbackRate: pitch, + }); } } } @@ -344,6 +361,10 @@ export class DropAndFusionGame extends EventEmitter<{ this.loaded = true; } + public setSfxVolume(volume: number) { + this.sfxVolume = volume; + } + public getTextureImageUrl(mono: Mono) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (this.monoTextureUrls[mono.img]) { @@ -369,25 +390,53 @@ export class DropAndFusionGame extends EventEmitter<{ if (Date.now() - this.latestDroppedAt < this.DROP_INTERVAL) { return; } - const st = this.stock.shift()!; + const head = this.stock.shift()!; this.stock.push({ id: Math.random().toString(), mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], }); this.emit('changeStock', this.stock); - const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (st.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (st.mono.size / 2), _x)); - const body = this.createBody(st.mono, x, 50 + st.mono.size / 2); + const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), _x)); + const body = this.createBody(head.mono, x, 50 + head.mono.size / 2); Matter.Composite.add(this.engine.world, body); this.activeBodyIds.push(body.id); this.latestDroppedBodyId = body.id; this.latestDroppedAt = Date.now(); this.emit('dropped'); - this.emit('monoAdded', st.mono); + this.emit('monoAdded', head.mono); // TODO: 効果音再生はコンポーネント側の責務なので移動する const pan = ((x / this.gameWidth) - 0.5) * 2; - sound.playUrl('/client-assets/drop-and-fusion/poi2.mp3', 1, pan); + sound.playUrl('/client-assets/drop-and-fusion/poi2.mp3', { + volume: this.sfxVolume, + pan, + }); + } + + public hold() { + if (this.isGameOver) return; + + if (this.holding) { + const head = this.stock.shift()!; + this.stock.unshift(this.holding); + this.holding = head; + this.emit('changeHolding', this.holding); + this.emit('changeStock', this.stock); + } else { + const head = this.stock.shift()!; + this.holding = head; + this.stock.push({ + id: Math.random().toString(), + mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], + }); + this.emit('changeHolding', this.holding); + this.emit('changeStock', this.stock); + } + + sound.playUrl('/client-assets/drop-and-fusion/hold.mp3', { + volume: this.sfxVolume, + }); } public dispose() { diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts index 690c342c85..142ddf87c9 100644 --- a/packages/frontend/src/scripts/sound.ts +++ b/packages/frontend/src/scripts/sound.ts @@ -126,13 +126,13 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; }) * 既定のスプライトを再生する * @param type スプライトの種類を指定 */ -export function play(operationType: OperationType) { +export function playMisskeySfx(operationType: OperationType) { const sound = defaultStore.state[`sound_${operationType}`]; if (_DEV_) console.log('play', operationType, sound); if (sound.type == null || !canPlay) return; canPlay = false; - playFile(sound).finally(() => { + playMisskeySfxFile(sound).finally(() => { // ごく短時間に音が重複しないように setTimeout(() => { canPlay = true; @@ -144,41 +144,53 @@ export function play(operationType: OperationType) { * サウンド設定形式で指定された音声を再生する * @param soundStore サウンド設定 */ -export async function playFile(soundStore: SoundStore) { +export async function playMisskeySfxFile(soundStore: SoundStore) { if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) { return; } + const masterVolume = defaultStore.state.sound_masterVolume; + if (isMute() || masterVolume === 0 || soundStore.volume === 0) { + return; + } const url = soundStore.type === '_driveFile_' ? soundStore.fileUrl : `/client-assets/sounds/${soundStore.type}.mp3`; const buffer = await loadAudio(url); if (!buffer) return; - createSourceNode(buffer, soundStore.volume)?.soundSource.start(); + const volume = soundStore.volume * masterVolume; + createSourceNode(buffer, { volume }).soundSource.start(); } -export async function playUrl(url: string, volume = 1, pan = 0, playbackRate = 1) { +export async function playUrl(url: string, opts: { + volume?: number; + pan?: number; + playbackRate?: number; +}) { + if (opts.volume === 0) { + return; + } const buffer = await loadAudio(url); if (!buffer) return; - createSourceNode(buffer, volume, pan, playbackRate)?.soundSource.start(); + createSourceNode(buffer, opts).soundSource.start(); } -export function createSourceNode(buffer: AudioBuffer, volume: number, pan = 0, playbackRate = 1): { +export function createSourceNode(buffer: AudioBuffer, opts: { + volume?: number; + pan?: number; + playbackRate?: number; +}): { soundSource: AudioBufferSourceNode; panNode: StereoPannerNode; gainNode: GainNode; -} | null { - const masterVolume = defaultStore.state.sound_masterVolume; - if (isMute() || masterVolume === 0 || volume === 0) { - return null; - } - +} { const panNode = ctx.createStereoPanner(); - panNode.pan.value = pan; + panNode.pan.value = opts.pan ?? 0; const gainNode = ctx.createGain(); - gainNode.gain.value = masterVolume * volume; + + gainNode.gain.value = opts.volume ?? 1; const soundSource = ctx.createBufferSource(); soundSource.buffer = buffer; - soundSource.playbackRate.value = playbackRate; + soundSource.playbackRate.value = opts.playbackRate ?? 1; soundSource .connect(panNode) .connect(gainNode) diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 46634af96b..e3a85377d8 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -420,6 +420,13 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: false, }, + dropAndFusion: { + where: 'device', + default: { + bgmVolume: 0.25, + sfxVolume: 1, + }, + }, sound_masterVolume: { where: 'device', diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index 78af49cdc2..0ec036c5cb 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -83,7 +83,7 @@ function onNotification(notification: Misskey.entities.Notification, isClient = }, 6000); } - sound.play('notification'); + sound.playMisskeySfx('notification'); } if ($i) { diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue index 89ad3bf323..877406fe95 100644 --- a/packages/frontend/src/widgets/WidgetJobQueue.vue +++ b/packages/frontend/src/widgets/WidgetJobQueue.vue @@ -123,7 +123,7 @@ const onStats = (stats) => { current[domain].delayed = stats[domain].delayed; if (current[domain].waiting > 0 && widgetProps.sound && jammedAudioBuffer.value && !jammedSoundNodePlaying.value) { - const soundNode = sound.createSourceNode(jammedAudioBuffer.value, 1)?.soundSource; + const soundNode = sound.createSourceNode(jammedAudioBuffer.value, {}).soundSource; if (soundNode) { jammedSoundNodePlaying.value = true; soundNode.onended = () => jammedSoundNodePlaying.value = false; From 14aedc17ae4e3ca3db9e523f2663824e874e0569 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 Jan 2024 16:06:22 +0900 Subject: [PATCH 008/134] update sound --- .../frontend/assets/drop-and-fusion/click.mp3 | Bin 0 -> 26496 bytes .../frontend/assets/drop-and-fusion/hold.mp3 | Bin 26496 -> 21941 bytes .../src/scripts/drop-and-fusion-engine.ts | 7 +++---- 3 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 packages/frontend/assets/drop-and-fusion/click.mp3 diff --git a/packages/frontend/assets/drop-and-fusion/click.mp3 b/packages/frontend/assets/drop-and-fusion/click.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ef03e60f61f68af7b8f182db7e3148ea8ee8861f GIT binary patch literal 26496 zcmeH~c|2760>IA6*=bzLHZfy~N{tB_#$?|kVak>zq?NIzxeB*IsBDiYwU{qa8U{q;WYocB3DpEIBFJLhwJ&+q#^XN;+l77xH3 z*wcm<3-baX<()vFv1 zCo?lAr?9ZFq@=8@qN3v2vxbJ2mX^-WzP^EhfuW(Xv8k!4`T6B#5aj-?J?2z~v6edb zxrM6QKqQ_A0PR{u%zQfl)NXK?yJS>v>_Eo9+J*Rh zPiOHbs{4az7V7DE&W(D-cB5|d*Es zOA%`;k{#4$r;xp!as9!p09606jF3sm__Zr;9qap5%&a-BQW7~{cNfl8X9o>Wi>@bP z&t18-R6x+TdN7qFXU&ONUpiA_uiwLm@wk6YCg9P4f z0oZ+;cz9Wghr9tmVt@q@1B`$JkvG14?~G;g@Q4fXaYv{^B-k913t$g{9U(WSU#*9) zUfT0no?l)Xw7J>L#}|Q}`JQ1QdB)Dj$Xbei<+01-lgXtOv9W@_Nzav>$xZ~%hM4G{ zis#BQC`(%}y+aoHvU;b}7Tey@pb z6`wvIwbkHgqMq_F(}6-0+xfUH^O0wf_L(wW1!u|5ezFSxq}TpeH%m2-CpxrU$fHNc zP5n|A@ggJl)9S^V6YsMYJgX{7Nlyb}Ogg$BoNK-QE=h>ezL@p#=mR&A<%19Bn}QuR zorSi$iA=;NZ)I7l6{$o^&hT4_eNGBgF*P6YC>Kpy8{YI0@!IR#Ose37<(Q(D8FER7ahkTe;&Tv#xov-|Xs)DN4KvG@lbA!kH&f?YAa za;KP&!Wh22z9;G)69E~KD<};ge=Xo4r!LrmU3uw*Z%gjvPh&r2?C$Jdqqp>Z9N1yb z-x{1fK2b4Dj}PrpwRY`2RvpGEdw25N&3d)u?iH%o&r;lH)s;g&%nFdc;$CT{^%x7Rxvw_<YthJ9;U-pid#p}q2i`gX~rSZmjjeLh8Voh}@T zv746l;U|v5k1<=s2&BJMC&}xmyG!o1z1vsLSMksW&)jTly+?5WkHS0ZCI!mln(XL9 z=N=6$@Ah-DDQR44k}l%iyN931&Hz(9n;KZWtkd0I-*sT4AL*p|;sa7q!xn(KVN8&q ziWvZ){vrwh1n%cWGz;Lp>pz`^FCqD0cFt)rtO&ELJW^zkyvJ-IZw zpGYJMX_lwTxQiOoWtFRVyc!gZ4~r`&)^cp+rM4tu8+ktE7!d`cYL{5{io)zz1?p_H zCi?VS0d_(oCN3_fwv^^bi|kWXRRfo1t*mSWz@+@QX{N{Rtii&ka=x=?S3ZFARu7C0 z2o{;QTSU!Dv6!bmDq;7C^Pu_B6ZKMa7eWL9Jw8;^AZe?^6t#T}Rq!&$Ta$*DMkPi8 z?yMKffl>0x5?%s)`_R!WJgG>A9~)U*HGtz|)dTM2PJoYP-~j{)i($&Z^!8t}GEuPr z%e05jU~^RN%R!GkM@g>|zJ3E#<<^hOqWIjx>sVq`MkB%hl%7Naz^8?0VHHsZz%dp{ z5-WkKjRdQFy%uiwOS>!0N%#`x

Jg%9Pc~$r|Dm5Cq4ws;~@gd>{x$5r9_-`g_8J zo{M@5oz*jJd^2ha^53QnO2gMAQ|(l=3~t zQBY2s&DNcPaoTA-t3kz)<-ed67=XGJILt2T%JQ*rj}OX^yB?xsYl*TE>8aEa?I+qO z+Vp;RoX)T9cL~#QYQIud>3h>$>RW2X=gZ3+*K6Ur0j`2Ay~744vtm$wugX^yl11-0 zvTE-brF{i}v1?oD(ro9Pxmjk+i||jQDS{0i8WY~FR&M$uw7_mF%KXD--xyr`=jc(F zA4B6kEk=0q9Trqq@2B~F_ozAfCGh(Vic~JU>&1|RLTK8KFpBK7Q_HC-*Xi(qU7zBm zr#BYn?_<4v|C;No%`0u^|C|BAl~q5PtWzZJzb<@e*2cXGU~lHLk1m%HbEyRN!^H(- z?=F6E@bp|x_&_1D=W>=!pY|6p7r`5o{nv|Jp@e{?jE-pS|F!;L8 zlI{)76H@T+_XQz{;T3s z)$YTrR9!HIui*H5!QOZN`#@Tm*Ht?S$Jurr7neqcFbFQs(IhWY|B9=2Lj?zAa#t(s zto%z{^|9X~vVM%yh>VQnPW2hJlr+1V_)6S$?%i#d?PI~6TWOR#lOEG_(>#};*o|<3H=%8~d&TEB86B z@k0vsqF{=5o@2OFX5LkgYoBCANshJ*?H@Q~nkqmQL)T(;NOxo|Dd*p|>~kh7S@xck zNFF`;D7&#@;%Y>?=uReCMZ{XT*(|m{ouZL{M#u2-jSC4ryWDpeiOS@86m1(!*)lp? zwd?t3dPb}Fe8#T0rAL`c=7vjMR%SO1Q`_DgJ1@TFPWZurA0GFb4B6y#H`I338^;_4 zPtJJHsD?^n=sd>MVoPA(#(MlYM#%*#-j-X5?h0n^3Q-JxOmpDDtL!mCrQn!V?Y(HV znDhVm@?rO19UJ`sTV?>jXX6F{0734JfZF){;{M8gtIYxc-hYeoZ+HGz(UFrwUk$xZ`0mcYOKQNXD0d%Av2uOf20@4qR!`;2LcjcjDYk5V|frjNBV() x1Q;VA{lHir1kjOwARqz82uMFLmIncJq#p=KfH4Bn4~*qO03GQE0uo@1@HgbA>$(5{ literal 0 HcmV?d00001 diff --git a/packages/frontend/assets/drop-and-fusion/hold.mp3 b/packages/frontend/assets/drop-and-fusion/hold.mp3 index ef03e60f61f68af7b8f182db7e3148ea8ee8861f..f064c976d3b91b18fafe5efc68ca633a9cb334f1 100644 GIT binary patch literal 21941 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

Jg%9Pc~$r|Dm5Cq4ws;~@gd>{x$5r9_-`g_8J zo{M@5oz*jJd^2ha^53QnO2gMAQ|(l=3~t zQBY2s&DNcPaoTA-t3kz)<-ed67=XGJILt2T%JQ*rj}OX^yB?xsYl*TE>8aEa?I+qO z+Vp;RoX)T9cL~#QYQIud>3h>$>RW2X=gZ3+*K6Ur0j`2Ay~744vtm$wugX^yl11-0 zvTE-brF{i}v1?oD(ro9Pxmjk+i||jQDS{0i8WY~FR&M$uw7_mF%KXD--xyr`=jc(F zA4B6kEk=0q9Trqq@2B~F_ozAfCGh(Vic~JU>&1|RLTK8KFpBK7Q_HC-*Xi(qU7zBm zr#BYn?_<4v|C;No%`0u^|C|BAl~q5PtWzZJzb<@e*2cXGU~lHLk1m%HbEyRN!^H(- z?=F6E@bp|x_&_1D=W>=!pY|6p7r`5o{nv|Jp@e{?jE-pS|F!;L8 zlI{)76H@T+_XQz{;T3s z)$YTrR9!HIui*H5!QOZN`#@Tm*Ht?S$Jurr7neqcFbFQs(IhWY|B9=2Lj?zAa#t(s zto%z{^|9X~vVM%yh>VQnPW2hJlr+1V_)6S$?%i#d?PI~6TWOR#lOEG_(>#};*o|<3H=%8~d&TEB86B z@k0vsqF{=5o@2OFX5LkgYoBCANshJ*?H@Q~nkqmQL)T(;NOxo|Dd*p|>~kh7S@xck zNFF`;D7&#@;%Y>?=uReCMZ{XT*(|m{ouZL{M#u2-jSC4ryWDpeiOS@86m1(!*)lp? zwd?t3dPb}Fe8#T0rAL`c=7vjMR%SO1Q`_DgJ1@TFPWZurA0GFb4B6y#H`I338^;_4 zPtJJHsD?^n=sd>MVoPA(#(MlYM#%*#-j-X5?h0n^3Q-JxOmpDDtL!mCrQn!V?Y(HV znDhVm@?rO19UJ`sTV?>jXX6F{0734JfZF){;{M8gtIYxc-hYeoZ+HGz(UFrwUk$xZ`0mcYOKQNXD0d%Av2uOf20@4qR!`;2LcjcjDYk5V|frjNBV() x1Q;VA{lHir1kjOwARqz82uMFLmIncJq#p=KfH4Bn4~*qO03GQE0uo@1@HgbA>$(5{ diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index 03c52e00fe..f71f3a668e 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -387,9 +387,8 @@ export class DropAndFusionGame extends EventEmitter<{ public drop(_x: number) { if (this.isGameOver) return; - if (Date.now() - this.latestDroppedAt < this.DROP_INTERVAL) { - return; - } + if (Date.now() - this.latestDroppedAt < this.DROP_INTERVAL) return; + const head = this.stock.shift()!; this.stock.push({ id: Math.random().toString(), @@ -435,7 +434,7 @@ export class DropAndFusionGame extends EventEmitter<{ } sound.playUrl('/client-assets/drop-and-fusion/hold.mp3', { - volume: this.sfxVolume, + volume: 0.5 * this.sfxVolume, }); } From 1063d39de805a83169fc9ba1f841c1239be45da8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 Jan 2024 21:15:56 +0900 Subject: [PATCH 009/134] enhnace(frontend): tweak game --- locales/index.d.ts | 3 + locales/ja-JP.yml | 3 + .../assets/drop-and-fusion/gameover.mp3 | Bin 0 -> 31346 bytes packages/frontend/package.json | 1 + .../frontend/src/pages/drop-and-fusion.vue | 131 ++++++++++++-- .../src/scripts/drop-and-fusion-engine.ts | 163 +++++++++++++++--- pnpm-lock.yaml | 5 +- 7 files changed, 267 insertions(+), 39 deletions(-) create mode 100644 packages/frontend/assets/drop-and-fusion/gameover.mp3 diff --git a/locales/index.d.ts b/locales/index.d.ts index 96bc9099dd..df84412473 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1195,6 +1195,9 @@ export interface Locale { "bubbleGame": string; "sfx": string; "soundWillBePlayed": string; + "showReplay": string; + "replay": string; + "replaying": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index c28fde56cb..997ddf9c6e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1192,6 +1192,9 @@ enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する" bubbleGame: "バブルゲーム" sfx: "効果音" soundWillBePlayed: "サウンドが再生されます" +showReplay: "リプレイを見る" +replay: "リプレイ" +replaying: "リプレイ中" _announcement: forExistingUsers: "既存ユーザーのみ" diff --git a/packages/frontend/assets/drop-and-fusion/gameover.mp3 b/packages/frontend/assets/drop-and-fusion/gameover.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..23b41c56995a8adc0033d46b946f967876073d1b GIT binary patch literal 31346 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

|o%EnUC=>z1GR2{(NK@)YZP5tpIK9|o2oRV& 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 s`_whL&MocP@0V7;w|TAY

-
+
@@ -74,7 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only >
{{ comboPrev }} Chain!
-
+
-
+
SCORE:
MAX CHAIN:
-
- Restart - Share -
+
+
+
{{ i18n.ts.replaying }}
+
+
+
+
+ END REPLAY +
+
+
+
+
+
+ {{ i18n.ts.done }} + {{ i18n.ts.showReplay }} + {{ i18n.ts.share }} + Copy replay data
@@ -139,7 +153,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- Restart + Retry
@@ -168,6 +182,7 @@ import { DropAndFusionGame, Mono } from '@/scripts/drop-and-fusion-engine.js'; import * as sound from '@/scripts/sound.js'; import MkRange from '@/components/MkRange.vue'; import MkSwitch from '@/components/MkSwitch.vue'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; const NORMAL_BASE_SIZE = 30; const NORAML_MONOS: Mono[] = [{ @@ -401,6 +416,8 @@ const GAME_HEIGHT = 600; let viewScale = 1; let game: DropAndFusionGame; let containerElRect: DOMRect | null = null; +let seed: string; +let logs: ReturnType | null = null; const containerEl = shallowRef(); const canvasEl = shallowRef(); @@ -414,22 +431,25 @@ const comboPrev = ref(0); const maxCombo = ref(0); const dropReady = ref(true); const gameMode = ref<'normal' | 'square'>('normal'); -const gameOver = ref(false); +const isGameOver = ref(false); const gameStarted = ref(false); const highScore = ref(null); const showConfig = ref(false); +const replaying = ref(false); const mute = ref(false); const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume); const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume); function onClick(ev: MouseEvent) { if (!containerElRect) return; + if (replaying.value) return; const x = (ev.clientX - containerElRect.left) / viewScale; game.drop(x); } function onTouchend(ev: TouchEvent) { if (!containerElRect) return; + if (replaying.value) return; const x = (ev.changedTouches[0].clientX - containerElRect.left) / viewScale; game.drop(x); } @@ -454,9 +474,18 @@ function hold() { game.hold(); } -function restart() { +async function surrender() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.areYouSure, + }); + if (canceled) return; + game.surrender(); +} + +function end() { game.dispose(); - gameOver.value = false; + isGameOver.value = false; currentPick.value = null; dropReady.value = true; stock.value = []; @@ -467,6 +496,45 @@ function restart() { gameStarted.value = false; } +function replay() { + replaying.value = true; + game.dispose(); + game = new DropAndFusionGame({ + width: GAME_WIDTH, + height: GAME_HEIGHT, + canvas: canvasEl.value!, + seed: seed, + sfxVolume: mute.value ? 0 : sfxVolume.value, + ...( + gameMode.value === 'normal' ? { + monoDefinitions: NORAML_MONOS, + } : { + monoDefinitions: SQUARE_MONOS, + } + ), + }); + attachGameEvents(); + os.promiseDialog(game.load(), async () => { + game.start(logs!); + }); +} + +function endReplay() { + replaying.value = false; + game.dispose(); +} + +function exportLog() { + if (!logs) return; + const data = JSON.stringify({ + seed: seed, + date: new Date().toISOString(), + logs: logs, + }); + copyToClipboard(data); + os.success(); +} + function attachGameEvents() { game.addListener('changeScore', value => { score.value = value; @@ -492,9 +560,11 @@ function attachGameEvents() { }); game.addListener('dropped', () => { + if (replaying.value) return; + dropReady.value = false; window.setTimeout(() => { - if (!gameOver.value) { + if (!isGameOver.value) { dropReady.value = true; } }, game.DROP_INTERVAL); @@ -511,6 +581,8 @@ function attachGameEvents() { }); game.addListener('monoAdded', (mono) => { + if (replaying.value) return; + // 実績関連 if (mono.level === 10) { claimAchievement('bubbleGameExplodingHead'); @@ -523,9 +595,15 @@ function attachGameEvents() { }); game.addListener('gameOver', () => { + if (replaying.value) { + endReplay(); + return; + } + + logs = game.getLogs(); currentPick.value = null; dropReady.value = false; - gameOver.value = true; + isGameOver.value = true; if (score.value > (highScore.value ?? 0)) { highScore.value = score.value; @@ -551,10 +629,13 @@ async function start() { highScore.value = null; } + seed = Date.now().toString(); + game = new DropAndFusionGame({ width: GAME_WIDTH, height: GAME_HEIGHT, canvas: canvasEl.value!, + seed: seed, sfxVolume: mute.value ? 0 : sfxVolume.value, ...( gameMode.value === 'normal' ? { @@ -690,7 +771,7 @@ useInterval(() => { }, 1000, { immediate: false, afterMounted: true }); onDeactivated(() => { - restart(); + end(); }); definePageMetadata({ @@ -922,6 +1003,28 @@ definePageMetadata({ } } +.replayIndicator { + position: absolute; + z-index: 10; + left: 10px; + bottom: 10px; + padding: 6px 8px; + color: #f00; + background: #0008; + border-radius: 6px; + pointer-events: none; +} + +.replayIndicatorText { + animation: replayIndicator-blink 2s infinite; +} + +@keyframes replayIndicator-blink { + 0% { opacity: 1; } + 50% { opacity: 0; } + 100% { opacity: 1; } +} + @keyframes currentMonoArrow { 0% { transform: translateY(0); } 25% { transform: translateY(-8px); } diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index f71f3a668e..9db93d1534 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -5,6 +5,7 @@ import { EventEmitter } from 'eventemitter3'; import * as Matter from 'matter-js'; +import seedrandom from 'seedrandom'; import * as sound from '@/scripts/sound.js'; export type Mono = { @@ -20,6 +21,18 @@ export type Mono = { spriteScale: number; }; +type Log = { + frame: number; + operation: 'drop'; + x: number; +} | { + frame: number; + operation: 'hold'; +} | { + frame: number; + operation: 'surrender'; +}; + export class DropAndFusionGame extends EventEmitter<{ changeScore: (newScore: number) => void; changeCombo: (newCombo: number) => void; @@ -35,18 +48,23 @@ export class DropAndFusionGame extends EventEmitter<{ public readonly DROP_INTERVAL = 500; public readonly PLAYAREA_MARGIN = 25; private STOCK_MAX = 4; + private TICK_DELTA = 1000 / 60; // 60fps private loaded = false; + private frame = 0; private engine: Matter.Engine; private render: Matter.Render; - private runner: Matter.Runner; + private tickRaf: ReturnType | null = null; + private tickCallbackQueue: { frame: number; callback: () => void; }[] = []; private overflowCollider: Matter.Body; private isGameOver = false; - private gameWidth: number; private gameHeight: number; private monoDefinitions: Mono[] = []; private monoTextures: Record = {}; private monoTextureUrls: Record = {}; + private rng: () => number; + private logs: Log[] = []; + private replaying = false; private sfxVolume = 1; @@ -87,13 +105,17 @@ export class DropAndFusionGame extends EventEmitter<{ width: number; height: number; monoDefinitions: Mono[]; + seed: string; sfxVolume?: number; }) { super(); + this.tick = this.tick.bind(this); + this.gameWidth = opts.width; this.gameHeight = opts.height; this.monoDefinitions = opts.monoDefinitions; + this.rng = seedrandom(opts.seed); if (opts.sfxVolume) { this.sfxVolume = opts.sfxVolume; @@ -129,9 +151,6 @@ export class DropAndFusionGame extends EventEmitter<{ Matter.Render.run(this.render); - this.runner = Matter.Runner.create(); - Matter.Runner.run(this.runner, this.engine); - this.engine.world.bodies = []; //#region walls @@ -223,9 +242,12 @@ export class DropAndFusionGame extends EventEmitter<{ Matter.Composite.add(this.engine.world, body); // 連鎖してfusionした場合の分かりやすさのため少し間を置いてからfusion対象になるようにする - window.setTimeout(() => { - this.activeBodyIds.push(body.id); - }, 100); + this.tickCallbackQueue.push({ + frame: this.frame + 6, + callback: () => { + this.activeBodyIds.push(body.id); + }, + }); const comboBonus = 1 + ((this.combo - 1) / 5); const additionalScore = Math.round(currentMono.score * comboBonus); @@ -244,7 +266,7 @@ export class DropAndFusionGame extends EventEmitter<{ } else { //const VELOCITY = 30; //for (let i = 0; i < 10; i++) { - // const body = createBody(FRUITS.find(x => x.level === (1 + Math.floor(Math.random() * 3)))!, x + ((Math.random() * VELOCITY) - (VELOCITY / 2)), y + ((Math.random() * VELOCITY) - (VELOCITY / 2))); + // const body = createBody(FRUITS.find(x => x.level === (1 + Math.floor(this.rng() * 3)))!, x + ((this.rng() * VELOCITY) - (VELOCITY / 2)), y + ((this.rng() * VELOCITY) - (VELOCITY / 2))); // Matter.Composite.add(world, body); // bodies.push(body); //} @@ -255,10 +277,25 @@ export class DropAndFusionGame extends EventEmitter<{ } } + public surrender() { + this.logs.push({ + frame: this.frame, + operation: 'surrender', + }); + + this.gameOver(); + } + private gameOver() { this.isGameOver = true; - Matter.Runner.stop(this.runner); + if (this.tickRaf) window.cancelAnimationFrame(this.tickRaf); + this.tickRaf = null; this.emit('gameOver'); + + // TODO: 効果音再生はコンポーネント側の責務なので移動する + sound.playUrl('/client-assets/drop-and-fusion/gameover.mp3', { + volume: this.sfxVolume, + }); } /** テクスチャをすべてキャッシュする */ @@ -292,13 +329,14 @@ export class DropAndFusionGame extends EventEmitter<{ return Promise.all(this.monoDefinitions.map(x => loadSingleMonoTexture(x, this))); } - public start() { + public start(logs?: Log[]) { if (!this.loaded) throw new Error('game is not loaded yet'); + if (logs) this.replaying = true; for (let i = 0; i < this.STOCK_MAX; i++) { this.stock.push({ - id: Math.random().toString(), - mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], + id: this.rng().toString(), + mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)], }); } this.emit('changeStock', this.stock); @@ -327,10 +365,13 @@ export class DropAndFusionGame extends EventEmitter<{ this.fusion(bodyA, bodyB); } else { fusionReservedPairs.push({ bodyA, bodyB }); - window.setTimeout(() => { - fusionReservedPairs = fusionReservedPairs.filter(x => x.bodyA.id !== bodyA.id && x.bodyB.id !== bodyB.id); - this.fusion(bodyA, bodyB); - }, 100); + this.tickCallbackQueue.push({ + frame: this.frame + 6, + callback: () => { + fusionReservedPairs = fusionReservedPairs.filter(x => x.bodyA.id !== bodyA.id && x.bodyB.id !== bodyB.id); + this.fusion(bodyA, bodyB); + }, + }); } } else { const energy = pairs.collision.depth; @@ -354,6 +395,69 @@ export class DropAndFusionGame extends EventEmitter<{ this.combo = 0; } }, 500); + + if (logs) { + const playTick = () => { + this.frame++; + const log = logs.find(x => x.frame === this.frame - 1); + if (log) { + switch (log.operation) { + case 'drop': { + this.drop(log.x); + break; + } + case 'hold': { + this.hold(); + break; + } + case 'surrender': { + this.surrender(); + break; + } + default: + break; + } + } + this.tickCallbackQueue = this.tickCallbackQueue.filter(x => { + if (x.frame === this.frame) { + x.callback(); + return false; + } else { + return true; + } + }); + + Matter.Engine.update(this.engine, this.TICK_DELTA); + + if (!this.isGameOver) { + this.tickRaf = window.requestAnimationFrame(playTick); + } + }; + + playTick(); + } else { + this.tick(); + } + } + + public getLogs() { + return this.logs; + } + + private tick() { + this.frame++; + this.tickCallbackQueue = this.tickCallbackQueue.filter(x => { + if (x.frame === this.frame) { + x.callback(); + return false; + } else { + return true; + } + }); + Matter.Engine.update(this.engine, this.TICK_DELTA); + if (!this.isGameOver) { + this.tickRaf = window.requestAnimationFrame(this.tick); + } } public async load() { @@ -387,17 +491,22 @@ export class DropAndFusionGame extends EventEmitter<{ public drop(_x: number) { if (this.isGameOver) return; - if (Date.now() - this.latestDroppedAt < this.DROP_INTERVAL) return; + if (!this.replaying && (Date.now() - this.latestDroppedAt < this.DROP_INTERVAL)) return; const head = this.stock.shift()!; this.stock.push({ - id: Math.random().toString(), - mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], + id: this.rng().toString(), + mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)], }); this.emit('changeStock', this.stock); - const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), _x)); + const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), Math.round(_x))); const body = this.createBody(head.mono, x, 50 + head.mono.size / 2); + this.logs.push({ + frame: this.frame, + operation: 'drop', + x, + }); Matter.Composite.add(this.engine.world, body); this.activeBodyIds.push(body.id); this.latestDroppedBodyId = body.id; @@ -416,6 +525,11 @@ export class DropAndFusionGame extends EventEmitter<{ public hold() { if (this.isGameOver) return; + this.logs.push({ + frame: this.frame, + operation: 'hold', + }); + if (this.holding) { const head = this.stock.shift()!; this.stock.unshift(this.holding); @@ -426,8 +540,8 @@ export class DropAndFusionGame extends EventEmitter<{ const head = this.stock.shift()!; this.holding = head; this.stock.push({ - id: Math.random().toString(), - mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], + id: this.rng().toString(), + mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)], }); this.emit('changeHolding', this.holding); this.emit('changeStock', this.stock); @@ -440,8 +554,9 @@ export class DropAndFusionGame extends EventEmitter<{ public dispose() { if (this.comboIntervalId) window.clearInterval(this.comboIntervalId); + if (this.tickRaf) window.cancelAnimationFrame(this.tickRaf); + this.tickRaf = null; Matter.Render.stop(this.render); - Matter.Runner.stop(this.runner); Matter.World.clear(this.engine.world, false); Matter.Engine.clear(this.engine); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0f74de843..9d98224822 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -787,6 +787,9 @@ importers: sass: specifier: 1.69.5 version: 1.69.5 + seedrandom: + specifier: ^3.0.5 + version: 3.0.5 shiki: specifier: 0.14.7 version: 0.14.7 @@ -7401,7 +7404,7 @@ packages: hasBin: true peerDependencies: '@swc/core': ^1.2.66 - chokidar: ^3.5.1 + chokidar: 3.5.3 peerDependenciesMeta: chokidar: optional: true From 358dc6289bb0da03fa62b68111feda99da701d9c Mon Sep 17 00:00:00 2001 From: Camilla Ett Date: Tue, 9 Jan 2024 21:18:09 +0900 Subject: [PATCH 010/134] =?UTF-8?q?Enhance(frontend):=20=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E8=80=85=E3=81=AE=E5=A0=B4=E5=90=88=E3=81=AFAPI=20token?= =?UTF-8?q?=E3=81=AE=E7=99=BA=E8=A1=8C=E7=94=BB=E9=9D=A2=E3=81=A7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=A9=9F=E8=83=BD=E3=81=AB=E9=96=A2=E3=81=99=E3=82=8B?= =?UTF-8?q?=E6=A8=A9=E9=99=90=E3=82=92=E4=BB=98=E4=B8=8E=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#12944)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enhance(frontend): 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように * update CHANGELOG.md * tweak style * (refactor) remove unnecessary imports * fix lint --------- Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Co-authored-by: kakkokari-gtyih --- CHANGELOG.md | 1 + locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + .../src/components/MkTokenGenerateWindow.vue | 64 ++++++++++++++++--- 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04f4210913..2b56ff9fc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Feat: 新しいゲームを追加 - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように - Enhance: チャンネルノートのピン留めをノートのメニューからできるように +- Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index df84412473..aa74ba54b0 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -634,6 +634,7 @@ export interface Locale { "small": string; "generateAccessToken": string; "permission": string; + "adminPermission": string; "enableAll": string; "disableAll": string; "tokenRequested": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 997ddf9c6e..4863bbe770 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -631,6 +631,7 @@ medium: "中" small: "小" generateAccessToken: "アクセストークンの発行" permission: "権限" +adminPermission: "管理者権限" enableAll: "全て有効にする" disableAll: "全て無効にする" tokenRequested: "アカウントへのアクセス許可" diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue index d024e1e593..a42767e1b6 100644 --- a/packages/frontend/src/components/MkTokenGenerateWindow.vue +++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue @@ -33,7 +33,13 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.enableAll }}
- {{ i18n.t(`_permissions.${kind}`) }} + {{ i18n.t(`_permissions.${kind}`) }} +
+
+
{{ i18n.ts.adminPermission }}
+
+ {{ i18n.t(`_permissions.${kind}`) }} +
@@ -49,6 +55,7 @@ import MkButton from './MkButton.vue'; import MkInfo from './MkInfo.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; import { i18n } from '@/i18n.js'; +import { iAmAdmin } from '@/account.js'; const props = withDefaults(defineProps<{ title?: string | null; @@ -68,37 +75,76 @@ const emit = defineEmits<{ }>(); const defaultPermissions = Misskey.permissions.filter(p => !p.startsWith('read:admin') && !p.startsWith('write:admin')); +const adminPermissions = Misskey.permissions.filter(p => p.startsWith('read:admin') || p.startsWith('write:admin')); + const dialog = shallowRef>(); const name = ref(props.initialName); -const permissions = ref(>{}); +const permissionSwitches = ref(>{}); +const permissionSwitchesForAdmin = ref(>{}); if (props.initialPermissions) { for (const kind of props.initialPermissions) { - permissions.value[kind] = true; + permissionSwitches.value[kind] = true; } } else { for (const kind of defaultPermissions) { - permissions.value[kind] = false; + permissionSwitches.value[kind] = false; + } + + if (iAmAdmin) { + for (const kind of adminPermissions) { + permissionSwitchesForAdmin.value[kind] = false; + } } } function ok(): void { emit('done', { name: name.value, - permissions: Object.keys(permissions.value).filter(p => permissions.value[p]), + permissions: [ + ...Object.keys(permissionSwitches.value).filter(p => permissionSwitches.value[p]), + ...(iAmAdmin ? Object.keys(permissionSwitchesForAdmin.value).filter(p => permissionSwitchesForAdmin.value[p]) : []), + ], }); dialog.value?.close(); } function disableAll(): void { - for (const p in permissions.value) { - permissions.value[p] = false; + for (const p in permissionSwitches.value) { + permissionSwitches.value[p] = false; + } + if (iAmAdmin) { + for (const p in permissionSwitchesForAdmin.value) { + permissionSwitchesForAdmin.value[p] = false; + } } } function enableAll(): void { - for (const p in permissions.value) { - permissions.value[p] = true; + for (const p in permissionSwitches.value) { + permissionSwitches.value[p] = true; + } + if (iAmAdmin) { + for (const p in permissionSwitchesForAdmin.value) { + permissionSwitchesForAdmin.value[p] = true; + } } } + + From 7e52ea4818029cbb7a981cb58a7eca0bf6b7e0e7 Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Wed, 10 Jan 2024 00:44:13 +0900 Subject: [PATCH 011/134] Update CHANGELOG.md (#12953) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b56ff9fc9..6963d45f63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 +- Fix: v2023.12.1で追加された`$[clickable ...]`および`onClickEv`が正しく機能していないのを修正 ### Server - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました From f5b864df7bedc3b4a7abdfb09a3df9c2db8c3627 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Jan 2024 07:26:16 +0900 Subject: [PATCH 012/134] fix(frontend): fix game replay --- packages/frontend/src/scripts/drop-and-fusion-engine.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index 9db93d1534..16fe87d97a 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -500,12 +500,13 @@ export class DropAndFusionGame extends EventEmitter<{ }); this.emit('changeStock', this.stock); - const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), Math.round(_x))); + const inputX = Math.round(_x); + const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), inputX)); const body = this.createBody(head.mono, x, 50 + head.mono.size / 2); this.logs.push({ frame: this.frame, operation: 'drop', - x, + x: inputX, }); Matter.Composite.add(this.engine.world, body); this.activeBodyIds.push(body.id); From 6bae440f3912f882fea1e3901aad2d18f2b6a3b8 Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Wed, 10 Jan 2024 09:47:47 +0900 Subject: [PATCH 013/134] bump aiscript version to 0.17.0 (#12955) * bump aiscript version to 0.17.0 * Update CHANGELOG.md --- CHANGELOG.md | 2 ++ packages/frontend/package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6963d45f63..244fd724a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように - Enhance: チャンネルノートのピン留めをノートのメニューからできるように - Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように +- Enhance: AiScriptを0.17.0に更新 [CHANGELOG](https://github.com/aiscript-dev/aiscript/blob/bb89d132b633a622d3cb0eff0d0cc7e476c0cfdd/CHANGELOG.md) + - 配列の範囲外・非整数のインデックスへの代入が完全禁止になるので注意 - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 895aa47419..8c3ce30668 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -24,7 +24,7 @@ "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.5", "@rollup/pluginutils": "5.1.0", - "@syuilo/aiscript": "0.16.0", + "@syuilo/aiscript": "0.17.0", "@tabler/icons-webfont": "2.44.0", "@twemoji/parser": "15.0.0", "@vitejs/plugin-vue": "5.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d98224822..400051bce7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -686,8 +686,8 @@ importers: specifier: 5.1.0 version: 5.1.0(rollup@4.9.1) '@syuilo/aiscript': - specifier: 0.16.0 - version: 0.16.0 + specifier: 0.17.0 + version: 0.17.0 '@tabler/icons-webfont': specifier: 2.44.0 version: 2.44.0 @@ -7649,8 +7649,8 @@ packages: dev: false optional: true - /@syuilo/aiscript@0.16.0: - resolution: {integrity: sha512-CXvoWOq6kmOSUQtKv0IEf7Ebfkk5PO1LxAgLqgRRPgssPvDvINCXu/gFNXKdapkFMkmX+Gj8qjemKR1vnUS4ZA==} + /@syuilo/aiscript@0.17.0: + resolution: {integrity: sha512-3JtQ1rWJHMxQ3153zLCXMUOwrOgjPPYGBl0dPHhR0ohm4tn7okMQRugxMCT0t3YxByemb9FfiM6TUjd0tEGxdA==} dependencies: seedrandom: 3.0.5 stringz: 2.1.0 From 138a248a6ce875af812c8ab126b78817d495b0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A1=E3=82=83=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Wed, 10 Jan 2024 10:40:09 +0900 Subject: [PATCH 014/134] =?UTF-8?q?fix(drop-and-fusion):=20=E3=83=90?= =?UTF-8?q?=E3=83=96=E3=83=AB=E3=82=B2=E3=83=BC=E3=83=A0=E3=81=AE=E3=83=AA?= =?UTF-8?q?=E3=83=88=E3=83=A9=E3=82=A4=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=A7?= =?UTF-8?q?=E3=83=AA=E3=83=88=E3=83=A9=E3=82=A4=E3=81=8C=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=20(#12957)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ゲーム中なら諦める、ゲームオーバー画面の表示中はリスタートになるように --- packages/frontend/src/pages/drop-and-fusion.vue | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 974daf35e4..d041a675f8 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -153,7 +153,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- Retry + Surrender + Retry
@@ -483,15 +484,22 @@ async function surrender() { game.surrender(); } +async function retry() { + end(); + await start(); +} + function end() { game.dispose(); isGameOver.value = false; + replaying.value = false; currentPick.value = null; dropReady.value = true; stock.value = []; score.value = 0; combo.value = 0; comboPrev.value = 0; + maxCombo.value = 0; bgmNodes?.soundSource.stop(); gameStarted.value = false; } From 3d9e42efca8792bcfa1be7bd6125cf732db50fdb Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Jan 2024 11:38:49 +0900 Subject: [PATCH 015/134] =?UTF-8?q?enhance(drop-and-fusion):=20=E3=83=AA?= =?UTF-8?q?=E3=83=97=E3=83=AC=E3=82=A4=E3=81=AE=E5=80=8D=E9=80=9F=E5=86=8D?= =?UTF-8?q?=E7=94=9F=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/drop-and-fusion.vue | 12 ++- .../src/scripts/drop-and-fusion-engine.ts | 79 ++++++++++--------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index d041a675f8..f585519459 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -103,7 +103,11 @@ SPDX-License-Identifier: AGPL-3.0-only
- END REPLAY +
+ END REPLAY + x2 + x4 +
@@ -437,10 +441,15 @@ const gameStarted = ref(false); const highScore = ref(null); const showConfig = ref(false); const replaying = ref(false); +const replayPlaybackRate = ref(1); const mute = ref(false); const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume); const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume); +watch(replayPlaybackRate, (newValue) => { + game.replayPlaybackRate = newValue; +}); + function onClick(ev: MouseEvent) { if (!containerElRect) return; if (replaying.value) return; @@ -493,6 +502,7 @@ function end() { game.dispose(); isGameOver.value = false; replaying.value = false; + replayPlaybackRate.value = 1; currentPick.value = null; dropReady.value = true; stock.value = []; diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index 16fe87d97a..a59eb271ec 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -44,7 +44,7 @@ export class DropAndFusionGame extends EventEmitter<{ gameOver: () => void; }> { private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる - private COMBO_INTERVAL = 1000; + private COMBO_INTERVAL = 60; // frame public readonly DROP_INTERVAL = 500; public readonly PLAYAREA_MARGIN = 25; private STOCK_MAX = 4; @@ -76,7 +76,7 @@ export class DropAndFusionGame extends EventEmitter<{ private latestDroppedBodyId: Matter.Body['id'] | null = null; private latestDroppedAt = 0; - private latestFusionedAt = 0; + private latestFusionedAt = 0; // frame private stock: { id: string; mono: Mono }[] = []; private holding: { id: string; mono: Mono } | null = null; @@ -100,6 +100,8 @@ export class DropAndFusionGame extends EventEmitter<{ private comboIntervalId: number | null = null; + public replayPlaybackRate = 1; + constructor(opts: { canvas: HTMLCanvasElement; width: number; @@ -219,13 +221,12 @@ export class DropAndFusionGame extends EventEmitter<{ } private fusion(bodyA: Matter.Body, bodyB: Matter.Body) { - const now = Date.now(); - if (this.latestFusionedAt > now - this.COMBO_INTERVAL) { + if (this.latestFusionedAt > this.frame - this.COMBO_INTERVAL) { this.combo++; } else { this.combo = 1; } - this.latestFusionedAt = now; + this.latestFusionedAt = this.frame; // TODO: 単に位置だけでなくそれぞれの動きベクトルも融合する? const newX = (bodyA.position.x + bodyB.position.x) / 2; @@ -390,44 +391,43 @@ export class DropAndFusionGame extends EventEmitter<{ } }); - this.comboIntervalId = window.setInterval(() => { - if (this.latestFusionedAt < Date.now() - this.COMBO_INTERVAL) { - this.combo = 0; - } - }, 500); - if (logs) { const playTick = () => { - this.frame++; - const log = logs.find(x => x.frame === this.frame - 1); - if (log) { - switch (log.operation) { - case 'drop': { - this.drop(log.x); - break; - } - case 'hold': { - this.hold(); - break; - } - case 'surrender': { - this.surrender(); - break; - } - default: - break; + for (let i = 0; i < this.replayPlaybackRate; i++) { + this.frame++; + if (this.latestFusionedAt < this.frame - this.COMBO_INTERVAL) { + this.combo = 0; } - } - this.tickCallbackQueue = this.tickCallbackQueue.filter(x => { - if (x.frame === this.frame) { - x.callback(); - return false; - } else { - return true; + const log = logs.find(x => x.frame === this.frame - 1); + if (log) { + switch (log.operation) { + case 'drop': { + this.drop(log.x); + break; + } + case 'hold': { + this.hold(); + break; + } + case 'surrender': { + this.surrender(); + break; + } + default: + break; + } } - }); + this.tickCallbackQueue = this.tickCallbackQueue.filter(x => { + if (x.frame === this.frame) { + x.callback(); + return false; + } else { + return true; + } + }); - Matter.Engine.update(this.engine, this.TICK_DELTA); + Matter.Engine.update(this.engine, this.TICK_DELTA); + } if (!this.isGameOver) { this.tickRaf = window.requestAnimationFrame(playTick); @@ -446,6 +446,9 @@ export class DropAndFusionGame extends EventEmitter<{ private tick() { this.frame++; + if (this.latestFusionedAt < this.frame - this.COMBO_INTERVAL) { + this.combo = 0; + } this.tickCallbackQueue = this.tickCallbackQueue.filter(x => { if (x.frame === this.frame) { x.callback(); From 4bd9f664d7213e9d6d507ae1b8cb67e2b78e766a Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Jan 2024 13:44:00 +0900 Subject: [PATCH 016/134] enhance(drop-and-fusion): some tweaks --- .../frontend/src/pages/drop-and-fusion.vue | 1 + .../src/scripts/drop-and-fusion-engine.ts | 29 +++++++++++++------ packages/frontend/src/scripts/sound.ts | 2 -- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index f585519459..c5ab7a33f5 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -1028,6 +1028,7 @@ definePageMetadata({ bottom: 10px; padding: 6px 8px; color: #f00; + font-weight: bold; background: #0008; border-radius: 6px; pointer-events: none; diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index a59eb271ec..342e818905 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -157,6 +157,7 @@ export class DropAndFusionGame extends EventEmitter<{ //#region walls const WALL_OPTIONS: Matter.IChamferableBodyDefinition = { + label: '_wall_', isStatic: true, friction: 0.7, slop: 1.0, @@ -254,12 +255,14 @@ export class DropAndFusionGame extends EventEmitter<{ const additionalScore = Math.round(currentMono.score * comboBonus); this.score += additionalScore; - // TODO: 効果音再生はコンポーネント側の責務なので移動する - const pan = ((newX / this.gameWidth) - 0.5) * 2; + // TODO: 効果音再生はコンポーネント側の責務なので移動するべき? + const panV = newX - this.PLAYAREA_MARGIN; + const panW = this.gameWidth - this.PLAYAREA_MARGIN - this.PLAYAREA_MARGIN; + const pan = ((panV / panW) - 0.5) * 2; sound.playUrl('/client-assets/drop-and-fusion/bubble2.mp3', { volume: this.sfxVolume, pan, - playbackRate: nextMono.sfxPitch, + playbackRate: nextMono.sfxPitch * this.replayPlaybackRate, }); this.emit('monoAdded', nextMono); @@ -293,7 +296,7 @@ export class DropAndFusionGame extends EventEmitter<{ this.tickRaf = null; this.emit('gameOver'); - // TODO: 効果音再生はコンポーネント側の責務なので移動する + // TODO: 効果音再生はコンポーネント側の責務なので移動するべき? sound.playUrl('/client-assets/drop-and-fusion/gameover.mp3', { volume: this.sfxVolume, }); @@ -377,14 +380,19 @@ export class DropAndFusionGame extends EventEmitter<{ } else { const energy = pairs.collision.depth; if (energy > minCollisionEnergyForSound) { - // TODO: 効果音再生はコンポーネント側の責務なので移動する + // TODO: 効果音再生はコンポーネント側の責務なので移動するべき? const vol = ((Math.min(maxCollisionEnergyForSound, energy - minCollisionEnergyForSound) / maxCollisionEnergyForSound) / 4) * this.sfxVolume; - const pan = ((((bodyA.position.x + bodyB.position.x) / 2) / this.gameWidth) - 0.5) * 2; + const panV = + pairs.bodyA.label === '_wall_' ? bodyB.position.x - this.PLAYAREA_MARGIN : + pairs.bodyB.label === '_wall_' ? bodyA.position.x - this.PLAYAREA_MARGIN : + ((bodyA.position.x + bodyB.position.x) / 2) - this.PLAYAREA_MARGIN; + const panW = this.gameWidth - this.PLAYAREA_MARGIN - this.PLAYAREA_MARGIN; + const pan = ((panV / panW) - 0.5) * 2; const pitch = soundPitchMin + ((soundPitchMax - soundPitchMin) * (1 - (Math.min(10, energy) / 10))); sound.playUrl('/client-assets/drop-and-fusion/poi1.mp3', { volume: vol, pan, - playbackRate: pitch, + playbackRate: pitch * this.replayPlaybackRate, }); } } @@ -518,11 +526,14 @@ export class DropAndFusionGame extends EventEmitter<{ this.emit('dropped'); this.emit('monoAdded', head.mono); - // TODO: 効果音再生はコンポーネント側の責務なので移動する - const pan = ((x / this.gameWidth) - 0.5) * 2; + // TODO: 効果音再生はコンポーネント側の責務なので移動するべき? + const panV = x - this.PLAYAREA_MARGIN; + const panW = this.gameWidth - this.PLAYAREA_MARGIN - this.PLAYAREA_MARGIN; + const pan = ((panV / panW) - 0.5) * 2; sound.playUrl('/client-assets/drop-and-fusion/poi2.mp3', { volume: this.sfxVolume, pan, + playbackRate: this.replayPlaybackRate, }); } diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts index 142ddf87c9..05c8977ecf 100644 --- a/packages/frontend/src/scripts/sound.ts +++ b/packages/frontend/src/scripts/sound.ts @@ -99,7 +99,6 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; }) } if (options?.useCache ?? true) { if (cache.has(url)) { - if (_DEV_) console.log('use cache'); return cache.get(url) as AudioBuffer; } } @@ -128,7 +127,6 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; }) */ export function playMisskeySfx(operationType: OperationType) { const sound = defaultStore.state[`sound_${operationType}`]; - if (_DEV_) console.log('play', operationType, sound); if (sound.type == null || !canPlay) return; canPlay = false; From c1c363bf08a391400e4b8b1df91962c26f2f3192 Mon Sep 17 00:00:00 2001 From: 1Step621 <86859447+1STEP621@users.noreply.github.com> Date: Wed, 10 Jan 2024 15:06:04 +0900 Subject: [PATCH 017/134] =?UTF-8?q?Enhance(frontend):=20=E7=B5=B5=E6=96=87?= =?UTF-8?q?=E5=AD=97=E3=83=94=E3=83=83=E3=82=AB=E3=83=BC/=E3=82=AA?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=82=B3=E3=83=B3=E3=83=97=E3=83=AA=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=A7=E5=AE=8C=E5=85=A8=E4=B8=80=E8=87=B4=E3=81=AE?= =?UTF-8?q?=E7=B5=B5=E6=96=87=E5=AD=97=E3=82=92=E5=84=AA=E5=85=88=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#12928)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 絵文字ピッカー/オートコンプリートで完全一致の絵文字を優先するように * update CHANGELOG.md * improve performance --- CHANGELOG.md | 1 + .../frontend/src/components/MkAutocomplete.vue | 17 +++++++++++++---- .../frontend/src/components/MkEmojiPicker.vue | 13 +++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 244fd724a9..13ad3a3508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように - Enhance: AiScriptを0.17.0に更新 [CHANGELOG](https://github.com/aiscript-dev/aiscript/blob/bb89d132b633a622d3cb0eff0d0cc7e476c0cfdd/CHANGELOG.md) - 配列の範囲外・非整数のインデックスへの代入が完全禁止になるので注意 +- Enhance: 絵文字ピッカー・オートコンプリートで、完全一致した絵文字を優先的に表示するように - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 49884c705f..15eda4499f 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -262,15 +262,24 @@ function emojiAutoComplete(query: string | null, emojiDb: EmojiDef[], max = 30): } const matched = new Map(); - - // 前方一致(エイリアスなし) + // 完全一致(エイリアス込み) emojiDb.some(x => { - if (x.name.startsWith(query) && !x.aliasOf) { - matched.set(x.name, { emoji: x, score: query.length + 1 }); + if (x.name === query && !matched.has(x.aliasOf ?? x.name)) { + matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length + 2 }); } return matched.size === max; }); + // 前方一致(エイリアスなし) + if (matched.size < max) { + emojiDb.some(x => { + if (x.name.startsWith(query) && !x.aliasOf) { + matched.set(x.name, { emoji: x, score: query.length + 1 }); + } + return matched.size === max; + }); + } + // 前方一致(エイリアス込み) if (matched.size < max) { emojiDb.some(x => { diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index f36d46506f..84424c58ed 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -221,6 +221,19 @@ watch(q, () => { } } } else { + if (customEmojisMap.has(newQ)) { + matches.add(customEmojisMap.get(newQ)!); + } + if (matches.size >= max) return matches; + + for (const emoji of emojis) { + if (emoji.aliases.some(alias => alias === newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; + for (const emoji of emojis) { if (emoji.name.startsWith(newQ)) { matches.add(emoji); From 5c786cace839147a11fadac6ee46da29db5f2457 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Jan 2024 17:31:59 +0900 Subject: [PATCH 018/134] enhance(drop-and-fusion): add game description --- locales/index.d.ts | 8 ++++++++ locales/ja-JP.yml | 7 +++++++ packages/frontend/src/pages/drop-and-fusion.vue | 16 +++++++++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index aa74ba54b0..852cbdd27d 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1199,6 +1199,14 @@ export interface Locale { "showReplay": string; "replay": string; "replaying": string; + "_bubbleGame": { + "howToPlay": string; + "_howToPlay": { + "section1": string; + "section2": string; + "section3": string; + }; + }; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 4863bbe770..f85dc0fcf8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1197,6 +1197,13 @@ showReplay: "リプレイを見る" replay: "リプレイ" replaying: "リプレイ中" +_bubbleGame: + howToPlay: "遊び方" + _howToPlay: + section1: "位置を調整してハコにモノを落とします。" + section2: "同じ種類のモノがくっつくと別のモノに変化して、スコアが得られます。" + section3: "モノがハコからあふれるとゲームオーバーです。ハコからあふれないようにしつつモノを融合させてハイスコアを目指そう!" + _announcement: forExistingUsers: "既存ユーザーのみ" forExistingUsersDescription: "有効にすると、このお知らせ作成時点で存在するユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。" diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index c5ab7a33f5..9fb7ab2e23 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -8,13 +8,13 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
+
+
-
+
@@ -33,6 +33,16 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
{{ i18n.ts._bubbleGame.howToPlay }}
+
    +
  1. {{ i18n.ts._bubbleGame._howToPlay.section1 }}
  2. +
  3. {{ i18n.ts._bubbleGame._howToPlay.section2 }}
  4. +
  5. {{ i18n.ts._bubbleGame._howToPlay.section3 }}
  6. +
+
+
From 36fd7d17cf1c71fa59eae445d05498a7bf5ab173 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Jan 2024 19:54:59 +0900 Subject: [PATCH 019/134] enhance(drop-and-fusion): some tweaks --- .../src/pages/drop-and-fusion.game.vue | 1052 +++++++++++++++++ .../frontend/src/pages/drop-and-fusion.vue | 982 +-------------- .../src/scripts/drop-and-fusion-engine.ts | 2 +- 3 files changed, 1088 insertions(+), 948 deletions(-) create mode 100644 packages/frontend/src/pages/drop-and-fusion.game.vue diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue new file mode 100644 index 0000000000..acaebbadf7 --- /dev/null +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -0,0 +1,1052 @@ + + + + + + + diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 9fb7ab2e23..7bd0eef000 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -4,10 +4,16 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index 342e818905..d64c6015a5 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -33,6 +33,7 @@ type Log = { operation: 'surrender'; }; +// TODO: インスタンスを作り直さなくてもゲームをリスタートできるようにする export class DropAndFusionGame extends EventEmitter<{ changeScore: (newScore: number) => void; changeCombo: (newCombo: number) => void; @@ -307,7 +308,6 @@ export class DropAndFusionGame extends EventEmitter<{ async function loadSingleMonoTexture(mono: Mono, game: DropAndFusionGame) { // Matter-js内にキャッシュがある場合はスキップ if (game.render.textures[mono.img]) return; - console.log('loading', mono.img); let src = mono.img; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition From 762fa6a8d85691e2d5d94a46b23d7641feefd402 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 11 Jan 2024 12:34:03 +0900 Subject: [PATCH 020/134] enhance(drop-and-fusion): make game engine headless for server-side running --- .../src/pages/drop-and-fusion.game.vue | 458 ++++++++++++------ .../src/scripts/drop-and-fusion-engine.ts | 359 ++++---------- 2 files changed, 397 insertions(+), 420 deletions(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index acaebbadf7..3fefb49fae 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
HOLD - +
- +
@@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only :moveClass="$style.transition_picked_move" mode="out-in" > - + diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index 9930b321f7..b970ff1df4 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -27,11 +27,6 @@ function toolsMenuItems(): MenuItem[] { to: '/clicker', text: '🍪👈', icon: 'ti ti-cookie', - }, { - type: 'link', - to: '/bubble-game', - text: i18n.ts.bubbleGame, - icon: 'ti ti-apple', }, ($i && ($i.isAdmin || $i.policies.canManageCustomEmojis)) ? { type: 'link', to: '/custom-emojis-manager', From 8b0fdfcd69334dbf934a69cf707826b3be8cf2d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:17:01 +0900 Subject: [PATCH 051/134] =?UTF-8?q?enhance:=20=E5=8B=95=E7=94=BB=E3=83=BB?= =?UTF-8?q?=E9=9F=B3=E5=A3=B0=E5=91=A8=E3=82=8A=E3=81=AEUI=E3=81=A8?= =?UTF-8?q?=E5=8B=95=E4=BD=9C=E6=94=B9=E8=89=AF=20(#12925)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * (fix) `/files` をバイトレンジリクエストに対応させる * video * audio * fix * fix * spdx * fix (rangeRequest) * fix * Update CHANGELOG.md * (add) ボリュームを保存できるように * (fix) ミュート復帰時に音量が固定される * named export * tweak design * Add sensitive class for audio component * Refactor seekbar styles * Refactor hms * Revert "(add) ボリュームを保存できるように" This reverts commit 6271f9493b63f96d0dd9915207e97fe120ef9037. * Revert "(fix) ミュート復帰時に音量が固定される" This reverts commit a65002b56ecdcb10f76bcc2debbe38593a69643f. * revert revert changes --------- Co-authored-by: syuilo --- CHANGELOG.md | 2 + locales/index.d.ts | 2 + locales/ja-JP.yml | 2 + .../backend/src/server/FileServerService.ts | 111 +++- .../frontend/src/components/MkMediaAudio.vue | 363 ++++++++++++ .../frontend/src/components/MkMediaBanner.vue | 13 +- .../frontend/src/components/MkMediaRange.vue | 150 +++++ .../frontend/src/components/MkMediaVideo.vue | 540 ++++++++++++++++-- packages/frontend/src/filters/hms.ts | 65 +++ packages/frontend/src/scripts/device-kind.ts | 7 + 10 files changed, 1180 insertions(+), 75 deletions(-) create mode 100644 packages/frontend/src/components/MkMediaAudio.vue create mode 100644 packages/frontend/src/components/MkMediaRange.vue create mode 100644 packages/frontend/src/filters/hms.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1fdcf9ee..945b6ac1ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Client - Feat: 新しいゲームを追加 +- Feat: 音声・映像プレイヤーを追加 - Feat: 絵文字の詳細ダイアログを追加 - Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加 - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように @@ -38,6 +39,7 @@ - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました - Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916) - Enhance: クリップをエクスポートできるように +- Enhance: `/files`のファイルに対してHTTP Rangeリクエストを行えるように - Enhance: `api.json`のOpenAPI Specificationを3.1.0に更新 - Fix: `drive/files/update`でファイル名のバリデーションが機能していない問題を修正 - Fix: `notes/create`で、`text`が空白文字のみで構成されているか`null`であって、かつ`text`だけであるリクエストに対するレスポンスが400になるように変更 diff --git a/locales/index.d.ts b/locales/index.d.ts index dafbdd3559..71134544d9 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1061,6 +1061,8 @@ export interface Locale { "noteIdOrUrl": string; "video": string; "videos": string; + "audio": string; + "audioFiles": string; "dataSaver": string; "accountMigration": string; "accountMoved": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 58952894b3..743a3ca38e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1058,6 +1058,8 @@ limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小し noteIdOrUrl: "ノートIDまたはURL" video: "動画" videos: "動画" +audio: "音声" +audioFiles: "音声" dataSaver: "データセーバー" accountMigration: "アカウントの移行" accountMoved: "このユーザーは新しいアカウントに移行しました:" diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index f59996ce17..7745a6cb78 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -168,11 +168,35 @@ export class FileServerService { } if (!image) { - image = { - data: fs.createReadStream(file.path), - ext: file.ext, - type: file.mime, - }; + if (request.headers.range && file.file.size > 0) { + const range = request.headers.range as string; + const parts = range.replace(/bytes=/, '').split('-'); + const start = parseInt(parts[0], 10); + let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; + if (end > file.file.size) { + end = file.file.size - 1; + } + const chunksize = end - start + 1; + + image = { + data: fs.createReadStream(file.path, { + start, + end, + }), + ext: file.ext, + type: file.mime, + }; + + reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); + reply.header('Accept-Ranges', 'bytes'); + reply.header('Content-Length', chunksize); + } else { + image = { + data: fs.createReadStream(file.path), + ext: file.ext, + type: file.mime, + }; + } } if ('pipe' in image.data && typeof image.data.pipe === 'function') { @@ -203,11 +227,54 @@ export class FileServerService { reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.mime) ? file.mime : 'application/octet-stream'); reply.header('Cache-Control', 'max-age=31536000, immutable'); reply.header('Content-Disposition', contentDisposition('inline', filename)); + + if (request.headers.range && file.file.size > 0) { + const range = request.headers.range as string; + const parts = range.replace(/bytes=/, '').split('-'); + const start = parseInt(parts[0], 10); + let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; + if (end > file.file.size) { + end = file.file.size - 1; + } + const chunksize = end - start + 1; + const fileStream = fs.createReadStream(file.path, { + start, + end, + }); + reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); + reply.header('Accept-Ranges', 'bytes'); + reply.header('Content-Length', chunksize); + reply.code(206); + return fileStream; + } + return fs.createReadStream(file.path); } else { reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.file.type) ? file.file.type : 'application/octet-stream'); reply.header('Cache-Control', 'max-age=31536000, immutable'); reply.header('Content-Disposition', contentDisposition('inline', file.filename)); + + if (request.headers.range && file.file.size > 0) { + const range = request.headers.range as string; + const parts = range.replace(/bytes=/, '').split('-'); + const start = parseInt(parts[0], 10); + let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; + console.log(end); + if (end > file.file.size) { + end = file.file.size - 1; + } + const chunksize = end - start + 1; + const fileStream = fs.createReadStream(file.path, { + start, + end, + }); + reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); + reply.header('Accept-Ranges', 'bytes'); + reply.header('Content-Length', chunksize); + reply.code(206); + return fileStream; + } + return fs.createReadStream(file.path); } } catch (e) { @@ -340,11 +407,35 @@ export class FileServerService { } if (!image) { - image = { - data: fs.createReadStream(file.path), - ext: file.ext, - type: file.mime, - }; + if (request.headers.range && file.file && file.file.size > 0) { + const range = request.headers.range as string; + const parts = range.replace(/bytes=/, '').split('-'); + const start = parseInt(parts[0], 10); + let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; + if (end > file.file.size) { + end = file.file.size - 1; + } + const chunksize = end - start + 1; + + image = { + data: fs.createReadStream(file.path, { + start, + end, + }), + ext: file.ext, + type: file.mime, + }; + + reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); + reply.header('Accept-Ranges', 'bytes'); + reply.header('Content-Length', chunksize); + } else { + image = { + data: fs.createReadStream(file.path), + ext: file.ext, + type: file.mime, + }; + } } if ('cleanup' in file) { diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue new file mode 100644 index 0000000000..75b31b9a49 --- /dev/null +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -0,0 +1,363 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue index 3f8fef6632..b21960a490 100644 --- a/packages/frontend/src/components/MkMediaBanner.vue +++ b/packages/frontend/src/components/MkMediaBanner.vue @@ -5,20 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts index b7190f6335..9eab855004 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts @@ -13,6 +13,7 @@ import MkMention from '@/components/MkMention.vue'; import MkEmoji from '@/components/global/MkEmoji.vue'; import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; import MkCode from '@/components/MkCode.vue'; +import MkCodeInline from '@/components/MkCodeInline.vue'; import MkGoogle from '@/components/MkGoogle.vue'; import MkSparkle from '@/components/MkSparkle.vue'; import MkA from '@/components/global/MkA.vue'; @@ -373,10 +374,9 @@ export default function(props: MfmProps, context: SetupContext) { } case 'inlineCode': { - return [h(MkCode, { + return [h(MkCodeInline, { key: Math.random(), code: token.props.code, - inline: true, })]; } diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index 4318694d4f..fabbc1c05d 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
From 67a41c09ae5924ec97237dc7869de579b5f74510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 18 Jan 2024 14:45:11 +0900 Subject: [PATCH 062/134] =?UTF-8?q?fix(frontend/MediaVideo):=20=E5=86=8D?= =?UTF-8?q?=E7=94=9F=E3=82=B7=E3=83=BC=E3=82=AF=E3=83=90=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E5=BD=93=E3=81=9F=E3=82=8A=E5=88=A4=E5=AE=9A=E3=82=92=E8=AA=BF?= =?UTF-8?q?=E6=95=B4=20(#13027)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend/MediaVideo): 再生シークバーの当たり判定を調整 * fix --- packages/frontend/src/components/MkMediaAudio.vue | 6 +++--- packages/frontend/src/components/MkMediaRange.vue | 8 +++++--- packages/frontend/src/components/MkMediaVideo.vue | 7 +++++-- packages/frontend/src/filters/hms.ts | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index 75b31b9a49..53fd3b2d55 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -138,7 +138,7 @@ const rangePercent = computed({ audioEl.value.currentTime = to * durationMs.value / 1000; }, }); -const volume = ref(.5); +const volume = ref(.25); const bufferedEnd = ref(0); const bufferedDataRatio = computed(() => { if (!audioEl.value) return 0; @@ -161,7 +161,7 @@ function togglePlayPause() { function toggleMute() { if (volume.value === 0) { - volume.value = .5; + volume.value = .25; } else { volume.value = 0; } @@ -207,7 +207,7 @@ function init() { isActuallyPlaying.value = false; isPlaying.value = false; }); - + durationMs.value = audioEl.value.duration * 1000; audioEl.value.addEventListener('durationchange', () => { if (audioEl.value) { diff --git a/packages/frontend/src/components/MkMediaRange.vue b/packages/frontend/src/components/MkMediaRange.vue index e6303a5c41..a150ae9843 100644 --- a/packages/frontend/src/components/MkMediaRange.vue +++ b/packages/frontend/src/components/MkMediaRange.vue @@ -5,9 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 977c9020c7..0a113458a1 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -176,7 +176,7 @@ const rangePercent = computed({ videoEl.value.currentTime = to * durationMs.value / 1000; }, }); -const volume = ref(.5); +const volume = ref(.25); const bufferedEnd = ref(0); const bufferedDataRatio = computed(() => { if (!videoEl.value) return 0; @@ -236,7 +236,7 @@ function toggleFullscreen() { function toggleMute() { if (volume.value === 0) { - volume.value = .5; + volume.value = .25; } else { volume.value = 0; } @@ -535,6 +535,9 @@ onDeactivated(() => { .seekbarRoot { grid-area: seekbar; + /* ▼シークバー操作をやりやすくするためにクリックイベントが伝播されないエリアを拡張する */ + margin: -10px; + padding: 10px; } @container (min-width: 500px) { diff --git a/packages/frontend/src/filters/hms.ts b/packages/frontend/src/filters/hms.ts index 7b5da965ff..73db7becc2 100644 --- a/packages/frontend/src/filters/hms.ts +++ b/packages/frontend/src/filters/hms.ts @@ -5,7 +5,7 @@ import { i18n } from '@/i18n.js'; -export function hms(ms: number, options: { +export function hms(ms: number, options?: { textFormat?: 'colon' | 'locale'; enableSeconds?: boolean; enableMs?: boolean; From c1019a006bfa48ba7398daa02788c50be0bfb35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:21:33 +0900 Subject: [PATCH 063/134] =?UTF-8?q?feat(frontend):=20=E6=A8=AA=E3=82=B9?= =?UTF-8?q?=E3=83=AF=E3=82=A4=E3=83=97=E3=81=A7=E3=82=BF=E3=83=96=E3=82=92?= =?UTF-8?q?=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88=E3=82=8B=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=20(#13011)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * (add) 横スワイプでタブを切り替える機能 * Change Changelog * y方向の移動が一定量を超えたらスワイプを中断するように * Update swipe distance thresholds * Remove console.log * adjust threshold * rename, use v-model * fix * Update MkHorizontalSwipe.vue Co-authored-by: syuilo * use css module --------- Co-authored-by: syuilo --- CHANGELOG.md | 1 + locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + .../src/components/MkHorizontalSwipe.vue | 209 ++++++++++++++++++ packages/frontend/src/pages/about.vue | 171 +++++++------- packages/frontend/src/pages/announcements.vue | 57 ++--- packages/frontend/src/pages/channel.vue | 90 ++++---- packages/frontend/src/pages/channels.vue | 77 +++---- packages/frontend/src/pages/drive.file.vue | 15 +- packages/frontend/src/pages/explore.vue | 11 +- .../frontend/src/pages/flash/flash-index.vue | 41 ++-- packages/frontend/src/pages/gallery/index.vue | 11 +- packages/frontend/src/pages/instance-info.vue | 206 ++++++++--------- .../frontend/src/pages/my-clips/index.vue | 26 ++- packages/frontend/src/pages/notifications.vue | 28 ++- packages/frontend/src/pages/pages.vue | 47 ++-- packages/frontend/src/pages/search.vue | 25 ++- .../frontend/src/pages/settings/general.vue | 2 + packages/frontend/src/pages/timeline.vue | 46 ++-- packages/frontend/src/pages/user/index.vue | 30 +-- packages/frontend/src/store.ts | 4 + 21 files changed, 685 insertions(+), 414 deletions(-) create mode 100644 packages/frontend/src/components/MkHorizontalSwipe.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b43319614..605b8af92c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Feat: 絵文字の詳細ダイアログを追加 - Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加 - デフォルトで枠線からはみ出る部分が隠されるようにしました。初期と同じ挙動にするには`$[border.noclip`が必要です +- Feat: スワイプでタブを切り替えられるように - Enhance: MFM等のコードブロックに全文コピー用のボタンを追加 - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように - Enhance: チャンネルノートのピン留めをノートのメニューからできるように diff --git a/locales/index.d.ts b/locales/index.d.ts index 71134544d9..a659e790cc 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1204,6 +1204,7 @@ export interface Locale { "ranking": string; "lastNDays": string; "backToTitle": string; + "enableHorizontalSwipe": string; "_bubbleGame": { "howToPlay": string; "_howToPlay": { diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 743a3ca38e..8749a5f49f 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1201,6 +1201,7 @@ replaying: "リプレイ中" ranking: "ランキング" lastNDays: "直近{n}日" backToTitle: "タイトルへ" +enableHorizontalSwipe: "スワイプしてタブを切り替える" _bubbleGame: howToPlay: "遊び方" diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue new file mode 100644 index 0000000000..2c62aadbf4 --- /dev/null +++ b/packages/frontend/src/components/MkHorizontalSwipe.vue @@ -0,0 +1,209 @@ + + + + + + diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue index f402b26ad8..4ba1b6da76 100644 --- a/packages/frontend/src/pages/about.vue +++ b/packages/frontend/src/pages/about.vue @@ -6,98 +6,100 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -114,6 +116,7 @@ import FormSplit from '@/components/form/split.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import MkInstanceStats from '@/components/MkInstanceStats.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; import number from '@/filters/number.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index 5632bf7caf..c31c6d0903 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -7,34 +7,36 @@ SPDX-License-Identifier: AGPL-3.0-only -
- {{ i18n.ts.youHaveUnreadAnnouncements }} - -
-
{{ i18n.ts.forYou }}
-
- 🆕 - - - - - - - {{ announcement.title }} -
-
- - -
- + +
+ {{ i18n.ts.youHaveUnreadAnnouncements }} + +
+
{{ i18n.ts.forYou }}
+
+ 🆕 + + + + + + + {{ announcement.title }}
-
-
- {{ i18n.ts.gotIt }} -
-
-
-
+
+ + +
+ +
+
+
+ {{ i18n.ts.gotIt }} +
+ + +
+ @@ -44,6 +46,7 @@ import { ref, computed } from 'vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; import MkInfo from '@/components/MkInfo.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 971eca8cae..4cdf2eea7d 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -7,53 +7,55 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
- - - -
-
-
-
+ +
+
+ + + +
+
+
+
+
+
{{ i18n.ts.sensitive }}
+
+
+
+
-
{{ i18n.ts.sensitive }}
-
-
- + + + +
+ +
+
+
+
+ {{ i18n.ts.thisChannelArchived }} + + + + + +
+
+ +
+
+
+
+ + + + {{ i18n.ts.search }} +
+
- - - -
- -
-
-
-
- {{ i18n.ts.thisChannelArchived }} - - - - - -
-
- -
-
-
-
- - - - {{ i18n.ts.search }} -
- -
-
+
@@ -58,6 +60,7 @@ import MkInput from '@/components/MkInput.vue'; import MkRadios from '@/components/MkRadios.vue'; import MkButton from '@/components/MkButton.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { useRouter } from '@/global/router/supplier.js'; diff --git a/packages/frontend/src/pages/drive.file.vue b/packages/frontend/src/pages/drive.file.vue index 2c1e5d20a7..6a9e907963 100644 --- a/packages/frontend/src/pages/drive.file.vue +++ b/packages/frontend/src/pages/drive.file.vue @@ -9,13 +9,15 @@ SPDX-License-Identifier: AGPL-3.0-only - - - + + + + - - - + + + + @@ -23,6 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref, defineAsyncComponent } from 'vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; const props = defineProps<{ fileId: string; diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue index f068de8880..1b80014366 100644 --- a/packages/frontend/src/pages/explore.vue +++ b/packages/frontend/src/pages/explore.vue @@ -6,17 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -26,6 +26,7 @@ import XFeatured from './explore.featured.vue'; import XUsers from './explore.users.vue'; import XRoles from './explore.roles.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue index 7852018894..53510ea232 100644 --- a/packages/frontend/src/pages/flash/flash-index.vue +++ b/packages/frontend/src/pages/flash/flash-index.vue @@ -7,32 +7,34 @@ SPDX-License-Identifier: AGPL-3.0-only -
- -
- -
-
-
- -
-
- - + +
+
-
-
- -
- +
+
+ + +
+ +
+
- -
+
+ +
+ +
+ +
+
+
+ @@ -42,6 +44,7 @@ import { computed, ref } from 'vue'; import MkFlashPreview from '@/components/MkFlashPreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { useRouter } from '@/global/router/supplier.js'; diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue index 0198ab9700..9749888fe9 100644 --- a/packages/frontend/src/pages/gallery/index.vue +++ b/packages/frontend/src/pages/gallery/index.vue @@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
+ +
@@ -26,14 +26,14 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
-
+
{{ i18n.ts.postToGallery }}
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+ @@ -51,6 +51,7 @@ import { watch, ref, computed } from 'vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { useRouter } from '@/global/router/supplier.js'; diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index c8a0eeeeaa..4211dc0d87 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -7,111 +7,113 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
- - {{ instance.name || `(${i18n.ts.unknown})` }} -
-
- - - - - - - - - - - - -
- - - - - - - -
- {{ i18n.ts.stopActivityDelivery }} - {{ i18n.ts.blockThisInstance }} - {{ i18n.ts.silenceThisInstance }} - Refresh metadata + +
+
+ + {{ instance.name || `(${i18n.ts.unknown})` }}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - host-meta - host-meta.json - nodeinfo - robots.txt - manifest.json - -
-
-
-
- - - - - - - - - - - - - +
+ + + + + + + + + + + +
-
-
{{ i18n.t('recentNHours', { n: 90 }) }}
- -
{{ i18n.t('recentNDays', { n: 90 }) }}
- + + + + + + + +
+ {{ i18n.ts.stopActivityDelivery }} + {{ i18n.ts.blockThisInstance }} + {{ i18n.ts.silenceThisInstance }} + Refresh metadata +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + host-meta + host-meta.json + nodeinfo + robots.txt + manifest.json + +
+
+
+
+ + + + + + + + + + + + + +
+
+
{{ i18n.t('recentNHours', { n: 90 }) }}
+ +
{{ i18n.t('recentNDays', { n: 90 }) }}
+ +
-
-
- - - - - -
-
- - -
+
+ + + + + +
+
+ + +
+ @@ -136,6 +138,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkPagination from '@/components/MkPagination.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; import { dateString } from '@/filters/date.js'; @@ -144,6 +147,7 @@ const props = defineProps<{ }>(); const tab = ref('overview'); + const chartSrc = ref('instance-requests'); const meta = ref(null); const instance = ref(null); diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue index 850222708e..468e46838b 100644 --- a/packages/frontend/src/pages/my-clips/index.vue +++ b/packages/frontend/src/pages/my-clips/index.vue @@ -7,20 +7,22 @@ SPDX-License-Identifier: AGPL-3.0-only -
- {{ i18n.ts.add }} + +
+ {{ i18n.ts.add }} - - + + + + + +
+
+ - -
-
- - - -
+
+
@@ -36,6 +38,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { clipsCache } from '@/cache.js'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; const pagination = { endpoint: 'clips/list' as const, @@ -44,6 +47,7 @@ const pagination = { }; const tab = ref('my'); + const favorites = ref(null); const pagingComponent = shallowRef>(); diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue index d57bda41b5..8913a89adb 100644 --- a/packages/frontend/src/pages/notifications.vue +++ b/packages/frontend/src/pages/notifications.vue @@ -7,15 +7,17 @@ SPDX-License-Identifier: AGPL-3.0-only -
- -
-
- -
-
- -
+ +
+ +
+
+ +
+
+ +
+
@@ -24,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref } from 'vue'; import XNotifications from '@/components/MkNotifications.vue'; import MkNotes from '@/components/MkNotes.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; @@ -96,3 +99,10 @@ definePageMetadata(computed(() => ({ icon: 'ti ti-bell', }))); + + diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue index 22ab9ced09..8b57b1af9f 100644 --- a/packages/frontend/src/pages/pages.vue +++ b/packages/frontend/src/pages/pages.vue @@ -7,30 +7,32 @@ SPDX-License-Identifier: AGPL-3.0-only -
- -
- -
-
-
+ +
+ +
+ +
+
+
-
- - -
- -
-
-
+
+ + +
+ +
+
+
-
- -
- -
-
-
+
+ +
+ +
+
+
+
@@ -40,6 +42,7 @@ import { computed, ref } from 'vue'; import MkPagePreview from '@/components/MkPagePreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { useRouter } from '@/global/router/supplier.js'; diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index 9d5e5697ce..b68de805cf 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -7,18 +7,20 @@ SPDX-License-Identifier: AGPL-3.0-only - -
- -
-
- {{ i18n.ts.notesSearchNotAvailable }} -
-
+ + +
+ +
+
+ {{ i18n.ts.notesSearchNotAvailable }} +
+
- - - + + + +
@@ -29,6 +31,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import { $i } from '@/account.js'; import { instance } from '@/instance.js'; import MkInfo from '@/components/MkInfo.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; const XNote = defineAsyncComponent(() => import('./search.note.vue')); const XUser = defineAsyncComponent(() => import('./search.user.vue')); diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index 607aaec521..e52a5ee04f 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -155,6 +155,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.enableInfiniteScroll }} {{ i18n.ts.keepScreenOn }} {{ i18n.ts.disableStreamingTimeline }} + {{ i18n.ts.enableHorizontalSwipe }}
@@ -296,6 +297,7 @@ const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn')); const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline')); const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications')); const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect')); +const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe')); watch(lang, () => { miLocalStorage.setItem('lang', lang.value as string); diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 6fe8963f51..666a9968b2 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -7,27 +7,28 @@ SPDX-License-Identifier: AGPL-3.0-only -
- - {{ i18n.ts._timelineDescription[src] }} - - - -
-
- + +
+ + {{ i18n.ts._timelineDescription[src] }} + + +
+
+ +
-
+ @@ -38,6 +39,7 @@ import type { Tab } from '@/components/global/MkPageHeader.tabs.vue'; import MkTimeline from '@/components/MkTimeline.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkPostForm from '@/components/MkPostForm.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { scroll } from '@/scripts/scroll.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; @@ -69,7 +71,9 @@ const withRenotes = ref(true); const withReplies = ref($i ? defaultStore.state.tlWithReplies : false); const onlyFiles = ref(false); -watch(src, () => queue.value = 0); +watch(src, () => { + queue.value = 0; +}); watch(withReplies, (x) => { if ($i) defaultStore.set('tlWithReplies', x); diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue index 95869e7b8c..603f1bef33 100644 --- a/packages/frontend/src/pages/user/index.vue +++ b/packages/frontend/src/pages/user/index.vue @@ -8,19 +8,21 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - - - - - - - - - - - + + + + + + + + + + + + + + +
@@ -36,6 +38,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; const XHome = defineAsyncComponent(() => import('./home.vue')); const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue')); @@ -57,6 +60,7 @@ const props = withDefaults(defineProps<{ }); const tab = ref(props.page); + const user = ref(null); const error = ref(null); diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index e3a85377d8..21b796caa1 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -427,6 +427,10 @@ export const defaultStore = markRaw(new Storage('base', { sfxVolume: 1, }, }, + enableHorizontalSwipe: { + where: 'device', + default: true, + }, sound_masterVolume: { where: 'device', From 43401210c3691ffd8f8ac78b67bd5898e7981335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Fri, 19 Jan 2024 07:58:07 +0900 Subject: [PATCH 064/134] refactor: fully typed locales (#13033) * refactor: fully typed locales * refactor: hide parameterized locale strings from type data in ts access * refactor: missing assertions * docs: annotation --- locales/generateDTS.js | 201 ++++++++++++++++++---- locales/index.d.ts | 229 +++++++++++++------------- packages/frontend/src/i18n.ts | 5 +- packages/frontend/src/scripts/i18n.ts | 113 +++++++++++-- 4 files changed, 383 insertions(+), 165 deletions(-) diff --git a/locales/generateDTS.js b/locales/generateDTS.js index d3afdd6e15..6eb5bd630d 100644 --- a/locales/generateDTS.js +++ b/locales/generateDTS.js @@ -6,54 +6,171 @@ import ts from 'typescript'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); +const parameterRegExp = /\{(\w+)\}/g; + +function createMemberType(item) { + if (typeof item !== 'string') { + return ts.factory.createTypeLiteralNode(createMembers(item)); + } + const parameters = Array.from( + item.matchAll(parameterRegExp), + ([, parameter]) => parameter, + ); + if (!parameters.length) { + return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); + } + return ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('ParameterizedString'), + [ + ts.factory.createUnionTypeNode( + parameters.map((parameter) => + ts.factory.createLiteralTypeNode( + ts.factory.createStringLiteral(parameter), + ), + ), + ), + ], + ); +} function createMembers(record) { - return Object.entries(record) - .map(([k, v]) => ts.factory.createPropertySignature( + return Object.entries(record).map(([k, v]) => + ts.factory.createPropertySignature( undefined, ts.factory.createStringLiteral(k), undefined, - typeof v === 'string' - ? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) - : ts.factory.createTypeLiteralNode(createMembers(v)), - )); + createMemberType(v), + ), + ); } export default function generateDTS() { const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8')); const members = createMembers(locale); const elements = [ + ts.factory.createVariableStatement( + [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + ts.factory.createIdentifier('kParameters'), + undefined, + ts.factory.createTypeOperatorNode( + ts.SyntaxKind.UniqueKeyword, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.SymbolKeyword), + ), + undefined, + ), + ], + ts.NodeFlags.Const, + ), + ), + ts.factory.createInterfaceDeclaration( + [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], + ts.factory.createIdentifier('ParameterizedString'), + [ + ts.factory.createTypeParameterDeclaration( + undefined, + ts.factory.createIdentifier('T'), + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('string'), + undefined, + ), + ), + ], + undefined, + [ + ts.factory.createPropertySignature( + undefined, + ts.factory.createComputedPropertyName( + ts.factory.createIdentifier('kParameters'), + ), + undefined, + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('T'), + undefined, + ), + ), + ], + ), + ts.factory.createInterfaceDeclaration( + [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], + ts.factory.createIdentifier('ILocale'), + undefined, + undefined, + [ + ts.factory.createIndexSignature( + undefined, + [ + ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createIdentifier('_'), + undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + undefined, + ), + ], + ts.factory.createUnionTypeNode([ + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('ParameterizedString'), + [ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)], + ), + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('ILocale'), + undefined, + ), + ]), + ), + ], + ), ts.factory.createInterfaceDeclaration( [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier('Locale'), undefined, - undefined, + [ + ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [ + ts.factory.createExpressionWithTypeArguments( + ts.factory.createIdentifier('ILocale'), + undefined, + ), + ]), + ], members, ), ts.factory.createVariableStatement( [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration( - ts.factory.createIdentifier('locales'), - undefined, - ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature( + [ + ts.factory.createVariableDeclaration( + ts.factory.createIdentifier('locales'), undefined, - [ts.factory.createParameterDeclaration( - undefined, - undefined, - ts.factory.createIdentifier('lang'), - undefined, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - undefined, - )], - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('Locale'), - undefined, - ), - )]), - undefined, - )], - ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags, + ts.factory.createTypeLiteralNode([ + ts.factory.createIndexSignature( + undefined, + [ + ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createIdentifier('lang'), + undefined, + ts.factory.createKeywordTypeNode( + ts.SyntaxKind.StringKeyword, + ), + undefined, + ), + ], + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('Locale'), + undefined, + ), + ), + ]), + undefined, + ), + ], + ts.NodeFlags.Const, ), ), ts.factory.createFunctionDeclaration( @@ -70,16 +187,28 @@ export default function generateDTS() { ), ts.factory.createExportDefault(ts.factory.createIdentifier('locales')), ]; - const printed = ts.createPrinter({ - newLine: ts.NewLineKind.LineFeed, - }).printList( - ts.ListFormat.MultiLine, - ts.factory.createNodeArray(elements), - ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS), - ); + const printed = ts + .createPrinter({ + newLine: ts.NewLineKind.LineFeed, + }) + .printList( + ts.ListFormat.MultiLine, + ts.factory.createNodeArray(elements), + ts.createSourceFile( + 'index.d.ts', + '', + ts.ScriptTarget.ESNext, + true, + ts.ScriptKind.TS, + ), + ); - fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */ + fs.writeFileSync( + `${__dirname}/index.d.ts`, + `/* eslint-disable */ // This file is generated by locales/generateDTS.js // Do not edit this file directly. -${printed}`, 'utf-8'); +${printed}`, + 'utf-8', + ); } diff --git a/locales/index.d.ts b/locales/index.d.ts index a659e790cc..a22cb63507 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1,12 +1,19 @@ /* eslint-disable */ // This file is generated by locales/generateDTS.js // Do not edit this file directly. -export interface Locale { +declare const kParameters: unique symbol; +export interface ParameterizedString { + [kParameters]: T; +} +export interface ILocale { + [_: string]: string | ParameterizedString | ILocale; +} +export interface Locale extends ILocale { "_lang_": string; "headlineMisskey": string; "introMisskey": string; - "poweredByMisskeyDescription": string; - "monthAndDay": string; + "poweredByMisskeyDescription": ParameterizedString<"name">; + "monthAndDay": ParameterizedString<"month" | "day">; "search": string; "notifications": string; "username": string; @@ -18,7 +25,7 @@ export interface Locale { "cancel": string; "noThankYou": string; "enterUsername": string; - "renotedBy": string; + "renotedBy": ParameterizedString<"user">; "noNotes": string; "noNotifications": string; "instance": string; @@ -78,8 +85,8 @@ export interface Locale { "export": string; "files": string; "download": string; - "driveFileDeleteConfirm": string; - "unfollowConfirm": string; + "driveFileDeleteConfirm": ParameterizedString<"name">; + "unfollowConfirm": ParameterizedString<"name">; "exportRequested": string; "importRequested": string; "lists": string; @@ -183,9 +190,9 @@ export interface Locale { "wallpaper": string; "setWallpaper": string; "removeWallpaper": string; - "searchWith": string; + "searchWith": ParameterizedString<"q">; "youHaveNoLists": string; - "followConfirm": string; + "followConfirm": ParameterizedString<"name">; "proxyAccount": string; "proxyAccountDescription": string; "host": string; @@ -208,7 +215,7 @@ export interface Locale { "software": string; "version": string; "metadata": string; - "withNFiles": string; + "withNFiles": ParameterizedString<"n">; "monitor": string; "jobQueue": string; "cpuAndMemory": string; @@ -237,7 +244,7 @@ export interface Locale { "processing": string; "preview": string; "default": string; - "defaultValueIs": string; + "defaultValueIs": ParameterizedString<"value">; "noCustomEmojis": string; "noJobs": string; "federating": string; @@ -266,8 +273,8 @@ export interface Locale { "imageUrl": string; "remove": string; "removed": string; - "removeAreYouSure": string; - "deleteAreYouSure": string; + "removeAreYouSure": ParameterizedString<"x">; + "deleteAreYouSure": ParameterizedString<"x">; "resetAreYouSure": string; "areYouSure": string; "saved": string; @@ -285,8 +292,8 @@ export interface Locale { "messageRead": string; "noMoreHistory": string; "startMessaging": string; - "nUsersRead": string; - "agreeTo": string; + "nUsersRead": ParameterizedString<"n">; + "agreeTo": ParameterizedString<"0">; "agree": string; "agreeBelow": string; "basicNotesBeforeCreateAccount": string; @@ -298,7 +305,7 @@ export interface Locale { "images": string; "image": string; "birthday": string; - "yearsOld": string; + "yearsOld": ParameterizedString<"age">; "registeredDate": string; "location": string; "theme": string; @@ -353,9 +360,9 @@ export interface Locale { "thisYear": string; "thisMonth": string; "today": string; - "dayX": string; - "monthX": string; - "yearX": string; + "dayX": ParameterizedString<"day">; + "monthX": ParameterizedString<"month">; + "yearX": ParameterizedString<"year">; "pages": string; "integration": string; "connectService": string; @@ -420,7 +427,7 @@ export interface Locale { "recentlyUpdatedUsers": string; "recentlyRegisteredUsers": string; "recentlyDiscoveredUsers": string; - "exploreUsersCount": string; + "exploreUsersCount": ParameterizedString<"count">; "exploreFediverse": string; "popularTags": string; "userList": string; @@ -437,16 +444,16 @@ export interface Locale { "moderationNote": string; "addModerationNote": string; "moderationLogs": string; - "nUsersMentioned": string; + "nUsersMentioned": ParameterizedString<"n">; "securityKeyAndPasskey": string; "securityKey": string; "lastUsed": string; - "lastUsedAt": string; + "lastUsedAt": ParameterizedString<"t">; "unregister": string; "passwordLessLogin": string; "passwordLessLoginDescription": string; "resetPassword": string; - "newPasswordIs": string; + "newPasswordIs": ParameterizedString<"password">; "reduceUiAnimation": string; "share": string; "notFound": string; @@ -466,7 +473,7 @@ export interface Locale { "enable": string; "next": string; "retype": string; - "noteOf": string; + "noteOf": ParameterizedString<"user">; "quoteAttached": string; "quoteQuestion": string; "noMessagesYet": string; @@ -486,12 +493,12 @@ export interface Locale { "strongPassword": string; "passwordMatched": string; "passwordNotMatched": string; - "signinWith": string; + "signinWith": ParameterizedString<"x">; "signinFailed": string; "or": string; "language": string; "uiLanguage": string; - "aboutX": string; + "aboutX": ParameterizedString<"x">; "emojiStyle": string; "native": string; "disableDrawer": string; @@ -509,7 +516,7 @@ export interface Locale { "regenerate": string; "fontSize": string; "mediaListWithOneImageAppearance": string; - "limitTo": string; + "limitTo": ParameterizedString<"x">; "noFollowRequests": string; "openImageInNewTab": string; "dashboard": string; @@ -587,7 +594,7 @@ export interface Locale { "deleteAllFiles": string; "deleteAllFilesConfirm": string; "removeAllFollowing": string; - "removeAllFollowingDescription": string; + "removeAllFollowingDescription": ParameterizedString<"host">; "userSuspended": string; "userSilenced": string; "yourAccountSuspendedTitle": string; @@ -658,9 +665,9 @@ export interface Locale { "wordMute": string; "hardWordMute": string; "regexpError": string; - "regexpErrorDescription": string; + "regexpErrorDescription": ParameterizedString<"tab" | "line">; "instanceMute": string; - "userSaysSomething": string; + "userSaysSomething": ParameterizedString<"name">; "makeActive": string; "display": string; "copy": string; @@ -686,7 +693,7 @@ export interface Locale { "abuseReports": string; "reportAbuse": string; "reportAbuseRenote": string; - "reportAbuseOf": string; + "reportAbuseOf": ParameterizedString<"name">; "fillAbuseReportDescription": string; "abuseReported": string; "reporter": string; @@ -701,7 +708,7 @@ export interface Locale { "defaultNavigationBehaviour": string; "editTheseSettingsMayBreakAccount": string; "instanceTicker": string; - "waitingFor": string; + "waitingFor": ParameterizedString<"x">; "random": string; "system": string; "switchUi": string; @@ -711,10 +718,10 @@ export interface Locale { "optional": string; "createNewClip": string; "unclip": string; - "confirmToUnclipAlreadyClippedNote": string; + "confirmToUnclipAlreadyClippedNote": ParameterizedString<"name">; "public": string; "private": string; - "i18nInfo": string; + "i18nInfo": ParameterizedString<"link">; "manageAccessTokens": string; "accountInfo": string; "notesCount": string; @@ -764,9 +771,9 @@ export interface Locale { "needReloadToApply": string; "showTitlebar": string; "clearCache": string; - "onlineUsersCount": string; - "nUsers": string; - "nNotes": string; + "onlineUsersCount": ParameterizedString<"n">; + "nUsers": ParameterizedString<"n">; + "nNotes": ParameterizedString<"n">; "sendErrorReports": string; "sendErrorReportsDescription": string; "myTheme": string; @@ -798,7 +805,7 @@ export interface Locale { "publish": string; "inChannelSearch": string; "useReactionPickerForContextMenu": string; - "typingUsers": string; + "typingUsers": ParameterizedString<"users">; "jumpToSpecifiedDate": string; "showingPastTimeline": string; "clear": string; @@ -865,7 +872,7 @@ export interface Locale { "misskeyUpdated": string; "whatIsNew": string; "translate": string; - "translatedFrom": string; + "translatedFrom": ParameterizedString<"x">; "accountDeletionInProgress": string; "usernameInfo": string; "aiChanMode": string; @@ -896,11 +903,11 @@ export interface Locale { "continueThread": string; "deleteAccountConfirm": string; "incorrectPassword": string; - "voteConfirm": string; + "voteConfirm": ParameterizedString<"choice">; "hide": string; "useDrawerReactionPickerForMobile": string; - "welcomeBackWithName": string; - "clickToFinishEmailVerification": string; + "welcomeBackWithName": ParameterizedString<"name">; + "clickToFinishEmailVerification": ParameterizedString<"ok">; "overridedDeviceKind": string; "smartphone": string; "tablet": string; @@ -928,8 +935,8 @@ export interface Locale { "cropYes": string; "cropNo": string; "file": string; - "recentNHours": string; - "recentNDays": string; + "recentNHours": ParameterizedString<"n">; + "recentNDays": ParameterizedString<"n">; "noEmailServerWarning": string; "thereIsUnresolvedAbuseReportWarning": string; "recommended": string; @@ -938,7 +945,7 @@ export interface Locale { "driveCapOverrideCaption": string; "requireAdminForView": string; "isSystemAccount": string; - "typeToConfirm": string; + "typeToConfirm": ParameterizedString<"x">; "deleteAccount": string; "document": string; "numberOfPageCache": string; @@ -992,7 +999,7 @@ export interface Locale { "neverShow": string; "remindMeLater": string; "didYouLikeMisskey": string; - "pleaseDonate": string; + "pleaseDonate": ParameterizedString<"host">; "roles": string; "role": string; "noRole": string; @@ -1090,7 +1097,7 @@ export interface Locale { "preservedUsernamesDescription": string; "createNoteFromTheFile": string; "archive": string; - "channelArchiveConfirmTitle": string; + "channelArchiveConfirmTitle": ParameterizedString<"name">; "channelArchiveConfirmDescription": string; "thisChannelArchived": string; "displayOfNote": string; @@ -1120,8 +1127,8 @@ export interface Locale { "createCount": string; "inviteCodeCreated": string; "inviteLimitExceeded": string; - "createLimitRemaining": string; - "inviteLimitResetCycle": string; + "createLimitRemaining": ParameterizedString<"limit">; + "inviteLimitResetCycle": ParameterizedString<"time" | "limit">; "expirationDate": string; "noExpirationDate": string; "inviteCodeUsedAt": string; @@ -1134,7 +1141,7 @@ export interface Locale { "expired": string; "doYouAgree": string; "beSureToReadThisAsItIsImportant": string; - "iHaveReadXCarefullyAndAgree": string; + "iHaveReadXCarefullyAndAgree": ParameterizedString<"x">; "dialog": string; "icon": string; "forYou": string; @@ -1189,7 +1196,7 @@ export interface Locale { "doReaction": string; "code": string; "reloadRequiredToApplySettings": string; - "remainingN": string; + "remainingN": ParameterizedString<"n">; "overwriteContentConfirm": string; "seasonalScreenEffect": string; "decorate": string; @@ -1202,7 +1209,7 @@ export interface Locale { "replay": string; "replaying": string; "ranking": string; - "lastNDays": string; + "lastNDays": ParameterizedString<"n">; "backToTitle": string; "enableHorizontalSwipe": string; "_bubbleGame": { @@ -1221,7 +1228,7 @@ export interface Locale { "end": string; "tooManyActiveAnnouncementDescription": string; "readConfirmTitle": string; - "readConfirmText": string; + "readConfirmText": ParameterizedString<"title">; "shouldNotBeUsedToPresentPermanentInfo": string; "dialogAnnouncementUxWarn": string; "silence": string; @@ -1236,10 +1243,10 @@ export interface Locale { "theseSettingsCanEditLater": string; "youCanEditMoreSettingsInSettingsPageLater": string; "followUsers": string; - "pushNotificationDescription": string; + "pushNotificationDescription": ParameterizedString<"name">; "initialAccountSettingCompleted": string; - "haveFun": string; - "youCanContinueTutorial": string; + "haveFun": ParameterizedString<"name">; + "youCanContinueTutorial": ParameterizedString<"name">; "startTutorial": string; "skipAreYouSure": string; "laterAreYouSure": string; @@ -1277,7 +1284,7 @@ export interface Locale { "social": string; "global": string; "description2": string; - "description3": string; + "description3": ParameterizedString<"link">; }; "_postNote": { "title": string; @@ -1315,7 +1322,7 @@ export interface Locale { }; "_done": { "title": string; - "description": string; + "description": ParameterizedString<"link">; }; }; "_timelineDescription": { @@ -1329,10 +1336,10 @@ export interface Locale { }; "_serverSettings": { "iconUrl": string; - "appIconDescription": string; + "appIconDescription": ParameterizedString<"host">; "appIconUsageExample": string; "appIconStyleRecommendation": string; - "appIconResolutionMustBe": string; + "appIconResolutionMustBe": ParameterizedString<"resolution">; "manifestJsonOverride": string; "shortName": string; "shortNameDescription": string; @@ -1343,7 +1350,7 @@ export interface Locale { "_accountMigration": { "moveFrom": string; "moveFromSub": string; - "moveFromLabel": string; + "moveFromLabel": ParameterizedString<"n">; "moveFromDescription": string; "moveTo": string; "moveToLabel": string; @@ -1351,7 +1358,7 @@ export interface Locale { "moveAccountDescription": string; "moveAccountHowTo": string; "startMigration": string; - "migrationConfirm": string; + "migrationConfirm": ParameterizedString<"account">; "movedAndCannotBeUndone": string; "postMigrationNote": string; "movedTo": string; @@ -1793,7 +1800,7 @@ export interface Locale { "_signup": { "almostThere": string; "emailAddressInfo": string; - "emailSent": string; + "emailSent": ParameterizedString<"email">; }; "_accountDelete": { "accountDelete": string; @@ -1846,14 +1853,14 @@ export interface Locale { "save": string; "inputName": string; "cannotSave": string; - "nameAlreadyExists": string; - "applyConfirm": string; - "saveConfirm": string; - "deleteConfirm": string; - "renameConfirm": string; + "nameAlreadyExists": ParameterizedString<"name">; + "applyConfirm": ParameterizedString<"name">; + "saveConfirm": ParameterizedString<"name">; + "deleteConfirm": ParameterizedString<"name">; + "renameConfirm": ParameterizedString<"old" | "new">; "noBackups": string; - "createdAt": string; - "updatedAt": string; + "createdAt": ParameterizedString<"date" | "time">; + "updatedAt": ParameterizedString<"date" | "time">; "cannotLoad": string; "invalidFile": string; }; @@ -1898,8 +1905,8 @@ export interface Locale { "featured": string; "owned": string; "following": string; - "usersCount": string; - "notesCount": string; + "usersCount": ParameterizedString<"n">; + "notesCount": ParameterizedString<"n">; "nameAndDescription": string; "nameOnly": string; "allowRenoteToExternal": string; @@ -1927,7 +1934,7 @@ export interface Locale { "manage": string; "code": string; "description": string; - "installed": string; + "installed": ParameterizedString<"name">; "installedThemes": string; "builtinThemes": string; "alreadyInstalled": string; @@ -1950,7 +1957,7 @@ export interface Locale { "lighten": string; "inputConstantName": string; "importInfo": string; - "deleteConstantConfirm": string; + "deleteConstantConfirm": ParameterizedString<"const">; "keys": { "accent": string; "bg": string; @@ -2013,23 +2020,23 @@ export interface Locale { "_ago": { "future": string; "justNow": string; - "secondsAgo": string; - "minutesAgo": string; - "hoursAgo": string; - "daysAgo": string; - "weeksAgo": string; - "monthsAgo": string; - "yearsAgo": string; + "secondsAgo": ParameterizedString<"n">; + "minutesAgo": ParameterizedString<"n">; + "hoursAgo": ParameterizedString<"n">; + "daysAgo": ParameterizedString<"n">; + "weeksAgo": ParameterizedString<"n">; + "monthsAgo": ParameterizedString<"n">; + "yearsAgo": ParameterizedString<"n">; "invalid": string; }; "_timeIn": { - "seconds": string; - "minutes": string; - "hours": string; - "days": string; - "weeks": string; - "months": string; - "years": string; + "seconds": ParameterizedString<"n">; + "minutes": ParameterizedString<"n">; + "hours": ParameterizedString<"n">; + "days": ParameterizedString<"n">; + "weeks": ParameterizedString<"n">; + "months": ParameterizedString<"n">; + "years": ParameterizedString<"n">; }; "_time": { "second": string; @@ -2040,7 +2047,7 @@ export interface Locale { "_2fa": { "alreadyRegistered": string; "registerTOTP": string; - "step1": string; + "step1": ParameterizedString<"a" | "b">; "step2": string; "step2Click": string; "step2Uri": string; @@ -2055,7 +2062,7 @@ export interface Locale { "securityKeyName": string; "tapSecurityKey": string; "removeKey": string; - "removeKeyConfirm": string; + "removeKeyConfirm": ParameterizedString<"name">; "whyTOTPOnlyRenew": string; "renewTOTP": string; "renewTOTPConfirm": string; @@ -2156,9 +2163,9 @@ export interface Locale { }; "_auth": { "shareAccessTitle": string; - "shareAccess": string; + "shareAccess": ParameterizedString<"name">; "shareAccessAsk": string; - "permission": string; + "permission": ParameterizedString<"name">; "permissionAsk": string; "pleaseGoBack": string; "callback": string; @@ -2217,12 +2224,12 @@ export interface Locale { "_cw": { "hide": string; "show": string; - "chars": string; - "files": string; + "chars": ParameterizedString<"count">; + "files": ParameterizedString<"count">; }; "_poll": { "noOnlyOneChoice": string; - "choiceN": string; + "choiceN": ParameterizedString<"n">; "noMore": string; "canMultipleVote": string; "expiration": string; @@ -2232,16 +2239,16 @@ export interface Locale { "deadlineDate": string; "deadlineTime": string; "duration": string; - "votesCount": string; - "totalVotes": string; + "votesCount": ParameterizedString<"n">; + "totalVotes": ParameterizedString<"n">; "vote": string; "showResult": string; "voted": string; "closed": string; - "remainingDays": string; - "remainingHours": string; - "remainingMinutes": string; - "remainingSeconds": string; + "remainingDays": ParameterizedString<"d" | "h">; + "remainingHours": ParameterizedString<"h" | "m">; + "remainingMinutes": ParameterizedString<"m" | "s">; + "remainingSeconds": ParameterizedString<"s">; }; "_visibility": { "public": string; @@ -2281,7 +2288,7 @@ export interface Locale { "changeAvatar": string; "changeBanner": string; "verifiedLinkDescription": string; - "avatarDecorationMax": string; + "avatarDecorationMax": ParameterizedString<"max">; }; "_exportOrImport": { "allNotes": string; @@ -2404,16 +2411,16 @@ export interface Locale { }; "_notification": { "fileUploaded": string; - "youGotMention": string; - "youGotReply": string; - "youGotQuote": string; - "youRenoted": string; + "youGotMention": ParameterizedString<"name">; + "youGotReply": ParameterizedString<"name">; + "youGotQuote": ParameterizedString<"name">; + "youRenoted": ParameterizedString<"name">; "youWereFollowed": string; "youReceivedFollowRequest": string; "yourFollowRequestAccepted": string; "pollEnded": string; "newNote": string; - "unreadAntennaNote": string; + "unreadAntennaNote": ParameterizedString<"name">; "roleAssigned": string; "emptyPushNotificationMessage": string; "achievementEarned": string; @@ -2421,9 +2428,9 @@ export interface Locale { "checkNotificationBehavior": string; "sendTestNotification": string; "notificationWillBeDisplayedLikeThis": string; - "reactedBySomeUsers": string; - "renotedBySomeUsers": string; - "followedBySomeUsers": string; + "reactedBySomeUsers": ParameterizedString<"n">; + "renotedBySomeUsers": ParameterizedString<"n">; + "followedBySomeUsers": ParameterizedString<"n">; "_types": { "all": string; "note": string; @@ -2480,8 +2487,8 @@ export interface Locale { }; }; "_dialog": { - "charactersExceeded": string; - "charactersBelow": string; + "charactersExceeded": ParameterizedString<"current" | "max">; + "charactersBelow": ParameterizedString<"current" | "min">; }; "_disabledTimeline": { "title": string; diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts index 858db74dac..c5c4ccf820 100644 --- a/packages/frontend/src/i18n.ts +++ b/packages/frontend/src/i18n.ts @@ -10,6 +10,7 @@ import { I18n } from '@/scripts/i18n.js'; export const i18n = markRaw(new I18n(locale)); -export function updateI18n(newLocale) { - i18n.ts = newLocale; +export function updateI18n(newLocale: Locale) { + // @ts-expect-error -- private field + i18n.locale = newLocale; } diff --git a/packages/frontend/src/scripts/i18n.ts b/packages/frontend/src/scripts/i18n.ts index 8e5f17f38a..55b5371950 100644 --- a/packages/frontend/src/scripts/i18n.ts +++ b/packages/frontend/src/scripts/i18n.ts @@ -2,33 +2,114 @@ * SPDX-FileCopyrightText: syuilo and other misskey contributors * SPDX-License-Identifier: AGPL-3.0-only */ +import type { ILocale, ParameterizedString } from '../../../../locales/index.js'; -export class I18n> { - public ts: T; +type FlattenKeys = keyof { + [K in keyof T as T[K] extends ILocale + ? FlattenKeys extends infer C extends string + ? `${K & string}.${C}` + : never + : T[K] extends TPrediction + ? K + : never]: T[K]; +}; - constructor(locale: T) { - this.ts = locale; +type ParametersOf>> = T extends ILocale + ? TKey extends `${infer K}.${infer C}` + // @ts-expect-error -- C は明らかに FlattenKeys> になるが、型システムはここでは TKey がドット区切りであることのコンテキストを持たないので、型システムに合法にて示すことはできない。 + ? ParametersOf + : TKey extends keyof T + ? T[TKey] extends ParameterizedString + ? P + : never + : never + : never; +type Ts = { + readonly [K in keyof T as T[K] extends ParameterizedString ? never : K]: T[K] extends ILocale ? Ts : string; +}; + +export class I18n { + constructor(private locale: T) { //#region BIND this.t = this.t.bind(this); //#endregion } - // string にしているのは、ドット区切りでのパス指定を許可するため - // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも - public t(key: string, args?: Record): string { - try { - let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string; + public get ts(): Ts { + if (_DEV_) { + class Handler implements ProxyHandler { + get(target: TTarget, p: string | symbol): unknown { + const value = target[p as keyof TTarget]; - if (args) { - for (const [k, v] of Object.entries(args)) { - str = str.replace(`{${k}}`, v.toString()); + if (typeof value === 'object') { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- 実際には null がくることはないので。 + return new Proxy(value!, new Handler()); + } + + if (typeof value === 'string') { + const parameters = Array.from(value.matchAll(/\{(\w+)\}/g)).map(([, parameter]) => parameter); + + if (parameters.length) { + console.error(`Missing locale parameters: ${parameters.join(', ')} at ${String(p)}`); + } + + return value; + } + + console.error(`Unexpected locale key: ${String(p)}`); + + return p; } } - return str; - } catch (err) { - console.warn(`missing localization '${key}'`); - return key; + + return new Proxy(this.locale, new Handler()) as Ts; } + + return this.locale as Ts; + } + + /** + * @deprecated なるべくこのメソッド使うよりも locale 直接参照の方が vue のキャッシュ効いてパフォーマンスが良いかも + */ + public t>(key: TKey): string; + public t>>(key: TKey, args: { readonly [_ in ParametersOf]: string | number }): string; + public t(key: string, args?: { readonly [_: string]: string | number }) { + let str: string | ParameterizedString | ILocale = this.locale; + + for (const k of key.split('.')) { + str = str[k]; + + if (_DEV_) { + if (typeof str === 'undefined') { + console.error(`Unexpected locale key: ${key}`); + return key; + } + } + } + + if (args) { + if (_DEV_) { + const missing = Array.from((str as string).matchAll(/\{(\w+)\}/g), ([, parameter]) => parameter).filter(parameter => !Object.hasOwn(args, parameter)); + + if (missing.length) { + console.error(`Missing locale parameters: ${missing.join(', ')} at ${key}`); + } + } + + for (const [k, v] of Object.entries(args)) { + const search = `{${k}}`; + + if (_DEV_) { + if (!(str as string).includes(search)) { + console.error(`Unexpected locale parameter: ${k} at ${key}`); + } + } + + str = (str as string).replace(search, v.toString()); + } + } + + return str; } } From d85085d16fb6952e742eeca38b98f9442b7ec984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Fri, 19 Jan 2024 11:54:00 +0900 Subject: [PATCH 065/134] refactor: style --- packages/frontend/src/scripts/i18n.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/scripts/i18n.ts b/packages/frontend/src/scripts/i18n.ts index 55b5371950..3366f3eac3 100644 --- a/packages/frontend/src/scripts/i18n.ts +++ b/packages/frontend/src/scripts/i18n.ts @@ -48,7 +48,7 @@ export class I18n { } if (typeof value === 'string') { - const parameters = Array.from(value.matchAll(/\{(\w+)\}/g)).map(([, parameter]) => parameter); + const parameters = Array.from(value.matchAll(/\{(\w+)\}/g), ([, parameter]) => parameter); if (parameters.length) { console.error(`Missing locale parameters: ${parameters.join(', ')} at ${String(p)}`); From 3ad273237557eefd75a715fe3bcb86f3d2703396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:19:06 +0900 Subject: [PATCH 066/134] =?UTF-8?q?fix(frontend/HorizontalSwipe):=20?= =?UTF-8?q?=E3=83=9A=E3=83=BC=E3=82=B8=E3=81=AE=E8=A6=81=E7=B4=A0=E3=81=8C?= =?UTF-8?q?=E3=81=AF=E3=81=BF=E5=87=BA=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#13036)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/MkHorizontalSwipe.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue index 2c62aadbf4..a7d0d5a3e4 100644 --- a/packages/frontend/src/components/MkHorizontalSwipe.vue +++ b/packages/frontend/src/components/MkHorizontalSwipe.vue @@ -180,6 +180,7 @@ watch(tabModel, (newTab, oldTab) => { diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue new file mode 100644 index 0000000000..301a177de1 --- /dev/null +++ b/packages/frontend/src/pages/reversi/game.setting.vue @@ -0,0 +1,236 @@ + + + + + + + diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue new file mode 100644 index 0000000000..dbbeb20f42 --- /dev/null +++ b/packages/frontend/src/pages/reversi/game.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue new file mode 100644 index 0000000000..c483e36c24 --- /dev/null +++ b/packages/frontend/src/pages/reversi/index.vue @@ -0,0 +1,271 @@ + + + + + + + diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 98fe0043c1..8cdc7b59c6 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -103,7 +103,7 @@ export function getConfig(): UserConfig { // https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies optimizeDeps: { - include: ['misskey-js'], + include: ['misskey-js', 'misskey-reversi'], }, build: { @@ -135,7 +135,7 @@ export function getConfig(): UserConfig { // https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies commonjsOptions: { - include: [/misskey-js/, /node_modules/], + include: [/misskey-js/, /misskey-reversi/, /node_modules/], }, }, diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index f955cc5cc1..2b95e01533 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1623,6 +1623,16 @@ declare namespace entities { BubbleGameRegisterResponse, BubbleGameRankingRequest, BubbleGameRankingResponse, + ReversiCancelMatchRequest, + ReversiCancelMatchResponse, + ReversiGamesRequest, + ReversiGamesResponse, + ReversiMatchRequest, + ReversiMatchResponse, + ReversiInvitationsResponse, + ReversiShowGameRequest, + ReversiShowGameResponse, + ReversiSurrenderRequest, Error_2 as Error, UserLite, UserDetailedNotMeOnly, @@ -1659,7 +1669,9 @@ declare namespace entities { Flash, Signin, RoleLite, - Role + Role, + ReversiGameLite, + ReversiGameDetailed } } export { entities } @@ -2596,6 +2608,42 @@ type ResetPasswordRequest = operations['reset-password']['requestBody']['content // @public (undocumented) type RetentionResponse = operations['retention']['responses']['200']['content']['application/json']; +// @public (undocumented) +type ReversiCancelMatchRequest = operations['reversi/cancel-match']['requestBody']['content']['application/json']; + +// @public (undocumented) +type ReversiCancelMatchResponse = operations['reversi/cancel-match']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type ReversiGameDetailed = components['schemas']['ReversiGameDetailed']; + +// @public (undocumented) +type ReversiGameLite = components['schemas']['ReversiGameLite']; + +// @public (undocumented) +type ReversiGamesRequest = operations['reversi/games']['requestBody']['content']['application/json']; + +// @public (undocumented) +type ReversiGamesResponse = operations['reversi/games']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type ReversiInvitationsResponse = operations['reversi/invitations']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type ReversiMatchRequest = operations['reversi/match']['requestBody']['content']['application/json']; + +// @public (undocumented) +type ReversiMatchResponse = operations['reversi/match']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json']; + +// @public (undocumented) +type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json']; + // @public (undocumented) type Role = components['schemas']['Role']; diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index b60f449a71..e4e7d13668 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -1,6 +1,6 @@ /* * version: 2023.12.2 - * generatedAt: 2024-01-13T04:31:38.782Z + * generatedAt: 2024-01-19T11:00:07.160Z */ import type { SwitchCaseResponseType } from '../api.js'; @@ -4007,5 +4007,71 @@ declare module '../api.js' { params: P, credential?: string | null, ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; } } diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index dc591a7046..671abd78ce 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -1,6 +1,6 @@ /* * version: 2023.12.2 - * generatedAt: 2024-01-13T04:31:38.778Z + * generatedAt: 2024-01-19T11:00:07.158Z */ import type { @@ -544,6 +544,16 @@ import type { BubbleGameRegisterResponse, BubbleGameRankingRequest, BubbleGameRankingResponse, + ReversiCancelMatchRequest, + ReversiCancelMatchResponse, + ReversiGamesRequest, + ReversiGamesResponse, + ReversiMatchRequest, + ReversiMatchResponse, + ReversiInvitationsResponse, + ReversiShowGameRequest, + ReversiShowGameResponse, + ReversiSurrenderRequest, } from './entities.js'; export type Endpoints = { @@ -907,4 +917,10 @@ export type Endpoints = { 'retention': { req: EmptyRequest; res: RetentionResponse }; 'bubble-game/register': { req: BubbleGameRegisterRequest; res: BubbleGameRegisterResponse }; 'bubble-game/ranking': { req: BubbleGameRankingRequest; res: BubbleGameRankingResponse }; + 'reversi/cancel-match': { req: ReversiCancelMatchRequest; res: ReversiCancelMatchResponse }; + 'reversi/games': { req: ReversiGamesRequest; res: ReversiGamesResponse }; + 'reversi/match': { req: ReversiMatchRequest; res: ReversiMatchResponse }; + 'reversi/invitations': { req: EmptyRequest; res: ReversiInvitationsResponse }; + 'reversi/show-game': { req: ReversiShowGameRequest; res: ReversiShowGameResponse }; + 'reversi/surrender': { req: ReversiSurrenderRequest; res: EmptyResponse }; } diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index dfe24ce0d8..c14876c0e3 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -1,6 +1,6 @@ /* * version: 2023.12.2 - * generatedAt: 2024-01-13T04:31:38.775Z + * generatedAt: 2024-01-19T11:00:07.156Z */ import { operations } from './types.js'; @@ -546,3 +546,13 @@ export type BubbleGameRegisterRequest = operations['bubble-game/register']['requ export type BubbleGameRegisterResponse = operations['bubble-game/register']['responses']['200']['content']['application/json']; export type BubbleGameRankingRequest = operations['bubble-game/ranking']['requestBody']['content']['application/json']; export type BubbleGameRankingResponse = operations['bubble-game/ranking']['responses']['200']['content']['application/json']; +export type ReversiCancelMatchRequest = operations['reversi/cancel-match']['requestBody']['content']['application/json']; +export type ReversiCancelMatchResponse = operations['reversi/cancel-match']['responses']['200']['content']['application/json']; +export type ReversiGamesRequest = operations['reversi/games']['requestBody']['content']['application/json']; +export type ReversiGamesResponse = operations['reversi/games']['responses']['200']['content']['application/json']; +export type ReversiMatchRequest = operations['reversi/match']['requestBody']['content']['application/json']; +export type ReversiMatchResponse = operations['reversi/match']['responses']['200']['content']['application/json']; +export type ReversiInvitationsResponse = operations['reversi/invitations']['responses']['200']['content']['application/json']; +export type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json']; +export type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json']; +export type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts index 5c6bebf2fd..78f14d2250 100644 --- a/packages/misskey-js/src/autogen/models.ts +++ b/packages/misskey-js/src/autogen/models.ts @@ -1,6 +1,6 @@ /* * version: 2023.12.2 - * generatedAt: 2024-01-13T04:31:38.773Z + * generatedAt: 2024-01-19T11:00:07.155Z */ import { components } from './types.js'; @@ -41,3 +41,5 @@ export type Flash = components['schemas']['Flash']; export type Signin = components['schemas']['Signin']; export type RoleLite = components['schemas']['RoleLite']; export type Role = components['schemas']['Role']; +export type ReversiGameLite = components['schemas']['ReversiGameLite']; +export type ReversiGameDetailed = components['schemas']['ReversiGameDetailed']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 76e2b5309c..36facf6e28 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -3,7 +3,7 @@ /* * version: 2023.12.2 - * generatedAt: 2024-01-13T04:31:38.633Z + * generatedAt: 2024-01-19T11:00:07.077Z */ /** @@ -3472,6 +3472,60 @@ export type paths = { */ post: operations['bubble-game/ranking']; }; + '/reversi/cancel-match': { + /** + * reversi/cancel-match + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['reversi/cancel-match']; + }; + '/reversi/games': { + /** + * reversi/games + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['reversi/games']; + }; + '/reversi/match': { + /** + * reversi/match + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['reversi/match']; + }; + '/reversi/invitations': { + /** + * reversi/invitations + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + post: operations['reversi/invitations']; + }; + '/reversi/show-game': { + /** + * reversi/show-game + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['reversi/show-game']; + }; + '/reversi/surrender': { + /** + * reversi/surrender + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['reversi/surrender']; + }; }; export type webhooks = Record; @@ -4404,6 +4458,72 @@ export type components = { }; usersCount: number; }); + ReversiGameLite: { + /** Format: id */ + id: string; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + startedAt: string | null; + isStarted: boolean; + isEnded: boolean; + form1: Record | null; + form2: Record | null; + user1Ready: boolean; + user2Ready: boolean; + /** Format: id */ + user1Id: string; + /** Format: id */ + user2Id: string; + user1: components['schemas']['User']; + user2: components['schemas']['User']; + /** Format: id */ + winnerId: string | null; + winner: components['schemas']['User'] | null; + /** Format: id */ + surrendered: string | null; + black: number | null; + bw: string; + isLlotheo: boolean; + canPutEverywhere: boolean; + loopedBoard: boolean; + }; + ReversiGameDetailed: { + /** Format: id */ + id: string; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + startedAt: string | null; + isStarted: boolean; + isEnded: boolean; + form1: Record | null; + form2: Record | null; + user1Ready: boolean; + user2Ready: boolean; + /** Format: id */ + user1Id: string; + /** Format: id */ + user2Id: string; + user1: components['schemas']['User']; + user2: components['schemas']['User']; + /** Format: id */ + winnerId: string | null; + winner: components['schemas']['User'] | null; + /** Format: id */ + surrendered: string | null; + black: number | null; + bw: string; + isLlotheo: boolean; + canPutEverywhere: boolean; + loopedBoard: boolean; + logs: { + at: number; + color: boolean; + pos: number; + }[]; + map: string[]; + }; }; responses: never; parameters: never; @@ -25542,5 +25662,325 @@ export type operations = { }; }; }; + /** + * reversi/cancel-match + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + 'reversi/cancel-match': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + userId?: string | null; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': unknown; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * reversi/games + * @description No description provided. + * + * **Credential required**: *No* + */ + 'reversi/games': { + requestBody: { + content: { + 'application/json': { + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default false */ + my?: boolean; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['ReversiGameLite'][]; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * reversi/match + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + 'reversi/match': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + userId?: string | null; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': unknown; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * reversi/invitations + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + 'reversi/invitations': { + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['UserLite'][]; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * reversi/show-game + * @description No description provided. + * + * **Credential required**: *No* + */ + 'reversi/show-game': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + gameId: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['ReversiGameDetailed']; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * reversi/surrender + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + 'reversi/surrender': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + gameId: string; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; }; diff --git a/packages/misskey-reversi/package.json b/packages/misskey-reversi/package.json new file mode 100644 index 0000000000..8d3ca30166 --- /dev/null +++ b/packages/misskey-reversi/package.json @@ -0,0 +1,26 @@ +{ + "name": "misskey-reversi", + "version": "0.0.1", + "main": "./built/index.js", + "types": "./built/index.d.ts", + "scripts": { + "build": "tsc", + "watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run build\"", + "eslint": "eslint . --ext .js,.jsx,.ts,.tsx", + "typecheck": "tsc --noEmit", + "lint": "pnpm typecheck && pnpm eslint" + }, + "devDependencies": { + "@misskey-dev/eslint-plugin": "1.0.0", + "@types/node": "20.11.5", + "@typescript-eslint/eslint-plugin": "6.19.0", + "@typescript-eslint/parser": "6.19.0", + "eslint": "8.56.0", + "typescript": "5.3.3" + }, + "files": [ + "built" + ], + "dependencies": { + } +} diff --git a/packages/misskey-reversi/src/game.ts b/packages/misskey-reversi/src/game.ts new file mode 100644 index 0000000000..55d0b84da7 --- /dev/null +++ b/packages/misskey-reversi/src/game.ts @@ -0,0 +1,216 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * true ... 黒 + * false ... 白 + */ +export type Color = boolean; +const BLACK = true; +const WHITE = false; + +export type MapCell = 'null' | 'empty'; + +export type Options = { + isLlotheo: boolean; + canPutEverywhere: boolean; + loopedBoard: boolean; +}; + +export type Undo = { + color: Color; + pos: number; + + /** + * 反転した石の位置の配列 + */ + effects: number[]; + + turn: Color | null; +}; + +export class Game { + public map: MapCell[]; + public mapWidth: number; + public mapHeight: number; + public board: (Color | null | undefined)[]; + public turn: Color | null = BLACK; + public opts: Options; + + public prevPos = -1; + public prevColor: Color | null = null; + + private logs: Undo[] = []; + + constructor(map: string[], opts: Options) { + //#region binds + this.put = this.put.bind(this); + //#endregion + + //#region Options + this.opts = opts; + if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; + if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; + if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; + //#endregion + + //#region Parse map data + this.mapWidth = map[0].length; + this.mapHeight = map.length; + const mapData = map.join(''); + + this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined); + + this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null'); + //#endregion + + // ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある + if (!this.canPutSomewhere(BLACK)) + this.turn = this.canPutSomewhere(WHITE) ? WHITE : null; + } + + public get blackCount() { + return this.board.filter(x => x === BLACK).length; + } + + public get whiteCount() { + return this.board.filter(x => x === WHITE).length; + } + + public posToXy(pos: number): number[] { + const x = pos % this.mapWidth; + const y = Math.floor(pos / this.mapWidth); + return [x, y]; + } + + public xyToPos(x: number, y: number): number { + return x + (y * this.mapWidth); + } + + public put(color: Color, pos: number) { + this.prevPos = pos; + this.prevColor = color; + + this.board[pos] = color; + + // 反転させられる石を取得 + const effects = this.effects(color, pos); + + // 反転させる + for (const pos of effects) { + this.board[pos] = color; + } + + const turn = this.turn; + + this.logs.push({ + color, + pos, + effects, + turn + }); + + this.calcTurn(); + } + + private calcTurn() { + // ターン計算 + this.turn = + this.canPutSomewhere(!this.prevColor) ? !this.prevColor : + this.canPutSomewhere(this.prevColor!) ? this.prevColor : + null; + } + + public undo() { + const undo = this.logs.pop()!; + this.prevColor = undo.color; + this.prevPos = undo.pos; + this.board[undo.pos] = null; + for (const pos of undo.effects) { + const color = this.board[pos]; + this.board[pos] = !color; + } + this.turn = undo.turn; + } + + public mapDataGet(pos: number): MapCell { + const [x, y] = this.posToXy(pos); + return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos]; + } + + public getPuttablePlaces(color: Color): number[] { + return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); + } + + public canPutSomewhere(color: Color): boolean { + return this.getPuttablePlaces(color).length > 0; + } + + public canPut(color: Color, pos: number): boolean { + return ( + this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない + this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード + this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか + } + + /** + * 指定のマスに石を置いた時の、反転させられる石を取得します + * @param color 自分の色 + * @param initPos 位置 + */ + public effects(color: Color, initPos: number): number[] { + const enemyColor = !color; + + const diffVectors: [number, number][] = [ + [ 0, -1], // 上 + [+1, -1], // 右上 + [+1, 0], // 右 + [+1, +1], // 右下 + [ 0, +1], // 下 + [-1, +1], // 左下 + [-1, 0], // 左 + [-1, -1] // 左上 + ]; + + const effectsInLine = ([dx, dy]: [number, number]): number[] => { + const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy]; + + const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列 + let [x, y] = this.posToXy(initPos); + while (true) { + [x, y] = nextPos(x, y); + + // 座標が指し示す位置がボード外に出たとき + if (this.opts.loopedBoard && this.xyToPos( + (x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), + (y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) + // 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ) + return found; + else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) + return []; // 挟めないことが確定 (盤面外に到達) + + const pos = this.xyToPos(x, y); + if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達) + const stone = this.board[pos]; + if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達) + if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見) + if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見) + } + }; + + return ([] as number[]).concat(...diffVectors.map(effectsInLine)); + } + + public get isEnded(): boolean { + return this.turn === null; + } + + public get winner(): Color | null { + return this.isEnded ? + this.blackCount == this.whiteCount ? null : + this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : + undefined as never; + } +} diff --git a/packages/misskey-reversi/src/index.ts b/packages/misskey-reversi/src/index.ts new file mode 100644 index 0000000000..20ed36f208 --- /dev/null +++ b/packages/misskey-reversi/src/index.ts @@ -0,0 +1,7 @@ +import { Game } from './game.js'; + +export { + Game, +}; + +export * as maps from './maps.js'; diff --git a/packages/misskey-reversi/src/maps.ts b/packages/misskey-reversi/src/maps.ts new file mode 100644 index 0000000000..85cf1a0485 --- /dev/null +++ b/packages/misskey-reversi/src/maps.ts @@ -0,0 +1,715 @@ +/** + * 組み込みマップ定義 + * + * データ値: + * (スペース) ... マス無し + * - ... マス + * b ... 初期配置される黒石 + * w ... 初期配置される白石 + */ + +export type Map = { + name?: string; + category?: string; + author?: string; + data: string[]; +}; + +export const fourfour: Map = { + name: '4x4', + category: '4x4', + data: [ + '----', + '-wb-', + '-bw-', + '----' + ] +}; + +export const sixsix: Map = { + name: '6x6', + category: '6x6', + data: [ + '------', + '------', + '--wb--', + '--bw--', + '------', + '------' + ] +}; + +export const roundedSixsix: Map = { + name: '6x6 rounded', + category: '6x6', + author: 'syuilo', + data: [ + ' ---- ', + '------', + '--wb--', + '--bw--', + '------', + ' ---- ' + ] +}; + +export const roundedSixsix2: Map = { + name: '6x6 rounded 2', + category: '6x6', + author: 'syuilo', + data: [ + ' -- ', + ' ---- ', + '--wb--', + '--bw--', + ' ---- ', + ' -- ' + ] +}; + +export const eighteight: Map = { + name: '8x8', + category: '8x8', + data: [ + '--------', + '--------', + '--------', + '---wb---', + '---bw---', + '--------', + '--------', + '--------' + ] +}; + +export const eighteightH28: Map = { + name: '8x8 handicap 28', + category: '8x8', + data: [ + 'bbbbbbbb', + 'b------b', + 'b------b', + 'b--wb--b', + 'b--bw--b', + 'b------b', + 'b------b', + 'bbbbbbbb' + ] +}; + +export const roundedEighteight: Map = { + name: '8x8 rounded', + category: '8x8', + author: 'syuilo', + data: [ + ' ------ ', + '--------', + '--------', + '---wb---', + '---bw---', + '--------', + '--------', + ' ------ ' + ] +}; + +export const roundedEighteight2: Map = { + name: '8x8 rounded 2', + category: '8x8', + author: 'syuilo', + data: [ + ' ---- ', + ' ------ ', + '--------', + '---wb---', + '---bw---', + '--------', + ' ------ ', + ' ---- ' + ] +}; + +export const roundedEighteight3: Map = { + name: '8x8 rounded 3', + category: '8x8', + author: 'syuilo', + data: [ + ' -- ', + ' ---- ', + ' ------ ', + '---wb---', + '---bw---', + ' ------ ', + ' ---- ', + ' -- ' + ] +}; + +export const eighteightWithNotch: Map = { + name: '8x8 with notch', + category: '8x8', + author: 'syuilo', + data: [ + '--- ---', + '--------', + '--------', + ' --wb-- ', + ' --bw-- ', + '--------', + '--------', + '--- ---' + ] +}; + +export const eighteightWithSomeHoles: Map = { + name: '8x8 with some holes', + category: '8x8', + author: 'syuilo', + data: [ + '--- ----', + '----- --', + '-- -----', + '---wb---', + '---bw- -', + ' -------', + '--- ----', + '--------' + ] +}; + +export const circle: Map = { + name: 'Circle', + category: '8x8', + author: 'syuilo', + data: [ + ' -- ', + ' ------ ', + ' ------ ', + '---wb---', + '---bw---', + ' ------ ', + ' ------ ', + ' -- ' + ] +}; + +export const smile: Map = { + name: 'Smile', + category: '8x8', + author: 'syuilo', + data: [ + ' ------ ', + '--------', + '-- -- --', + '---wb---', + '-- bw --', + '--- ---', + '--------', + ' ------ ' + ] +}; + +export const window: Map = { + name: 'Window', + category: '8x8', + author: 'syuilo', + data: [ + '--------', + '- -- -', + '- -- -', + '---wb---', + '---bw---', + '- -- -', + '- -- -', + '--------' + ] +}; + +export const reserved: Map = { + name: 'Reserved', + category: '8x8', + author: 'Aya', + data: [ + 'w------b', + '--------', + '--------', + '---wb---', + '---bw---', + '--------', + '--------', + 'b------w' + ] +}; + +export const x: Map = { + name: 'X', + category: '8x8', + author: 'Aya', + data: [ + 'w------b', + '-w----b-', + '--w--b--', + '---wb---', + '---bw---', + '--b--w--', + '-b----w-', + 'b------w' + ] +}; + +export const parallel: Map = { + name: 'Parallel', + category: '8x8', + author: 'Aya', + data: [ + '--------', + '--------', + '--------', + '---bb---', + '---ww---', + '--------', + '--------', + '--------' + ] +}; + +export const lackOfBlack: Map = { + name: 'Lack of Black', + category: '8x8', + data: [ + '--------', + '--------', + '--------', + '---w----', + '---bw---', + '--------', + '--------', + '--------' + ] +}; + +export const squareParty: Map = { + name: 'Square Party', + category: '8x8', + author: 'syuilo', + data: [ + '--------', + '-wwwbbb-', + '-w-wb-b-', + '-wwwbbb-', + '-bbbwww-', + '-b-bw-w-', + '-bbbwww-', + '--------' + ] +}; + +export const minesweeper: Map = { + name: 'Minesweeper', + category: '8x8', + author: 'syuilo', + data: [ + 'b-b--w-w', + '-w-wb-b-', + 'w-b--w-b', + '-b-wb-w-', + '-w-bw-b-', + 'b-w--b-w', + '-b-bw-w-', + 'w-w--b-b' + ] +}; + +export const tenthtenth: Map = { + name: '10x10', + category: '10x10', + data: [ + '----------', + '----------', + '----------', + '----------', + '----wb----', + '----bw----', + '----------', + '----------', + '----------', + '----------' + ] +}; + +export const hole: Map = { + name: 'The Hole', + category: '10x10', + author: 'syuilo', + data: [ + '----------', + '----------', + '--wb--wb--', + '--bw--bw--', + '---- ----', + '---- ----', + '--wb--wb--', + '--bw--bw--', + '----------', + '----------' + ] +}; + +export const grid: Map = { + name: 'Grid', + category: '10x10', + author: 'syuilo', + data: [ + '----------', + '- - -- - -', + '----------', + '- - -- - -', + '----wb----', + '----bw----', + '- - -- - -', + '----------', + '- - -- - -', + '----------' + ] +}; + +export const cross: Map = { + name: 'Cross', + category: '10x10', + author: 'Aya', + data: [ + ' ---- ', + ' ---- ', + ' ---- ', + '----------', + '----wb----', + '----bw----', + '----------', + ' ---- ', + ' ---- ', + ' ---- ' + ] +}; + +export const charX: Map = { + name: 'Char X', + category: '10x10', + author: 'syuilo', + data: [ + '--- ---', + '---- ----', + '----------', + ' -------- ', + ' --wb-- ', + ' --bw-- ', + ' -------- ', + '----------', + '---- ----', + '--- ---' + ] +}; + +export const charY: Map = { + name: 'Char Y', + category: '10x10', + author: 'syuilo', + data: [ + '--- ---', + '---- ----', + '----------', + ' -------- ', + ' --wb-- ', + ' --bw-- ', + ' ------ ', + ' ------ ', + ' ------ ', + ' ------ ' + ] +}; + +export const walls: Map = { + name: 'Walls', + category: '10x10', + author: 'Aya', + data: [ + ' bbbbbbbb ', + 'w--------w', + 'w--------w', + 'w--------w', + 'w---wb---w', + 'w---bw---w', + 'w--------w', + 'w--------w', + 'w--------w', + ' bbbbbbbb ' + ] +}; + +export const cpu: Map = { + name: 'CPU', + category: '10x10', + author: 'syuilo', + data: [ + ' b b b b ', + 'w--------w', + ' -------- ', + 'w--------w', + ' ---wb--- ', + ' ---bw--- ', + 'w--------w', + ' -------- ', + 'w--------w', + ' b b b b ' + ] +}; + +export const checker: Map = { + name: 'Checker', + category: '10x10', + author: 'Aya', + data: [ + '----------', + '----------', + '----------', + '---wbwb---', + '---bwbw---', + '---wbwb---', + '---bwbw---', + '----------', + '----------', + '----------' + ] +}; + +export const japaneseCurry: Map = { + name: 'Japanese curry', + category: '10x10', + author: 'syuilo', + data: [ + 'w-b-b-b-b-', + '-w-b-b-b-b', + 'w-w-b-b-b-', + '-w-w-b-b-b', + 'w-w-wwb-b-', + '-w-wbb-b-b', + 'w-w-w-b-b-', + '-w-w-w-b-b', + 'w-w-w-w-b-', + '-w-w-w-w-b' + ] +}; + +export const mosaic: Map = { + name: 'Mosaic', + category: '10x10', + author: 'syuilo', + data: [ + '- - - - - ', + ' - - - - -', + '- - - - - ', + ' - w w - -', + '- - b b - ', + ' - w w - -', + '- - b b - ', + ' - - - - -', + '- - - - - ', + ' - - - - -', + ] +}; + +export const arena: Map = { + name: 'Arena', + category: '10x10', + author: 'syuilo', + data: [ + '- - -- - -', + ' - - - - ', + '- ------ -', + ' -------- ', + '- --wb-- -', + '- --bw-- -', + ' -------- ', + '- ------ -', + ' - - - - ', + '- - -- - -' + ] +}; + +export const reactor: Map = { + name: 'Reactor', + category: '10x10', + author: 'syuilo', + data: [ + '-w------b-', + 'b- - - -w', + '- --wb-- -', + '---b w---', + '- b wb w -', + '- w bw b -', + '---w b---', + '- --bw-- -', + 'w- - - -b', + '-b------w-' + ] +}; + +export const sixeight: Map = { + name: '6x8', + category: 'Special', + data: [ + '------', + '------', + '------', + '--wb--', + '--bw--', + '------', + '------', + '------' + ] +}; + +export const spark: Map = { + name: 'Spark', + category: 'Special', + author: 'syuilo', + data: [ + ' - - ', + '----------', + ' -------- ', + ' -------- ', + ' ---wb--- ', + ' ---bw--- ', + ' -------- ', + ' -------- ', + '----------', + ' - - ' + ] +}; + +export const islands: Map = { + name: 'Islands', + category: 'Special', + author: 'syuilo', + data: [ + '-------- ', + '---wb--- ', + '---bw--- ', + '-------- ', + ' - - ', + ' - - ', + ' --------', + ' --------', + ' --------', + ' --------' + ] +}; + +export const galaxy: Map = { + name: 'Galaxy', + category: 'Special', + author: 'syuilo', + data: [ + ' ------ ', + ' --www--- ', + ' ------w--- ', + '---bbb--w---', + '--b---b-w-b-', + '-b--wwb-w-b-', + '-b-w-bww--b-', + '-b-w-b---b--', + '---w--bbb---', + ' ---w------ ', + ' ---www-- ', + ' ------ ' + ] +}; + +export const triangle: Map = { + name: 'Triangle', + category: 'Special', + author: 'syuilo', + data: [ + ' -- ', + ' -- ', + ' ---- ', + ' ---- ', + ' --wb-- ', + ' --bw-- ', + ' -------- ', + ' -------- ', + '----------', + '----------' + ] +}; + +export const iphonex: Map = { + name: 'iPhone X', + category: 'Special', + author: 'syuilo', + data: [ + ' -- -- ', + '--------', + '--------', + '--------', + '--------', + '---wb---', + '---bw---', + '--------', + '--------', + '--------', + '--------', + ' ------ ' + ] +}; + +export const dealWithIt: Map = { + name: 'Deal with it!', + category: 'Special', + author: 'syuilo', + data: [ + '------------', + '--w-b-------', + ' --b-w------', + ' --w-b---- ', + ' ------- ' + ] +}; + +export const bigBoard: Map = { + name: 'Big board', + category: 'Special', + data: [ + '----------------', + '----------------', + '----------------', + '----------------', + '----------------', + '----------------', + '----------------', + '-------wb-------', + '-------bw-------', + '----------------', + '----------------', + '----------------', + '----------------', + '----------------', + '----------------', + '----------------' + ] +}; + +export const twoBoard: Map = { + name: 'Two board', + category: 'Special', + author: 'Aya', + data: [ + '-------- --------', + '-------- --------', + '-------- --------', + '---wb--- ---wb---', + '---bw--- ---bw---', + '-------- --------', + '-------- --------', + '-------- --------' + ] +}; diff --git a/packages/misskey-reversi/tsconfig.json b/packages/misskey-reversi/tsconfig.json new file mode 100644 index 0000000000..f56b65e868 --- /dev/null +++ b/packages/misskey-reversi/tsconfig.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "module": "nodenext", + "moduleResolution": "nodenext", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./built/", + "removeComments": true, + "strict": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "experimentalDecorators": true, + "noImplicitReturns": true, + "esModuleInterop": true, + "typeRoots": [ + "./node_modules/@types" + ], + "lib": [ + "esnext", + "dom" + ] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "test/**/*" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 825a7ab860..31394eb081 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,6 +185,9 @@ importers: content-disposition: specifier: 0.5.4 version: 0.5.4 + crc-32: + specifier: ^1.2.2 + version: 1.2.2 date-fns: specifier: 2.30.0 version: 2.30.0 @@ -263,6 +266,9 @@ importers: misskey-js: specifier: workspace:* version: link:../misskey-js + misskey-reversi: + specifier: workspace:* + version: link:../misskey-reversi ms: specifier: 3.0.0-canary.1 version: 3.0.0-canary.1 @@ -736,6 +742,9 @@ importers: compare-versions: specifier: 6.1.0 version: 6.1.0 + crc-32: + specifier: ^1.2.2 + version: 1.2.2 cropperjs: specifier: 2.0.0-beta.4 version: 2.0.0-beta.4 @@ -772,6 +781,9 @@ importers: misskey-js: specifier: workspace:* version: link:../misskey-js + misskey-reversi: + specifier: workspace:* + version: link:../misskey-reversi photoswipe: specifier: 5.4.3 version: 5.4.3 @@ -1114,6 +1126,27 @@ importers: specifier: 5.3.3 version: 5.3.3 + packages/misskey-reversi: + devDependencies: + '@misskey-dev/eslint-plugin': + specifier: 1.0.0 + version: 1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.19.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) + '@types/node': + specifier: 20.11.5 + version: 20.11.5 + '@typescript-eslint/eslint-plugin': + specifier: 6.19.0 + version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': + specifier: 6.19.0 + version: 6.19.0(eslint@8.56.0)(typescript@5.3.3) + eslint: + specifier: 8.56.0 + version: 8.56.0 + typescript: + specifier: 5.3.3 + version: 5.3.3 + packages/sw: dependencies: esbuild: @@ -1128,7 +1161,7 @@ importers: devDependencies: '@misskey-dev/eslint-plugin': specifier: ^1.0.0 - version: 1.0.0(@typescript-eslint/eslint-plugin@6.14.0)(@typescript-eslint/parser@6.14.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) + version: 1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.14.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) '@typescript-eslint/parser': specifier: 6.14.0 version: 6.14.0(eslint@8.56.0)(typescript@5.3.3) @@ -1812,7 +1845,7 @@ packages: '@babel/traverse': 7.22.11 '@babel/types': 7.22.17 convert-source-map: 1.9.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1835,7 +1868,7 @@ packages: '@babel/traverse': 7.23.5 '@babel/types': 7.23.5 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1937,7 +1970,7 @@ packages: '@babel/core': 7.23.5 '@babel/helper-compilation-targets': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -3336,7 +3369,7 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.23.5 '@babel/types': 7.22.17 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3354,7 +3387,7 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.23.5 '@babel/types': 7.23.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -4233,7 +4266,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.19.0 ignore: 5.2.4 @@ -4250,7 +4283,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.19.0 ignore: 5.2.4 @@ -4515,7 +4548,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 2.0.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -4571,7 +4604,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -4592,14 +4625,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.7.1 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.10.5) + jest-config: 29.7.0(@types/node@20.11.5) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -4634,7 +4667,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 jest-mock: 29.7.0 dev: true @@ -4661,7 +4694,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.10.5 + '@types/node': 20.11.5 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -4694,7 +4727,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.18 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -4788,7 +4821,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.10.5 + '@types/node': 20.11.5 '@types/yargs': 16.0.5 chalk: 4.1.2 dev: true @@ -4800,7 +4833,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.10.5 + '@types/node': 20.11.5 '@types/yargs': 17.0.19 chalk: 4.1.2 dev: true @@ -4992,6 +5025,34 @@ packages: eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.14.0)(eslint@8.56.0) dev: true + /@misskey-dev/eslint-plugin@1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.14.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0): + resolution: {integrity: sha512-dh6UbcrNDVg5DD8k8Qh4ab30OPpuEYIlJCqaBV/lkIV8wNN/AfCJ2V7iTP8V8KjryM4t+sf5IqzQLQnT0mWI4A==} + peerDependencies: + '@typescript-eslint/eslint-plugin': '>= 6' + '@typescript-eslint/parser': '>= 6' + eslint: '>= 3' + eslint-plugin-import: '>= 2' + dependencies: + '@typescript-eslint/eslint-plugin': 6.19.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.14.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.14.0)(eslint@8.56.0) + dev: true + + /@misskey-dev/eslint-plugin@1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.19.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0): + resolution: {integrity: sha512-dh6UbcrNDVg5DD8k8Qh4ab30OPpuEYIlJCqaBV/lkIV8wNN/AfCJ2V7iTP8V8KjryM4t+sf5IqzQLQnT0mWI4A==} + peerDependencies: + '@typescript-eslint/eslint-plugin': '>= 6' + '@typescript-eslint/parser': '>= 6' + eslint: '>= 3' + eslint-plugin-import: '>= 2' + dependencies: + '@typescript-eslint/eslint-plugin': 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint@8.56.0) + dev: true + /@misskey-dev/sharp-read-bmp@1.1.1: resolution: {integrity: sha512-X52BQYL/I9mafypQ+wBhst+BUlYiPWnHhKGcF6ybcYSLl+zhcV0q5mezIXHozhM0Sv0A7xCdrWmR7TCNxHLrtQ==} dependencies: @@ -5089,7 +5150,7 @@ packages: '@open-draft/until': 1.0.3 '@types/debug': 4.1.7 '@xmldom/xmldom': 0.8.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) headers-polyfill: 3.2.5 outvariant: 1.4.0 strict-event-emitter: 0.2.8 @@ -7992,7 +8053,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 20.10.5 + '@types/node': 20.11.5 '@types/responselike': 1.0.0 dev: false @@ -8025,7 +8086,7 @@ packages: /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/content-disposition@0.5.8: @@ -8039,7 +8100,7 @@ packages: /@types/cross-spawn@6.0.2: resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/debug@4.1.7: @@ -8097,7 +8158,7 @@ packages: /@types/express-serve-static-core@4.17.33: resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 dev: true @@ -8125,13 +8186,13 @@ packages: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/graceful-fs@4.1.6: resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/http-cache-semantics@4.0.1: @@ -8212,7 +8273,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: false /@types/lodash@4.14.191: @@ -8261,7 +8322,7 @@ packages: /@types/node-fetch@2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 form-data: 3.0.1 /@types/node-fetch@3.0.3: @@ -8279,6 +8340,11 @@ packages: dependencies: undici-types: 5.26.5 + /@types/node@20.11.5: + resolution: {integrity: sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==} + dependencies: + undici-types: 5.26.5 + /@types/node@20.9.1: resolution: {integrity: sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==} dependencies: @@ -8381,7 +8447,7 @@ packages: /@types/readdir-glob@1.1.1: resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/rename@1.0.7: @@ -8395,7 +8461,7 @@ packages: /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: false /@types/sanitize-html@2.9.5: @@ -8421,7 +8487,7 @@ packages: resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} dependencies: '@types/mime': 3.0.1 - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/serviceworker@0.0.67: @@ -8431,7 +8497,7 @@ packages: /@types/set-cookie-parser@2.4.3: resolution: {integrity: sha512-7QhnH7bi+6KAhBB+Auejz1uV9DHiopZqu7LfR/5gZZTkejJV5nYeZZpgfFoE0N8aDsXuiYpfKyfyMatCwQhyTQ==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/sharp@0.32.0: @@ -8534,7 +8600,7 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true optional: true @@ -8555,7 +8621,7 @@ packages: '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.53.0 graphemer: 1.4.0 ignore: 5.2.4 @@ -8584,7 +8650,65 @@ packages: '@typescript-eslint/type-utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.14.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) + eslint: 8.56.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/eslint-plugin@6.19.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.6.2 + '@typescript-eslint/parser': 6.14.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.19.0 + '@typescript-eslint/type-utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.19.0 + debug: 4.3.4(supports-color@5.5.0) + eslint: 8.56.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/eslint-plugin@6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.6.2 + '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.19.0 + '@typescript-eslint/type-utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.19.0 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 graphemer: 1.4.0 ignore: 5.2.4 @@ -8610,7 +8734,7 @@ packages: '@typescript-eslint/types': 6.11.0 '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.53.0 typescript: 5.3.3 transitivePeerDependencies: @@ -8631,7 +8755,28 @@ packages: '@typescript-eslint/types': 6.14.0 '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.14.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) + eslint: 8.56.0 + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser@6.19.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 6.19.0 + '@typescript-eslint/types': 6.19.0 + '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.19.0 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 typescript: 5.3.3 transitivePeerDependencies: @@ -8654,6 +8799,14 @@ packages: '@typescript-eslint/visitor-keys': 6.14.0 dev: true + /@typescript-eslint/scope-manager@6.19.0: + resolution: {integrity: sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.19.0 + '@typescript-eslint/visitor-keys': 6.19.0 + dev: true + /@typescript-eslint/type-utils@6.11.0(eslint@8.53.0)(typescript@5.3.3): resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -8666,7 +8819,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3) '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.53.0 ts-api-utils: 1.0.1(typescript@5.3.3) typescript: 5.3.3 @@ -8686,7 +8839,27 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3) '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) + eslint: 8.56.0 + ts-api-utils: 1.0.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/type-utils@6.19.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3) + '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 ts-api-utils: 1.0.1(typescript@5.3.3) typescript: 5.3.3 @@ -8704,6 +8877,11 @@ packages: engines: {node: ^16.0.0 || >=18.0.0} dev: true + /@typescript-eslint/types@6.19.0: + resolution: {integrity: sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + /@typescript-eslint/typescript-estree@6.11.0(typescript@5.3.3): resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -8715,7 +8893,7 @@ packages: dependencies: '@typescript-eslint/types': 6.11.0 '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -8736,7 +8914,7 @@ packages: dependencies: '@typescript-eslint/types': 6.14.0 '@typescript-eslint/visitor-keys': 6.14.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -8746,6 +8924,28 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree@6.19.0(typescript@5.3.3): + resolution: {integrity: sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.19.0 + '@typescript-eslint/visitor-keys': 6.19.0 + debug: 4.3.4(supports-color@5.5.0) + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/utils@6.11.0(eslint@8.53.0)(typescript@5.3.3): resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==} engines: {node: ^16.0.0 || >=18.0.0} @@ -8784,6 +8984,25 @@ packages: - typescript dev: true + /@typescript-eslint/utils@6.19.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.6 + '@typescript-eslint/scope-manager': 6.19.0 + '@typescript-eslint/types': 6.19.0 + '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3) + eslint: 8.56.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys@6.11.0: resolution: {integrity: sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -8800,6 +9019,14 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@typescript-eslint/visitor-keys@6.19.0: + resolution: {integrity: sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.19.0 + eslint-visitor-keys: 3.4.3 + dev: true + /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true @@ -9193,7 +9420,7 @@ packages: engines: {node: '>= 6.0.0'} requiresBuild: true dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -9201,7 +9428,7 @@ packages: resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} engines: {node: '>= 14'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -9587,7 +9814,7 @@ packages: resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==} dependencies: archy: 1.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) fastq: 1.15.0 transitivePeerDependencies: - supports-color @@ -11036,7 +11263,6 @@ packages: dependencies: ms: 2.1.2 supports-color: 5.5.0 - dev: true /debug@4.3.4(supports-color@8.1.1): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} @@ -11049,6 +11275,7 @@ packages: dependencies: ms: 2.1.2 supports-color: 8.1.1 + dev: true /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} @@ -11265,7 +11492,7 @@ packages: hasBin: true dependencies: address: 1.2.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -11589,7 +11816,7 @@ packages: peerDependencies: esbuild: '>=0.12 <1' dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) esbuild: 0.18.20 transitivePeerDependencies: - supports-color @@ -11806,6 +12033,35 @@ packages: - supports-color dev: true + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0): + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + debug: 3.2.7(supports-color@8.1.1) + eslint: 8.56.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + dev: true + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.11.0)(eslint@8.53.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} @@ -11876,6 +12132,41 @@ packages: - supports-color dev: true + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.19.0)(eslint@8.56.0): + resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + array-includes: 3.1.7 + array.prototype.findlastindex: 1.2.3 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7(supports-color@8.1.1) + doctrine: 2.1.0 + eslint: 8.56.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0) + hasown: 2.0.0 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.7 + object.groupby: 1.0.1 + object.values: 1.1.7 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + /eslint-plugin-vue@9.19.2(eslint@8.56.0): resolution: {integrity: sha512-CPDqTOG2K4Ni2o4J5wixkLVNwgctKXFu6oBpVJlpNq7f38lh9I80pRTouZSJ2MAebPJlINU/KTFSXyQfBUlymA==} engines: {node: ^14.17.0 || >=16.0.0} @@ -11927,7 +12218,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -11974,7 +12265,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -12604,7 +12895,7 @@ packages: debug: optional: true dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -13160,7 +13451,6 @@ packages: /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - dev: true /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -13298,7 +13588,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -13360,7 +13650,7 @@ packages: engines: {node: '>= 6.0.0'} dependencies: agent-base: 5.1.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -13370,7 +13660,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -13379,7 +13669,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -13389,7 +13679,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -13549,7 +13839,7 @@ packages: dependencies: '@ioredis/commands': 1.2.0 cluster-key-slot: 1.1.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -13990,7 +14280,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -14044,7 +14334,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 co: 4.6.0 dedent: 1.3.0 @@ -14133,6 +14423,46 @@ packages: - supports-color dev: true + /jest-config@29.7.0(@types/node@20.11.5): + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.22.11 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.5 + babel-jest: 29.7.0(@babel/core@7.22.11) + chalk: 4.1.2 + ci-info: 3.7.1 + deepmerge: 4.2.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + /jest-diff@28.1.3: resolution: {integrity: sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -14188,7 +14518,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 jest-mock: 29.7.0 jest-util: 29.7.0 dev: true @@ -14218,7 +14548,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.6 - '@types/node': 20.10.5 + '@types/node': 20.11.5 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -14279,7 +14609,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /jest-mock@29.7.0: @@ -14342,7 +14672,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -14373,7 +14703,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -14425,7 +14755,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 ci-info: 3.7.1 graceful-fs: 4.2.11 @@ -14450,7 +14780,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -14469,7 +14799,7 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -14667,7 +14997,7 @@ packages: resolution: {integrity: sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) rfdc: 1.3.0 uri-js: 4.4.1 transitivePeerDependencies: @@ -17278,7 +17608,7 @@ packages: engines: {node: '>=8.16.0'} dependencies: '@types/mime-types': 2.1.4 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 @@ -18275,7 +18605,7 @@ packages: dependencies: '@hapi/hoek': 10.0.1 '@hapi/wreck': 18.0.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) joi: 17.7.0 transitivePeerDependencies: - supports-color @@ -18475,7 +18805,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -18628,7 +18958,7 @@ packages: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -18892,7 +19222,6 @@ packages: engines: {node: '>=4'} dependencies: has-flag: 3.0.0 - dev: true /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -19515,7 +19844,7 @@ packages: chalk: 4.1.2 cli-highlight: 2.1.11 date-fns: 2.30.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) dotenv: 16.0.3 glob: 8.1.0 ioredis: 5.3.2 @@ -19880,7 +20209,7 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) mlly: 1.4.0 pathe: 1.1.1 picocolors: 1.0.0 @@ -19992,7 +20321,7 @@ packages: acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.10 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) happy-dom: 10.0.3 local-pkg: 0.4.3 magic-string: 0.30.3 @@ -20074,7 +20403,7 @@ packages: peerDependencies: eslint: '>=6.0.0' dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3b2ecec7fd..3a03a58253 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,3 +4,4 @@ packages: - 'packages/sw' - 'packages/misskey-js' - 'packages/misskey-js/generator' + - 'packages/misskey-reversi' From 7881f06be060dd955499a6beff3ad1967ed628f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Sat, 20 Jan 2024 08:11:59 +0900 Subject: [PATCH 071/134] refactor: deprecate i18n.t (#13039) * refactor: deprecate i18n.t * revert: deprecate i18n.t This reverts commit 7dbf873a2f745040ee723df5db659acacff84e12. * chore: reimpl --- locales/generateDTS.js | 78 +- locales/index.d.ts | 6878 ++++++++++++++++- packages/frontend/src/boot/main-boot.ts | 2 +- .../src/components/MkAnnouncementDialog.vue | 2 +- .../frontend/src/components/MkCwButton.vue | 4 +- .../src/components/MkDateSeparatedList.vue | 2 +- packages/frontend/src/components/MkDialog.vue | 4 +- packages/frontend/src/components/MkDrive.vue | 4 +- .../src/components/MkFollowButton.vue | 2 +- packages/frontend/src/components/MkNote.vue | 2 +- .../src/components/MkNoteDetailed.vue | 2 +- .../src/components/MkNotification.vue | 4 +- .../components/MkNotificationSelectWindow.vue | 2 +- packages/frontend/src/components/MkPoll.vue | 15 +- .../frontend/src/components/MkPollEditor.vue | 2 +- .../src/components/MkSignupDialog.form.vue | 2 +- .../src/components/MkSignupDialog.rules.vue | 6 +- .../src/components/MkSubNoteContent.vue | 2 +- .../src/components/MkTokenGenerateWindow.vue | 4 +- .../src/components/MkTutorialDialog.vue | 2 +- .../MkUserAnnouncementEditDialog.vue | 2 +- .../components/MkUserSetupDialog.Profile.vue | 2 +- .../src/components/MkUserSetupDialog.vue | 4 +- .../frontend/src/components/MkWidgets.vue | 4 +- .../frontend/src/components/global/I18n.vue | 46 + .../components/global/MkTime.stories.impl.ts | 10 +- .../frontend/src/components/global/MkTime.vue | 28 +- .../frontend/src/components/global/i18n.ts | 29 - packages/frontend/src/components/index.ts | 2 +- packages/frontend/src/pages/about.vue | 2 +- packages/frontend/src/pages/admin-file.vue | 2 +- packages/frontend/src/pages/admin-user.vue | 8 +- packages/frontend/src/pages/admin/ads.vue | 2 +- .../src/pages/admin/announcements.vue | 4 +- .../frontend/src/pages/admin/branding.vue | 8 +- packages/frontend/src/pages/admin/relays.vue | 2 +- .../frontend/src/pages/admin/roles.role.vue | 2 +- packages/frontend/src/pages/announcements.vue | 2 +- packages/frontend/src/pages/auth.form.vue | 6 +- packages/frontend/src/pages/auth.vue | 2 +- .../frontend/src/pages/avatar-decorations.vue | 2 +- .../frontend/src/pages/channel-editor.vue | 2 +- packages/frontend/src/pages/clip.vue | 2 +- .../frontend/src/pages/drive.file.info.vue | 2 +- .../frontend/src/pages/drop-and-fusion.vue | 2 +- .../frontend/src/pages/emoji-edit-dialog.vue | 2 +- .../frontend/src/pages/flash/flash-edit.vue | 2 +- packages/frontend/src/pages/follow.vue | 2 +- packages/frontend/src/pages/instance-info.vue | 4 +- packages/frontend/src/pages/invite.vue | 4 +- packages/frontend/src/pages/miauth.vue | 6 +- .../frontend/src/pages/my-antennas/editor.vue | 2 +- .../frontend/src/pages/my-lists/index.vue | 2 +- packages/frontend/src/pages/my-lists/list.vue | 4 +- packages/frontend/src/pages/note.vue | 2 +- packages/frontend/src/pages/notifications.vue | 2 +- packages/frontend/src/pages/oauth.vue | 6 +- .../src/pages/page-editor/page-editor.vue | 2 +- .../frontend/src/pages/reversi/game.board.vue | 8 +- packages/frontend/src/pages/settings/2fa.vue | 2 +- packages/frontend/src/pages/settings/apps.vue | 2 +- .../src/pages/settings/avatar-decoration.vue | 2 +- .../frontend/src/pages/settings/general.vue | 6 +- .../frontend/src/pages/settings/migration.vue | 4 +- .../pages/settings/mute-block.word-mute.vue | 2 +- .../src/pages/settings/notifications.vue | 2 +- .../frontend/src/pages/settings/profile.vue | 4 +- .../frontend/src/pages/settings/sounds.vue | 4 +- .../src/pages/settings/theme.install.vue | 2 +- .../src/pages/settings/webhook.edit.vue | 2 +- .../frontend/src/pages/signup-complete.vue | 2 +- packages/frontend/src/pages/theme-editor.vue | 2 +- packages/frontend/src/pages/user/home.vue | 2 +- .../src/scripts/get-drive-file-menu.ts | 2 +- .../frontend/src/scripts/get-note-menu.ts | 4 +- .../frontend/src/scripts/get-note-summary.ts | 2 +- packages/frontend/src/scripts/i18n.ts | 221 +- packages/frontend/src/ui/deck.vue | 6 +- .../frontend/src/widgets/WidgetCalendar.vue | 8 +- .../frontend/src/widgets/WidgetSlideshow.vue | 2 +- .../frontend/src/widgets/WidgetTimeline.vue | 2 +- .../frontend/src/widgets/WidgetTrends.vue | 2 +- packages/frontend/tsconfig.json | 1 + packages/frontend/vite.config.ts | 1 + 84 files changed, 7311 insertions(+), 222 deletions(-) create mode 100644 packages/frontend/src/components/global/I18n.vue delete mode 100644 packages/frontend/src/components/global/i18n.ts diff --git a/locales/generateDTS.js b/locales/generateDTS.js index 6eb5bd630d..49807144ec 100644 --- a/locales/generateDTS.js +++ b/locales/generateDTS.js @@ -16,32 +16,40 @@ function createMemberType(item) { item.matchAll(parameterRegExp), ([, parameter]) => parameter, ); - if (!parameters.length) { - return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); - } - return ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('ParameterizedString'), - [ - ts.factory.createUnionTypeNode( - parameters.map((parameter) => - ts.factory.createLiteralTypeNode( - ts.factory.createStringLiteral(parameter), + return parameters.length + ? ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('ParameterizedString'), + [ + ts.factory.createUnionTypeNode( + parameters.map((parameter) => + ts.factory.createStringLiteral(parameter), + ), ), - ), - ), - ], - ); + ], + ) + : ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); } function createMembers(record) { - return Object.entries(record).map(([k, v]) => - ts.factory.createPropertySignature( + return Object.entries(record).map(([k, v]) => { + const node = ts.factory.createPropertySignature( undefined, ts.factory.createStringLiteral(k), undefined, createMemberType(v), - ), - ); + ); + if (typeof v === 'string') { + ts.addSyntheticLeadingComment( + node, + ts.SyntaxKind.MultiLineCommentTrivia, + `* + * ${v.replace(/\n/g, '\n * ')} + `, + true, + ); + } + return node; + }); } export default function generateDTS() { @@ -72,10 +80,8 @@ export default function generateDTS() { ts.factory.createTypeParameterDeclaration( undefined, ts.factory.createIdentifier('T'), - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('string'), - undefined, - ), + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), ), ], undefined, @@ -115,7 +121,6 @@ export default function generateDTS() { ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), ts.factory.createTypeReferenceNode( ts.factory.createIdentifier('ParameterizedString'), - [ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)], ), ts.factory.createTypeReferenceNode( ts.factory.createIdentifier('ILocale'), @@ -187,6 +192,24 @@ export default function generateDTS() { ), ts.factory.createExportDefault(ts.factory.createIdentifier('locales')), ]; + ts.addSyntheticLeadingComment( + elements[0], + ts.SyntaxKind.MultiLineCommentTrivia, + ' eslint-disable ', + true, + ); + ts.addSyntheticLeadingComment( + elements[0], + ts.SyntaxKind.SingleLineCommentTrivia, + ' This file is generated by locales/generateDTS.js', + true, + ); + ts.addSyntheticLeadingComment( + elements[0], + ts.SyntaxKind.SingleLineCommentTrivia, + ' Do not edit this file directly.', + true, + ); const printed = ts .createPrinter({ newLine: ts.NewLineKind.LineFeed, @@ -203,12 +226,5 @@ export default function generateDTS() { ), ); - fs.writeFileSync( - `${__dirname}/index.d.ts`, - `/* eslint-disable */ -// This file is generated by locales/generateDTS.js -// Do not edit this file directly. -${printed}`, - 'utf-8', - ); + fs.writeFileSync(`${__dirname}/index.d.ts`, printed, 'utf-8'); } diff --git a/locales/index.d.ts b/locales/index.d.ts index 85e0c6b244..f7f952175f 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2,2670 +2,9544 @@ // This file is generated by locales/generateDTS.js // Do not edit this file directly. declare const kParameters: unique symbol; -export interface ParameterizedString { +export interface ParameterizedString { [kParameters]: T; } export interface ILocale { - [_: string]: string | ParameterizedString | ILocale; + [_: string]: string | ParameterizedString | ILocale; } export interface Locale extends ILocale { + /** + * 日本語 + */ "_lang_": string; + /** + * ノートでつながるネットワーク + */ "headlineMisskey": string; + /** + * ようこそ!Misskeyは、オープンソースの分散型マイクロブログサービスです。 + * 「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡 + * 「リアクション」機能で、皆のノートに素早く反応を追加することもできます👍 + * 新しい世界を探検しよう🚀 + */ "introMisskey": string; + /** + * {name}は、オープンソースのプラットフォームMisskeyのサーバーのひとつです。 + */ "poweredByMisskeyDescription": ParameterizedString<"name">; + /** + * {month}月 {day}日 + */ "monthAndDay": ParameterizedString<"month" | "day">; + /** + * 検索 + */ "search": string; + /** + * 通知 + */ "notifications": string; + /** + * ユーザー名 + */ "username": string; + /** + * パスワード + */ "password": string; + /** + * パスワードを忘れた + */ "forgotPassword": string; + /** + * 連合に照会中 + */ "fetchingAsApObject": string; + /** + * OK + */ "ok": string; + /** + * わかった + */ "gotIt": string; + /** + * キャンセル + */ "cancel": string; + /** + * やめておく + */ "noThankYou": string; + /** + * ユーザー名を入力 + */ "enterUsername": string; + /** + * {user}がリノート + */ "renotedBy": ParameterizedString<"user">; + /** + * ノートはありません + */ "noNotes": string; + /** + * 通知はありません + */ "noNotifications": string; + /** + * サーバー + */ "instance": string; + /** + * 設定 + */ "settings": string; + /** + * 通知の設定 + */ "notificationSettings": string; + /** + * 基本設定 + */ "basicSettings": string; + /** + * その他の設定 + */ "otherSettings": string; + /** + * ウィンドウで開く + */ "openInWindow": string; + /** + * プロフィール + */ "profile": string; + /** + * タイムライン + */ "timeline": string; + /** + * 自己紹介はありません + */ "noAccountDescription": string; + /** + * ログイン + */ "login": string; + /** + * ログイン中 + */ "loggingIn": string; + /** + * ログアウト + */ "logout": string; + /** + * 新規登録 + */ "signup": string; + /** + * アップロード中 + */ "uploading": string; + /** + * 保存 + */ "save": string; + /** + * ユーザー + */ "users": string; + /** + * ユーザーを追加 + */ "addUser": string; + /** + * お気に入り + */ "favorite": string; + /** + * お気に入り + */ "favorites": string; + /** + * お気に入り解除 + */ "unfavorite": string; + /** + * お気に入りに登録しました。 + */ "favorited": string; + /** + * 既にお気に入りに登録されています。 + */ "alreadyFavorited": string; + /** + * お気に入りに登録できませんでした。 + */ "cantFavorite": string; + /** + * ピン留め + */ "pin": string; + /** + * ピン留め解除 + */ "unpin": string; + /** + * 内容をコピー + */ "copyContent": string; + /** + * リンクをコピー + */ "copyLink": string; + /** + * リノートのリンクをコピー + */ "copyLinkRenote": string; + /** + * 削除 + */ "delete": string; + /** + * 削除して編集 + */ "deleteAndEdit": string; + /** + * このノートを削除してもう一度編集しますか?このノートへのリアクション、リノート、返信も全て削除されます。 + */ "deleteAndEditConfirm": string; + /** + * リストに追加 + */ "addToList": string; + /** + * アンテナに追加 + */ "addToAntenna": string; + /** + * メッセージを送信 + */ "sendMessage": string; + /** + * RSSをコピー + */ "copyRSS": string; + /** + * ユーザー名をコピー + */ "copyUsername": string; + /** + * ユーザーIDをコピー + */ "copyUserId": string; + /** + * ノートIDをコピー + */ "copyNoteId": string; + /** + * ファイルIDをコピー + */ "copyFileId": string; + /** + * フォルダーIDをコピー + */ "copyFolderId": string; + /** + * プロフィールURLをコピー + */ "copyProfileUrl": string; + /** + * ユーザーを検索 + */ "searchUser": string; + /** + * 返信 + */ "reply": string; + /** + * もっと見る + */ "loadMore": string; + /** + * もっと見る + */ "showMore": string; + /** + * 閉じる + */ "showLess": string; + /** + * フォローされました + */ "youGotNewFollower": string; + /** + * フォローリクエストされました + */ "receiveFollowRequest": string; + /** + * フォローが承認されました + */ "followRequestAccepted": string; + /** + * メンション + */ "mention": string; + /** + * あなた宛て + */ "mentions": string; + /** + * ダイレクト投稿 + */ "directNotes": string; + /** + * インポートとエクスポート + */ "importAndExport": string; + /** + * インポート + */ "import": string; + /** + * エクスポート + */ "export": string; + /** + * ファイル + */ "files": string; + /** + * ダウンロード + */ "download": string; + /** + * ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。 + */ "driveFileDeleteConfirm": ParameterizedString<"name">; + /** + * {name}のフォローを解除しますか? + */ "unfollowConfirm": ParameterizedString<"name">; + /** + * エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。 + */ "exportRequested": string; + /** + * インポートをリクエストしました。これには時間がかかる場合があります。 + */ "importRequested": string; + /** + * リスト + */ "lists": string; + /** + * リストはありません + */ "noLists": string; + /** + * ノート + */ "note": string; + /** + * ノート + */ "notes": string; + /** + * フォロー + */ "following": string; + /** + * フォロワー + */ "followers": string; + /** + * フォローされています + */ "followsYou": string; + /** + * リスト作成 + */ "createList": string; + /** + * リストの管理 + */ "manageLists": string; + /** + * エラー + */ "error": string; + /** + * 問題が発生しました + */ "somethingHappened": string; + /** + * 再試行 + */ "retry": string; + /** + * ページの読み込みに失敗しました。 + */ "pageLoadError": string; + /** + * これは通常、ネットワークまたはブラウザキャッシュが原因です。キャッシュをクリアするか、しばらく待ってから再度試してください。 + */ "pageLoadErrorDescription": string; + /** + * サーバーの応答がありません。しばらく待ってから再度試してください。 + */ "serverIsDead": string; + /** + * このページを表示するためには、リロードして新しいバージョンのクライアントをご利用ください。 + */ "youShouldUpgradeClient": string; + /** + * リスト名を入力 + */ "enterListName": string; + /** + * プライバシー + */ "privacy": string; + /** + * フォローを承認制にする + */ "makeFollowManuallyApprove": string; + /** + * デフォルトの公開範囲 + */ "defaultNoteVisibility": string; + /** + * フォロー + */ "follow": string; + /** + * フォロー申請 + */ "followRequest": string; + /** + * フォロー申請 + */ "followRequests": string; + /** + * フォロー解除 + */ "unfollow": string; + /** + * フォロー許可待ち + */ "followRequestPending": string; + /** + * 絵文字を入力 + */ "enterEmoji": string; + /** + * リノート + */ "renote": string; + /** + * リノート解除 + */ "unrenote": string; + /** + * リノートしました。 + */ "renoted": string; + /** + * この投稿はリノートできません。 + */ "cantRenote": string; + /** + * リノートをリノートすることはできません。 + */ "cantReRenote": string; + /** + * 引用 + */ "quote": string; + /** + * チャンネル内リノート + */ "inChannelRenote": string; + /** + * チャンネル内引用 + */ "inChannelQuote": string; + /** + * ピン留めされたノート + */ "pinnedNote": string; + /** + * ピン留め + */ "pinned": string; + /** + * あなた + */ "you": string; + /** + * クリックして表示 + */ "clickToShow": string; + /** + * センシティブ + */ "sensitive": string; + /** + * 追加 + */ "add": string; + /** + * リアクション + */ "reaction": string; + /** + * リアクション + */ "reactions": string; + /** + * 絵文字ピッカー + */ "emojiPicker": string; + /** + * リアクション時にピン留め表示する絵文字を設定できます + */ "pinnedEmojisForReactionSettingDescription": string; + /** + * 絵文字入力時にピン留め表示する絵文字を設定できます + */ "pinnedEmojisSettingDescription": string; + /** + * ピッカーの表示 + */ "emojiPickerDisplay": string; + /** + * リアクション設定から上書きする + */ "overwriteFromPinnedEmojisForReaction": string; + /** + * 全般設定から上書きする + */ "overwriteFromPinnedEmojis": string; + /** + * ドラッグして並び替え、クリックして削除、+を押して追加します。 + */ "reactionSettingDescription2": string; + /** + * 公開範囲を記憶する + */ "rememberNoteVisibility": string; + /** + * 添付取り消し + */ "attachCancel": string; + /** + * センシティブとして設定 + */ "markAsSensitive": string; + /** + * センシティブを解除する + */ "unmarkAsSensitive": string; + /** + * ファイル名を入力 + */ "enterFileName": string; + /** + * ミュート + */ "mute": string; + /** + * ミュート解除 + */ "unmute": string; + /** + * リノートをミュート + */ "renoteMute": string; + /** + * リノートのミュートを解除 + */ "renoteUnmute": string; + /** + * ブロック + */ "block": string; + /** + * ブロック解除 + */ "unblock": string; + /** + * 凍結 + */ "suspend": string; + /** + * 解凍 + */ "unsuspend": string; + /** + * ブロックしますか? + */ "blockConfirm": string; + /** + * ブロック解除しますか? + */ "unblockConfirm": string; + /** + * 凍結しますか? + */ "suspendConfirm": string; + /** + * 解凍しますか? + */ "unsuspendConfirm": string; + /** + * リストを選択 + */ "selectList": string; + /** + * リストを編集 + */ "editList": string; + /** + * チャンネルを選択 + */ "selectChannel": string; + /** + * アンテナを選択 + */ "selectAntenna": string; + /** + * アンテナを編集 + */ "editAntenna": string; + /** + * ウィジェットを選択 + */ "selectWidget": string; + /** + * ウィジェットを編集 + */ "editWidgets": string; + /** + * 編集を終了 + */ "editWidgetsExit": string; + /** + * カスタム絵文字 + */ "customEmojis": string; + /** + * 絵文字 + */ "emoji": string; + /** + * 絵文字 + */ "emojis": string; + /** + * 絵文字名 + */ "emojiName": string; + /** + * 絵文字画像URL + */ "emojiUrl": string; + /** + * 絵文字を追加 + */ "addEmoji": string; + /** + * おすすめ設定 + */ "settingGuide": string; + /** + * リモートのファイルをキャッシュする + */ "cacheRemoteFiles": string; + /** + * この設定を有効にすると、リモートファイルをこのサーバーのストレージにキャッシュするようになります。画像の表示が高速になりますが、サーバーのストレージを多く消費します。リモートユーザーがどれほどキャッシュを保持するかは、ロールによるドライブ容量制限によって決定されます。この制限を超えた場合、古いファイルからキャッシュが削除されリンクになります。この設定が無効の場合、リモートのファイルを最初からリンクとして保持しますが、画像のサムネイル生成やユーザーのプライバシー保護のために、default.ymlでproxyRemoteFilesをtrueにすることをお勧めします。 + */ "cacheRemoteFilesDescription": string; + /** + * ファイル管理の🗑️ボタンで全てのキャッシュを削除できます。 + */ "youCanCleanRemoteFilesCache": string; + /** + * リモートのセンシティブなファイルをキャッシュする + */ "cacheRemoteSensitiveFiles": string; + /** + * この設定を無効にすると、リモートのセンシティブなファイルはキャッシュせず直リンクするようになります。 + */ "cacheRemoteSensitiveFilesDescription": string; + /** + * Botとして設定 + */ "flagAsBot": string; + /** + * このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Misskeyのシステム上での扱いがBotに合ったものになります。 + */ "flagAsBotDescription": string; + /** + * にゃああああああああああああああ!!!!!!!!!!!! + */ "flagAsCat": string; + /** + * にゃにゃにゃ?? + */ "flagAsCatDescription": string; + /** + * タイムラインにノートへの返信を表示する + */ "flagShowTimelineReplies": string; + /** + * オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。 + */ "flagShowTimelineRepliesDescription": string; + /** + * フォロー中ユーザーからのフォロリクを自動承認 + */ "autoAcceptFollowed": string; + /** + * アカウントを追加 + */ "addAccount": string; + /** + * アカウントリストの情報を更新 + */ "reloadAccountsList": string; + /** + * ログインに失敗しました + */ "loginFailed": string; + /** + * リモートで表示 + */ "showOnRemote": string; + /** + * 全般 + */ "general": string; + /** + * 壁紙 + */ "wallpaper": string; + /** + * 壁紙を設定 + */ "setWallpaper": string; + /** + * 壁紙を削除 + */ "removeWallpaper": string; + /** + * 検索: {q} + */ "searchWith": ParameterizedString<"q">; + /** + * リストがありません + */ "youHaveNoLists": string; + /** + * {name}をフォローしますか? + */ "followConfirm": ParameterizedString<"name">; + /** + * プロキシアカウント + */ "proxyAccount": string; + /** + * プロキシアカウントは、特定の条件下でユーザーのリモートフォローを代行するアカウントです。例えば、ユーザーがリモートユーザーをリストに入れたとき、リストに入れられたユーザーを誰もフォローしていないとアクティビティがサーバーに配達されないため、代わりにプロキシアカウントがフォローするようにします。 + */ "proxyAccountDescription": string; + /** + * ホスト + */ "host": string; + /** + * ユーザーを選択 + */ "selectUser": string; + /** + * 宛先 + */ "recipient": string; + /** + * 注釈 + */ "annotation": string; + /** + * 連合 + */ "federation": string; + /** + * サーバー + */ "instances": string; + /** + * 初観測 + */ "registeredAt": string; + /** + * 直近のリクエスト受信 + */ "latestRequestReceivedAt": string; + /** + * 直近のステータス + */ "latestStatus": string; + /** + * ストレージ使用量 + */ "storageUsage": string; + /** + * チャート + */ "charts": string; + /** + * 1時間ごと + */ "perHour": string; + /** + * 1日ごと + */ "perDay": string; + /** + * アクティビティの配送を停止 + */ "stopActivityDelivery": string; + /** + * このサーバーをブロック + */ "blockThisInstance": string; + /** + * サーバーをサイレンス + */ "silenceThisInstance": string; + /** + * 操作 + */ "operations": string; + /** + * ソフトウェア + */ "software": string; + /** + * バージョン + */ "version": string; + /** + * メタデータ + */ "metadata": string; + /** + * {n}つのファイル + */ "withNFiles": ParameterizedString<"n">; + /** + * モニター + */ "monitor": string; + /** + * ジョブキュー + */ "jobQueue": string; + /** + * CPUとメモリ + */ "cpuAndMemory": string; + /** + * ネットワーク + */ "network": string; + /** + * ディスク + */ "disk": string; + /** + * サーバー情報 + */ "instanceInfo": string; + /** + * 統計 + */ "statistics": string; + /** + * キューをクリア + */ "clearQueue": string; + /** + * キューをクリアしますか? + */ "clearQueueConfirmTitle": string; + /** + * 未配達の投稿は配送されなくなります。通常この操作を行う必要はありません。 + */ "clearQueueConfirmText": string; + /** + * キャッシュをクリア + */ "clearCachedFiles": string; + /** + * キャッシュされたリモートファイルをすべて削除しますか? + */ "clearCachedFilesConfirm": string; + /** + * ブロックしたサーバー + */ "blockedInstances": string; + /** + * ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このインスタンスとやり取りできなくなります。 + */ "blockedInstancesDescription": string; + /** + * サイレンスしたサーバー + */ "silencedInstances": string; + /** + * サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。 + */ "silencedInstancesDescription": string; + /** + * ミュートとブロック + */ "muteAndBlock": string; + /** + * ミュートしたユーザー + */ "mutedUsers": string; + /** + * ブロックしたユーザー + */ "blockedUsers": string; + /** + * ユーザーはいません + */ "noUsers": string; + /** + * プロフィールを編集 + */ "editProfile": string; + /** + * このノートを削除しますか? + */ "noteDeleteConfirm": string; + /** + * これ以上ピン留めできません + */ "pinLimitExceeded": string; + /** + * Misskeyのインストールが完了しました!管理者アカウントを作成しましょう。 + */ "intro": string; + /** + * 完了 + */ "done": string; + /** + * 処理中 + */ "processing": string; + /** + * プレビュー + */ "preview": string; + /** + * デフォルト + */ "default": string; + /** + * デフォルト: {value} + */ "defaultValueIs": ParameterizedString<"value">; + /** + * 絵文字はありません + */ "noCustomEmojis": string; + /** + * ジョブはありません + */ "noJobs": string; + /** + * 連合中 + */ "federating": string; + /** + * ブロック中 + */ "blocked": string; + /** + * 配信停止 + */ "suspended": string; + /** + * 全て + */ "all": string; + /** + * 購読中 + */ "subscribing": string; + /** + * 配信中 + */ "publishing": string; + /** + * 応答なし + */ "notResponding": string; + /** + * サーバーのフォロー + */ "instanceFollowing": string; + /** + * サーバーのフォロワー + */ "instanceFollowers": string; + /** + * サーバーのユーザー + */ "instanceUsers": string; + /** + * パスワードを変更 + */ "changePassword": string; + /** + * セキュリティ + */ "security": string; + /** + * 入力が一致しません。 + */ "retypedNotMatch": string; + /** + * 現在のパスワード + */ "currentPassword": string; + /** + * 新しいパスワード + */ "newPassword": string; + /** + * 新しいパスワード(再入力) + */ "newPasswordRetype": string; + /** + * ファイルを添付 + */ "attachFile": string; + /** + * もっと! + */ "more": string; + /** + * ハイライト + */ "featured": string; + /** + * ユーザー名かユーザーID + */ "usernameOrUserId": string; + /** + * ユーザーが見つかりません + */ "noSuchUser": string; + /** + * 照会 + */ "lookup": string; + /** + * お知らせ + */ "announcements": string; + /** + * 画像URL + */ "imageUrl": string; + /** + * 削除 + */ "remove": string; + /** + * 削除しました + */ "removed": string; + /** + * 「{x}」を削除しますか? + */ "removeAreYouSure": ParameterizedString<"x">; + /** + * 「{x}」を削除しますか? + */ "deleteAreYouSure": ParameterizedString<"x">; + /** + * リセットしますか? + */ "resetAreYouSure": string; + /** + * よろしいですか? + */ "areYouSure": string; + /** + * 保存しました + */ "saved": string; + /** + * チャット + */ "messaging": string; + /** + * アップロード + */ "upload": string; + /** + * オリジナル画像を保持 + */ "keepOriginalUploading": string; + /** + * 画像をアップロードする時にオリジナル版を保持します。オフにするとアップロード時にブラウザでWeb公開用画像を生成します。 + */ "keepOriginalUploadingDescription": string; + /** + * ドライブから + */ "fromDrive": string; + /** + * URLから + */ "fromUrl": string; + /** + * URLアップロード + */ "uploadFromUrl": string; + /** + * アップロードしたいファイルのURL + */ "uploadFromUrlDescription": string; + /** + * アップロードをリクエストしました + */ "uploadFromUrlRequested": string; + /** + * アップロードが完了するまで時間がかかる場合があります。 + */ "uploadFromUrlMayTakeTime": string; + /** + * みつける + */ "explore": string; + /** + * 既読 + */ "messageRead": string; + /** + * これより過去の履歴はありません + */ "noMoreHistory": string; + /** + * チャットを開始 + */ "startMessaging": string; + /** + * {n}人が読みました + */ "nUsersRead": ParameterizedString<"n">; + /** + * {0}に同意 + */ "agreeTo": ParameterizedString<"0">; + /** + * 同意する + */ "agree": string; + /** + * 下記に同意する + */ "agreeBelow": string; + /** + * 基本的な注意事項 + */ "basicNotesBeforeCreateAccount": string; + /** + * 利用規約 + */ "termsOfService": string; + /** + * 始める + */ "start": string; + /** + * ホーム + */ "home": string; + /** + * リモートユーザーのため、情報が不完全です。 + */ "remoteUserCaution": string; + /** + * アクティビティ + */ "activity": string; + /** + * 画像 + */ "images": string; + /** + * 画像 + */ "image": string; + /** + * 誕生日 + */ "birthday": string; + /** + * {age}歳 + */ "yearsOld": ParameterizedString<"age">; + /** + * 登録日 + */ "registeredDate": string; + /** + * 場所 + */ "location": string; + /** + * テーマ + */ "theme": string; + /** + * ライトモードで使うテーマ + */ "themeForLightMode": string; + /** + * ダークモードで使うテーマ + */ "themeForDarkMode": string; + /** + * ライト + */ "light": string; + /** + * ダーク + */ "dark": string; + /** + * 明るいテーマ + */ "lightThemes": string; + /** + * 暗いテーマ + */ "darkThemes": string; + /** + * デバイスのダークモードと同期する + */ "syncDeviceDarkMode": string; + /** + * ドライブ + */ "drive": string; + /** + * ファイル名 + */ "fileName": string; + /** + * ファイルを選択 + */ "selectFile": string; + /** + * ファイルを選択 + */ "selectFiles": string; + /** + * フォルダーを選択 + */ "selectFolder": string; + /** + * フォルダーを選択 + */ "selectFolders": string; + /** + * ファイル名を変更 + */ "renameFile": string; + /** + * フォルダー名 + */ "folderName": string; + /** + * フォルダーを作成 + */ "createFolder": string; + /** + * フォルダー名を変更 + */ "renameFolder": string; + /** + * フォルダーを削除 + */ "deleteFolder": string; + /** + * フォルダー + */ "folder": string; + /** + * ファイルを追加 + */ "addFile": string; + /** + * ドライブは空です + */ "emptyDrive": string; + /** + * フォルダーは空です + */ "emptyFolder": string; + /** + * 削除できません + */ "unableToDelete": string; + /** + * 新しいファイル名を入力してください + */ "inputNewFileName": string; + /** + * 新しいキャプションを入力してください + */ "inputNewDescription": string; + /** + * 新しいフォルダ名を入力してください + */ "inputNewFolderName": string; + /** + * 移動先のフォルダーは、移動するフォルダーのサブフォルダーです。 + */ "circularReferenceFolder": string; + /** + * このフォルダは空でないため、削除できません。 + */ "hasChildFilesOrFolders": string; + /** + * URLをコピー + */ "copyUrl": string; + /** + * 名前を変更 + */ "rename": string; + /** + * アイコン + */ "avatar": string; + /** + * バナー + */ "banner": string; + /** + * センシティブなメディアの表示 + */ "displayOfSensitiveMedia": string; + /** + * サーバーとの接続が失われたとき + */ "whenServerDisconnected": string; + /** + * サーバーから切断されました + */ "disconnectedFromServer": string; + /** + * リロード + */ "reload": string; + /** + * なにもしない + */ "doNothing": string; + /** + * リロードしますか? + */ "reloadConfirm": string; + /** + * ウォッチ + */ "watch": string; + /** + * ウォッチ解除 + */ "unwatch": string; + /** + * 許可 + */ "accept": string; + /** + * 拒否 + */ "reject": string; + /** + * 通常 + */ "normal": string; + /** + * サーバー名 + */ "instanceName": string; + /** + * サーバーの紹介 + */ "instanceDescription": string; + /** + * 管理者の名前 + */ "maintainerName": string; + /** + * 管理者のメールアドレス + */ "maintainerEmail": string; + /** + * 利用規約URL + */ "tosUrl": string; + /** + * 今年 + */ "thisYear": string; + /** + * 今月 + */ "thisMonth": string; + /** + * 今日 + */ "today": string; + /** + * {day}日 + */ "dayX": ParameterizedString<"day">; + /** + * {month}月 + */ "monthX": ParameterizedString<"month">; + /** + * {year}年 + */ "yearX": ParameterizedString<"year">; + /** + * ページ + */ "pages": string; + /** + * 連携 + */ "integration": string; + /** + * 接続する + */ "connectService": string; + /** + * 切断する + */ "disconnectService": string; + /** + * ローカルタイムラインを有効にする + */ "enableLocalTimeline": string; + /** + * グローバルタイムラインを有効にする + */ "enableGlobalTimeline": string; + /** + * これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。 + */ "disablingTimelinesInfo": string; + /** + * 登録 + */ "registration": string; + /** + * 誰でも新規登録できるようにする + */ "enableRegistration": string; + /** + * 招待 + */ "invite": string; + /** + * ローカルユーザーひとりあたりのドライブ容量 + */ "driveCapacityPerLocalAccount": string; + /** + * リモートユーザーひとりあたりのドライブ容量 + */ "driveCapacityPerRemoteAccount": string; + /** + * メガバイト単位 + */ "inMb": string; + /** + * バナー画像のURL + */ "bannerUrl": string; + /** + * 背景画像のURL + */ "backgroundImageUrl": string; + /** + * 基本情報 + */ "basicInfo": string; + /** + * ピン留めユーザー + */ "pinnedUsers": string; + /** + * 「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。 + */ "pinnedUsersDescription": string; + /** + * ピン留めページ + */ "pinnedPages": string; + /** + * サーバーのトップページにピン留めしたいページのパスを改行で区切って記述します。 + */ "pinnedPagesDescription": string; + /** + * ピン留めするクリップのID + */ "pinnedClipId": string; + /** + * ピン留めされたノート + */ "pinnedNotes": string; + /** + * hCaptcha + */ "hcaptcha": string; + /** + * hCaptchaを有効にする + */ "enableHcaptcha": string; + /** + * サイトキー + */ "hcaptchaSiteKey": string; + /** + * シークレットキー + */ "hcaptchaSecretKey": string; + /** + * mCaptcha + */ "mcaptcha": string; + /** + * mCaptchaを有効にする + */ "enableMcaptcha": string; + /** + * サイトキー + */ "mcaptchaSiteKey": string; + /** + * シークレットキー + */ "mcaptchaSecretKey": string; + /** + * mCaptchaのインスタンスのURL + */ "mcaptchaInstanceUrl": string; + /** + * reCAPTCHA + */ "recaptcha": string; + /** + * reCAPTCHAを有効にする + */ "enableRecaptcha": string; + /** + * サイトキー + */ "recaptchaSiteKey": string; + /** + * シークレットキー + */ "recaptchaSecretKey": string; + /** + * Turnstile + */ "turnstile": string; + /** + * Turnstileを有効にする + */ "enableTurnstile": string; + /** + * サイトキー + */ "turnstileSiteKey": string; + /** + * シークレットキー + */ "turnstileSecretKey": string; + /** + * 複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。 + */ "avoidMultiCaptchaConfirm": string; + /** + * アンテナ + */ "antennas": string; + /** + * アンテナの管理 + */ "manageAntennas": string; + /** + * 名前 + */ "name": string; + /** + * 受信ソース + */ "antennaSource": string; + /** + * 受信キーワード + */ "antennaKeywords": string; + /** + * 除外キーワード + */ "antennaExcludeKeywords": string; + /** + * スペースで区切るとAND指定になり、改行で区切るとOR指定になります + */ "antennaKeywordsDescription": string; + /** + * 新しいノートを通知する + */ "notifyAntenna": string; + /** + * ファイルが添付されたノートのみ + */ "withFileAntenna": string; + /** + * ブラウザへのプッシュ通知を有効にする + */ "enableServiceworker": string; + /** + * ユーザー名を改行で区切って指定します + */ "antennaUsersDescription": string; + /** + * 大文字小文字を区別する + */ "caseSensitive": string; + /** + * 返信を含む + */ "withReplies": string; + /** + * 次のアカウントに接続されています + */ "connectedTo": string; + /** + * 投稿と返信 + */ "notesAndReplies": string; + /** + * ファイル付き + */ "withFiles": string; + /** + * サイレンス + */ "silence": string; + /** + * サイレンスしますか? + */ "silenceConfirm": string; + /** + * サイレンス解除 + */ "unsilence": string; + /** + * サイレンス解除しますか? + */ "unsilenceConfirm": string; + /** + * 人気のユーザー + */ "popularUsers": string; + /** + * 最近投稿したユーザー + */ "recentlyUpdatedUsers": string; + /** + * 最近登録したユーザー + */ "recentlyRegisteredUsers": string; + /** + * 最近発見されたユーザー + */ "recentlyDiscoveredUsers": string; + /** + * {count}のユーザーがいます + */ "exploreUsersCount": ParameterizedString<"count">; + /** + * Fediverseを探索 + */ "exploreFediverse": string; + /** + * 人気のタグ + */ "popularTags": string; + /** + * リスト + */ "userList": string; + /** + * 情報 + */ "about": string; + /** + * Misskeyについて + */ "aboutMisskey": string; + /** + * 管理者 + */ "administrator": string; + /** + * 確認コード + */ "token": string; + /** + * 二要素認証 + */ "2fa": string; + /** + * 二要素認証のセットアップ + */ "setupOf2fa": string; + /** + * 認証アプリ + */ "totp": string; + /** + * 認証アプリを使ってワンタイムパスワードを入力 + */ "totpDescription": string; + /** + * モデレーター + */ "moderator": string; + /** + * モデレーション + */ "moderation": string; + /** + * モデレーションノート + */ "moderationNote": string; + /** + * モデレーションノートを追加する + */ "addModerationNote": string; + /** + * モデログ + */ "moderationLogs": string; + /** + * {n}人が投稿 + */ "nUsersMentioned": ParameterizedString<"n">; + /** + * セキュリティキー・パスキー + */ "securityKeyAndPasskey": string; + /** + * セキュリティキー + */ "securityKey": string; + /** + * 最後の使用 + */ "lastUsed": string; + /** + * 最後の使用: {t} + */ "lastUsedAt": ParameterizedString<"t">; + /** + * 登録を解除 + */ "unregister": string; + /** + * パスワードレスログイン + */ "passwordLessLogin": string; + /** + * パスワードを使用せず、セキュリティキーやパスキーなどのみでログインします + */ "passwordLessLoginDescription": string; + /** + * パスワードをリセット + */ "resetPassword": string; + /** + * 新しいパスワードは「{password}」です + */ "newPasswordIs": ParameterizedString<"password">; + /** + * UIのアニメーションを減らす + */ "reduceUiAnimation": string; + /** + * 共有 + */ "share": string; + /** + * 見つかりません + */ "notFound": string; + /** + * 指定されたURLに該当するページはありませんでした。 + */ "notFoundDescription": string; + /** + * 既定アップロード先 + */ "uploadFolder": string; + /** + * すべての通知を既読にする + */ "markAsReadAllNotifications": string; + /** + * すべての投稿を既読にする + */ "markAsReadAllUnreadNotes": string; + /** + * すべてのチャットを既読にする + */ "markAsReadAllTalkMessages": string; + /** + * ヘルプ + */ "help": string; + /** + * ここにメッセージを入力 + */ "inputMessageHere": string; + /** + * 閉じる + */ "close": string; + /** + * 招待 + */ "invites": string; + /** + * メンバー + */ "members": string; + /** + * 譲渡 + */ "transfer": string; + /** + * タイトル + */ "title": string; + /** + * テキスト + */ "text": string; + /** + * 有効にする + */ "enable": string; + /** + * 次 + */ "next": string; + /** + * 再入力 + */ "retype": string; + /** + * {user}のノート + */ "noteOf": ParameterizedString<"user">; + /** + * 引用付き + */ "quoteAttached": string; + /** + * 引用として添付しますか? + */ "quoteQuestion": string; + /** + * まだチャットはありません + */ "noMessagesYet": string; + /** + * 新しいメッセージがあります + */ "newMessageExists": string; + /** + * メッセージに添付できるファイルはひとつです + */ "onlyOneFileCanBeAttached": string; + /** + * 続行する前に、サインアップまたはサインインが必要です + */ "signinRequired": string; + /** + * 招待 + */ "invitations": string; + /** + * 招待コード + */ "invitationCode": string; + /** + * 確認しています + */ "checking": string; + /** + * 利用できます + */ "available": string; + /** + * 利用できません + */ "unavailable": string; + /** + * a~z、A~Z、0~9、_が使えます + */ "usernameInvalidFormat": string; + /** + * 短すぎます + */ "tooShort": string; + /** + * 長すぎます + */ "tooLong": string; + /** + * 弱いパスワード + */ "weakPassword": string; + /** + * 普通のパスワード + */ "normalPassword": string; + /** + * 強いパスワード + */ "strongPassword": string; + /** + * 一致しました + */ "passwordMatched": string; + /** + * 一致していません + */ "passwordNotMatched": string; + /** + * {x}でログイン + */ "signinWith": ParameterizedString<"x">; + /** + * ログインできませんでした。ユーザー名とパスワードを確認してください。 + */ "signinFailed": string; + /** + * もしくは + */ "or": string; + /** + * 言語 + */ "language": string; + /** + * UIの表示言語 + */ "uiLanguage": string; + /** + * {x}について + */ "aboutX": ParameterizedString<"x">; + /** + * 絵文字のスタイル + */ "emojiStyle": string; + /** + * ネイティブ + */ "native": string; + /** + * メニューをドロワーで表示しない + */ "disableDrawer": string; + /** + * ノートのアクションをホバー時のみ表示する + */ "showNoteActionsOnlyHover": string; + /** + * 履歴はありません + */ "noHistory": string; + /** + * ログイン履歴 + */ "signinHistory": string; + /** + * 高度なMFMを有効にする + */ "enableAdvancedMfm": string; + /** + * 動きのあるMFMを有効にする + */ "enableAnimatedMfm": string; + /** + * やっています + */ "doing": string; + /** + * カテゴリ + */ "category": string; + /** + * タグ + */ "tags": string; + /** + * このドキュメントのソース + */ "docSource": string; + /** + * アカウントを作成 + */ "createAccount": string; + /** + * 既存のアカウント + */ "existingAccount": string; + /** + * 再生成 + */ "regenerate": string; + /** + * フォントサイズ + */ "fontSize": string; + /** + * 画像が1枚のみのメディアリストの高さ + */ "mediaListWithOneImageAppearance": string; + /** + * {x}を上限に + */ "limitTo": ParameterizedString<"x">; + /** + * フォロー申請はありません + */ "noFollowRequests": string; + /** + * 画像を新しいタブで開く + */ "openImageInNewTab": string; + /** + * ダッシュボード + */ "dashboard": string; + /** + * ローカル + */ "local": string; + /** + * リモート + */ "remote": string; + /** + * 合計 + */ "total": string; + /** + * 前週比 + */ "weekOverWeekChanges": string; + /** + * 前日比 + */ "dayOverDayChanges": string; + /** + * アピアランス + */ "appearance": string; + /** + * クライアント設定 + */ "clientSettings": string; + /** + * アカウント設定 + */ "accountSettings": string; + /** + * プロモーション + */ "promotion": string; + /** + * プロモート + */ "promote": string; + /** + * 日数 + */ "numberOfDays": string; + /** + * このノートを非表示 + */ "hideThisNote": string; + /** + * タイムラインにおすすめのノートを表示する + */ "showFeaturedNotesInTimeline": string; + /** + * オブジェクトストレージ + */ "objectStorage": string; + /** + * オブジェクトストレージを使用 + */ "useObjectStorage": string; + /** + * Base URL + */ "objectStorageBaseUrl": string; + /** + * 参照に使用するURL。CDNやProxyを使用している場合はそのURL、S3: 'https://.s3.amazonaws.com'、GCS等: 'https://storage.googleapis.com/'。 + */ "objectStorageBaseUrlDesc": string; + /** + * Bucket + */ "objectStorageBucket": string; + /** + * 使用サービスのbucket名を指定してください。 + */ "objectStorageBucketDesc": string; + /** + * Prefix + */ "objectStoragePrefix": string; + /** + * このprefixのディレクトリ下に格納されます。 + */ "objectStoragePrefixDesc": string; + /** + * Endpoint + */ "objectStorageEndpoint": string; + /** + * S3の場合は空、それ以外の場合は各サービスのendpointを指定してください。''または':'のように指定します。 + */ "objectStorageEndpointDesc": string; + /** + * Region + */ "objectStorageRegion": string; + /** + * 'xx-east-1'のようなregionを指定してください。使用サービスにregionの概念がない場合は'us-east-1'にしてください。AWS設定ファイルまたは環境変数を参照する場合は空にしてください。 + */ "objectStorageRegionDesc": string; + /** + * SSLを使用する + */ "objectStorageUseSSL": string; + /** + * API接続にhttpsを使用しない場合はオフにしてください + */ "objectStorageUseSSLDesc": string; + /** + * Proxyを利用する + */ "objectStorageUseProxy": string; + /** + * API接続にproxyを利用しない場合はオフにしてください + */ "objectStorageUseProxyDesc": string; + /** + * アップロード時に'public-read'を設定する + */ "objectStorageSetPublicRead": string; + /** + * s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。 + */ "s3ForcePathStyleDesc": string; + /** + * サーバーログ + */ "serverLogs": string; + /** + * 全て削除 + */ "deleteAll": string; + /** + * タイムライン上部に投稿フォームを表示する + */ "showFixedPostForm": string; + /** + * タイムライン上部に投稿フォームを表示する(チャンネル) + */ "showFixedPostFormInChannel": string; + /** + * フォローする際、デフォルトで返信をTLに含むようにする + */ "withRepliesByDefaultForNewlyFollowed": string; + /** + * 新しいノートがあります + */ "newNoteRecived": string; + /** + * サウンド + */ "sounds": string; + /** + * サウンド + */ "sound": string; + /** + * 聴く + */ "listen": string; + /** + * なし + */ "none": string; + /** + * ページで表示 + */ "showInPage": string; + /** + * ポップアウト + */ "popout": string; + /** + * 音量 + */ "volume": string; + /** + * マスター音量 + */ "masterVolume": string; + /** + * サウンドを出力しない + */ "notUseSound": string; + /** + * Misskeyがアクティブな時のみサウンドを出力する + */ "useSoundOnlyWhenActive": string; + /** + * 詳細 + */ "details": string; + /** + * 絵文字を選択 + */ "chooseEmoji": string; + /** + * 操作を完了できません + */ "unableToProcess": string; + /** + * 最近使用 + */ "recentUsed": string; + /** + * インストール + */ "install": string; + /** + * アンインストール + */ "uninstall": string; + /** + * インストールされたアプリ + */ "installedApps": string; + /** + * ありません + */ "nothing": string; + /** + * インストール日時 + */ "installedDate": string; + /** + * 最終使用日時 + */ "lastUsedDate": string; + /** + * 状態 + */ "state": string; + /** + * ソート + */ "sort": string; + /** + * 昇順 + */ "ascendingOrder": string; + /** + * 降順 + */ "descendingOrder": string; + /** + * スクラッチパッド + */ "scratchpad": string; + /** + * スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。 + */ "scratchpadDescription": string; + /** + * 出力 + */ "output": string; + /** + * スクリプト + */ "script": string; + /** + * Pagesのスクリプトを無効にする + */ "disablePagesScript": string; + /** + * リモートユーザー情報の更新 + */ "updateRemoteUser": string; + /** + * アイコンを解除 + */ "unsetUserAvatar": string; + /** + * アイコンを解除しますか? + */ "unsetUserAvatarConfirm": string; + /** + * バナーを解除 + */ "unsetUserBanner": string; + /** + * バナーを解除しますか? + */ "unsetUserBannerConfirm": string; + /** + * すべてのファイルを削除 + */ "deleteAllFiles": string; + /** + * すべてのファイルを削除しますか? + */ "deleteAllFilesConfirm": string; + /** + * フォローを全解除 + */ "removeAllFollowing": string; + /** + * {host}からのフォローをすべて解除します。そのサーバーがもう存在しなくなった場合などに実行してください。 + */ "removeAllFollowingDescription": ParameterizedString<"host">; + /** + * このユーザーは凍結されています。 + */ "userSuspended": string; + /** + * このユーザーはサイレンスされています。 + */ "userSilenced": string; + /** + * アカウントが凍結されています + */ "yourAccountSuspendedTitle": string; + /** + * このアカウントは、サーバーの利用規約に違反したなどの理由により、凍結されています。詳細については管理者までお問い合わせください。新しいアカウントを作らないでください。 + */ "yourAccountSuspendedDescription": string; + /** + * トークンが無効です + */ "tokenRevoked": string; + /** + * ログイントークンが失効しています。ログインし直してください。 + */ "tokenRevokedDescription": string; + /** + * アカウントは削除されています + */ "accountDeleted": string; + /** + * このアカウントは削除されています。 + */ "accountDeletedDescription": string; + /** + * メニュー + */ "menu": string; + /** + * 分割線 + */ "divider": string; + /** + * 項目を追加 + */ "addItem": string; + /** + * 並び替え + */ "rearrange": string; + /** + * リレー + */ "relays": string; + /** + * リレーの追加 + */ "addRelay": string; + /** + * inboxのURL + */ "inboxUrl": string; + /** + * 追加済みのリレー + */ "addedRelays": string; + /** + * プッシュ通知を行うには有効にする必要があります。 + */ "serviceworkerInfo": string; + /** + * 削除された投稿 + */ "deletedNote": string; + /** + * 非公開の投稿 + */ "invisibleNote": string; + /** + * 自動でもっと見る + */ "enableInfiniteScroll": string; + /** + * 公開範囲 + */ "visibility": string; + /** + * アンケート + */ "poll": string; + /** + * 内容を隠す + */ "useCw": string; + /** + * プレイヤーを開く + */ "enablePlayer": string; + /** + * プレイヤーを閉じる + */ "disablePlayer": string; + /** + * ポストを展開する + */ "expandTweet": string; + /** + * テーマエディター + */ "themeEditor": string; + /** + * 説明 + */ "description": string; + /** + * キャプションを付ける + */ "describeFile": string; + /** + * キャプションを入力 + */ "enterFileDescription": string; + /** + * 作者 + */ "author": string; + /** + * 未保存の変更があります。破棄しますか? + */ "leaveConfirm": string; + /** + * 管理 + */ "manage": string; + /** + * プラグイン + */ "plugins": string; + /** + * 設定のバックアップ + */ "preferencesBackups": string; + /** + * デッキ + */ "deck": string; + /** + * デッキ解除 + */ "undeck": string; + /** + * モーダルにぼかし効果を使用 + */ "useBlurEffectForModal": string; + /** + * フル機能リアクションピッカーを使用 + */ "useFullReactionPicker": string; + /** + * 幅 + */ "width": string; + /** + * 高さ + */ "height": string; + /** + * 大 + */ "large": string; + /** + * 中 + */ "medium": string; + /** + * 小 + */ "small": string; + /** + * アクセストークンの発行 + */ "generateAccessToken": string; + /** + * 権限 + */ "permission": string; + /** + * 管理者権限 + */ "adminPermission": string; + /** + * 全て有効にする + */ "enableAll": string; + /** + * 全て無効にする + */ "disableAll": string; + /** + * アカウントへのアクセス許可 + */ "tokenRequested": string; + /** + * このプラグインはここで設定した権限を行使できるようになります。 + */ "pluginTokenRequestedDescription": string; + /** + * 通知の種類 + */ "notificationType": string; + /** + * 編集 + */ "edit": string; + /** + * メールサーバー + */ "emailServer": string; + /** + * メール配信機能を有効化する + */ "enableEmail": string; + /** + * メールアドレスの確認やパスワードリセットの際に使います + */ "emailConfigInfo": string; + /** + * メール + */ "email": string; + /** + * メールアドレス + */ "emailAddress": string; + /** + * SMTP サーバーの設定 + */ "smtpConfig": string; + /** + * ホスト + */ "smtpHost": string; + /** + * ポート + */ "smtpPort": string; + /** + * ユーザー名 + */ "smtpUser": string; + /** + * パスワード + */ "smtpPass": string; + /** + * ユーザー名とパスワードを空欄にすることで、SMTP認証を無効化出来ます + */ "emptyToDisableSmtpAuth": string; + /** + * SMTP 接続に暗黙的なSSL/TLSを使用する + */ "smtpSecure": string; + /** + * STARTTLS使用時はオフにします。 + */ "smtpSecureInfo": string; + /** + * 配信テスト + */ "testEmail": string; + /** + * ワードミュート + */ "wordMute": string; + /** + * ハードワードミュート + */ "hardWordMute": string; + /** + * 正規表現エラー + */ "regexpError": string; + /** + * {tab}ワードミュートの{line}行目の正規表現にエラーが発生しました: + */ "regexpErrorDescription": ParameterizedString<"tab" | "line">; + /** + * サーバーミュート + */ "instanceMute": string; + /** + * {name}が何かを言いました + */ "userSaysSomething": ParameterizedString<"name">; + /** + * アクティブにする + */ "makeActive": string; + /** + * 表示 + */ "display": string; + /** + * コピー + */ "copy": string; + /** + * メトリクス + */ "metrics": string; + /** + * 概要 + */ "overview": string; + /** + * ログ + */ "logs": string; + /** + * 遅延 + */ "delayed": string; + /** + * データベース + */ "database": string; + /** + * チャンネル + */ "channel": string; + /** + * 作成 + */ "create": string; + /** + * 通知設定 + */ "notificationSetting": string; + /** + * 表示する通知の種別を選択してください。 + */ "notificationSettingDesc": string; + /** + * グローバル設定を使う + */ "useGlobalSetting": string; + /** + * オンにすると、アカウントの通知設定が使用されます。オフにすると、個別に設定できるようになります。 + */ "useGlobalSettingDesc": string; + /** + * その他 + */ "other": string; + /** + * ログイントークンを再生成 + */ "regenerateLoginToken": string; + /** + * ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。 + */ "regenerateLoginTokenDescription": string; + /** + * カスタム絵文字を検索する時のキーワードになります。 + */ "theKeywordWhenSearchingForCustomEmoji": string; + /** + * スペースで区切って複数設定できます。 + */ "setMultipleBySeparatingWithSpace": string; + /** + * ファイルIDまたはURL + */ "fileIdOrUrl": string; + /** + * 動作 + */ "behavior": string; + /** + * サンプル + */ "sample": string; + /** + * 通報 + */ "abuseReports": string; + /** + * 通報 + */ "reportAbuse": string; + /** + * リノートを通報 + */ "reportAbuseRenote": string; + /** + * {name}を通報する + */ "reportAbuseOf": ParameterizedString<"name">; + /** + * 通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。 + */ "fillAbuseReportDescription": string; + /** + * 内容が送信されました。ご報告ありがとうございました。 + */ "abuseReported": string; + /** + * 通報者 + */ "reporter": string; + /** + * 通報先 + */ "reporteeOrigin": string; + /** + * 通報元 + */ "reporterOrigin": string; + /** + * リモートサーバーに通報を転送する + */ "forwardReport": string; + /** + * リモートサーバーからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。 + */ "forwardReportIsAnonymous": string; + /** + * 送信 + */ "send": string; + /** + * 対応済みにする + */ "abuseMarkAsResolved": string; + /** + * 新しいタブで開く + */ "openInNewTab": string; + /** + * サイドビューで開く + */ "openInSideView": string; + /** + * デフォルトのナビゲーション + */ "defaultNavigationBehaviour": string; + /** + * これらの設定を編集するとアカウントが破損する可能性があります。 + */ "editTheseSettingsMayBreakAccount": string; + /** + * ノートのサーバー情報 + */ "instanceTicker": string; + /** + * {x}を待っています + */ "waitingFor": ParameterizedString<"x">; + /** + * ランダム + */ "random": string; + /** + * システム + */ "system": string; + /** + * UI切り替え + */ "switchUi": string; + /** + * デスクトップ + */ "desktop": string; + /** + * クリップ + */ "clip": string; + /** + * 新規作成 + */ "createNew": string; + /** + * 任意 + */ "optional": string; + /** + * 新しいクリップを作成 + */ "createNewClip": string; + /** + * クリップ解除 + */ "unclip": string; + /** + * このノートはすでにクリップ「{name}」に含まれています。ノートをこのクリップから除外しますか? + */ "confirmToUnclipAlreadyClippedNote": ParameterizedString<"name">; + /** + * パブリック + */ "public": string; + /** + * 非公開 + */ "private": string; + /** + * Misskeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。 + */ "i18nInfo": ParameterizedString<"link">; + /** + * アクセストークンの管理 + */ "manageAccessTokens": string; + /** + * アカウント情報 + */ "accountInfo": string; + /** + * ノートの数 + */ "notesCount": string; + /** + * 返信した数 + */ "repliesCount": string; + /** + * リノートした数 + */ "renotesCount": string; + /** + * 返信された数 + */ "repliedCount": string; + /** + * リノートされた数 + */ "renotedCount": string; + /** + * フォロー数 + */ "followingCount": string; + /** + * フォロワー数 + */ "followersCount": string; + /** + * リアクションした数 + */ "sentReactionsCount": string; + /** + * リアクションされた数 + */ "receivedReactionsCount": string; + /** + * アンケートに投票した数 + */ "pollVotesCount": string; + /** + * アンケートに投票された数 + */ "pollVotedCount": string; + /** + * はい + */ "yes": string; + /** + * いいえ + */ "no": string; + /** + * ドライブのファイル数 + */ "driveFilesCount": string; + /** + * ドライブ使用量 + */ "driveUsage": string; + /** + * クローラーによるインデックスを拒否 + */ "noCrawle": string; + /** + * 外部の検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要求します。 + */ "noCrawleDescription": string; + /** + * フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。 + */ "lockedAccountInfo": string; + /** + * デフォルトでメディアをセンシティブ設定にする + */ "alwaysMarkSensitive": string; + /** + * 添付画像のサムネイルをオリジナル画質にする + */ "loadRawImages": string; + /** + * アニメーション画像を再生しない + */ "disableShowingAnimatedImages": string; + /** + * メディアがセンシティブであることを分かりやすく表示 + */ "highlightSensitiveMedia": string; + /** + * 確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。 + */ "verificationEmailSent": string; + /** + * 未設定 + */ "notSet": string; + /** + * メールアドレスが確認されました + */ "emailVerified": string; + /** + * お気に入りノートの数 + */ "noteFavoritesCount": string; + /** + * Pageにいいねした数 + */ "pageLikesCount": string; + /** + * Pageにいいねされた数 + */ "pageLikedCount": string; + /** + * 連絡先 + */ "contact": string; + /** + * システムのデフォルトのフォントを使う + */ "useSystemFont": string; + /** + * クリップ + */ "clips": string; + /** + * 実験的機能 + */ "experimentalFeatures": string; + /** + * 実験的 + */ "experimental": string; + /** + * これは実験的な機能です。仕様が変更されたり、正常に動作しなかったりする可能性があります。 + */ "thisIsExperimentalFeature": string; + /** + * 開発者 + */ "developer": string; + /** + * アカウントを見つけやすくする + */ "makeExplorable": string; + /** + * オフにすると、「みつける」にアカウントが載らなくなります。 + */ "makeExplorableDescription": string; + /** + * タイムラインのノートを離して表示 + */ "showGapBetweenNotesInTimeline": string; + /** + * 複製 + */ "duplicate": string; + /** + * 左 + */ "left": string; + /** + * 中央 + */ "center": string; + /** + * 広い + */ "wide": string; + /** + * 狭い + */ "narrow": string; + /** + * 設定はページリロード後に反映されます。今すぐリロードしますか? + */ "reloadToApplySetting": string; + /** + * 反映には再起動が必要です。 + */ "needReloadToApply": string; + /** + * タイトルバーを表示する + */ "showTitlebar": string; + /** + * キャッシュをクリア + */ "clearCache": string; + /** + * {n}人がオンライン + */ "onlineUsersCount": ParameterizedString<"n">; + /** + * {n}ユーザー + */ "nUsers": ParameterizedString<"n">; + /** + * {n}ノート + */ "nNotes": ParameterizedString<"n">; + /** + * エラーリポートを送信 + */ "sendErrorReports": string; + /** + * オンにすると、問題が発生したときにエラーの詳細情報がMisskeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。 + */ "sendErrorReportsDescription": string; + /** + * マイテーマ + */ "myTheme": string; + /** + * 背景 + */ "backgroundColor": string; + /** + * アクセント + */ "accentColor": string; + /** + * 文字 + */ "textColor": string; + /** + * 名前を付けて保存 + */ "saveAs": string; + /** + * 高度 + */ "advanced": string; + /** + * 高度な設定 + */ "advancedSettings": string; + /** + * 値 + */ "value": string; + /** + * 作成日時 + */ "createdAt": string; + /** + * 更新日時 + */ "updatedAt": string; + /** + * 保存しますか? + */ "saveConfirm": string; + /** + * 削除しますか? + */ "deleteConfirm": string; + /** + * 有効な値ではありません。 + */ "invalidValue": string; + /** + * レジストリ + */ "registry": string; + /** + * アカウントを閉鎖する + */ "closeAccount": string; + /** + * 現在のバージョン + */ "currentVersion": string; + /** + * 最新のバージョン + */ "latestVersion": string; + /** + * お使いのクライアントは最新です。 + */ "youAreRunningUpToDateClient": string; + /** + * 新しいバージョンのクライアントが利用可能です。 + */ "newVersionOfClientAvailable": string; + /** + * 使用量 + */ "usageAmount": string; + /** + * 容量 + */ "capacity": string; + /** + * 使用中 + */ "inUse": string; + /** + * コードを編集 + */ "editCode": string; + /** + * 適用 + */ "apply": string; + /** + * サーバーからのお知らせを受け取る + */ "receiveAnnouncementFromInstance": string; + /** + * メール通知 + */ "emailNotification": string; + /** + * 公開 + */ "publish": string; + /** + * チャンネル内検索 + */ "inChannelSearch": string; + /** + * 右クリックでリアクションピッカーを開く + */ "useReactionPickerForContextMenu": string; + /** + * {users}が入力中 + */ "typingUsers": ParameterizedString<"users">; + /** + * 特定の日付にジャンプ + */ "jumpToSpecifiedDate": string; + /** + * 過去のタイムラインを表示しています + */ "showingPastTimeline": string; + /** + * クリア + */ "clear": string; + /** + * 全て既読にする + */ "markAllAsRead": string; + /** + * 戻る + */ "goBack": string; + /** + * いいね解除しますか? + */ "unlikeConfirm": string; + /** + * フルビュー + */ "fullView": string; + /** + * フルビュー解除 + */ "quitFullView": string; + /** + * 説明を追加 + */ "addDescription": string; + /** + * 個々のノートのメニューから「ピン留め」を選択することで、ここにノートを表示しておくことができます。 + */ "userPagePinTip": string; + /** + * 宛先に含まれていないメンションがあります + */ "notSpecifiedMentionWarning": string; + /** + * 情報 + */ "info": string; + /** + * ユーザー情報 + */ "userInfo": string; + /** + * 不明 + */ "unknown": string; + /** + * オンライン状態 + */ "onlineStatus": string; + /** + * オンライン状態を隠す + */ "hideOnlineStatus": string; + /** + * オンライン状態を隠すと、検索などの一部機能において利便性が低下することがあります。 + */ "hideOnlineStatusDescription": string; + /** + * オンライン + */ "online": string; + /** + * アクティブ + */ "active": string; + /** + * オフライン + */ "offline": string; + /** + * 非推奨 + */ "notRecommended": string; + /** + * Botプロテクション + */ "botProtection": string; + /** + * サーバーブロック・サイレンス + */ "instanceBlocking": string; + /** + * アカウントを選択 + */ "selectAccount": string; + /** + * アカウントを切り替え + */ "switchAccount": string; + /** + * 有効 + */ "enabled": string; + /** + * 無効 + */ "disabled": string; + /** + * クイックアクション + */ "quickAction": string; + /** + * ユーザー + */ "user": string; + /** + * 管理 + */ "administration": string; + /** + * アカウント + */ "accounts": string; + /** + * 切り替え + */ "switch": string; + /** + * 管理者情報が設定されていません。 + */ "noMaintainerInformationWarning": string; + /** + * Botプロテクションが設定されていません。 + */ "noBotProtectionWarning": string; + /** + * 設定する + */ "configure": string; + /** + * ギャラリーへ投稿 + */ "postToGallery": string; + /** + * このハッシュタグで投稿 + */ "postToHashtag": string; + /** + * ギャラリー + */ "gallery": string; + /** + * 最近の投稿 + */ "recentPosts": string; + /** + * 人気の投稿 + */ "popularPosts": string; + /** + * ノートで共有 + */ "shareWithNote": string; + /** + * 広告 + */ "ads": string; + /** + * 期限 + */ "expiration": string; + /** + * 開始期間 + */ "startingperiod": string; + /** + * メモ + */ "memo": string; + /** + * 優先度 + */ "priority": string; + /** + * 高 + */ "high": string; + /** + * 中 + */ "middle": string; + /** + * 低 + */ "low": string; + /** + * メールアドレスの設定がされていません。 + */ "emailNotConfiguredWarning": string; + /** + * 比率 + */ "ratio": string; + /** + * 本文をプレビュー + */ "previewNoteText": string; + /** + * カスタムCSS + */ "customCss": string; + /** + * この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。 + */ "customCssWarn": string; + /** + * グローバル + */ "global": string; + /** + * アイコンを四角形で表示 + */ "squareAvatars": string; + /** + * 送信 + */ "sent": string; + /** + * 受信 + */ "received": string; + /** + * 検索結果 + */ "searchResult": string; + /** + * ハッシュタグ + */ "hashtags": string; + /** + * トラブルシューティング + */ "troubleshooting": string; + /** + * UIにぼかし効果を使用 + */ "useBlurEffect": string; + /** + * 詳しく + */ "learnMore": string; + /** + * Misskeyが更新されました! + */ "misskeyUpdated": string; + /** + * 更新情報を見る + */ "whatIsNew": string; + /** + * 翻訳 + */ "translate": string; + /** + * {x}から翻訳 + */ "translatedFrom": ParameterizedString<"x">; + /** + * アカウントの削除が進行中です + */ "accountDeletionInProgress": string; + /** + * サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。 + */ "usernameInfo": string; + /** + * 藍モード + */ "aiChanMode": string; + /** + * 開発者モード + */ "devMode": string; + /** + * CWを維持する + */ "keepCw": string; + /** + * Pub/Subのアカウント + */ "pubSub": string; + /** + * 直近の通信 + */ "lastCommunication": string; + /** + * 解決済み + */ "resolved": string; + /** + * 未解決 + */ "unresolved": string; + /** + * フォロワーを解除 + */ "breakFollow": string; + /** + * フォロワー解除しますか? + */ "breakFollowConfirm": string; + /** + * オンになっています + */ "itsOn": string; + /** + * オフになっています + */ "itsOff": string; + /** + * オン + */ "on": string; + /** + * オフ + */ "off": string; + /** + * アカウント登録にメールアドレスを必須にする + */ "emailRequiredForSignup": string; + /** + * 未読 + */ "unread": string; + /** + * フィルタ + */ "filter": string; + /** + * コントロールパネル + */ "controlPanel": string; + /** + * アカウントを管理 + */ "manageAccounts": string; + /** + * リアクション一覧を公開する + */ "makeReactionsPublic": string; + /** + * あなたがしたリアクション一覧を誰でも見れるようにします。 + */ "makeReactionsPublicDescription": string; + /** + * クラシック + */ "classic": string; + /** + * スレッドをミュート + */ "muteThread": string; + /** + * スレッドのミュートを解除 + */ "unmuteThread": string; + /** + * フォローの公開範囲 + */ "followingVisibility": string; + /** + * フォロワーの公開範囲 + */ "followersVisibility": string; + /** + * さらにスレッドを見る + */ "continueThread": string; + /** + * アカウントが削除されます。よろしいですか? + */ "deleteAccountConfirm": string; + /** + * パスワードが間違っています。 + */ "incorrectPassword": string; + /** + * 「{choice}」に投票しますか? + */ "voteConfirm": ParameterizedString<"choice">; + /** + * 隠す + */ "hide": string; + /** + * モバイルデバイスのときドロワーで表示 + */ "useDrawerReactionPickerForMobile": string; + /** + * おかえりなさい、{name}さん + */ "welcomeBackWithName": ParameterizedString<"name">; + /** + * [{ok}]を押して、メールアドレスの確認を完了してください。 + */ "clickToFinishEmailVerification": ParameterizedString<"ok">; + /** + * デバイスタイプ + */ "overridedDeviceKind": string; + /** + * スマートフォン + */ "smartphone": string; + /** + * タブレット + */ "tablet": string; + /** + * 自動 + */ "auto": string; + /** + * テーマカラー + */ "themeColor": string; + /** + * サイズ + */ "size": string; + /** + * 列の数 + */ "numberOfColumn": string; + /** + * 検索 + */ "searchByGoogle": string; + /** + * サーバーデフォルトのライトテーマ + */ "instanceDefaultLightTheme": string; + /** + * サーバーデフォルトのダークテーマ + */ "instanceDefaultDarkTheme": string; + /** + * オブジェクト形式のテーマコードを記入します。 + */ "instanceDefaultThemeDescription": string; + /** + * ミュートする期限 + */ "mutePeriod": string; + /** + * 期限 + */ "period": string; + /** + * 無期限 + */ "indefinitely": string; + /** + * 10分 + */ "tenMinutes": string; + /** + * 1時間 + */ "oneHour": string; + /** + * 1日 + */ "oneDay": string; + /** + * 1週間 + */ "oneWeek": string; + /** + * 1ヶ月 + */ "oneMonth": string; + /** + * 反映されるまで時間がかかる場合があります。 + */ "reflectMayTakeTime": string; + /** + * アカウント情報の取得に失敗しました + */ "failedToFetchAccountInformation": string; + /** + * レート制限を超えました + */ "rateLimitExceeded": string; + /** + * 画像のクロップ + */ "cropImage": string; + /** + * 画像をクロップしますか? + */ "cropImageAsk": string; + /** + * クロップする + */ "cropYes": string; + /** + * そのまま使う + */ "cropNo": string; + /** + * ファイル + */ "file": string; + /** + * 直近{n}時間 + */ "recentNHours": ParameterizedString<"n">; + /** + * 直近{n}日 + */ "recentNDays": ParameterizedString<"n">; + /** + * メールサーバーの設定がされていません。 + */ "noEmailServerWarning": string; + /** + * 未対応の通報があります。 + */ "thereIsUnresolvedAbuseReportWarning": string; + /** + * 推奨 + */ "recommended": string; + /** + * チェック + */ "check": string; + /** + * このユーザーのドライブ容量上限を変更 + */ "driveCapOverrideLabel": string; + /** + * 0以下を指定すると解除されます。 + */ "driveCapOverrideCaption": string; + /** + * 閲覧するには管理者アカウントでログインしている必要があります。 + */ "requireAdminForView": string; + /** + * システムにより自動で作成・管理されているアカウントです。 + */ "isSystemAccount": string; + /** + * この操作を行うには {x} と入力してください + */ "typeToConfirm": ParameterizedString<"x">; + /** + * アカウント削除 + */ "deleteAccount": string; + /** + * ドキュメント + */ "document": string; + /** + * ページキャッシュ数 + */ "numberOfPageCache": string; + /** + * 多くすると利便性が向上しますが、負荷とメモリ使用量が増えます。 + */ "numberOfPageCacheDescription": string; + /** + * ログアウトしますか? + */ "logoutConfirm": string; + /** + * 最終利用日時 + */ "lastActiveDate": string; + /** + * ステータスバー + */ "statusbar": string; + /** + * 選択してください + */ "pleaseSelect": string; + /** + * 反転 + */ "reverse": string; + /** + * 色付き + */ "colored": string; + /** + * 更新間隔 + */ "refreshInterval": string; + /** + * ラベル + */ "label": string; + /** + * タイプ + */ "type": string; + /** + * 速度 + */ "speed": string; + /** + * 遅い + */ "slow": string; + /** + * 速い + */ "fast": string; + /** + * センシティブなメディアの検出 + */ "sensitiveMediaDetection": string; + /** + * ローカルのみ + */ "localOnly": string; + /** + * リモートのみ + */ "remoteOnly": string; + /** + * アップロード失敗 + */ "failedToUpload": string; + /** + * 不適切な内容を含む可能性があると判定されたためアップロードできません。 + */ "cannotUploadBecauseInappropriate": string; + /** + * ドライブの空き容量が無いためアップロードできません。 + */ "cannotUploadBecauseNoFreeSpace": string; + /** + * ファイルサイズの制限を超えているためアップロードできません。 + */ "cannotUploadBecauseExceedsFileSizeLimit": string; + /** + * ベータ + */ "beta": string; + /** + * 自動センシティブ判定 + */ "enableAutoSensitive": string; + /** + * 利用可能な場合は、機械学習を利用して自動でメディアにセンシティブフラグを設定します。この機能をオフにしても、サーバーによっては自動で設定されることがあります。 + */ "enableAutoSensitiveDescription": string; + /** + * ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかなどを判定しより積極的に行います。オフにすると単に文字列として正しいかどうかのみチェックされます。 + */ "activeEmailValidationDescription": string; + /** + * ナビゲーションバー + */ "navbar": string; + /** + * シャッフル + */ "shuffle": string; + /** + * アカウント + */ "account": string; + /** + * 移動 + */ "move": string; + /** + * プッシュ通知 + */ "pushNotification": string; + /** + * プッシュ通知を有効化 + */ "subscribePushNotification": string; + /** + * プッシュ通知を停止する + */ "unsubscribePushNotification": string; + /** + * プッシュ通知は有効です + */ "pushNotificationAlreadySubscribed": string; + /** + * ブラウザかサーバーがプッシュ通知に非対応 + */ "pushNotificationNotSupported": string; + /** + * 通知が既読になったらプッシュ通知を削除する + */ "sendPushNotificationReadMessage": string; + /** + * 端末の電池消費量が増加する可能性があります。 + */ "sendPushNotificationReadMessageCaption": string; + /** + * 最大化 + */ "windowMaximize": string; + /** + * 最小化 + */ "windowMinimize": string; + /** + * 元に戻す + */ "windowRestore": string; + /** + * キャプション + */ "caption": string; + /** + * Botアカウントでログイン中 + */ "loggedInAsBot": string; + /** + * ツール + */ "tools": string; + /** + * 読み込めません + */ "cannotLoad": string; + /** + * プロフィール表示回数 + */ "numberOfProfileView": string; + /** + * いいね! + */ "like": string; + /** + * いいねを解除 + */ "unlike": string; + /** + * いいね数 + */ "numberOfLikes": string; + /** + * 表示 + */ "show": string; + /** + * 今後表示しない + */ "neverShow": string; + /** + * また後で + */ "remindMeLater": string; + /** + * Misskeyを気に入っていただけましたか? + */ "didYouLikeMisskey": string; + /** + * Misskeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします! + */ "pleaseDonate": ParameterizedString<"host">; + /** + * ロール + */ "roles": string; + /** + * ロール + */ "role": string; + /** + * ロールはありません + */ "noRole": string; + /** + * 一般ユーザー + */ "normalUser": string; + /** + * 未定義 + */ "undefined": string; + /** + * アサイン + */ "assign": string; + /** + * アサインを解除 + */ "unassign": string; + /** + * 色 + */ "color": string; + /** + * カスタム絵文字の管理 + */ "manageCustomEmojis": string; + /** + * アバターデコレーションの管理 + */ "manageAvatarDecorations": string; + /** + * これ以上作成することはできません。 + */ "youCannotCreateAnymore": string; + /** + * 一時的に利用できません + */ "cannotPerformTemporary": string; + /** + * 操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。 + */ "cannotPerformTemporaryDescription": string; + /** + * パラメータエラー + */ "invalidParamError": string; + /** + * リクエストパラメータに問題があります。通常これはバグですが、入力した文字数が多すぎる等の可能性もあります。 + */ "invalidParamErrorDescription": string; + /** + * 操作が拒否されました + */ "permissionDeniedError": string; + /** + * このアカウントにはこの操作を行うための権限がありません。 + */ "permissionDeniedErrorDescription": string; + /** + * プリセット + */ "preset": string; + /** + * プリセットから選択 + */ "selectFromPresets": string; + /** + * 実績 + */ "achievements": string; + /** + * サーバーの応答が無効です + */ "gotInvalidResponseError": string; + /** + * サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。 + */ "gotInvalidResponseErrorDescription": string; + /** + * この投稿は迷惑になる可能性があります。 + */ "thisPostMayBeAnnoying": string; + /** + * ホームに投稿 + */ "thisPostMayBeAnnoyingHome": string; + /** + * やめる + */ "thisPostMayBeAnnoyingCancel": string; + /** + * このまま投稿 + */ "thisPostMayBeAnnoyingIgnore": string; + /** + * 見たことのあるリノートを省略して表示 + */ "collapseRenotes": string; + /** + * サーバー内部エラー + */ "internalServerError": string; + /** + * サーバー内部で予期しないエラーが発生しました。 + */ "internalServerErrorDescription": string; + /** + * エラー情報をコピー + */ "copyErrorInfo": string; + /** + * このサーバーに登録する + */ "joinThisServer": string; + /** + * 他のサーバーを探す + */ "exploreOtherServers": string; + /** + * タイムラインを見てみる + */ "letsLookAtTimeline": string; + /** + * 連合なしにしますか? + */ "disableFederationConfirm": string; + /** + * 連合なしにしても投稿は非公開になりません。ほとんどの場合、連合なしにする必要はありません。 + */ "disableFederationConfirmWarn": string; + /** + * 連合なしにする + */ "disableFederationOk": string; + /** + * 現在このサーバーは招待制です。招待コードをお持ちの方のみ登録できます。 + */ "invitationRequiredToRegister": string; + /** + * このサーバーではメール配信はサポートされていません + */ "emailNotSupported": string; + /** + * チャンネルに投稿 + */ "postToTheChannel": string; + /** + * 後から変更できません。 + */ "cannotBeChangedLater": string; + /** + * リアクションの受け入れ + */ "reactionAcceptance": string; + /** + * いいねのみ + */ "likeOnly": string; + /** + * 全て (リモートはいいねのみ) + */ "likeOnlyForRemote": string; + /** + * 非センシティブのみ + */ "nonSensitiveOnly": string; + /** + * 非センシティブのみ (リモートはいいねのみ) + */ "nonSensitiveOnlyForLocalLikeOnlyForRemote": string; + /** + * 自分に割り当てられたロール + */ "rolesAssignedToMe": string; + /** + * パスワードリセットしますか? + */ "resetPasswordConfirm": string; + /** + * センシティブワード + */ "sensitiveWords": string; + /** + * 設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。 + */ "sensitiveWordsDescription": string; + /** + * スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。 + */ "sensitiveWordsDescription2": string; + /** + * 非表示ハッシュタグ + */ "hiddenTags": string; + /** + * 設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。 + */ "hiddenTagsDescription": string; + /** + * ノート検索は利用できません。 + */ "notesSearchNotAvailable": string; + /** + * ライセンス + */ "license": string; + /** + * お気に入り解除しますか? + */ "unfavoriteConfirm": string; + /** + * 自分のクリップ + */ "myClips": string; + /** + * ドライブクリーナー + */ "drivecleaner": string; + /** + * すべてのキューを今すぐ再試行 + */ "retryAllQueuesNow": string; + /** + * 今すぐ再試行しますか? + */ "retryAllQueuesConfirmTitle": string; + /** + * 一時的にサーバーの負荷が増大することがあります。 + */ "retryAllQueuesConfirmText": string; + /** + * リモートユーザーのチャートを生成 + */ "enableChartsForRemoteUser": string; + /** + * リモートサーバーのチャートを生成 + */ "enableChartsForFederatedInstances": string; + /** + * ノートのアクションにクリップを追加 + */ "showClipButtonInNoteFooter": string; + /** + * リアクションの表示サイズ + */ "reactionsDisplaySize": string; + /** + * リアクションの最大横幅を制限し、縮小して表示する + */ "limitWidthOfReaction": string; + /** + * ノートIDまたはURL + */ "noteIdOrUrl": string; + /** + * 動画 + */ "video": string; + /** + * 動画 + */ "videos": string; + /** + * 音声 + */ "audio": string; + /** + * 音声 + */ "audioFiles": string; + /** + * データセーバー + */ "dataSaver": string; + /** + * アカウントの移行 + */ "accountMigration": string; + /** + * このユーザーは新しいアカウントに移行しました: + */ "accountMoved": string; + /** + * このアカウントは移行されています + */ "accountMovedShort": string; + /** + * この操作はできません + */ "operationForbidden": string; + /** + * 常に広告を表示する + */ "forceShowAds": string; + /** + * メモを追加 + */ "addMemo": string; + /** + * メモを編集 + */ "editMemo": string; + /** + * リアクション一覧 + */ "reactionsList": string; + /** + * リノート一覧 + */ "renotesList": string; + /** + * 通知の表示 + */ "notificationDisplay": string; + /** + * 左上 + */ "leftTop": string; + /** + * 右上 + */ "rightTop": string; + /** + * 左下 + */ "leftBottom": string; + /** + * 右下 + */ "rightBottom": string; + /** + * スタック方向 + */ "stackAxis": string; + /** + * 縦 + */ "vertical": string; + /** + * 横 + */ "horizontal": string; + /** + * 位置 + */ "position": string; + /** + * サーバールール + */ "serverRules": string; + /** + * このサーバーに登録するには、以下の内容を確認し同意する必要があります。 + */ "pleaseConfirmBelowBeforeSignup": string; + /** + * 続けるには、全ての「同意する」にチェックが入っている必要があります。 + */ "pleaseAgreeAllToContinue": string; + /** + * 続ける + */ "continue": string; + /** + * 予約ユーザー名 + */ "preservedUsernames": string; + /** + * 予約するユーザー名を改行で列挙します。ここで指定されたユーザー名はアカウント作成時に使えなくなりますが、管理者によるアカウント作成時はこの制限を受けません。また、既に存在するアカウントも影響を受けません。 + */ "preservedUsernamesDescription": string; + /** + * このファイルからノートを作成 + */ "createNoteFromTheFile": string; + /** + * アーカイブ + */ "archive": string; + /** + * {name}をアーカイブしますか? + */ "channelArchiveConfirmTitle": ParameterizedString<"name">; + /** + * アーカイブすると、チャンネル一覧や検索結果に表示されなくなり、新たな書き込みもできなくなります。 + */ "channelArchiveConfirmDescription": string; + /** + * このチャンネルはアーカイブされています。 + */ "thisChannelArchived": string; + /** + * ノートの表示 + */ "displayOfNote": string; + /** + * 初期設定 + */ "initialAccountSetting": string; + /** + * フォロー中 + */ "youFollowing": string; + /** + * 生成AIによる学習を拒否 + */ "preventAiLearning": string; + /** + * 外部の文章生成AIや画像生成AIに対して、投稿したノートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。 + */ "preventAiLearningDescription": string; + /** + * オプション + */ "options": string; + /** + * ユーザー指定 + */ "specifyUser": string; + /** + * プレビューできません + */ "failedToPreviewUrl": string; + /** + * 更新 + */ "update": string; + /** + * リアクションとして使えるロール + */ "rolesThatCanBeUsedThisEmojiAsReaction": string; + /** + * ロールの指定が一つもない場合、誰でもリアクションとして使えます。 + */ "rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription": string; + /** + * ロールは公開ロールである必要があります。 + */ "rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn": string; + /** + * リアクションを取り消しますか? + */ "cancelReactionConfirm": string; + /** + * リアクションを変更しますか? + */ "changeReactionConfirm": string; + /** + * あとで + */ "later": string; + /** + * Misskeyへ + */ "goToMisskey": string; + /** + * 絵文字の追加辞書 + */ "additionalEmojiDictionary": string; + /** + * インストール済み + */ "installed": string; + /** + * ブランディング + */ "branding": string; + /** + * サーバーのマシン情報を公開する + */ "enableServerMachineStats": string; + /** + * ユーザーごとのIdenticon生成を有効にする + */ "enableIdenticonGeneration": string; + /** + * オフにするとパフォーマンスが向上します。 + */ "turnOffToImprovePerformance": string; + /** + * 招待コードを作成 + */ "createInviteCode": string; + /** + * オプションを指定して作成 + */ "createWithOptions": string; + /** + * 作成数 + */ "createCount": string; + /** + * 招待コードを作成しました + */ "inviteCodeCreated": string; + /** + * 作成できる招待コードの数が上限に達しています。 + */ "inviteLimitExceeded": string; + /** + * 作成できる招待コード: 残り {limit} 個 + */ "createLimitRemaining": ParameterizedString<"limit">; + /** + * {time}で最大 {limit} 個の招待コードを作成できます。 + */ "inviteLimitResetCycle": ParameterizedString<"time" | "limit">; + /** + * 有効期限 + */ "expirationDate": string; + /** + * 有効期限を設けない + */ "noExpirationDate": string; + /** + * 招待コードが使用された日時 + */ "inviteCodeUsedAt": string; + /** + * 招待コードを使用したユーザー + */ "registeredUserUsingInviteCode": string; + /** + * メール認証待ち + */ "waitingForMailAuth": string; + /** + * 招待コードを作成したユーザー + */ "inviteCodeCreator": string; + /** + * 使用日時 + */ "usedAt": string; + /** + * 未使用 + */ "unused": string; + /** + * 使用済み + */ "used": string; + /** + * 期限切れ + */ "expired": string; + /** + * 同意しますか? + */ "doYouAgree": string; + /** + * 重要ですので必ずお読みください。 + */ "beSureToReadThisAsItIsImportant": string; + /** + * 「{x}」の内容をよく読み、同意します。 + */ "iHaveReadXCarefullyAndAgree": ParameterizedString<"x">; + /** + * ダイアログ + */ "dialog": string; + /** + * アイコン + */ "icon": string; + /** + * あなたへ + */ "forYou": string; + /** + * 現在のお知らせ + */ "currentAnnouncements": string; + /** + * 過去のお知らせ + */ "pastAnnouncements": string; + /** + * 未読のお知らせがあります。 + */ "youHaveUnreadAnnouncements": string; + /** + * ブラウザまたはデバイスの指示に従って、セキュリティキーまたはパスキーを使用してください。 + */ "useSecurityKey": string; + /** + * 返信 + */ "replies": string; + /** + * リノート + */ "renotes": string; + /** + * 返信を見る + */ "loadReplies": string; + /** + * 会話を見る + */ "loadConversation": string; + /** + * ピン留めされたリスト + */ "pinnedList": string; + /** + * デバイスの画面を常にオンにする + */ "keepScreenOn": string; + /** + * このリンク先の所有者であることが確認されました + */ "verifiedLink": string; + /** + * 投稿を通知 + */ "notifyNotes": string; + /** + * 投稿の通知を解除 + */ "unnotifyNotes": string; + /** + * 認証 + */ "authentication": string; + /** + * 続けるには認証を行ってください + */ "authenticationRequiredToContinue": string; + /** + * 日時 + */ "dateAndTime": string; + /** + * リノートを表示 + */ "showRenotes": string; + /** + * 編集済み + */ "edited": string; + /** + * 通知の受信設定 + */ "notificationRecieveConfig": string; + /** + * 相互フォロー + */ "mutualFollow": string; + /** + * ファイル付きのみ + */ "fileAttachedOnly": string; + /** + * TLに他の人への返信を含める + */ "showRepliesToOthersInTimeline": string; + /** + * TLに他の人への返信を含めない + */ "hideRepliesToOthersInTimeline": string; + /** + * TLに現在フォロー中の人全員の返信を含めるようにする + */ "showRepliesToOthersInTimelineAll": string; + /** + * TLに現在フォロー中の人全員の返信を含めないようにする + */ "hideRepliesToOthersInTimelineAll": string; + /** + * この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めるようにしますか? + */ "confirmShowRepliesAll": string; + /** + * この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めないようにしますか? + */ "confirmHideRepliesAll": string; + /** + * 外部サービス + */ "externalServices": string; + /** + * 運営者情報 + */ "impressum": string; + /** + * 運営者情報URL + */ "impressumUrl": string; + /** + * ドイツなどの一部の国と地域では表示が義務付けられています(Impressum)。 + */ "impressumDescription": string; + /** + * プライバシーポリシー + */ "privacyPolicy": string; + /** + * プライバシーポリシーURL + */ "privacyPolicyUrl": string; + /** + * 利用規約・プライバシーポリシー + */ "tosAndPrivacyPolicy": string; + /** + * アイコンデコレーション + */ "avatarDecorations": string; + /** + * 付ける + */ "attach": string; + /** + * 外す + */ "detach": string; + /** + * 全て外す + */ "detachAll": string; + /** + * 角度 + */ "angle": string; + /** + * 反転 + */ "flip": string; + /** + * アイコンのデコレーションを表示 + */ "showAvatarDecorations": string; + /** + * 離してリロード + */ "releaseToRefresh": string; + /** + * リロード中 + */ "refreshing": string; + /** + * 引っ張ってリロード + */ "pullDownToRefresh": string; + /** + * タイムラインのリアルタイム更新を無効にする + */ "disableStreamingTimeline": string; + /** + * 通知をグルーピングして表示する + */ "useGroupedNotifications": string; + /** + * メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。 + */ "signupPendingError": string; + /** + * 「内容を隠す」がオンの場合は注釈の記述が必要です。 + */ "cwNotationRequired": string; + /** + * リアクションする + */ "doReaction": string; + /** + * コード + */ "code": string; + /** + * 設定の反映にはリロードが必要です。 + */ "reloadRequiredToApplySettings": string; + /** + * 残り: {n} + */ "remainingN": ParameterizedString<"n">; + /** + * 現在の内容に上書きされますがよろしいですか? + */ "overwriteContentConfirm": string; + /** + * 季節に応じた画面の演出 + */ "seasonalScreenEffect": string; + /** + * デコる + */ "decorate": string; + /** + * 装飾を追加 + */ "addMfmFunction": string; + /** + * 高度なMFMのピッカーを表示する + */ "enableQuickAddMfmFunction": string; + /** + * バブルゲーム + */ "bubbleGame": string; + /** + * 効果音 + */ "sfx": string; + /** + * サウンドが再生されます + */ "soundWillBePlayed": string; + /** + * リプレイを見る + */ "showReplay": string; + /** + * リプレイ + */ "replay": string; + /** + * リプレイ中 + */ "replaying": string; + /** + * ランキング + */ "ranking": string; + /** + * 直近{n}日 + */ "lastNDays": ParameterizedString<"n">; + /** + * タイトルへ + */ "backToTitle": string; + /** + * スワイプしてタブを切り替える + */ "enableHorizontalSwipe": string; "_bubbleGame": { + /** + * 遊び方 + */ "howToPlay": string; "_howToPlay": { + /** + * 位置を調整してハコにモノを落とします。 + */ "section1": string; + /** + * 同じ種類のモノがくっつくと別のモノに変化して、スコアが得られます。 + */ "section2": string; + /** + * モノがハコからあふれるとゲームオーバーです。ハコからあふれないようにしつつモノを融合させてハイスコアを目指そう! + */ "section3": string; }; }; "_announcement": { + /** + * 既存ユーザーのみ + */ "forExistingUsers": string; + /** + * 有効にすると、このお知らせ作成時点で存在するユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。 + */ "forExistingUsersDescription": string; + /** + * 既読にするのに確認が必要 + */ "needConfirmationToRead": string; + /** + * 有効にすると、このお知らせを既読にする際に確認ダイアログが表示されます。また、一括既読操作の対象になりません。 + */ "needConfirmationToReadDescription": string; + /** + * お知らせを終了 + */ "end": string; + /** + * アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討してください。 + */ "tooManyActiveAnnouncementDescription": string; + /** + * 既読にしますか? + */ "readConfirmTitle": string; + /** + * 「{title}」の内容を読み、既読にします。 + */ "readConfirmText": ParameterizedString<"title">; + /** + * 特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。 + */ "shouldNotBeUsedToPresentPermanentInfo": string; + /** + * ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が非常に高いため、使用は慎重に行うことを推奨します。 + */ "dialogAnnouncementUxWarn": string; + /** + * 非通知 + */ "silence": string; + /** + * オンにすると、このお知らせは通知されず、既読にする必要もなくなります。 + */ "silenceDescription": string; }; "_initialAccountSetting": { + /** + * アカウントの作成が完了しました! + */ "accountCreated": string; + /** + * さっそくアカウントの初期設定を行いましょう。 + */ "letsStartAccountSetup": string; + /** + * まずはあなたのプロフィールを設定しましょう。 + */ "letsFillYourProfile": string; + /** + * プロフィール設定 + */ "profileSetting": string; + /** + * プライバシー設定 + */ "privacySetting": string; + /** + * これらの設定は後から変更できます。 + */ "theseSettingsCanEditLater": string; + /** + * この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。 + */ "youCanEditMoreSettingsInSettingsPageLater": string; + /** + * タイムラインを構築するため、気になるユーザーをフォローしてみましょう。 + */ "followUsers": string; + /** + * プッシュ通知を有効にすると{name}の通知をお使いのデバイスで受け取ることができます。 + */ "pushNotificationDescription": ParameterizedString<"name">; + /** + * 初期設定が完了しました! + */ "initialAccountSettingCompleted": string; + /** + * {name}をお楽しみください! + */ "haveFun": ParameterizedString<"name">; + /** + * このまま{name}(Misskey)の使い方についてのチュートリアルに進むこともできますが、ここで中断してすぐに使い始めることもできます。 + */ "youCanContinueTutorial": ParameterizedString<"name">; + /** + * チュートリアルを開始 + */ "startTutorial": string; + /** + * 初期設定をスキップしますか? + */ "skipAreYouSure": string; + /** + * 初期設定をあとでやり直しますか? + */ "laterAreYouSure": string; }; "_initialTutorial": { + /** + * チュートリアルを見る + */ "launchTutorial": string; + /** + * チュートリアル + */ "title": string; + /** + * よくできました + */ "wellDone": string; + /** + * チュートリアルを終了しますか? + */ "skipAreYouSure": string; "_landing": { + /** + * チュートリアルへようこそ + */ "title": string; + /** + * ここでは、Misskeyの基本的な使い方や機能を確認できます。 + */ "description": string; }; "_note": { + /** + * ノートって何? + */ "title": string; + /** + * Misskeyでの投稿は「ノート」と呼びます。ノートはタイムラインに時系列で並んでいて、リアルタイムで更新されていきます。 + */ "description": string; + /** + * 返信することができます。返信に対しての返信も可能で、スレッドのように会話を続けることもできます。 + */ "reply": string; + /** + * そのノートを自分のタイムラインに流して共有することができます。テキストを追加して引用することも可能です。 + */ "renote": string; + /** + * リアクションをつけることができます。詳しくは次のページで解説します。 + */ "reaction": string; + /** + * ノートの詳細を表示したり、リンクをコピーしたりなどの様々な操作が行えます。 + */ "menu": string; }; "_reaction": { + /** + * リアクションって何? + */ "title": string; + /** + * ノートには「リアクション」をつけることができます。「いいね」では伝わらないニュアンスも、リアクションで簡単・気軽に表現できます。 + */ "description": string; + /** + * リアクションは、ノートの「+」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください! + */ "letsTryReacting": string; + /** + * リアクションをつけると先に進めるようになります。 + */ "reactToContinue": string; + /** + * あなたのノートが誰かにリアクションされると、リアルタイムで通知を受け取ります。 + */ "reactNotification": string; + /** + * 「ー」ボタンを押すとリアクションを取り消すことができます。 + */ "reactDone": string; }; "_timeline": { + /** + * タイムラインのしくみ + */ "title": string; + /** + * Misskeyには、使い方に応じて複数のタイムラインが用意されています(サーバーによってはいずれかが無効になっていることがあります)。 + */ "description1": string; + /** + * あなたがフォローしているアカウントの投稿を見られます。 + */ "home": string; + /** + * このサーバーにいるユーザー全員の投稿を見られます。 + */ "local": string; + /** + * ホームタイムラインとローカルタイムラインの投稿が両方表示されます。 + */ "social": string; + /** + * 接続している他のすべてのサーバーからの投稿を見られます。 + */ "global": string; + /** + * それぞれのタイムラインは、画面上部でいつでも切り替えられます。 + */ "description2": string; + /** + * その他にも、リストタイムラインやチャンネルタイムラインなどがあります。詳しくは{link}をご覧ください。 + */ "description3": ParameterizedString<"link">; }; "_postNote": { + /** + * ノートの投稿設定 + */ "title": string; + /** + * Misskeyにノートを投稿する際には、様々なオプションの設定が可能です。投稿フォームはこのようになっています。 + */ "description1": string; "_visibility": { + /** + * ノートを表示できる相手を制限できます。 + */ "description": string; + /** + * すべてのユーザーに公開。 + */ "public": string; + /** + * ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・リノートから、他のユーザーも見ることができます。 + */ "home": string; + /** + * フォロワーにのみ公開。本人以外がリノートすることはできず、またフォロワー以外は閲覧できません。 + */ "followers": string; + /** + * 指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。 + */ "direct": string; + /** + * 機密情報は送信する際は注意してください。 + */ "doNotSendConfidencialOnDirect1": string; + /** + * 送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。 + */ "doNotSendConfidencialOnDirect2": string; + /** + * 他のサーバーに投稿を連合しません。上記の公開範囲に関わらず、他のサーバーのユーザーは、この設定がついたノートを直接閲覧することができなくなります。 + */ "localOnly": string; }; "_cw": { + /** + * 内容を隠す(CW) + */ "title": string; + /** + * 本文のかわりに「注釈」に書いた内容が表示されます。「もっと見る」を押すと本文が表示されます。 + */ "description": string; "_exampleNote": { + /** + * 飯テロ注意 + */ "cw": string; + /** + * チョコのかかったドーナツを食べました🍩😋 + */ "note": string; }; + /** + * サーバーのガイドラインにより必要とされるノートに指定したり、ネタバレ投稿やセンシティブな文章を自主規制したりするときに使います。 + */ "useCases": string; }; }; "_howToMakeAttachmentsSensitive": { + /** + * 添付ファイルをセンシティブにするには? + */ "title": string; + /** + * サーバーのガイドラインにより必要とされる際や、そのまま見れる状態にしておくべきではない添付ファイルには、「センシティブ」設定を付けます。 + */ "description": string; + /** + * 試しに、このフォームに添付された画像をセンシティブにしてみてください! + */ "tryThisFile": string; "_exampleNote": { + /** + * 納豆のフタ開けるのミスったわね… + */ "note": string; }; + /** + * 添付ファイルをセンシティブにする際は、そのファイルをクリックしてメニューを開き、「センシティブとして設定」をクリックします。 + */ "method": string; + /** + * ファイルを添付する際は、サーバーのガイドラインに従ってセンシティブを適切に設定してください。 + */ "sensitiveSucceeded": string; + /** + * 画像をセンシティブに設定すると先に進めるようになります。 + */ "doItToContinue": string; }; "_done": { + /** + * チュートリアルは終了です🎉 + */ "title": string; + /** + * ここで紹介した機能はほんの一部にすぎません。Misskeyの使い方をより詳しく知るには、{link}をご覧ください。 + */ "description": ParameterizedString<"link">; }; }; "_timelineDescription": { + /** + * ホームタイムラインでは、あなたがフォローしているアカウントの投稿を見られます。 + */ "home": string; + /** + * ローカルタイムラインでは、このサーバーにいるユーザー全員の投稿を見られます。 + */ "local": string; + /** + * ソーシャルタイムラインには、ホームタイムラインとローカルタイムラインの投稿が両方表示されます。 + */ "social": string; + /** + * グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。 + */ "global": string; }; "_serverRules": { + /** + * 新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。 + */ "description": string; }; "_serverSettings": { + /** + * アイコン画像のURL + */ "iconUrl": string; + /** + * {host}がアプリとして表示される際のアイコンを指定します。 + */ "appIconDescription": ParameterizedString<"host">; + /** + * 例: PWAや、スマートフォンのホーム画面にブックマークとして追加された時など + */ "appIconUsageExample": string; + /** + * 円形もしくは角丸にクロップされる場合があるため、塗り潰された余白のある背景を持つことが推奨されます。 + */ "appIconStyleRecommendation": string; + /** + * 解像度は必ず{resolution}である必要があります。 + */ "appIconResolutionMustBe": ParameterizedString<"resolution">; + /** + * manifest.jsonのオーバーライド + */ "manifestJsonOverride": string; + /** + * 略称 + */ "shortName": string; + /** + * サーバーの正式名称が長い場合に、代わりに表示することのできる略称や通称。 + */ "shortNameDescription": string; + /** + * 有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。 + */ "fanoutTimelineDescription": string; + /** + * データベースへのフォールバック + */ "fanoutTimelineDbFallback": string; + /** + * 有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。 + */ "fanoutTimelineDbFallbackDescription": string; }; "_accountMigration": { + /** + * 別のアカウントからこのアカウントに移行 + */ "moveFrom": string; + /** + * 別のアカウントへエイリアスを作成 + */ "moveFromSub": string; + /** + * 移行元のアカウント #{n} + */ "moveFromLabel": ParameterizedString<"n">; + /** + * 別のアカウントからこのアカウントに移行したい場合、ここでエイリアスを作成しておく必要があります。 + * 移行元のアカウントをこのように入力してください: @username@server.example.com + * 削除するには、入力欄を空にして保存します(非推奨)。 + */ "moveFromDescription": string; + /** + * このアカウントを新しいアカウントへ移行 + */ "moveTo": string; + /** + * 移行先のアカウント: + */ "moveToLabel": string; + /** + * アカウントを移行すると、取り消すことはできません。 + */ "moveCannotBeUndone": string; + /** + * 新しいアカウントへ移行します。 + *  ・フォロワーが新しいアカウントを自動でフォローします + *  ・このアカウントからのフォローは全て解除されます + *  ・このアカウントではノートの作成などができなくなります + * + * フォロワーの移行は自動ですが、フォローの移行は手動で行う必要があります。移行前にこのアカウントでフォローエクスポートし、移行後すぐに移行先アカウントでインポートを行なってください。 + * リスト・ミュート・ブロックについても同様ですので、手動で移行する必要があります。 + * + * (この説明はこのサーバー(Misskey v13.12.0以降)の仕様です。Mastodonなどの他のActivityPubソフトウェアでは挙動が異なる場合があります。) + */ "moveAccountDescription": string; + /** + * アカウントの移行には、まずは移行先のアカウントでこのアカウントに対しエイリアスを作成します。 + * エイリアス作成後、移行先のアカウントを次のように入力してください: @username@server.example.com + */ "moveAccountHowTo": string; + /** + * 移行する + */ "startMigration": string; + /** + * 本当にこのアカウントを {account} に移行しますか?一度移行すると取り消せず、二度とこのアカウントを元の状態で使用できなくなります。 + */ "migrationConfirm": ParameterizedString<"account">; + /** + * + * アカウントは移行されています。 + * 移行を取り消すことはできません。 + */ "movedAndCannotBeUndone": string; + /** + * このアカウントからのフォロー解除は移行操作から24時間後に実行されます。 + * このアカウントのフォロー・フォロワー数は0になっています。フォロワーの解除はされないため、あなたのフォロワーはこのアカウントのフォロワー向け投稿を引き続き閲覧できます。 + */ "postMigrationNote": string; + /** + * 移行先のアカウント: + */ "movedTo": string; }; "_achievements": { + /** + * 獲得日時 + */ "earnedAt": string; "_types": { "_notes1": { + /** + * just setting up my msky + */ "title": string; + /** + * 初めてノートを投稿した + */ "description": string; + /** + * 良いMisskeyライフを! + */ "flavor": string; }; "_notes10": { + /** + * いくつかのノート + */ "title": string; + /** + * ノートを10回投稿した + */ "description": string; }; "_notes100": { + /** + * たくさんのノート + */ "title": string; + /** + * ノートを100回投稿した + */ "description": string; }; "_notes500": { + /** + * ノートまみれ + */ "title": string; + /** + * ノートを500回投稿した + */ "description": string; }; "_notes1000": { + /** + * ノートの山 + */ "title": string; + /** + * ノートを1,000回投稿した + */ "description": string; }; "_notes5000": { + /** + * 湧き出るノート + */ "title": string; + /** + * ノートを5,000回投稿した + */ "description": string; }; "_notes10000": { + /** + * スーパーノート + */ "title": string; + /** + * ノートを10,000回投稿した + */ "description": string; }; "_notes20000": { + /** + * ニードモアノート + */ "title": string; + /** + * ノートを20,000回投稿した + */ "description": string; }; "_notes30000": { + /** + * ノートノートノート + */ "title": string; + /** + * ノートを30,000回投稿した + */ "description": string; }; "_notes40000": { + /** + * ノート工場 + */ "title": string; + /** + * ノートを40,000回投稿した + */ "description": string; }; "_notes50000": { + /** + * ノートの惑星 + */ "title": string; + /** + * ノートを50,000回投稿した + */ "description": string; }; "_notes60000": { + /** + * ノートクエーサー + */ "title": string; + /** + * ノートを60,000回投稿した + */ "description": string; }; "_notes70000": { + /** + * ブラックノートホール + */ "title": string; + /** + * ノートを70,000回投稿した + */ "description": string; }; "_notes80000": { + /** + * ノートギャラクシー + */ "title": string; + /** + * ノートを80,000回投稿した + */ "description": string; }; "_notes90000": { + /** + * ノートバース + */ "title": string; + /** + * ノートを90,000回投稿した + */ "description": string; }; "_notes100000": { + /** + * ALL YOUR NOTE ARE BELONG TO US + */ "title": string; + /** + * ノートを100,000回投稿した + */ "description": string; + /** + * そんなに書くことある? + */ "flavor": string; }; "_login3": { + /** + * ビギナーⅠ + */ "title": string; + /** + * 通算ログイン日数が3日 + */ "description": string; + /** + * 今日からね僕は ミスキストってことで + */ "flavor": string; }; "_login7": { + /** + * ビギナーⅡ + */ "title": string; + /** + * 通算ログイン日数が7日 + */ "description": string; + /** + * 慣れてきましたか? + */ "flavor": string; }; "_login15": { + /** + * ビギナーⅢ + */ "title": string; + /** + * 通算ログイン日数が15日 + */ "description": string; }; "_login30": { + /** + * ミスキストⅠ + */ "title": string; + /** + * 通算ログイン日数が30日 + */ "description": string; }; "_login60": { + /** + * ミスキストⅡ + */ "title": string; + /** + * 通算ログイン日数が60日 + */ "description": string; }; "_login100": { + /** + * ミスキストⅢ + */ "title": string; + /** + * 通算ログイン日数が100日 + */ "description": string; + /** + * そのユーザー、ミスキストにつき + */ "flavor": string; }; "_login200": { + /** + * 常連Ⅰ + */ "title": string; + /** + * 通算ログイン日数が200日 + */ "description": string; }; "_login300": { + /** + * 常連Ⅱ + */ "title": string; + /** + * 通算ログイン日数が300日 + */ "description": string; }; "_login400": { + /** + * 常連Ⅲ + */ "title": string; + /** + * 通算ログイン日数が400日 + */ "description": string; }; "_login500": { + /** + * ベテランⅠ + */ "title": string; + /** + * 通算ログイン日数が500日 + */ "description": string; + /** + * 諸君、私はノートが好きだ + */ "flavor": string; }; "_login600": { + /** + * ベテランⅡ + */ "title": string; + /** + * 通算ログイン日数が600日 + */ "description": string; }; "_login700": { + /** + * ベテランⅢ + */ "title": string; + /** + * 通算ログイン日数が700日 + */ "description": string; }; "_login800": { + /** + * ノートマスターⅠ + */ "title": string; + /** + * 通算ログイン日数が800日 + */ "description": string; }; "_login900": { + /** + * ノートマスターⅡ + */ "title": string; + /** + * 通算ログイン日数が900日 + */ "description": string; }; "_login1000": { + /** + * ノートマスターⅢ + */ "title": string; + /** + * 通算ログイン日数が1,000日 + */ "description": string; + /** + * Misskeyを使ってくれてありがとう! + */ "flavor": string; }; "_noteClipped1": { + /** + * クリップせずにはいられないな + */ "title": string; + /** + * 初めてノートをクリップした + */ "description": string; }; "_noteFavorited1": { + /** + * 星をみるひと + */ "title": string; + /** + * 初めてノートをお気に入りに登録した + */ "description": string; }; "_myNoteFavorited1": { + /** + * 星が欲しい + */ "title": string; + /** + * 自分のノートが他の人からお気に入りに登録された + */ "description": string; }; "_profileFilled": { + /** + * 準備万端 + */ "title": string; + /** + * プロフィール設定を行った + */ "description": string; }; "_markedAsCat": { + /** + * 吾輩は猫である + */ "title": string; + /** + * アカウントをCatとして設定した + */ "description": string; + /** + * 名前はまだない。 + */ "flavor": string; }; "_following1": { + /** + * はじめてのフォロー + */ "title": string; + /** + * 初めてフォローした + */ "description": string; }; "_following10": { + /** + * ついてく、ついてく + */ "title": string; + /** + * フォローが10人を超した + */ "description": string; }; "_following50": { + /** + * 友達たくさん + */ "title": string; + /** + * フォローが50人を超した + */ "description": string; }; "_following100": { + /** + * 友達100人 + */ "title": string; + /** + * フォローが100人を超した + */ "description": string; }; "_following300": { + /** + * 友達過多 + */ "title": string; + /** + * フォローが300人を超した + */ "description": string; }; "_followers1": { + /** + * はじめてのフォロワー + */ "title": string; + /** + * 初めてフォローされた + */ "description": string; }; "_followers10": { + /** + * フォローミー! + */ "title": string; + /** + * フォロワーが10人を超した + */ "description": string; }; "_followers50": { + /** + * ぞろぞろ + */ "title": string; + /** + * フォロワーが50人を超した + */ "description": string; }; "_followers100": { + /** + * 人気者 + */ "title": string; + /** + * フォロワーが100人を超した + */ "description": string; }; "_followers300": { + /** + * 一列でお並びください + */ "title": string; + /** + * フォロワーが300人を超した + */ "description": string; }; "_followers500": { + /** + * 基地局 + */ "title": string; + /** + * フォロワーが500人を超した + */ "description": string; }; "_followers1000": { + /** + * インフルエンサー + */ "title": string; + /** + * フォロワーが1,000人を超した + */ "description": string; }; "_collectAchievements30": { + /** + * 実績コレクター + */ "title": string; + /** + * 実績を30個以上獲得した + */ "description": string; }; "_viewAchievements3min": { + /** + * 実績好き + */ "title": string; + /** + * 実績一覧を3分以上眺め続けた + */ "description": string; }; "_iLoveMisskey": { + /** + * I Love Misskey + */ "title": string; + /** + * "I ❤ #Misskey"を投稿した + */ "description": string; + /** + * Misskeyを使ってくださりありがとうございます! by 開発チーム + */ "flavor": string; }; "_foundTreasure": { + /** + * 宝探し + */ "title": string; + /** + * 隠されたお宝を発見した + */ "description": string; }; "_client30min": { + /** + * ひとやすみ + */ "title": string; + /** + * クライアントを起動してから30分以上経過した + */ "description": string; }; "_client60min": { + /** + * Misskeyの見すぎ + */ "title": string; + /** + * クライアントを起動してから60分以上経過した + */ "description": string; }; "_noteDeletedWithin1min": { + /** + * いまのなし + */ "title": string; + /** + * 投稿してから1分以内にその投稿を削除した + */ "description": string; }; "_postedAtLateNight": { + /** + * 夜行性 + */ "title": string; + /** + * 深夜にノートを投稿した + */ "description": string; + /** + * そろそろ寝よう。 + */ "flavor": string; }; "_postedAt0min0sec": { + /** + * 時報 + */ "title": string; + /** + * 0分0秒にノートを投稿した + */ "description": string; + /** + * ポッ ポッ ポッ ピーン + */ "flavor": string; }; "_selfQuote": { + /** + * 自己言及 + */ "title": string; + /** + * 自分のノートを引用した + */ "description": string; }; "_htl20npm": { + /** + * 流れるTL + */ "title": string; + /** + * ホームタイムラインの流速が20npmを越す + */ "description": string; }; "_viewInstanceChart": { + /** + * アナリスト + */ "title": string; + /** + * サーバーのチャートを表示した + */ "description": string; }; "_outputHelloWorldOnScratchpad": { + /** + * Hello, world! + */ "title": string; + /** + * スクラッチパッドで hello world を出力した + */ "description": string; }; "_open3windows": { + /** + * マルチウィンドウ + */ "title": string; + /** + * ウィンドウを3つ以上開いた状態にした + */ "description": string; }; "_driveFolderCircularReference": { + /** + * 循環参照 + */ "title": string; + /** + * ドライブのフォルダを再帰的な入れ子にしようとした + */ "description": string; }; "_reactWithoutRead": { + /** + * ちゃんと読んだ? + */ "title": string; + /** + * 100文字以上のテキストを含むノートに投稿されてから3秒以内にリアクションした + */ "description": string; }; "_clickedClickHere": { + /** + * ここをクリック + */ "title": string; + /** + * ここをクリックした + */ "description": string; }; "_justPlainLucky": { + /** + * 単なるラッキー + */ "title": string; + /** + * 10秒ごとに0.005%の確率で獲得 + */ "description": string; }; "_setNameToSyuilo": { + /** + * 神様コンプレックス + */ "title": string; + /** + * 名前を syuilo に設定した + */ "description": string; }; "_passedSinceAccountCreated1": { + /** + * 一周年 + */ "title": string; + /** + * アカウント作成から1年経過した + */ "description": string; }; "_passedSinceAccountCreated2": { + /** + * 二周年 + */ "title": string; + /** + * アカウント作成から2年経過した + */ "description": string; }; "_passedSinceAccountCreated3": { + /** + * 三周年 + */ "title": string; + /** + * アカウント作成から3年経過した + */ "description": string; }; "_loggedInOnBirthday": { + /** + * ハッピーバースデー + */ "title": string; + /** + * 誕生日にログインした + */ "description": string; }; "_loggedInOnNewYearsDay": { + /** + * あけましておめでとうございます + */ "title": string; + /** + * 元日にログインした + */ "description": string; + /** + * 今年も弊サーバーをよろしくお願いします + */ "flavor": string; }; "_cookieClicked": { + /** + * クッキーをクリックするゲーム + */ "title": string; + /** + * クッキーをクリックした + */ "description": string; + /** + * ソフト間違ってない? + */ "flavor": string; }; "_brainDiver": { + /** + * Brain Diver + */ "title": string; + /** + * Brain Diverへのリンクを投稿した + */ "description": string; + /** + * Misskey-Misskey La-Tu-Ma + */ "flavor": string; }; "_smashTestNotificationButton": { + /** + * テスト過剰 + */ "title": string; + /** + * 通知のテストをごく短時間のうちに連続して行った + */ "description": string; }; "_tutorialCompleted": { + /** + * Misskey初心者講座 修了証 + */ "title": string; + /** + * チュートリアルを完了した + */ "description": string; }; "_bubbleGameExplodingHead": { + /** + * 🤯 + */ "title": string; + /** + * バブルゲームで最も大きいモノを出した + */ "description": string; }; "_bubbleGameDoubleExplodingHead": { + /** + * ダブル🤯 + */ "title": string; + /** + * バブルゲームで最も大きいモノを2つ同時に出した + */ "description": string; + /** + * これくらいの おべんとばこに 🤯 🤯 ちょっとつめて + */ "flavor": string; }; }; }; "_role": { + /** + * ロールの作成 + */ "new": string; + /** + * ロールの編集 + */ "edit": string; + /** + * ロール名 + */ "name": string; + /** + * ロールの説明 + */ "description": string; + /** + * ロールの権限 + */ "permission": string; + /** + * モデレーターは基本的なモデレーションに関する操作を行えます。 + * 管理者はサーバーの全ての設定を変更できます。 + */ "descriptionOfPermission": string; + /** + * アサイン + */ "assignTarget": string; + /** + * マニュアルは誰がこのロールに含まれるかを手動で管理します。 + * コンディショナルは条件を設定し、それに合致するユーザーが自動で含まれるようになります。 + */ "descriptionOfAssignTarget": string; + /** + * マニュアル + */ "manual": string; + /** + * マニュアルロール + */ "manualRoles": string; + /** + * コンディショナル + */ "conditional": string; + /** + * コンディショナルロール + */ "conditionalRoles": string; + /** + * 条件 + */ "condition": string; + /** + * これはコンディショナルロールです。 + */ "isConditionalRole": string; + /** + * 公開ロール + */ "isPublic": string; + /** + * ユーザーのプロフィールでこのロールが表示されます。 + */ "descriptionOfIsPublic": string; + /** + * オプション + */ "options": string; + /** + * ポリシー + */ "policies": string; + /** + * ベースロール + */ "baseRole": string; + /** + * ベースロールの値を使用 + */ "useBaseValue": string; + /** + * アサインするロールを選択 + */ "chooseRoleToAssign": string; + /** + * アイコン画像のURL + */ "iconUrl": string; + /** + * バッジとして表示 + */ "asBadge": string; + /** + * オンにすると、ユーザー名の横にロールのアイコンが表示されます。 + */ "descriptionOfAsBadge": string; + /** + * ユーザーを見つけやすくする + */ "isExplorable": string; + /** + * オンにすると、「みつける」でメンバー一覧が公開されるほか、ロールのタイムラインが利用可能になります。 + */ "descriptionOfIsExplorable": string; + /** + * 表示順 + */ "displayOrder": string; + /** + * 数値が大きいほどUI上で先頭に表示されます。 + */ "descriptionOfDisplayOrder": string; + /** + * モデレーターのメンバー編集を許可 + */ "canEditMembersByModerator": string; + /** + * オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。 + */ "descriptionOfCanEditMembersByModerator": string; + /** + * 優先度 + */ "priority": string; "_priority": { + /** + * 低 + */ "low": string; + /** + * 中 + */ "middle": string; + /** + * 高 + */ "high": string; }; "_options": { + /** + * グローバルタイムラインの閲覧 + */ "gtlAvailable": string; + /** + * ローカルタイムラインの閲覧 + */ "ltlAvailable": string; + /** + * パブリック投稿の許可 + */ "canPublicNote": string; + /** + * サーバー招待コードの発行 + */ "canInvite": string; + /** + * 招待コードの作成可能数 + */ "inviteLimit": string; + /** + * 招待コードの発行間隔 + */ "inviteLimitCycle": string; + /** + * 招待コードの有効期限 + */ "inviteExpirationTime": string; + /** + * カスタム絵文字の管理 + */ "canManageCustomEmojis": string; + /** + * アバターデコレーションの管理 + */ "canManageAvatarDecorations": string; + /** + * ドライブ容量 + */ "driveCapacity": string; + /** + * ファイルにNSFWを常に付与 + */ "alwaysMarkNsfw": string; + /** + * ノートのピン留めの最大数 + */ "pinMax": string; + /** + * アンテナの作成可能数 + */ "antennaMax": string; + /** + * ワードミュートの最大文字数 + */ "wordMuteMax": string; + /** + * Webhookの作成可能数 + */ "webhookMax": string; + /** + * クリップの作成可能数 + */ "clipMax": string; + /** + * クリップ内のノートの最大数 + */ "noteEachClipsMax": string; + /** + * ユーザーリストの作成可能数 + */ "userListMax": string; + /** + * ユーザーリスト内のユーザーの最大数 + */ "userEachUserListsMax": string; + /** + * レートリミット + */ "rateLimitFactor": string; + /** + * 小さいほど制限が緩和され、大きいほど制限が強化されます。 + */ "descriptionOfRateLimitFactor": string; + /** + * 広告の非表示 + */ "canHideAds": string; + /** + * ノート検索の利用 + */ "canSearchNotes": string; + /** + * 翻訳機能の利用 + */ "canUseTranslator": string; + /** + * アイコンデコレーションの最大取付個数 + */ "avatarDecorationLimit": string; }; "_condition": { + /** + * ローカルユーザー + */ "isLocal": string; + /** + * リモートユーザー + */ "isRemote": string; + /** + * アカウント作成から~以内 + */ "createdLessThan": string; + /** + * アカウント作成から~経過 + */ "createdMoreThan": string; + /** + * フォロワー数が~以下 + */ "followersLessThanOrEq": string; + /** + * フォロワー数が~以上 + */ "followersMoreThanOrEq": string; + /** + * フォロー数が~以下 + */ "followingLessThanOrEq": string; + /** + * フォロー数が~以上 + */ "followingMoreThanOrEq": string; + /** + * 投稿数が~以下 + */ "notesLessThanOrEq": string; + /** + * 投稿数が~以上 + */ "notesMoreThanOrEq": string; + /** + * ~かつ~ + */ "and": string; + /** + * ~または~ + */ "or": string; + /** + * ~ではない + */ "not": string; }; }; "_sensitiveMediaDetection": { + /** + * 機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。 + */ "description": string; + /** + * 検出感度 + */ "sensitivity": string; + /** + * 感度を低くすると、誤検知(偽陽性)が減ります。感度を高くすると、検知漏れ(偽陰性)が減ります。 + */ "sensitivityDescription": string; + /** + * センシティブフラグを設定する + */ "setSensitiveFlagAutomatically": string; + /** + * この設定をオフにしても内部的に判定結果は保持されます。 + */ "setSensitiveFlagAutomaticallyDescription": string; + /** + * 動画の解析を有効化 + */ "analyzeVideos": string; + /** + * 静止画に加えて動画も解析するようにします。サーバーの負荷が少し増えます。 + */ "analyzeVideosDescription": string; }; "_emailUnavailable": { + /** + * 既に使用されています + */ "used": string; + /** + * 形式が正しくありません + */ "format": string; + /** + * 恒久的に使用可能なアドレスではありません + */ "disposable": string; + /** + * 正しいメールサーバーではありません + */ "mx": string; + /** + * メールサーバーが応答しません + */ "smtp": string; + /** + * このメールアドレスでは登録できません + */ "banned": string; }; "_ffVisibility": { + /** + * 公開 + */ "public": string; + /** + * フォロワーだけに公開 + */ "followers": string; + /** + * 非公開 + */ "private": string; }; "_signup": { + /** + * ほとんど完了です + */ "almostThere": string; + /** + * あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。 + */ "emailAddressInfo": string; + /** + * 入力されたメールアドレス({email})宛に確認のメールが送信されました。メールに記載されたリンクにアクセスすると、アカウントの作成が完了します。メールに記載されているリンクの有効期限は30分です。 + */ "emailSent": ParameterizedString<"email">; }; "_accountDelete": { + /** + * アカウントの削除 + */ "accountDelete": string; + /** + * アカウントの削除は負荷のかかる処理であるため、作成したコンテンツの数やアップロードしたファイルの数が多いと完了までに時間がかかることがあります。 + */ "mayTakeTime": string; + /** + * アカウントの削除が完了する際は、登録してあったメールアドレス宛に通知を送信します。 + */ "sendEmail": string; + /** + * アカウント削除をリクエスト + */ "requestAccountDelete": string; + /** + * 削除処理が開始されました。 + */ "started": string; + /** + * 削除が進行中 + */ "inProgress": string; }; "_ad": { + /** + * 戻る + */ "back": string; + /** + * この広告の表示頻度を下げる + */ "reduceFrequencyOfThisAd": string; + /** + * 表示しない + */ "hide": string; + /** + * 曜日はサーバーのタイムゾーンを元に指定されます。 + */ "timezoneinfo": string; + /** + * 広告配信設定 + */ "adsSettings": string; + /** + * リアルタイム更新中に広告を配信する間隔(ノートの個数) + */ "notesPerOneAd": string; + /** + * 0でリアルタイム更新時の広告配信を無効 + */ "setZeroToDisable": string; + /** + * 広告の配信間隔が極めて短いため、ユーザー体験が著しく損われる可能性があります。 + */ "adsTooClose": string; }; "_forgotPassword": { + /** + * アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。 + */ "enterEmail": string; + /** + * メールアドレスを登録していない場合は、管理者までお問い合わせください。 + */ "ifNoEmail": string; + /** + * このサーバーではメールがサポートされていないため、パスワードリセットを行う場合は管理者までお問い合わせください。 + */ "contactAdmin": string; }; "_gallery": { + /** + * 自分の投稿 + */ "my": string; + /** + * いいねした投稿 + */ "liked": string; + /** + * いいね! + */ "like": string; + /** + * いいね解除 + */ "unlike": string; }; "_email": { "_follow": { + /** + * フォローされました + */ "title": string; }; "_receiveFollowRequest": { + /** + * フォローリクエストを受け取りました + */ "title": string; }; }; "_plugin": { + /** + * プラグインのインストール + */ "install": string; + /** + * 信頼できないプラグインはインストールしないでください。 + */ "installWarn": string; + /** + * プラグインの管理 + */ "manage": string; + /** + * ソースを表示 + */ "viewSource": string; }; "_preferencesBackups": { + /** + * 作成したバックアップ + */ "list": string; + /** + * 新規保存 + */ "saveNew": string; + /** + * ファイルを読み込み + */ "loadFile": string; + /** + * このデバイスに適用 + */ "apply": string; + /** + * 上書き保存 + */ "save": string; + /** + * バックアップ名を入力 + */ "inputName": string; + /** + * 保存できません + */ "cannotSave": string; + /** + * バックアップ名「{name}」は既に存在します。違う名前を指定してください。 + */ "nameAlreadyExists": ParameterizedString<"name">; + /** + * バックアップ「{name}」を現在のデバイスに適用しますか?現在のデバイス設定は失われます。 + */ "applyConfirm": ParameterizedString<"name">; + /** + * {name}に上書き保存しますか? + */ "saveConfirm": ParameterizedString<"name">; + /** + * {name}を削除しますか? + */ "deleteConfirm": ParameterizedString<"name">; + /** + * 「{old}」を「{new}」に変更しますか? + */ "renameConfirm": ParameterizedString<"old" | "new">; + /** + * バックアップはありません。「新規保存」で現在のクライアント設定をサーバーに保存できます。 + */ "noBackups": string; + /** + * 作成日時: {date} {time} + */ "createdAt": ParameterizedString<"date" | "time">; + /** + * 更新日時: {date} {time} + */ "updatedAt": ParameterizedString<"date" | "time">; + /** + * 読み込みできません + */ "cannotLoad": string; + /** + * ファイル形式が違います。 + */ "invalidFile": string; }; "_registry": { + /** + * スコープ + */ "scope": string; + /** + * キー + */ "key": string; + /** + * キー + */ "keys": string; + /** + * ドメイン + */ "domain": string; + /** + * キーを作成 + */ "createKey": string; }; "_aboutMisskey": { + /** + * Misskeyはsyuiloによって2014年から開発されている、オープンソースのソフトウェアです。 + */ "about": string; + /** + * コントリビューター + */ "contributors": string; + /** + * 全てのコントリビューター + */ "allContributors": string; + /** + * ソースコード + */ "source": string; + /** + * Misskeyを翻訳 + */ "translation": string; + /** + * Misskeyに寄付 + */ "donate": string; + /** + * 他にも多くの方が支援してくれています。ありがとうございます🥰 + */ "morePatrons": string; + /** + * 支援者 + */ "patrons": string; + /** + * プロジェクトメンバー + */ "projectMembers": string; }; "_displayOfSensitiveMedia": { + /** + * センシティブ設定されたメディアを隠す + */ "respect": string; + /** + * センシティブ設定されたメディアを隠さない + */ "ignore": string; + /** + * 常にメディアを隠す + */ "force": string; }; "_instanceTicker": { + /** + * 表示しない + */ "none": string; + /** + * リモートユーザーに表示 + */ "remote": string; + /** + * 常に表示 + */ "always": string; }; "_serverDisconnectedBehavior": { + /** + * 自動でリロード + */ "reload": string; + /** + * ダイアログで警告 + */ "dialog": string; + /** + * 控えめに警告 + */ "quiet": string; }; "_channel": { + /** + * チャンネルを作成 + */ "create": string; + /** + * チャンネルを編集 + */ "edit": string; + /** + * バナーを設定 + */ "setBanner": string; + /** + * バナーを削除 + */ "removeBanner": string; + /** + * トレンド + */ "featured": string; + /** + * 管理中 + */ "owned": string; + /** + * フォロー中 + */ "following": string; + /** + * {n}人が参加中 + */ "usersCount": ParameterizedString<"n">; + /** + * {n}投稿があります + */ "notesCount": ParameterizedString<"n">; + /** + * 名前と説明 + */ "nameAndDescription": string; + /** + * 名前のみ + */ "nameOnly": string; + /** + * チャンネル外へのリノートと引用リノートを許可する + */ "allowRenoteToExternal": string; }; "_menuDisplay": { + /** + * 横 + */ "sideFull": string; + /** + * 横(アイコン) + */ "sideIcon": string; + /** + * 上部 + */ "top": string; + /** + * 隠す + */ "hide": string; }; "_wordMute": { + /** + * ミュートするワード + */ "muteWords": string; + /** + * スペースで区切るとAND指定になり、改行で区切るとOR指定になります。 + */ "muteWordsDescription": string; + /** + * キーワードをスラッシュで囲むと正規表現になります。 + */ "muteWordsDescription2": string; }; "_instanceMute": { + /** + * ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとRenoteをミュートします。 + */ "instanceMuteDescription": string; + /** + * 改行で区切って設定します + */ "instanceMuteDescription2": string; + /** + * 設定したサーバーのノートを隠します。 + */ "title": string; + /** + * ミュートするサーバー + */ "heading": string; }; "_theme": { + /** + * テーマを探す + */ "explore": string; + /** + * テーマのインストール + */ "install": string; + /** + * テーマの管理 + */ "manage": string; + /** + * テーマコード + */ "code": string; + /** + * 説明 + */ "description": string; + /** + * {name}をインストールしました + */ "installed": ParameterizedString<"name">; + /** + * インストールされたテーマ + */ "installedThemes": string; + /** + * 標準のテーマ + */ "builtinThemes": string; + /** + * そのテーマは既にインストールされています + */ "alreadyInstalled": string; + /** + * テーマの形式が間違っています + */ "invalid": string; + /** + * テーマを作る + */ "make": string; + /** + * ベース + */ "base": string; + /** + * 定数を追加 + */ "addConstant": string; + /** + * 定数 + */ "constant": string; + /** + * デフォルト値 + */ "defaultValue": string; + /** + * 色 + */ "color": string; + /** + * プロパティを参照 + */ "refProp": string; + /** + * 定数を参照 + */ "refConst": string; + /** + * キー + */ "key": string; + /** + * 関数 + */ "func": string; + /** + * 関数の種類 + */ "funcKind": string; + /** + * 引数 + */ "argument": string; + /** + * 元にするプロパティの名前 + */ "basedProp": string; + /** + * 不透明度 + */ "alpha": string; + /** + * 暗さ + */ "darken": string; + /** + * 明るさ + */ "lighten": string; + /** + * 定数名を入力してください + */ "inputConstantName": string; + /** + * ここにテーマコードを貼り付けて、エディターにインポートできます + */ "importInfo": string; + /** + * 定数 {const} を削除しても良いですか? + */ "deleteConstantConfirm": ParameterizedString<"const">; "keys": { + /** + * アクセント + */ "accent": string; + /** + * 背景 + */ "bg": string; + /** + * 文字 + */ "fg": string; + /** + * フォーカス + */ "focus": string; + /** + * インジケーター + */ "indicator": string; + /** + * パネル + */ "panel": string; + /** + * 影 + */ "shadow": string; + /** + * ヘッダー + */ "header": string; + /** + * サイドバーの背景 + */ "navBg": string; + /** + * サイドバーの文字 + */ "navFg": string; + /** + * サイドバー文字(ホバー) + */ "navHoverFg": string; + /** + * サイドバー文字(アクティブ) + */ "navActive": string; + /** + * サイドバーのインジケーター + */ "navIndicator": string; + /** + * リンク + */ "link": string; + /** + * ハッシュタグ + */ "hashtag": string; + /** + * メンション + */ "mention": string; + /** + * あなた宛てメンション + */ "mentionMe": string; + /** + * Renote + */ "renote": string; + /** + * モーダルの背景 + */ "modalBg": string; + /** + * 分割線 + */ "divider": string; + /** + * スクロールバーの取っ手 + */ "scrollbarHandle": string; + /** + * スクロールバーの取っ手(ホバー) + */ "scrollbarHandleHover": string; + /** + * 日付ラベルの文字 + */ "dateLabelFg": string; + /** + * 情報の背景 + */ "infoBg": string; + /** + * 情報の文字 + */ "infoFg": string; + /** + * 警告の背景 + */ "infoWarnBg": string; + /** + * 警告の文字 + */ "infoWarnFg": string; + /** + * 通知トーストの背景 + */ "toastBg": string; + /** + * 通知トーストの文字 + */ "toastFg": string; + /** + * ボタンの背景 + */ "buttonBg": string; + /** + * ボタンの背景 (ホバー) + */ "buttonHoverBg": string; + /** + * 入力ボックスの縁取り + */ "inputBorder": string; + /** + * リスト項目の背景 (ホバー) + */ "listItemHoverBg": string; + /** + * ドライブフォルダーの背景 + */ "driveFolderBg": string; + /** + * 壁紙のオーバーレイ + */ "wallpaperOverlay": string; + /** + * バッジ + */ "badge": string; + /** + * チャットの背景 + */ "messageBg": string; + /** + * アクセント (暗め) + */ "accentDarken": string; + /** + * アクセント (明るめ) + */ "accentLighten": string; + /** + * 強調された文字 + */ "fgHighlighted": string; }; }; "_sfx": { + /** + * ノート + */ "note": string; + /** + * ノート(自分) + */ "noteMy": string; + /** + * 通知 + */ "notification": string; + /** + * アンテナ受信 + */ "antenna": string; + /** + * チャンネル通知 + */ "channel": string; + /** + * リアクション選択時 + */ "reaction": string; }; "_soundSettings": { + /** + * ドライブの音声を使用 + */ "driveFile": string; + /** + * ドライブのファイルを選択してください + */ "driveFileWarn": string; + /** + * このファイルは対応していません + */ "driveFileTypeWarn": string; + /** + * 音声ファイルを選択してください + */ "driveFileTypeWarnDescription": string; + /** + * 音声が長すぎます + */ "driveFileDurationWarn": string; + /** + * 長い音声を使用するとMisskeyの使用に支障をきたす可能性があります。それでも続行しますか? + */ "driveFileDurationWarnDescription": string; }; "_ago": { + /** + * 未来 + */ "future": string; + /** + * たった今 + */ "justNow": string; + /** + * {n}秒前 + */ "secondsAgo": ParameterizedString<"n">; + /** + * {n}分前 + */ "minutesAgo": ParameterizedString<"n">; + /** + * {n}時間前 + */ "hoursAgo": ParameterizedString<"n">; + /** + * {n}日前 + */ "daysAgo": ParameterizedString<"n">; + /** + * {n}週間前 + */ "weeksAgo": ParameterizedString<"n">; + /** + * {n}ヶ月前 + */ "monthsAgo": ParameterizedString<"n">; + /** + * {n}年前 + */ "yearsAgo": ParameterizedString<"n">; + /** + * 日時の解析に失敗 + */ "invalid": string; }; "_timeIn": { + /** + * {n}秒後 + */ "seconds": ParameterizedString<"n">; + /** + * {n}分後 + */ "minutes": ParameterizedString<"n">; + /** + * {n}時間後 + */ "hours": ParameterizedString<"n">; + /** + * {n}日後 + */ "days": ParameterizedString<"n">; + /** + * {n}週間後 + */ "weeks": ParameterizedString<"n">; + /** + * {n}ヶ月後 + */ "months": ParameterizedString<"n">; + /** + * {n}年後 + */ "years": ParameterizedString<"n">; }; "_time": { + /** + * 秒 + */ "second": string; + /** + * 分 + */ "minute": string; + /** + * 時間 + */ "hour": string; + /** + * 日 + */ "day": string; }; "_2fa": { + /** + * 既に設定は完了しています。 + */ "alreadyRegistered": string; + /** + * 認証アプリの設定を開始 + */ "registerTOTP": string; + /** + * まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。 + */ "step1": ParameterizedString<"a" | "b">; + /** + * 次に、表示されているQRコードをアプリでスキャンします。 + */ "step2": string; + /** + * QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。 + */ "step2Click": string; + /** + * デスクトップアプリを使用する場合は次のURIを入力します + */ "step2Uri": string; + /** + * 確認コードを入力 + */ "step3Title": string; + /** + * アプリに表示されている確認コード(トークン)を入力します。 + */ "step3": string; + /** + * 設定が完了しました + */ "setupCompleted": string; + /** + * これからログインするときも、同じようにコードを入力します。 + */ "step4": string; + /** + * お使いのブラウザはセキュリティキーに対応していません。 + */ "securityKeyNotSupported": string; + /** + * セキュリティキー・パスキーを登録するには、まず認証アプリの設定を行なってください。 + */ "registerTOTPBeforeKey": string; + /** + * FIDO2をサポートするハードウェアセキュリティキー、端末の生体認証やPINロック、パスキーといった、WebAuthn由来の鍵を登録します。 + */ "securityKeyInfo": string; + /** + * セキュリティキー・パスキーを登録する + */ "registerSecurityKey": string; + /** + * キーの名前を入力 + */ "securityKeyName": string; + /** + * ブラウザの指示に従い、セキュリティキーやパスキーを登録してください + */ "tapSecurityKey": string; + /** + * セキュリティキーを削除 + */ "removeKey": string; + /** + * {name}を削除しますか? + */ "removeKeyConfirm": ParameterizedString<"name">; + /** + * セキュリティキーが登録されている場合、認証アプリの設定は解除できません。 + */ "whyTOTPOnlyRenew": string; + /** + * 認証アプリを再設定 + */ "renewTOTP": string; + /** + * 今までの認証アプリの確認コードおよびバックアップコードは使用できなくなります + */ "renewTOTPConfirm": string; + /** + * 再設定する + */ "renewTOTPOk": string; + /** + * やめておく + */ "renewTOTPCancel": string; + /** + * このウィザードを閉じる前に、以下のバックアップコードを確認してください。 + */ "checkBackupCodesBeforeCloseThisWizard": string; + /** + * バックアップコード + */ "backupCodes": string; + /** + * 認証アプリが使用できなくなった場合、以下のバックアップコードを使ってアカウントにアクセスできます。これらのコードは必ず安全な場所に保管してください。各コードは一回だけ使用できます。 + */ "backupCodesDescription": string; + /** + * バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。 + */ "backupCodeUsedWarning": string; + /** + * バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。 + */ "backupCodesExhaustedWarning": string; }; "_permissions": { + /** + * アカウントの情報を見る + */ "read:account": string; + /** + * アカウントの情報を変更する + */ "write:account": string; + /** + * ブロックを見る + */ "read:blocks": string; + /** + * ブロックを操作する + */ "write:blocks": string; + /** + * ドライブを見る + */ "read:drive": string; + /** + * ドライブを操作する + */ "write:drive": string; + /** + * お気に入りを見る + */ "read:favorites": string; + /** + * お気に入りを操作する + */ "write:favorites": string; + /** + * フォローの情報を見る + */ "read:following": string; + /** + * フォロー・フォロー解除する + */ "write:following": string; + /** + * チャットを見る + */ "read:messaging": string; + /** + * チャットを操作する + */ "write:messaging": string; + /** + * ミュートを見る + */ "read:mutes": string; + /** + * ミュートを操作する + */ "write:mutes": string; + /** + * ノートを作成・削除する + */ "write:notes": string; + /** + * 通知を見る + */ "read:notifications": string; + /** + * 通知を操作する + */ "write:notifications": string; + /** + * リアクションを見る + */ "read:reactions": string; + /** + * リアクションを操作する + */ "write:reactions": string; + /** + * 投票する + */ "write:votes": string; + /** + * ページを見る + */ "read:pages": string; + /** + * ページを操作する + */ "write:pages": string; + /** + * ページのいいねを見る + */ "read:page-likes": string; + /** + * ページのいいねを操作する + */ "write:page-likes": string; + /** + * ユーザーグループを見る + */ "read:user-groups": string; + /** + * ユーザーグループを操作する + */ "write:user-groups": string; + /** + * チャンネルを見る + */ "read:channels": string; + /** + * チャンネルを操作する + */ "write:channels": string; + /** + * ギャラリーを見る + */ "read:gallery": string; + /** + * ギャラリーを操作する + */ "write:gallery": string; + /** + * ギャラリーのいいねを見る + */ "read:gallery-likes": string; + /** + * ギャラリーのいいねを操作する + */ "write:gallery-likes": string; + /** + * Playを見る + */ "read:flash": string; + /** + * Playを操作する + */ "write:flash": string; + /** + * Playのいいねを見る + */ "read:flash-likes": string; + /** + * Playのいいねを操作する + */ "write:flash-likes": string; + /** + * ユーザーからの通報を見る + */ "read:admin:abuse-user-reports": string; + /** + * ユーザーアカウントを削除する + */ "write:admin:delete-account": string; + /** + * ユーザーのすべてのファイルを削除する + */ "write:admin:delete-all-files-of-a-user": string; + /** + * データベースインデックスに関する情報を見る + */ "read:admin:index-stats": string; + /** + * データベーステーブルに関する情報を見る + */ "read:admin:table-stats": string; + /** + * ユーザーのIPアドレスを見る + */ "read:admin:user-ips": string; + /** + * インスタンスのメタデータを見る + */ "read:admin:meta": string; + /** + * ユーザーのパスワードをリセットする + */ "write:admin:reset-password": string; + /** + * ユーザーからの通報を解決する + */ "write:admin:resolve-abuse-user-report": string; + /** + * メールを送る + */ "write:admin:send-email": string; + /** + * サーバーの情報を見る + */ "read:admin:server-info": string; + /** + * モデレーションログを見る + */ "read:admin:show-moderation-log": string; + /** + * ユーザーのプライベートな情報を見る + */ "read:admin:show-user": string; + /** + * ユーザーのプライベートな情報を見る + */ "read:admin:show-users": string; + /** + * ユーザーを凍結する + */ "write:admin:suspend-user": string; + /** + * ユーザーのアバターを削除する + */ "write:admin:unset-user-avatar": string; + /** + * ユーザーのバーナーを削除する + */ "write:admin:unset-user-banner": string; + /** + * ユーザーの凍結を解除する + */ "write:admin:unsuspend-user": string; + /** + * インスタンスのメタデータを操作する + */ "write:admin:meta": string; + /** + * モデレーションノートを操作する + */ "write:admin:user-note": string; + /** + * ロールを操作する + */ "write:admin:roles": string; + /** + * ロールを見る + */ "read:admin:roles": string; + /** + * リレーを操作する + */ "write:admin:relays": string; + /** + * リレーを見る + */ "read:admin:relays": string; + /** + * 招待コードを操作する + */ "write:admin:invite-codes": string; + /** + * 招待コードを見る + */ "read:admin:invite-codes": string; + /** + * お知らせを操作する + */ "write:admin:announcements": string; + /** + * お知らせを見る + */ "read:admin:announcements": string; + /** + * アバターデコレーションを操作する + */ "write:admin:avatar-decorations": string; + /** + * アバターデコレーションを見る + */ "read:admin:avatar-decorations": string; + /** + * 連合に関する情報を操作する + */ "write:admin:federation": string; + /** + * ユーザーアカウントを操作する + */ "write:admin:account": string; + /** + * ユーザーに関する情報を見る + */ "read:admin:account": string; + /** + * 絵文字を操作する + */ "write:admin:emoji": string; + /** + * 絵文字を見る + */ "read:admin:emoji": string; + /** + * ジョブキューを操作する + */ "write:admin:queue": string; + /** + * ジョブキューに関する情報を見る + */ "read:admin:queue": string; + /** + * プロモーションノートを操作する + */ "write:admin:promo": string; + /** + * ユーザーのドライブを操作する + */ "write:admin:drive": string; + /** + * ユーザーのドライブの関する情報を見る + */ "read:admin:drive": string; + /** + * 管理者用のWebsocket APIを使う + */ "read:admin:stream": string; + /** + * 広告を操作する + */ "write:admin:ad": string; + /** + * 広告を見る + */ "read:admin:ad": string; + /** + * 招待コードを作成する + */ "write:invite-codes": string; + /** + * 招待コードを取得する + */ "read:invite-codes": string; + /** + * クリップのいいねを操作する + */ "write:clip-favorite": string; + /** + * クリップのいいねを見る + */ "read:clip-favorite": string; + /** + * 連合に関する情報を取得する + */ "read:federation": string; + /** + * 違反を報告する + */ "write:report-abuse": string; }; "_auth": { + /** + * アプリへのアクセス許可 + */ "shareAccessTitle": string; + /** + * 「{name}」がアカウントにアクセスすることを許可しますか? + */ "shareAccess": ParameterizedString<"name">; + /** + * アカウントへのアクセスを許可しますか? + */ "shareAccessAsk": string; + /** + * {name}は次の権限を要求しています + */ "permission": ParameterizedString<"name">; + /** + * このアプリは次の権限を要求しています + */ "permissionAsk": string; + /** + * アプリケーションに戻ってやっていってください + */ "pleaseGoBack": string; + /** + * アプリケーションに戻っています + */ "callback": string; + /** + * アクセスを拒否しました + */ "denied": string; + /** + * アプリケーションにアクセス許可を与えるには、ログインが必要です。 + */ "pleaseLogin": string; }; "_antennaSources": { + /** + * 全てのノート + */ "all": string; + /** + * フォローしているユーザーのノート + */ "homeTimeline": string; + /** + * 指定した一人または複数のユーザーのノート + */ "users": string; + /** + * 指定したリストのユーザーのノート + */ "userList": string; + /** + * 指定した一人または複数のユーザーを除いた全てのノート + */ "userBlacklist": string; }; "_weekday": { + /** + * 日曜日 + */ "sunday": string; + /** + * 月曜日 + */ "monday": string; + /** + * 火曜日 + */ "tuesday": string; + /** + * 水曜日 + */ "wednesday": string; + /** + * 木曜日 + */ "thursday": string; + /** + * 金曜日 + */ "friday": string; + /** + * 土曜日 + */ "saturday": string; }; "_widgets": { + /** + * プロフィール + */ "profile": string; + /** + * サーバー情報 + */ "instanceInfo": string; + /** + * 付箋 + */ "memo": string; + /** + * 通知 + */ "notifications": string; + /** + * タイムライン + */ "timeline": string; + /** + * カレンダー + */ "calendar": string; + /** + * トレンド + */ "trends": string; + /** + * 時計 + */ "clock": string; + /** + * RSSリーダー + */ "rss": string; + /** + * RSSティッカー + */ "rssTicker": string; + /** + * アクティビティ + */ "activity": string; + /** + * フォト + */ "photos": string; + /** + * デジタル時計 + */ "digitalClock": string; + /** + * UNIX時計 + */ "unixClock": string; + /** + * 連合 + */ "federation": string; + /** + * サーバークラウド + */ "instanceCloud": string; + /** + * 投稿フォーム + */ "postForm": string; + /** + * スライドショー + */ "slideshow": string; + /** + * ボタン + */ "button": string; + /** + * オンラインユーザー + */ "onlineUsers": string; + /** + * ジョブキュー + */ "jobQueue": string; + /** + * サーバーメトリクス + */ "serverMetric": string; + /** + * AiScriptコンソール + */ "aiscript": string; + /** + * AiScript App + */ "aiscriptApp": string; + /** + * 藍 + */ "aichan": string; + /** + * ユーザーリスト + */ "userList": string; "_userList": { + /** + * リストを選択 + */ "chooseList": string; }; + /** + * クリッカー + */ "clicker": string; + /** + * 今日誕生日のユーザー + */ "birthdayFollowings": string; }; "_cw": { + /** + * 隠す + */ "hide": string; + /** + * もっと見る + */ "show": string; + /** + * {count}文字 + */ "chars": ParameterizedString<"count">; + /** + * {count}ファイル + */ "files": ParameterizedString<"count">; }; "_poll": { + /** + * 選択肢は最低2つ必要です + */ "noOnlyOneChoice": string; + /** + * 選択肢{n} + */ "choiceN": ParameterizedString<"n">; + /** + * これ以上追加できません + */ "noMore": string; + /** + * 複数回答可 + */ "canMultipleVote": string; + /** + * 期限 + */ "expiration": string; + /** + * 無期限 + */ "infinite": string; + /** + * 日時指定 + */ "at": string; + /** + * 経過指定 + */ "after": string; + /** + * 期日 + */ "deadlineDate": string; + /** + * 時間 + */ "deadlineTime": string; + /** + * 期間 + */ "duration": string; + /** + * {n}票 + */ "votesCount": ParameterizedString<"n">; + /** + * 計{n}票 + */ "totalVotes": ParameterizedString<"n">; + /** + * 投票する + */ "vote": string; + /** + * 結果を見る + */ "showResult": string; + /** + * 投票済み + */ "voted": string; + /** + * 終了済み + */ "closed": string; + /** + * 終了まであと{d}日{h}時間 + */ "remainingDays": ParameterizedString<"d" | "h">; + /** + * 終了まであと{h}時間{m}分 + */ "remainingHours": ParameterizedString<"h" | "m">; + /** + * 終了まであと{m}分{s}秒 + */ "remainingMinutes": ParameterizedString<"m" | "s">; + /** + * 終了まであと{s}秒 + */ "remainingSeconds": ParameterizedString<"s">; }; "_visibility": { + /** + * パブリック + */ "public": string; + /** + * 全てのユーザーに公開 + */ "publicDescription": string; + /** + * ホーム + */ "home": string; + /** + * ホームタイムラインのみに公開 + */ "homeDescription": string; + /** + * フォロワー + */ "followers": string; + /** + * 自分のフォロワーのみに公開 + */ "followersDescription": string; + /** + * ダイレクト + */ "specified": string; + /** + * 指定したユーザーのみに公開 + */ "specifiedDescription": string; + /** + * 連合なし + */ "disableFederation": string; + /** + * 他サーバーへの配信を行いません + */ "disableFederationDescription": string; }; "_postForm": { + /** + * このノートに返信... + */ "replyPlaceholder": string; + /** + * このノートを引用... + */ "quotePlaceholder": string; + /** + * チャンネルに投稿... + */ "channelPlaceholder": string; "_placeholders": { + /** + * いまどうしてる? + */ "a": string; + /** + * 何かありましたか? + */ "b": string; + /** + * 何をお考えですか? + */ "c": string; + /** + * 言いたいことは? + */ "d": string; + /** + * ここに書いてください + */ "e": string; + /** + * あなたが書くのを待っています... + */ "f": string; }; }; "_profile": { + /** + * 名前 + */ "name": string; + /** + * ユーザー名 + */ "username": string; + /** + * 自己紹介 + */ "description": string; + /** + * ハッシュタグを含めることができます。 + */ "youCanIncludeHashtags": string; + /** + * 追加情報 + */ "metadata": string; + /** + * 追加情報を編集 + */ "metadataEdit": string; + /** + * プロフィールに表として追加情報を表示することができます。 + */ "metadataDescription": string; + /** + * ラベル + */ "metadataLabel": string; + /** + * 内容 + */ "metadataContent": string; + /** + * アイコン画像を変更 + */ "changeAvatar": string; + /** + * バナー画像を変更 + */ "changeBanner": string; + /** + * 内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。 + */ "verifiedLinkDescription": string; + /** + * 最大{max}つまでデコレーションを付けられます。 + */ "avatarDecorationMax": ParameterizedString<"max">; }; "_exportOrImport": { + /** + * 全てのノート + */ "allNotes": string; + /** + * お気に入りにしたノート + */ "favoritedNotes": string; + /** + * クリップ + */ "clips": string; + /** + * フォロー + */ "followingList": string; + /** + * ミュート + */ "muteList": string; + /** + * ブロック + */ "blockingList": string; + /** + * リスト + */ "userLists": string; + /** + * ミュートしているユーザーを除外 + */ "excludeMutingUsers": string; + /** + * 使われていないアカウントを除外 + */ "excludeInactiveUsers": string; + /** + * インポートした人による返信をTLに含むようにする + */ "withReplies": string; }; "_charts": { + /** + * 連合 + */ "federation": string; + /** + * リクエスト + */ "apRequest": string; + /** + * ユーザーの増減 + */ "usersIncDec": string; + /** + * ユーザーの合計 + */ "usersTotal": string; + /** + * アクティブユーザー数 + */ "activeUsers": string; + /** + * ノートの増減 + */ "notesIncDec": string; + /** + * ローカルのノートの増減 + */ "localNotesIncDec": string; + /** + * リモートのノートの増減 + */ "remoteNotesIncDec": string; + /** + * ノートの合計 + */ "notesTotal": string; + /** + * ファイルの増減 + */ "filesIncDec": string; + /** + * ファイルの合計 + */ "filesTotal": string; + /** + * ストレージ使用量の増減 + */ "storageUsageIncDec": string; + /** + * ストレージ使用量の合計 + */ "storageUsageTotal": string; }; "_instanceCharts": { + /** + * リクエスト + */ "requests": string; + /** + * ユーザーの増減 + */ "users": string; + /** + * ユーザーの累積 + */ "usersTotal": string; + /** + * ノートの増減 + */ "notes": string; + /** + * ノートの累積 + */ "notesTotal": string; + /** + * フォロー/フォロワーの増減 + */ "ff": string; + /** + * フォロー/フォロワーの累積 + */ "ffTotal": string; + /** + * キャッシュサイズの増減 + */ "cacheSize": string; + /** + * キャッシュサイズの累積 + */ "cacheSizeTotal": string; + /** + * ファイル数の増減 + */ "files": string; + /** + * ファイル数の累積 + */ "filesTotal": string; }; "_timelines": { + /** + * ホーム + */ "home": string; + /** + * ローカル + */ "local": string; + /** + * ソーシャル + */ "social": string; + /** + * グローバル + */ "global": string; }; "_play": { + /** + * Playの作成 + */ "new": string; + /** + * Playの編集 + */ "edit": string; + /** + * Playを作成しました + */ "created": string; + /** + * Playを更新しました + */ "updated": string; + /** + * Playを削除しました + */ "deleted": string; + /** + * Play設定 + */ "pageSetting": string; + /** + * このPlayを編集 + */ "editThisPage": string; + /** + * ソースを表示 + */ "viewSource": string; + /** + * 自分のPlay + */ "my": string; + /** + * いいねしたPlay + */ "liked": string; + /** + * 人気 + */ "featured": string; + /** + * タイトル + */ "title": string; + /** + * スクリプト + */ "script": string; + /** + * 説明 + */ "summary": string; }; "_pages": { + /** + * ページの作成 + */ "newPage": string; + /** + * ページの編集 + */ "editPage": string; + /** + * ソースを表示中 + */ "readPage": string; + /** + * ページを作成しました + */ "created": string; + /** + * ページを更新しました + */ "updated": string; + /** + * ページを削除しました + */ "deleted": string; + /** + * ページ設定 + */ "pageSetting": string; + /** + * 指定されたページURLは既に存在しています + */ "nameAlreadyExists": string; + /** + * 不正なページURLです + */ "invalidNameTitle": string; + /** + * 空白でないか確認してください + */ "invalidNameText": string; + /** + * このページを編集 + */ "editThisPage": string; + /** + * ソースを表示 + */ "viewSource": string; + /** + * ページを見る + */ "viewPage": string; + /** + * いいね + */ "like": string; + /** + * いいね解除 + */ "unlike": string; + /** + * 自分のページ + */ "my": string; + /** + * いいねしたページ + */ "liked": string; + /** + * 人気 + */ "featured": string; + /** + * インスペクター + */ "inspector": string; + /** + * コンテンツ + */ "contents": string; + /** + * ページブロック + */ "content": string; + /** + * 変数 + */ "variables": string; + /** + * タイトル + */ "title": string; + /** + * ページURL + */ "url": string; + /** + * ページの要約 + */ "summary": string; + /** + * 中央寄せ + */ "alignCenter": string; + /** + * ピン留めされているときにタイトルを非表示 + */ "hideTitleWhenPinned": string; + /** + * フォント + */ "font": string; + /** + * セリフ + */ "fontSerif": string; + /** + * サンセリフ + */ "fontSansSerif": string; + /** + * アイキャッチ画像を設定 + */ "eyeCatchingImageSet": string; + /** + * アイキャッチ画像を削除 + */ "eyeCatchingImageRemove": string; + /** + * ブロックを追加 + */ "chooseBlock": string; + /** + * 種類を選択 + */ "selectType": string; + /** + * コンテンツ + */ "contentBlocks": string; + /** + * 入力 + */ "inputBlocks": string; + /** + * 特殊 + */ "specialBlocks": string; "blocks": { + /** + * テキスト + */ "text": string; + /** + * テキストエリア + */ "textarea": string; + /** + * セクション + */ "section": string; + /** + * 画像 + */ "image": string; + /** + * ボタン + */ "button": string; + /** + * ノート埋め込み + */ "note": string; "_note": { + /** + * ノートID + */ "id": string; + /** + * ノートURLをペーストして設定することもできます。 + */ "idDescription": string; + /** + * 詳細な表示 + */ "detailed": string; }; }; }; "_relayStatus": { + /** + * 承認待ち + */ "requesting": string; + /** + * 承認済み + */ "accepted": string; + /** + * 拒否済み + */ "rejected": string; }; "_notification": { + /** + * ファイルがアップロードされました + */ "fileUploaded": string; + /** + * {name}からのメンション + */ "youGotMention": ParameterizedString<"name">; + /** + * {name}からのリプライ + */ "youGotReply": ParameterizedString<"name">; + /** + * {name}による引用 + */ "youGotQuote": ParameterizedString<"name">; + /** + * {name}がRenoteしました + */ "youRenoted": ParameterizedString<"name">; + /** + * フォローされました + */ "youWereFollowed": string; + /** + * フォローリクエストが来ました + */ "youReceivedFollowRequest": string; + /** + * フォローリクエストが承認されました + */ "yourFollowRequestAccepted": string; + /** + * アンケートの結果が出ました + */ "pollEnded": string; + /** + * 新しい投稿 + */ "newNote": string; + /** + * アンテナ {name} + */ "unreadAntennaNote": ParameterizedString<"name">; + /** + * ロールが付与されました + */ "roleAssigned": string; + /** + * プッシュ通知の更新をしました + */ "emptyPushNotificationMessage": string; + /** + * 実績を獲得 + */ "achievementEarned": string; + /** + * 通知テスト + */ "testNotification": string; + /** + * 通知の表示を確かめる + */ "checkNotificationBehavior": string; + /** + * テスト通知を送信する + */ "sendTestNotification": string; + /** + * 通知はこのように表示されます + */ "notificationWillBeDisplayedLikeThis": string; + /** + * {n}人がリアクションしました + */ "reactedBySomeUsers": ParameterizedString<"n">; + /** + * {n}人がリノートしました + */ "renotedBySomeUsers": ParameterizedString<"n">; + /** + * {n}人にフォローされました + */ "followedBySomeUsers": ParameterizedString<"n">; "_types": { + /** + * すべて + */ "all": string; + /** + * ユーザーの新規投稿 + */ "note": string; + /** + * フォロー + */ "follow": string; + /** + * メンション + */ "mention": string; + /** + * リプライ + */ "reply": string; + /** + * Renote + */ "renote": string; + /** + * 引用 + */ "quote": string; + /** + * リアクション + */ "reaction": string; + /** + * アンケートが終了 + */ "pollEnded": string; + /** + * フォロー申請を受け取った + */ "receiveFollowRequest": string; + /** + * フォローが受理された + */ "followRequestAccepted": string; + /** + * ロールが付与された + */ "roleAssigned": string; + /** + * 実績の獲得 + */ "achievementEarned": string; + /** + * 連携アプリからの通知 + */ "app": string; }; "_actions": { + /** + * フォローバック + */ "followBack": string; + /** + * 返信 + */ "reply": string; + /** + * Renote + */ "renote": string; }; }; "_deck": { + /** + * 常にメインカラムを表示 + */ "alwaysShowMainColumn": string; + /** + * カラムの寄せ + */ "columnAlign": string; + /** + * カラムを追加 + */ "addColumn": string; + /** + * カラムの設定 + */ "configureColumn": string; + /** + * 左に移動 + */ "swapLeft": string; + /** + * 右に移動 + */ "swapRight": string; + /** + * 上に移動 + */ "swapUp": string; + /** + * 下に移動 + */ "swapDown": string; + /** + * 左にスタック + */ "stackLeft": string; + /** + * 右に出す + */ "popRight": string; + /** + * プロファイル + */ "profile": string; + /** + * 新規プロファイル + */ "newProfile": string; + /** + * プロファイルを削除 + */ "deleteProfile": string; + /** + * カラムを組み合わせて自分だけのインターフェイスを作りましょう! + */ "introduction": string; + /** + * 画面の右にある + を押して、いつでもカラムを追加できます。 + */ "introduction2": string; + /** + * カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください + */ "widgetsIntroduction": string; + /** + * 非ルートページは簡易UIで表示 + */ "useSimpleUiForNonRootPages": string; + /** + * 「幅を自動調整」が有効の場合、これが幅の最小値となります + */ "usedAsMinWidthWhenFlexible": string; + /** + * 幅を自動調整 + */ "flexible": string; "_columns": { + /** + * メイン + */ "main": string; + /** + * ウィジェット + */ "widgets": string; + /** + * 通知 + */ "notifications": string; + /** + * タイムライン + */ "tl": string; + /** + * アンテナ + */ "antenna": string; + /** + * リスト + */ "list": string; + /** + * チャンネル + */ "channel": string; + /** + * あなた宛て + */ "mentions": string; + /** + * ダイレクト + */ "direct": string; + /** + * ロールタイムライン + */ "roleTimeline": string; }; }; "_dialog": { + /** + * 最大文字数を超えています! 現在 {current} / 制限 {max} + */ "charactersExceeded": ParameterizedString<"current" | "max">; + /** + * 最小文字数を下回っています! 現在 {current} / 制限 {min} + */ "charactersBelow": ParameterizedString<"current" | "min">; }; "_disabledTimeline": { + /** + * 無効化されたタイムライン + */ "title": string; + /** + * 現在のロールでは、このタイムラインを使用することはできません。 + */ "description": string; }; "_drivecleaner": { + /** + * サイズが大きい順 + */ "orderBySizeDesc": string; + /** + * 追加日が古い順 + */ "orderByCreatedAtAsc": string; }; "_webhookSettings": { + /** + * Webhookを作成 + */ "createWebhook": string; + /** + * 名前 + */ "name": string; + /** + * シークレット + */ "secret": string; + /** + * Webhookを実行するタイミング + */ "events": string; + /** + * 有効 + */ "active": string; "_events": { + /** + * フォローしたとき + */ "follow": string; + /** + * フォローされたとき + */ "followed": string; + /** + * ノートを投稿したとき + */ "note": string; + /** + * 返信されたとき + */ "reply": string; + /** + * Renoteされたとき + */ "renote": string; + /** + * リアクションがあったとき + */ "reaction": string; + /** + * メンションされたとき + */ "mention": string; }; }; "_moderationLogTypes": { + /** + * ロールを作成 + */ "createRole": string; + /** + * ロールを削除 + */ "deleteRole": string; + /** + * ロールを更新 + */ "updateRole": string; + /** + * ロールへアサイン + */ "assignRole": string; + /** + * ロールのアサイン解除 + */ "unassignRole": string; + /** + * 凍結 + */ "suspend": string; + /** + * 凍結解除 + */ "unsuspend": string; + /** + * カスタム絵文字追加 + */ "addCustomEmoji": string; + /** + * カスタム絵文字更新 + */ "updateCustomEmoji": string; + /** + * カスタム絵文字削除 + */ "deleteCustomEmoji": string; + /** + * サーバー設定更新 + */ "updateServerSettings": string; + /** + * モデレーションノート更新 + */ "updateUserNote": string; + /** + * ファイルを削除 + */ "deleteDriveFile": string; + /** + * ノートを削除 + */ "deleteNote": string; + /** + * 全体のお知らせを作成 + */ "createGlobalAnnouncement": string; + /** + * ユーザーへお知らせを作成 + */ "createUserAnnouncement": string; + /** + * 全体のお知らせを更新 + */ "updateGlobalAnnouncement": string; + /** + * ユーザーのお知らせを更新 + */ "updateUserAnnouncement": string; + /** + * 全体のお知らせを削除 + */ "deleteGlobalAnnouncement": string; + /** + * ユーザーのお知らせを削除 + */ "deleteUserAnnouncement": string; + /** + * パスワードをリセット + */ "resetPassword": string; + /** + * リモートサーバーを停止 + */ "suspendRemoteInstance": string; + /** + * リモートサーバーを再開 + */ "unsuspendRemoteInstance": string; + /** + * ファイルをセンシティブ付与 + */ "markSensitiveDriveFile": string; + /** + * ファイルをセンシティブ解除 + */ "unmarkSensitiveDriveFile": string; + /** + * 通報を解決 + */ "resolveAbuseReport": string; + /** + * 招待コードを作成 + */ "createInvitation": string; + /** + * 広告を作成 + */ "createAd": string; + /** + * 広告を削除 + */ "deleteAd": string; + /** + * 広告を更新 + */ "updateAd": string; + /** + * アイコンデコレーションを作成 + */ "createAvatarDecoration": string; + /** + * アイコンデコレーションを更新 + */ "updateAvatarDecoration": string; + /** + * アイコンデコレーションを削除 + */ "deleteAvatarDecoration": string; + /** + * ユーザーのアイコンを解除 + */ "unsetUserAvatar": string; + /** + * ユーザーのバナーを解除 + */ "unsetUserBanner": string; }; "_fileViewer": { + /** + * ファイルの詳細 + */ "title": string; + /** + * ファイルタイプ + */ "type": string; + /** + * ファイルサイズ + */ "size": string; + /** + * URL + */ "url": string; + /** + * 追加日 + */ "uploadedAt": string; + /** + * 添付されているノート + */ "attachedNotes": string; + /** + * このページは、このファイルをアップロードしたユーザーしか閲覧できません。 + */ "thisPageCanBeSeenFromTheAuthor": string; }; "_externalResourceInstaller": { + /** + * 外部サイトからインストール + */ "title": string; + /** + * 配布元が信頼できるかを確認した上でインストールしてください。 + */ "checkVendorBeforeInstall": string; "_plugin": { + /** + * このプラグインをインストールしますか? + */ "title": string; + /** + * プラグイン情報 + */ "metaTitle": string; }; "_theme": { + /** + * このテーマをインストールしますか? + */ "title": string; + /** + * テーマ情報 + */ "metaTitle": string; }; "_meta": { + /** + * 基本のカラースキーム + */ "base": string; }; "_vendorInfo": { + /** + * 配布元情報 + */ "title": string; + /** + * 参照したエンドポイント + */ "endpoint": string; + /** + * ファイル整合性の確認 + */ "hashVerify": string; }; "_errors": { "_invalidParams": { + /** + * パラメータが不足しています + */ "title": string; + /** + * 外部サイトからデータを取得するために必要な情報が不足しています。URLをお確かめください。 + */ "description": string; }; "_resourceTypeNotSupported": { + /** + * この外部リソースには対応していません + */ "title": string; + /** + * この外部サイトから取得したリソースの種別には対応していません。サイト管理者にお問い合わせください。 + */ "description": string; }; "_failedToFetch": { + /** + * データの取得に失敗しました + */ "title": string; + /** + * 外部サイトとの通信に失敗しました。もう一度試しても改善しない場合、サイト管理者にお問い合わせください。 + */ "fetchErrorDescription": string; + /** + * 外部サイトから取得したデータが読み取れませんでした。サイト管理者にお問い合わせください。 + */ "parseErrorDescription": string; }; "_hashUnmatched": { + /** + * 正しいデータが取得できませんでした + */ "title": string; + /** + * 提供されたデータの整合性の確認に失敗しました。セキュリティ上、インストールは続行できません。サイト管理者にお問い合わせください。 + */ "description": string; }; "_pluginParseFailed": { + /** + * AiScript エラー + */ "title": string; + /** + * データは取得できたものの、AiScriptの解析時にエラーがあったため読み込めませんでした。プラグインの作者にお問い合わせください。エラーの詳細はJavascriptコンソールをご確認ください。 + */ "description": string; }; "_pluginInstallFailed": { + /** + * プラグインのインストールに失敗しました + */ "title": string; + /** + * プラグインのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。 + */ "description": string; }; "_themeParseFailed": { + /** + * テーマ解析エラー + */ "title": string; + /** + * データは取得できたものの、テーマファイルの解析時にエラーがあったため読み込めませんでした。テーマの作者にお問い合わせください。エラーの詳細はJavascriptコンソールをご確認ください。 + */ "description": string; }; "_themeInstallFailed": { + /** + * テーマのインストールに失敗しました + */ "title": string; + /** + * テーマのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。 + */ "description": string; }; }; }; "_dataSaver": { "_media": { + /** + * メディアの読み込み + */ "title": string; + /** + * 画像・動画が自動で読み込まれるのを防止します。隠れている画像・動画はタップすると読み込まれます。 + */ "description": string; }; "_avatar": { + /** + * アイコン画像 + */ "title": string; + /** + * アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。 + */ "description": string; }; "_urlPreview": { + /** + * URLプレビューのサムネイル + */ "title": string; + /** + * URLプレビューのサムネイル画像が読み込まれなくなります。 + */ "description": string; }; "_code": { + /** + * コードハイライト + */ "title": string; + /** + * MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。 + */ "description": string; }; }; "_reversi": { + /** + * リバーシ + */ "reversi": string; + /** + * 対局の設定 + */ "gameSettings": string; + /** + * ボードを選択 + */ "chooseBoard": string; + /** + * 先行/後攻 + */ "blackOrWhite": string; + /** + * {name}が黒(先行) + */ "blackIs": ParameterizedString<"name">; + /** + * ルール + */ "rules": string; + /** + * 対局はまもなく開始されます + */ "thisGameIsStartedSoon": string; + /** + * 相手の準備が完了するのを待っています + */ "waitingForOther": string; + /** + * あなたの準備が完了するのを待っています + */ "waitingForMe": string; + /** + * 準備してください + */ "waitingBoth": string; + /** + * 準備完了 + */ "ready": string; + /** + * 準備を再開 + */ "cancelReady": string; + /** + * 相手のターンです + */ "opponentTurn": string; + /** + * あなたのターンです + */ "myTurn": string; + /** + * {name}のターンです + */ "turnOf": ParameterizedString<"name">; + /** + * {name}のターン + */ "pastTurnOf": ParameterizedString<"name">; + /** + * 投了 + */ "surrender": string; + /** + * 投了により + */ "surrendered": string; + /** + * 引き分け + */ "drawn": string; + /** + * {name}の勝ち + */ "won": ParameterizedString<"name">; + /** + * 黒 + */ "black": string; + /** + * 白 + */ "white": string; + /** + * 合計 + */ "total": string; + /** + * {count}ターン目 + */ "turnCount": ParameterizedString<"count">; + /** + * 自分の対局 + */ "myGames": string; + /** + * みんなの対局 + */ "allGames": string; + /** + * 終了 + */ "ended": string; + /** + * 対局中 + */ "playing": string; + /** + * 石の少ない方が勝ち(ロセオ) + */ "isLlotheo": string; + /** + * ループマップ + */ "loopedMap": string; + /** + * どこでも置けるモード + */ "canPutEverywhere": string; + /** + * フリーマッチ + */ "freeMatch": string; + /** + * 対戦相手を探しています + */ "lookingForPlayer": string; }; } diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index bdb145b39a..c99118f9b2 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -205,7 +205,7 @@ export async function mainBoot() { const lastUsedDate = parseInt(lastUsed, 10); // 二時間以上前なら if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) { - toast(i18n.t('welcomeBackWithName', { + toast(i18n.tsx.welcomeBackWithName({ name: $i.name || $i.username, })); } diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue index c649e69cd0..54cbbe18c2 100644 --- a/packages/frontend/src/components/MkAnnouncementDialog.vue +++ b/packages/frontend/src/components/MkAnnouncementDialog.vue @@ -44,7 +44,7 @@ async function ok() { const confirm = await os.confirm({ type: 'question', title: i18n.ts._announcement.readConfirmTitle, - text: i18n.t('_announcement.readConfirmText', { title: props.announcement.title }), + text: i18n.tsx._announcement.readConfirmText({ title: props.announcement.title }), }); if (confirm.canceled) return; } diff --git a/packages/frontend/src/components/MkCwButton.vue b/packages/frontend/src/components/MkCwButton.vue index 4a6d2dfba2..ca19a2122d 100644 --- a/packages/frontend/src/components/MkCwButton.vue +++ b/packages/frontend/src/components/MkCwButton.vue @@ -41,9 +41,9 @@ const emit = defineEmits<{ const label = computed(() => { return concat([ - props.text ? [i18n.t('_cw.chars', { count: props.text.length })] : [], + props.text ? [i18n.tsx._cw.chars({ count: props.text.length })] : [], props.renote ? [i18n.ts.quote] : [], - props.files.length !== 0 ? [i18n.t('_cw.files', { count: props.files.length })] : [], + props.files.length !== 0 ? [i18n.tsx._cw.files({ count: props.files.length })] : [], props.poll != null ? [i18n.ts.poll] : [], ] as string[][]).join(' / '); }); diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue index 0a71b689fe..7b9a052868 100644 --- a/packages/frontend/src/components/MkDateSeparatedList.vue +++ b/packages/frontend/src/components/MkDateSeparatedList.vue @@ -46,7 +46,7 @@ export default defineComponent({ function getDateText(time: string) { const date = new Date(time).getDate(); const month = new Date(time).getMonth() + 1; - return i18n.t('monthAndDay', { + return i18n.tsx.monthAndDay({ month: month.toString(), day: date.toString(), }); diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue index 3c1f83d335..3fc9f0e357 100644 --- a/packages/frontend/src/components/MkDialog.vue +++ b/packages/frontend/src/components/MkDialog.vue @@ -30,8 +30,8 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index dbf98cd622..560d5502d4 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -82,8 +82,8 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.loadMore }}
-
{{ i18n.t('empty-draghover') }}
-
{{ i18n.ts.emptyDrive }}
{{ i18n.t('empty-drive-description') }}
+
{{ i18n.ts['empty-draghover'] }}
+
{{ i18n.ts.emptyDrive }}
{{ i18n.ts['empty-drive-description'] }}
{{ i18n.ts.emptyFolder }}
diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index 78c4fb3cd2..6bc96be1b9 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -84,7 +84,7 @@ async function onClick() { if (isFollowing.value) { const { canceled } = await os.confirm({ type: 'warning', - text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }), + text: i18n.tsx.unfollowConfirm({ name: props.user.name || props.user.username }), }); if (canceled) return; diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 9c4354ef5f..d7bb64661b 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -73,7 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- {{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: + {{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}:
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index e941827d74..1f5b283cfe 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- {{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: + {{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}:
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index ce8b054b39..92be20c6f6 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -56,8 +56,8 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._notification.achievementEarned }} {{ i18n.ts._notification.testNotification }} - {{ i18n.t('_notification.reactedBySomeUsers', { n: notification.reactions.length }) }} - {{ i18n.t('_notification.renotedBySomeUsers', { n: notification.users.length }) }} + {{ i18n.tsx._notification.reactedBySomeUsers({ n: notification.reactions.length }) }} + {{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }} {{ notification.header }} diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue index 6725776f43..0e77d2a6aa 100644 --- a/packages/frontend/src/components/MkNotificationSelectWindow.vue +++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue @@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.disableAll }} {{ i18n.ts.enableAll }}
- {{ i18n.t(`_notification._types.${ntype}`) }} + {{ i18n.ts._notification._types[ntype] }}
diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 4cac1fe9c3..7c58b58697 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -11,12 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only - ({{ i18n.t('_poll.votesCount', { n: choice.votes }) }}) + ({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }})

- {{ i18n.t('_poll.totalVotes', { n: total }) }} + {{ i18n.tsx._poll.totalVotes({ n: total }) }} · {{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }} {{ i18n.ts._poll.voted }} @@ -47,10 +47,11 @@ const remaining = ref(-1); const total = computed(() => sum(props.note.poll.choices.map(x => x.votes))); const closed = computed(() => remaining.value === 0); const isVoted = computed(() => !props.note.poll.multiple && props.note.poll.choices.some(c => c.isVoted)); -const timer = computed(() => i18n.t( - remaining.value >= 86400 ? '_poll.remainingDays' : - remaining.value >= 3600 ? '_poll.remainingHours' : - remaining.value >= 60 ? '_poll.remainingMinutes' : '_poll.remainingSeconds', { +const timer = computed(() => i18n.tsx._poll[ + remaining.value >= 86400 ? 'remainingDays' : + remaining.value >= 3600 ? 'remainingHours' : + remaining.value >= 60 ? 'remainingMinutes' : 'remainingSeconds' + ]({ s: Math.floor(remaining.value % 60), m: Math.floor(remaining.value / 60) % 60, h: Math.floor(remaining.value / 3600) % 24, @@ -81,7 +82,7 @@ const vote = async (id) => { const { canceled } = await os.confirm({ type: 'question', - text: i18n.t('voteConfirm', { choice: props.note.poll.choices[id].text }), + text: i18n.tsx.voteConfirm({ choice: props.note.poll.choices[id].text }), }); if (canceled) return; diff --git a/packages/frontend/src/components/MkPollEditor.vue b/packages/frontend/src/components/MkPollEditor.vue index 43e576d1ab..34f6c72b6e 100644 --- a/packages/frontend/src/components/MkPollEditor.vue +++ b/packages/frontend/src/components/MkPollEditor.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only

  • - +
- ({{ i18n.t('withNFiles', { n: note.files.length }) }}) + ({{ i18n.tsx.withNFiles({ n: note.files.length }) }})
diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue index a42767e1b6..28f2f29b7d 100644 --- a/packages/frontend/src/components/MkTokenGenerateWindow.vue +++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue @@ -33,12 +33,12 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.enableAll }}
- {{ i18n.t(`_permissions.${kind}`) }} + {{ i18n.ts._permissions[kind] }}
{{ i18n.ts.adminPermission }}
- {{ i18n.t(`_permissions.${kind}`) }} + {{ i18n.ts._permissions[kind] }}
diff --git a/packages/frontend/src/components/MkTutorialDialog.vue b/packages/frontend/src/components/MkTutorialDialog.vue index 963e78a1ff..9c3b46e133 100644 --- a/packages/frontend/src/components/MkTutorialDialog.vue +++ b/packages/frontend/src/components/MkTutorialDialog.vue @@ -133,7 +133,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.help }} -
{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}
+
{{ i18n.tsx._initialAccountSetting.haveFun({ name: instance.name ?? host }) }}
{{ i18n.ts.goBack }} {{ i18n.ts.close }} diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue index af094a8e8c..9e3a78fe22 100644 --- a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue +++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue @@ -118,7 +118,7 @@ async function done() { async function del() { const { canceled } = await os.confirm({ type: 'warning', - text: i18n.t('removeAreYouSure', { x: title.value }), + text: i18n.tsx.removeAreYouSure({ x: title.value }), }); if (canceled) return; diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue index 37aa677b44..f082833838 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue @@ -68,7 +68,7 @@ function setAvatar(ev) { const { canceled } = await os.confirm({ type: 'question', - text: i18n.t('cropImageAsk'), + text: i18n.ts.cropImageAsk, okText: i18n.ts.cropYes, cancelText: i18n.ts.cropNo, }); diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue index 05b55f77a7..4b0c540829 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.vue @@ -93,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.pushNotification }}
-
{{ i18n.t('_initialAccountSetting.pushNotificationDescription', { name: instance.name ?? host }) }}
+
{{ i18n.tsx._initialAccountSetting.pushNotificationDescription({ name: instance.name ?? host }) }}
{{ i18n.ts.goBack }} @@ -110,7 +110,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}
-
{{ i18n.t('_initialAccountSetting.youCanContinueTutorial', { name: instance.name ?? host }) }}
+
{{ i18n.tsx._initialAccountSetting.youCanContinueTutorial({ name: instance.name ?? host }) }}
{{ i18n.ts._initialAccountSetting.startTutorial }}
diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index ee4e29dd8f..b8b253de06 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- + {{ i18n.ts.add }} {{ i18n.ts.close }} @@ -109,7 +109,7 @@ function onContextmenu(widget: Widget, ev: MouseEvent) { os.contextMenu([{ type: 'label', - text: i18n.t(`_widgets.${widget.name}`), + text: i18n.ts._widgets[widget.name], }, { icon: 'ti ti-settings', text: i18n.ts.settings, diff --git a/packages/frontend/src/components/global/I18n.vue b/packages/frontend/src/components/global/I18n.vue new file mode 100644 index 0000000000..162aa2bcf8 --- /dev/null +++ b/packages/frontend/src/components/global/I18n.vue @@ -0,0 +1,46 @@ + + + diff --git a/packages/frontend/src/components/global/MkTime.stories.impl.ts b/packages/frontend/src/components/global/MkTime.stories.impl.ts index 0eeefa4859..0e7f6a9bdf 100644 --- a/packages/frontend/src/components/global/MkTime.stories.impl.ts +++ b/packages/frontend/src/components/global/MkTime.stories.impl.ts @@ -123,7 +123,7 @@ export const DetailNow = { export const RelativeOneHourAgo = { ...Empty, async play({ canvasElement }) { - await expect(canvasElement).toHaveTextContent(i18n.t('_ago.hoursAgo', { n: 1 })); + await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.hoursAgo({ n: 1 })); }, args: { ...Empty.args, @@ -162,7 +162,7 @@ export const DetailOneHourAgo = { export const RelativeOneDayAgo = { ...Empty, async play({ canvasElement }) { - await expect(canvasElement).toHaveTextContent(i18n.t('_ago.daysAgo', { n: 1 })); + await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.daysAgo({ n: 1 })); }, args: { ...Empty.args, @@ -201,7 +201,7 @@ export const DetailOneDayAgo = { export const RelativeOneWeekAgo = { ...Empty, async play({ canvasElement }) { - await expect(canvasElement).toHaveTextContent(i18n.t('_ago.weeksAgo', { n: 1 })); + await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.weeksAgo({ n: 1 })); }, args: { ...Empty.args, @@ -240,7 +240,7 @@ export const DetailOneWeekAgo = { export const RelativeOneMonthAgo = { ...Empty, async play({ canvasElement }) { - await expect(canvasElement).toHaveTextContent(i18n.t('_ago.monthsAgo', { n: 1 })); + await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.monthsAgo({ n: 1 })); }, args: { ...Empty.args, @@ -279,7 +279,7 @@ export const DetailOneMonthAgo = { export const RelativeOneYearAgo = { ...Empty, async play({ canvasElement }) { - await expect(canvasElement).toHaveTextContent(i18n.t('_ago.yearsAgo', { n: 1 })); + await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.yearsAgo({ n: 1 })); }, args: { ...Empty.args, diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue index e11db9dc31..2b0bf246ad 100644 --- a/packages/frontend/src/components/global/MkTime.vue +++ b/packages/frontend/src/components/global/MkTime.vue @@ -55,21 +55,21 @@ const relative = computed(() => { if (invalid) return i18n.ts._ago.invalid; return ( - ago.value >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago.value / 31536000).toString() }) : - ago.value >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago.value / 2592000).toString() }) : - ago.value >= 604800 ? i18n.t('_ago.weeksAgo', { n: Math.round(ago.value / 604800).toString() }) : - ago.value >= 86400 ? i18n.t('_ago.daysAgo', { n: Math.round(ago.value / 86400).toString() }) : - ago.value >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago.value / 3600).toString() }) : - ago.value >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago.value / 60)).toString() }) : - ago.value >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago.value % 60)).toString() }) : + ago.value >= 31536000 ? i18n.tsx._ago.yearsAgo({ n: Math.round(ago.value / 31536000).toString() }) : + ago.value >= 2592000 ? i18n.tsx._ago.monthsAgo({ n: Math.round(ago.value / 2592000).toString() }) : + ago.value >= 604800 ? i18n.tsx._ago.weeksAgo({ n: Math.round(ago.value / 604800).toString() }) : + ago.value >= 86400 ? i18n.tsx._ago.daysAgo({ n: Math.round(ago.value / 86400).toString() }) : + ago.value >= 3600 ? i18n.tsx._ago.hoursAgo({ n: Math.round(ago.value / 3600).toString() }) : + ago.value >= 60 ? i18n.tsx._ago.minutesAgo({ n: (~~(ago.value / 60)).toString() }) : + ago.value >= 10 ? i18n.tsx._ago.secondsAgo({ n: (~~(ago.value % 60)).toString() }) : ago.value >= -3 ? i18n.ts._ago.justNow : - ago.value < -31536000 ? i18n.t('_timeIn.years', { n: Math.round(-ago.value / 31536000).toString() }) : - ago.value < -2592000 ? i18n.t('_timeIn.months', { n: Math.round(-ago.value / 2592000).toString() }) : - ago.value < -604800 ? i18n.t('_timeIn.weeks', { n: Math.round(-ago.value / 604800).toString() }) : - ago.value < -86400 ? i18n.t('_timeIn.days', { n: Math.round(-ago.value / 86400).toString() }) : - ago.value < -3600 ? i18n.t('_timeIn.hours', { n: Math.round(-ago.value / 3600).toString() }) : - ago.value < -60 ? i18n.t('_timeIn.minutes', { n: (~~(-ago.value / 60)).toString() }) : - i18n.t('_timeIn.seconds', { n: (~~(-ago.value % 60)).toString() }) + ago.value < -31536000 ? i18n.tsx._timeIn.years({ n: Math.round(-ago.value / 31536000).toString() }) : + ago.value < -2592000 ? i18n.tsx._timeIn.months({ n: Math.round(-ago.value / 2592000).toString() }) : + ago.value < -604800 ? i18n.tsx._timeIn.weeks({ n: Math.round(-ago.value / 604800).toString() }) : + ago.value < -86400 ? i18n.tsx._timeIn.days({ n: Math.round(-ago.value / 86400).toString() }) : + ago.value < -3600 ? i18n.tsx._timeIn.hours({ n: Math.round(-ago.value / 3600).toString() }) : + ago.value < -60 ? i18n.tsx._timeIn.minutes({ n: (~~(-ago.value / 60)).toString() }) : + i18n.tsx._timeIn.seconds({ n: (~~(-ago.value % 60)).toString() }) ); }); diff --git a/packages/frontend/src/components/global/i18n.ts b/packages/frontend/src/components/global/i18n.ts deleted file mode 100644 index 2f4d7edabd..0000000000 --- a/packages/frontend/src/components/global/i18n.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { h } from 'vue'; - -export default function(props: { src: string; tag?: string; textTag?: string; }, { slots }) { - let str = props.src; - const parsed = [] as (string | { arg: string; })[]; - while (true) { - const nextBracketOpen = str.indexOf('{'); - const nextBracketClose = str.indexOf('}'); - - if (nextBracketOpen === -1) { - parsed.push(str); - break; - } else { - if (nextBracketOpen > 0) parsed.push(str.substring(0, nextBracketOpen)); - parsed.push({ - arg: str.substring(nextBracketOpen + 1, nextBracketClose), - }); - } - - str = str.substring(nextBracketClose + 1); - } - - return h(props.tag ?? 'span', parsed.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]())); -} diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index a3e13c3a50..f3b476b15c 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -16,7 +16,7 @@ import MkUserName from './global/MkUserName.vue'; import MkEllipsis from './global/MkEllipsis.vue'; import MkTime from './global/MkTime.vue'; import MkUrl from './global/MkUrl.vue'; -import I18n from './global/i18n.js'; +import I18n from './global/I18n.vue'; import RouterView from './global/RouterView.vue'; import MkLoading from './global/MkLoading.vue'; import MkError from './global/MkError.vue'; diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue index 4ba1b6da76..69cb6ef647 100644 --- a/packages/frontend/src/pages/about.vue +++ b/packages/frontend/src/pages/about.vue @@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only -
+
{{ i18n.ts.aboutMisskey }}
diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue index 4a9c659a97..84f3d1f3f1 100644 --- a/packages/frontend/src/pages/admin-file.vue +++ b/packages/frontend/src/pages/admin-file.vue @@ -104,7 +104,7 @@ fetch(); async function del() { const { canceled } = await os.confirm({ type: 'warning', - text: i18n.t('removeAreYouSure', { x: file.value.name }), + text: i18n.tsx.removeAreYouSure({ x: file.value.name }), }); if (canceled) return; diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 85417f0ecb..530bcca04a 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -182,9 +182,9 @@ SPDX-License-Identifier: AGPL-3.0-only
-
{{ i18n.t('recentNHours', { n: 90 }) }}
+
{{ i18n.tsx.recentNHours({ n: 90 }) }}
-
{{ i18n.t('recentNDays', { n: 90 }) }}
+
{{ i18n.tsx.recentNDays({ n: 90 }) }}
@@ -307,7 +307,7 @@ async function resetPassword() { }); os.alert({ type: 'success', - text: i18n.t('newPasswordIs', { password }), + text: i18n.tsx.newPasswordIs({ password }), }); } } @@ -390,7 +390,7 @@ async function deleteAccount() { if (confirm.canceled) return; const typed = await os.inputText({ - text: i18n.t('typeToConfirm', { x: user.value?.username }), + text: i18n.tsx.typeToConfirm({ x: user.value?.username }), }); if (typed.canceled) return; diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue index eb9aef0e48..fe55fe3a02 100644 --- a/packages/frontend/src/pages/admin/ads.vue +++ b/packages/frontend/src/pages/admin/ads.vue @@ -160,7 +160,7 @@ function add() { function remove(ad) { os.confirm({ type: 'warning', - text: i18n.t('removeAreYouSure', { x: ad.url }), + text: i18n.tsx.removeAreYouSure({ x: ad.url }), }).then(({ canceled }) => { if (canceled) return; ads.value = ads.value.filter(x => x !== ad); diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue index f941d512b3..44552fb88c 100644 --- a/packages/frontend/src/pages/admin/announcements.vue +++ b/packages/frontend/src/pages/admin/announcements.vue @@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._announcement.needConfirmationToRead }} -

{{ i18n.t('nUsersRead', { n: announcement.reads }) }}

+

{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}

{{ i18n.ts.save }} {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }}) @@ -109,7 +109,7 @@ function add() { function del(announcement) { os.confirm({ type: 'warning', - text: i18n.t('deleteAreYouSure', { x: announcement.title }), + text: i18n.tsx.deleteAreYouSure({ x: announcement.title }), }).then(({ canceled }) => { if (canceled) return; announcements.value = announcements.value.filter(x => x !== announcement); diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue index 72b47949e7..dbbb3941d8 100644 --- a/packages/frontend/src/pages/admin/branding.vue +++ b/packages/frontend/src/pages/admin/branding.vue @@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -30,10 +30,10 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue index 6811a8eba5..847c8bc1d4 100644 --- a/packages/frontend/src/pages/admin/relays.vue +++ b/packages/frontend/src/pages/admin/relays.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only - {{ i18n.t(`_relayStatus.${relay.status}`) }} + {{ i18n.ts._relayStatus[relay.status] }}
{{ i18n.ts.remove }}
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue index ff29f4ec1f..ad58255576 100644 --- a/packages/frontend/src/pages/admin/roles.role.vue +++ b/packages/frontend/src/pages/admin/roles.role.vue @@ -104,7 +104,7 @@ function edit() { async function del() { const { canceled } = await os.confirm({ type: 'warning', - text: i18n.t('deleteAreYouSure', { x: role.name }), + text: i18n.tsx.deleteAreYouSure({ x: role.name }), }); if (canceled) return; diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index c31c6d0903..e3c0ea574a 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -78,7 +78,7 @@ async function read(announcement) { const confirm = await os.confirm({ type: 'question', title: i18n.ts._announcement.readConfirmTitle, - text: i18n.t('_announcement.readConfirmText', { title: announcement.title }), + text: i18n.tsx._announcement.readConfirmText({ title: announcement.title }), }); if (confirm.canceled) return; } diff --git a/packages/frontend/src/pages/auth.form.vue b/packages/frontend/src/pages/auth.form.vue index 39a7924f94..50fd696af3 100644 --- a/packages/frontend/src/pages/auth.form.vue +++ b/packages/frontend/src/pages/auth.form.vue @@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only