From ac2bace76408d71a9bd263322e8fd9e9e4d20f62 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, 28 Dec 2023 10:27:12 +0900 Subject: [PATCH 001/311] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 106cf3ce1582..30e2e57b7dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ ### General - v2023.12.1でDockerを利用してサーバーを起動できない問題を修正 +### Client +- Enhance: 検索画面においてEnterキー押下で検索できるように + ## 2023.12.1 ### Note @@ -124,7 +127,6 @@ - Fix: WebKitブラウザー上でも「デバイスの画面を常にオンにする」機能が効くように - Fix: ページ一覧ページの表示がモバイル環境において崩れているのを修正 - Fix: MFMでルビの中のテキストがnyaizeされない問題を修正 -- Enhance: 検索画面においてEnterキー押下で検索できるように ### Server - Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように From 7ca0af9e7e2a325171d2f26414165078af5d5249 Mon Sep 17 00:00:00 2001 From: zyoshoka <107108195+zyoshoka@users.noreply.github.com> Date: Thu, 28 Dec 2023 13:40:57 +0900 Subject: [PATCH 002/311] =?UTF-8?q?chore(misskey-js):=20`build-misskey-js-?= =?UTF-8?q?with-types`=E6=99=82=E3=81=AB`api-extractor`=E3=82=92=E8=B5=B0?= =?UTF-8?q?=E3=82=89=E3=81=9B=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#1283?= =?UTF-8?q?0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7579323bbe73..49de5a5efd6f 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "build-assets": "node ./scripts/build-assets.mjs", "build": "pnpm build-pre && pnpm -r build && pnpm build-assets", "build-storybook": "pnpm --filter frontend build-storybook", - "build-misskey-js-with-types": "pnpm --filter backend build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build", + "build-misskey-js-with-types": "pnpm --filter backend build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api", "start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js", "start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js", "init": "pnpm migrate", From 8fb8d7c10caac6696a9364beb3457521f3966c31 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, 29 Dec 2023 18:22:40 +0900 Subject: [PATCH 003/311] =?UTF-8?q?enhance(frontend):=20=E3=83=8F=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E3=82=BF=E3=82=B0=E5=85=A5=E5=8A=9B=E6=99=82?= =?UTF-8?q?=E3=81=AB=E3=80=81=E6=9C=AC=E6=96=87=E3=81=AE=E6=9C=AB=E5=B0=BE?= =?UTF-8?q?=E3=81=AE=E8=A1=8C=E3=81=AB=E4=BD=95=E3=82=82=E6=9B=B8=E3=81=8B?= =?UTF-8?q?=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AF=E6=96=B0=E3=81=9F=E3=81=AB=E3=82=B9=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=20(#12851)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * (enhance) ハッシュタグ入力時に、本文の末尾の行に何も書かれていないならスペースを追記しない * Updahe Changelog --- CHANGELOG.md | 5 +++++ packages/frontend/src/components/MkPostForm.vue | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e2e57b7dc3..ea34fa9ef7f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ --> +## 202x.x.x (Unreleased) + +### Client +- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように + ## 2023.12.2 ### General diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 3aacf4c2da01..b86f50eac614 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -752,7 +752,17 @@ async function post(ev?: MouseEvent) { if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') { const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); - postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_; + if (!postData.text) { + postData.text = hashtags_; + } else { + const postTextLines = postData.text.split('\n'); + if (postTextLines[postTextLines.length - 1].trim() === '') { + postTextLines[postTextLines.length - 1] += hashtags_; + } else { + postTextLines[postTextLines.length - 1] += ' ' + hashtags_; + } + postData.text = postTextLines.join('\n'); + } } // plugin From 7948018e6a4735fc32d61e8690319802e38baf3a Mon Sep 17 00:00:00 2001 From: MomentQYC <62551256+MomentQYC@users.noreply.github.com> Date: Fri, 29 Dec 2023 17:23:29 +0800 Subject: [PATCH 004/311] feat: Add support for TrueMail (#12850) Co-authored-by: MarryDream <2190758465@qq.com> --- .../1703658526000-supportTrueMailApi.js | 20 ++++++ packages/backend/src/core/EmailService.ts | 69 ++++++++++++++++++- packages/backend/src/models/Meta.ts | 17 +++++ .../src/server/api/endpoints/admin/meta.ts | 15 ++++ .../server/api/endpoints/admin/update-meta.ts | 23 +++++++ .../frontend/src/pages/admin/security.vue | 17 +++++ 6 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 packages/backend/migration/1703658526000-supportTrueMailApi.js diff --git a/packages/backend/migration/1703658526000-supportTrueMailApi.js b/packages/backend/migration/1703658526000-supportTrueMailApi.js new file mode 100644 index 000000000000..0054d54122e5 --- /dev/null +++ b/packages/backend/migration/1703658526000-supportTrueMailApi.js @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SupportTrueMailApi1703658526000 { + name = 'SupportTrueMailApi1703658526000' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "truemailInstance" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "truemailAuthKey" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "enableTruemailApi" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTruemailApi"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailInstance"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailAuthKey"`); + } +} diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 7fc7800783e0..7e812b4df243 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -156,7 +156,7 @@ export class EmailService { @bindThis public async validateEmailForAccount(emailAddress: string): Promise<{ available: boolean; - reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned'; + reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist'; }> { const meta = await this.metaService.fetch(); @@ -173,6 +173,8 @@ export class EmailService { if (meta.enableActiveEmailValidation) { if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) { validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey); + } else if (meta.enableTruemailApi && meta.truemailInstance && meta.truemailAuthKey != null) { + validated = await this.trueMail(meta.truemailInstance, emailAddress, meta.truemailAuthKey); } else { validated = await validateEmail({ email: emailAddress, @@ -201,6 +203,8 @@ export class EmailService { validated.reason === 'disposable' ? 'disposable' : validated.reason === 'mx' ? 'mx' : validated.reason === 'smtp' ? 'smtp' : + validated.reason === 'network' ? 'network' : + validated.reason === 'blacklist' ? 'blacklist' : null, }; } @@ -265,4 +269,67 @@ export class EmailService { reason: null, }; } + + private async trueMail(truemailInstance: string, emailAddress: string, truemailAuthKey: string): Promise<{ + valid: boolean; + reason: 'used' | 'format' | 'blacklist' | 'mx' | 'smtp' | 'network' | T | null; + }> { + const endpoint = truemailInstance + '?email=' + emailAddress; + try { + const res = await this.httpRequestService.send(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + Authorization: truemailAuthKey + }, + }); + + const json = (await res.json()) as { + email: string; + success: boolean; + errors?: { + list_match?: string; + regex?: string; + mx?: string; + smtp?: string; + } | null; + }; + + if (json.email === undefined || (json.email !== undefined && json.errors?.regex)) { + return { + valid: false, + reason: 'format', + }; + } + if (json.errors?.smtp) { + return { + valid: false, + reason: 'smtp', + }; + } + if (json.errors?.mx) { + return { + valid: false, + reason: 'mx', + }; + } + if (!json.success) { + return { + valid: false, + reason: json.errors?.list_match as T || 'blacklist', + }; + } + + return { + valid: true, + reason: null, + }; + } catch (error) { + return { + valid: false, + reason: 'network', + }; + } + } } diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 84ca762492ef..f5a75ed28a0b 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -457,6 +457,23 @@ export class MiMeta { }) public verifymailAuthKey: string | null; + @Column('boolean', { + default: false, + }) + public enableTruemailApi: boolean; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public truemailInstance: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public truemailAuthKey: string | null; + @Column('boolean', { default: true, }) diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index febc4ab1b172..281f6c484ca1 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -284,6 +284,18 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + enableTruemailApi: { + type: 'boolean', + optional: false, nullable: false, + }, + truemailInstance: { + type: 'string', + optional: false, nullable: true, + }, + truemailAuthKey: { + type: 'string', + optional: false, nullable: true, + }, enableChartsForRemoteUser: { type: 'boolean', optional: false, nullable: false, @@ -520,6 +532,9 @@ export default class extends Endpoint { // eslint- enableActiveEmailValidation: instance.enableActiveEmailValidation, enableVerifymailApi: instance.enableVerifymailApi, verifymailAuthKey: instance.verifymailAuthKey, + enableTruemailApi: instance.enableTruemailApi, + truemailInstance: instance.truemailInstance, + truemailAuthKey: instance.truemailAuthKey, enableChartsForRemoteUser: instance.enableChartsForRemoteUser, enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances, enableServerMachineStats: instance.enableServerMachineStats, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 5a215696fb11..3a6426435d24 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -116,6 +116,9 @@ export const paramDef = { enableActiveEmailValidation: { type: 'boolean' }, enableVerifymailApi: { type: 'boolean' }, verifymailAuthKey: { type: 'string', nullable: true }, + enableTruemailApi: { type: 'boolean' }, + truemailInstance: { type: 'string', nullable: true }, + truemailAuthKey: { type: 'string', nullable: true }, enableChartsForRemoteUser: { type: 'boolean' }, enableChartsForFederatedInstances: { type: 'boolean' }, enableServerMachineStats: { type: 'boolean' }, @@ -469,6 +472,26 @@ export default class extends Endpoint { // eslint- set.verifymailAuthKey = ps.verifymailAuthKey; } } + + if (ps.enableTruemailApi !== undefined) { + set.enableTruemailApi = ps.enableTruemailApi; + } + + if (ps.truemailInstance !== undefined) { + if (ps.truemailInstance === '') { + set.truemailInstance = null; + } else { + set.truemailInstance = ps.truemailInstance; + } + } + + if (ps.truemailAuthKey !== undefined) { + if (ps.truemailAuthKey === '') { + set.truemailAuthKey = null; + } else { + set.truemailAuthKey = ps.truemailAuthKey; + } + } if (ps.enableChartsForRemoteUser !== undefined) { set.enableChartsForRemoteUser = ps.enableChartsForRemoteUser; diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index 7070157ca994..8d79dea20ff7 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -80,6 +80,17 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + + + + + + @@ -153,6 +164,9 @@ const enableIpLogging = ref(false); const enableActiveEmailValidation = ref(false); const enableVerifymailApi = ref(false); const verifymailAuthKey = ref(null); +const enableTruemailApi = ref(false); +const truemailInstance = ref(null); +const truemailAuthKey = ref(null); const bannedEmailDomains = ref(''); async function init() { @@ -194,6 +208,9 @@ function save() { enableActiveEmailValidation: enableActiveEmailValidation.value, enableVerifymailApi: enableVerifymailApi.value, verifymailAuthKey: verifymailAuthKey.value, + enableTruemailApi: enableTruemailApi.value, + truemailInstance: truemailInstance.value, + truemailAuthKey: truemailAuthKey.value, bannedEmailDomains: bannedEmailDomains.value.split('\n'), }).then(() => { fetchInstance(); From 30594dde181e9d151542c41c6f09e673fcbb3124 Mon Sep 17 00:00:00 2001 From: woxtu Date: Fri, 29 Dec 2023 22:50:03 +0900 Subject: [PATCH 005/311] Fix a typo (#12853) --- COPYING | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COPYING b/COPYING index c218443d42ac..905d3e12366b 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ Unless otherwise stated this repository is -Copyright © 2014-2023 syuilo and contributers +Copyright © 2014-2023 syuilo and contributors And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE. From c0466d1585d37a32537e545ee9df3b990da44e85 Mon Sep 17 00:00:00 2001 From: woxtu Date: Sun, 31 Dec 2023 07:51:58 +0900 Subject: [PATCH 006/311] Convert symbols to strings explicitly (#12844) --- packages/frontend/src/pizzax.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts index b2254a061144..d26899dbde54 100644 --- a/packages/frontend/src/pizzax.ts +++ b/packages/frontend/src/pizzax.ts @@ -168,7 +168,7 @@ export class Storage { this.reactiveState[key].value = this.state[key] = rawValue; return this.addIdbSetJob(async () => { - if (_DEV_) console.log(`set ${key} start`); + if (_DEV_) console.log(`set ${String(key)} start`); switch (this.def[key].where) { case 'device': { this.pizzaxChannel.postMessage({ @@ -207,7 +207,7 @@ export class Storage { break; } } - if (_DEV_) console.log(`set ${key} complete`); + if (_DEV_) console.log(`set ${String(key)} complete`); }); } From 2a3398181104e8cfa1aed84badd2fdf64428ec2b Mon Sep 17 00:00:00 2001 From: tamaina Date: Sun, 31 Dec 2023 09:45:35 +0900 Subject: [PATCH 007/311] chore: use summaly, browser-image-resizer, and sharp-read-bmp on registry.npmjs.org instead of git (#12856) * chore: use @misskey-dev/summaly on registry.npmjs.org instead of git * fix backend dependency * fic backend dependency * @misskey-dev/sharp-read-bmp * fix * use @misskey-dev/browser-image-resizer --- packages/backend/package.json | 4 +- packages/backend/src/core/DriveService.ts | 2 +- .../backend/src/server/FileServerService.ts | 2 +- .../src/server/web/UrlPreviewService.ts | 2 +- packages/frontend/package.json | 4 +- .../frontend/src/components/MkUrlPreview.vue | 2 +- packages/frontend/src/scripts/upload.ts | 2 +- .../src/scripts/upload/compress-config.ts | 4 +- packages/frontend/test/url-preview.test.ts | 2 +- pnpm-lock.yaml | 130 ++++++------------ 10 files changed, 54 insertions(+), 100 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index 4d1e9936aa32..710412c43d39 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -74,6 +74,8 @@ "@fastify/multipart": "8.0.0", "@fastify/static": "6.12.0", "@fastify/view": "8.2.0", + "@misskey-dev/sharp-read-bmp": "^1.1.1", + "@misskey-dev/summaly": "^5.0.3", "@nestjs/common": "10.2.10", "@nestjs/core": "10.2.10", "@nestjs/testing": "10.2.10", @@ -157,11 +159,9 @@ "sanitize-html": "2.11.0", "secure-json-parse": "2.7.0", "sharp": "0.32.6", - "sharp-read-bmp": "github:misskey-dev/sharp-read-bmp", "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "summaly": "github:misskey-dev/summaly", "systeminformation": "5.21.20", "tinycolor2": "1.6.0", "tmp": "0.2.1", diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 484f4fc52e5a..04f0e38e6fc9 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -7,7 +7,7 @@ import { randomUUID } from 'node:crypto'; import * as fs from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; import sharp from 'sharp'; -import { sharpBmp } from 'sharp-read-bmp'; +import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; import { IsNull } from 'typeorm'; import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 0c7fc8cefe42..f59996ce17f5 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -9,7 +9,7 @@ import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; import rename from 'rename'; import sharp from 'sharp'; -import { sharpBmp } from 'sharp-read-bmp'; +import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; import type { Config } from '@/config.js'; import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index d590244e34a7..3fd88355dd9b 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import { summaly } from 'summaly'; +import { summaly } from '@misskey-dev/summaly'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 523fc281b3e3..d3c655b0db38 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -19,6 +19,7 @@ "dependencies": { "@discordapp/twemoji": "15.0.2", "@github/webauthn-json": "2.1.1", + "@misskey-dev/browser-image-resizer": "2.2.1-misskey.10", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.5", "@rollup/pluginutils": "5.1.0", @@ -30,7 +31,6 @@ "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6", "astring": "1.8.6", "broadcast-channel": "7.0.0", - "browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3", "buraha": "0.0.1", "canvas-confetti": "1.6.1", "chart.js": "4.4.1", @@ -74,6 +74,7 @@ "vuedraggable": "next" }, "devDependencies": { + "@misskey-dev/summaly": "^5.0.3", "@storybook/addon-actions": "7.6.5", "@storybook/addon-essentials": "7.6.5", "@storybook/addon-interactions": "7.6.5", @@ -127,7 +128,6 @@ "start-server-and-test": "2.0.3", "storybook": "7.6.5", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", - "summaly": "github:misskey-dev/summaly", "vite-plugin-turbosnap": "1.0.3", "vitest": "0.34.6", "vitest-fetch-mock": "0.2.2", diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index f0f1a13d0b59..54f23780c230 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -84,7 +84,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index baee85866cfc..9cf4be778cd5 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -527,6 +527,10 @@ export const routes = [{ path: '/clicker', component: page(() => import('./pages/clicker.vue')), loginRequired: true, +}, { + path: '/drop-and-fusion', + component: page(() => import('./pages/drop-and-fusion.vue')), + loginRequired: true, }, { path: '/timeline', component: page(() => import('./pages/timeline.vue')), diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts index 0b966ff199c7..acde78f5fdd8 100644 --- a/packages/frontend/src/scripts/sound.ts +++ b/packages/frontend/src/scripts/sound.ts @@ -92,7 +92,13 @@ export type OperationType = typeof operationTypes[number]; * @param soundStore サウンド設定 * @param options `useCache`: デフォルトは`true` 一度再生した音声はキャッシュする */ -export async function loadAudio(soundStore: SoundStore, options?: { useCache?: boolean; }) { +export async function loadAudio(soundStore: { + type: Exclude; +} | { + type: '_driveFile_'; + fileId: string; + fileUrl: string; +}, options?: { useCache?: boolean; }) { if (_DEV_) console.log('loading audio. opts:', options); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) { @@ -179,18 +185,31 @@ export async function playFile(soundStore: SoundStore) { createSourceNode(buffer, soundStore.volume)?.start(); } -export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBufferSourceNode | null { +export async function playRaw(type: Exclude, volume = 1, pan = 0, playbackRate = 1) { + const buffer = await loadAudio({ type }); + if (!buffer) return; + createSourceNode(buffer, volume, pan, playbackRate)?.start(); +} + +export function createSourceNode(buffer: AudioBuffer, volume: number, pan = 0, playbackRate = 1) : AudioBufferSourceNode | null { const masterVolume = defaultStore.state.sound_masterVolume; if (isMute() || masterVolume === 0 || volume === 0) { return null; } + const panNode = ctx.createStereoPanner(); + panNode.pan.value = pan; + const gainNode = ctx.createGain(); gainNode.gain.value = masterVolume * volume; const soundSource = ctx.createBufferSource(); soundSource.buffer = buffer; - soundSource.connect(gainNode).connect(ctx.destination); + soundSource.playbackRate.value = playbackRate; + soundSource + .connect(panNode) + .connect(gainNode) + .connect(ctx.destination); return soundSource; } diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index b970ff1df46a..e50002dc2cfa 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -27,6 +27,11 @@ function toolsMenuItems(): MenuItem[] { to: '/clicker', text: '🍪👈', icon: 'ti ti-cookie', + }, { + type: 'link', + to: '/drop-and-fusion', + text: 'Drop & Fusion', + icon: 'ti ti-apple', }, ($i && ($i.isAdmin || $i.policies.canManageCustomEmojis)) ? { type: 'link', to: '/custom-emojis-manager', From 9eae82de1d4f9157602451e26e734c8f4ae94bea Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Sat, 6 Jan 2024 13:33:56 +0100 Subject: [PATCH 036/311] chore(dependabot) open-pull-requests-limit=10? Somehow it's not opening any PR, so try higher count --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c5755315fc93..d4678ec5e042 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,7 +17,7 @@ updates: directory: "/" schedule: interval: daily - open-pull-requests-limit: 5 + open-pull-requests-limit: 10 # List dependencies required to be updated together, sharing the same version numbers. # Those who simply have the common owner (e.g. @fastify) don't need to be listed. groups: From 0815a5235d226434e17ead0166227f5ec60133b8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Jan 2024 09:24:04 +0900 Subject: [PATCH 037/311] tweak game --- .../assets/drop-and-fusion/dropper.png | Bin 0 -> 32415 bytes .../assets/drop-and-fusion/frame-dark.svg | 28 ++++++ .../assets/drop-and-fusion/frame-light.svg | 28 ++++++ .../frontend/assets/drop-and-fusion/frame.svg | 25 ------ .../frontend/src/pages/drop-and-fusion.vue | 80 ++++++++++++++---- 5 files changed, 120 insertions(+), 41 deletions(-) create mode 100644 packages/frontend/assets/drop-and-fusion/dropper.png create mode 100644 packages/frontend/assets/drop-and-fusion/frame-dark.svg create mode 100644 packages/frontend/assets/drop-and-fusion/frame-light.svg delete mode 100644 packages/frontend/assets/drop-and-fusion/frame.svg diff --git a/packages/frontend/assets/drop-and-fusion/dropper.png b/packages/frontend/assets/drop-and-fusion/dropper.png new file mode 100644 index 0000000000000000000000000000000000000000..f4300aa5c06f6001a87c01f8525847b294f43be3 GIT binary patch literal 32415 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 A_y7O^ literal 0 HcmV?d00001 diff --git a/packages/frontend/assets/drop-and-fusion/frame-dark.svg b/packages/frontend/assets/drop-and-fusion/frame-dark.svg new file mode 100644 index 000000000000..3fa7c0da81e0 --- /dev/null +++ b/packages/frontend/assets/drop-and-fusion/frame-dark.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/frontend/assets/drop-and-fusion/frame-light.svg b/packages/frontend/assets/drop-and-fusion/frame-light.svg new file mode 100644 index 000000000000..6052ccbaa09b --- /dev/null +++ b/packages/frontend/assets/drop-and-fusion/frame-light.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/frontend/assets/drop-and-fusion/frame.svg b/packages/frontend/assets/drop-and-fusion/frame.svg deleted file mode 100644 index 4276dae8333c..000000000000 --- a/packages/frontend/assets/drop-and-fusion/frame.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index d0ca5157ef4b..7f4a885b44ae 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -11,7 +11,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- SCORE: +
SCORE:
+
HIGH SCORE: -
@@ -33,7 +34,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- + +
{{ comboPrev }} Chain!
+ (); const canvasEl = shallowRef(); @@ -191,7 +196,7 @@ const FRUITS = [{ const GAME_WIDTH = 450; const GAME_HEIGHT = 600; -const PHYSICS_QUALITY_FACTOR = 32; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる +const PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる let viewScaleX = 1; let viewScaleY = 1; @@ -203,6 +208,7 @@ const comboPrev = ref(0); const dropReady = ref(true); const gameOver = ref(false); const gameStarted = ref(false); +const highScore = ref(null); class Game extends EventEmitter<{ changeScore: (score: number) => void; @@ -251,6 +257,8 @@ class Game extends EventEmitter<{ this.emit('changeScore', value); } + private comboIntervalId: number | null = null; + constructor() { super(); @@ -294,6 +302,8 @@ class Game extends EventEmitter<{ //#region walls const WALL_OPTIONS: Matter.IChamferableBodyDefinition = { isStatic: true, + friction: 0.7, + slop: 1.0, render: { strokeStyle: 'transparent', fillStyle: 'transparent', @@ -308,7 +318,7 @@ class Game extends EventEmitter<{ ]); //#endregion - this.overflowCollider = Matter.Bodies.rectangle(GAME_WIDTH / 2, 0, GAME_WIDTH, 125, { + this.overflowCollider = Matter.Bodies.rectangle(GAME_WIDTH / 2, 0, GAME_WIDTH, 200, { isStatic: true, isSensor: true, render: { @@ -328,11 +338,13 @@ class Game extends EventEmitter<{ private createBody(fruit: typeof FRUITS[number], x: number, y: number) { return Matter.Bodies.circle(x, y, fruit.size / 2, { label: fruit.id, - density: 0.0005, + //density: 0.0005, + density: fruit.size / 1000, + restitution: 0.2, frictionAir: 0.01, - restitution: 0.4, - friction: 0.5, + friction: 0.7, frictionStatic: 5, + slop: 1.0, //mass: 0, render: { sprite: { @@ -372,7 +384,7 @@ class Game extends EventEmitter<{ this.activeBodyIds.push(body.id); }, 100); - const additionalScore = Math.round(currentFruit.score * (1 + (this.combo / 3))); + const additionalScore = Math.round(currentFruit.score * (1 + ((this.combo - 1) / 3))); this.score += additionalScore; const pan = ((newX / GAME_WIDTH) - 0.5) * 2; @@ -449,7 +461,7 @@ class Game extends EventEmitter<{ } }); - window.setInterval(() => { + this.comboIntervalId = window.setInterval(() => { if (this.latestFusionedAt < Date.now() - this.COMBO_INTERVAL) { this.combo = 0; } @@ -469,7 +481,7 @@ class Game extends EventEmitter<{ this.emit('changeStock', this.stock); const x = Math.min(GAME_WIDTH - this.PLAYAREA_MARGIN - (st.fruit.size / 2), Math.max(this.PLAYAREA_MARGIN + (st.fruit.size / 2), _x)); - const body = this.createBody(st.fruit, x, st.fruit.size / 2); + const body = this.createBody(st.fruit, x, 50 + st.fruit.size / 2); Matter.Composite.add(this.engine.world, body); this.activeBodyIds.push(body.id); this.latestDroppedBodyId = body.id; @@ -480,6 +492,7 @@ class Game extends EventEmitter<{ } public dispose() { + if (this.comboIntervalId) window.clearInterval(this.comboIntervalId); Matter.Render.stop(this.render); Matter.Runner.stop(this.runner); Matter.World.clear(this.engine.world, false); @@ -567,10 +580,28 @@ function attachGame() { currentPick.value = null; dropReady.value = false; gameOver.value = true; + + if (score.value > (highScore.value ?? 0)) { + highScore.value = score.value; + + misskeyApi('i/registry/set', { + scope: ['dropAndFusionGame'], + key: 'highScore', + value: highScore.value, + }); + } }); } -onMounted(() => { +onMounted(async () => { + try { + highScore.value = await misskeyApi('i/registry/get', { + scope: ['dropAndFusionGame'], + key: 'highScore', + }); + } catch (err) { + } + game = new Game(); attachGame(); @@ -667,7 +698,9 @@ definePageMetadata({ top: 0; left: 0; width: 100%; - filter: drop-shadow(0 6px 16px #0007); + // なんかiOSでちらつく + //filter: drop-shadow(0 6px 16px #0007); + border-radius: 16px; pointer-events: none; user-select: none; } @@ -699,13 +732,28 @@ definePageMetadata({ text-align: center; font-weight: bold; font-style: oblique; + color: #fff; + -webkit-text-stroke: 1px rgb(255, 145, 0); + text-shadow: 0 0 6px #0005; pointer-events: none; user-select: none; } .currentFruit { position: absolute; - margin-top: 20px; + margin-top: 80px; + z-index: 2; + filter: drop-shadow(0 6px 16px #0007); + pointer-events: none; + user-select: none; +} + +.dropper { + position: absolute; + top: 0; + width: 70px; + margin-top: -10px; + margin-left: -30px; z-index: 2; filter: drop-shadow(0 6px 16px #0007); pointer-events: none; @@ -714,7 +762,7 @@ definePageMetadata({ .currentFruitArrow { position: absolute; - margin-top: 20px; + margin-top: 100px; z-index: 3; animation: currentFruitArrow 2s ease infinite; pointer-events: none; @@ -723,10 +771,10 @@ definePageMetadata({ .dropGuide { position: absolute; - top: 50px; + top: 120px; z-index: 3; width: 3px; - height: calc(100% - 50px); + height: calc(100% - 120px); background: #f002; pointer-events: none; user-select: none; From f2dee7b25eb473796ff77e2abfae88f174fd5b90 Mon Sep 17 00:00:00 2001 From: _ Date: Sun, 7 Jan 2024 09:57:01 +0900 Subject: [PATCH 038/311] =?UTF-8?q?Fix:=20=E3=83=AA=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=83=A9=E3=82=A4=E3=83=A0=E3=83=A9=E3=82=A4=E3=83=B3=E3=81=AE?= =?UTF-8?q?=E3=80=8C=E3=83=AA=E3=83=8E=E3=83=BC=E3=83=88=E3=82=92=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=80=8D=E3=81=8C=E6=AD=A3=E3=81=97=E3=81=8F=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=81=97=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(#12932)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: list timeline withRenotes * add CHANGELOG --- CHANGELOG.md | 1 + packages/backend/src/server/api/stream/channels/user-list.ts | 4 ++++ packages/frontend/src/components/MkTimeline.vue | 5 ++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a6e2db95053..8c27349f61db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### General - Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加 +- Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正 ### Client - Feat: 新しいゲームを追加 diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 909b5a5e031f..e0245814c47a 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -21,6 +21,7 @@ class UserListChannel extends Channel { private membershipsMap: Record | undefined> = {}; private listUsersClock: NodeJS.Timeout; private withFiles: boolean; + private withRenotes: boolean; constructor( private userListsRepository: UserListsRepository, @@ -39,6 +40,7 @@ class UserListChannel extends Channel { public async init(params: any) { this.listId = params.listId as string; this.withFiles = params.withFiles ?? false; + this.withRenotes = params.withRenotes ?? true; // Check existence and owner const listExist = await this.userListsRepository.exist({ @@ -104,6 +106,8 @@ class UserListChannel extends Channel { } } + if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index d5adc02ca708..63f779dbde47 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -132,6 +132,7 @@ function connectChannel() { connection.on('mention', onNote); } else if (props.src === 'list') { connection = stream.useChannel('userList', { + withRenotes: props.withRenotes, withFiles: props.onlyFiles ? true : undefined, listId: props.list, }); @@ -198,6 +199,7 @@ function updatePaginationQuery() { } else if (props.src === 'list') { endpoint = 'notes/user-list-timeline'; query = { + withRenotes: props.withRenotes, withFiles: props.onlyFiles ? true : undefined, listId: props.list, }; @@ -236,8 +238,9 @@ function refreshEndpointAndChannel() { updatePaginationQuery(); } +// デッキのリストカラムでwithRenotesを変更した場合に自動的に更新されるようにさせる // IDが切り替わったら切り替え先のTLを表示させたい -watch(() => [props.list, props.antenna, props.channel, props.role], refreshEndpointAndChannel); +watch(() => [props.list, props.antenna, props.channel, props.role, props.withRenotes], refreshEndpointAndChannel); // 初回表示用 refreshEndpointAndChannel(); From 4ea030d66916777595bf1429fab4d5c1b93d4a5d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Jan 2024 10:35:39 +0900 Subject: [PATCH 039/311] tweak game --- .../frontend/src/pages/drop-and-fusion.vue | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 7f4a885b44ae..601493156237 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only -
+
@@ -221,10 +221,10 @@ class Game extends EventEmitter<{ private COMBO_INTERVAL = 1000; public readonly DROP_INTERVAL = 500; private PLAYAREA_MARGIN = 25; + private STOCK_MAX = 4; private engine: Matter.Engine; private render: Matter.Render; private runner: Matter.Runner; - private detector: Matter.Detector; private overflowCollider: Matter.Body; private isGameOver = false; @@ -286,7 +286,7 @@ class Game extends EventEmitter<{ wireframeBackground: 'transparent', // transparent to hide wireframes: false, showSleeping: false, - pixelRatio: window.devicePixelRatio, + pixelRatio: Math.max(2, window.devicePixelRatio), }, }); @@ -295,8 +295,6 @@ class Game extends EventEmitter<{ this.runner = Matter.Runner.create(); Matter.Runner.run(this.runner, this.engine); - this.detector = Matter.Detector.create(); - this.engine.world.bodies = []; //#region walls @@ -412,7 +410,7 @@ class Game extends EventEmitter<{ } public start() { - for (let i = 0; i < 4; i++) { + for (let i = 0; i < this.STOCK_MAX; i++) { this.stock.push({ id: Math.random().toString(), fruit: FRUITS.filter(x => x.available)[Math.floor(Math.random() * FRUITS.filter(x => x.available).length)], @@ -423,8 +421,8 @@ class Game extends EventEmitter<{ // TODO: fusion予約状態のアイテムは光らせるなどの演出をすると楽しそう let fusionReservedPairs: { bodyA: Matter.Body; bodyB: Matter.Body }[] = []; - const minCollisionDepthForSound = 2.5; - const maxCollisionDepthForSound = 9; + const minCollisionEnergyForSound = 2.5; + const maxCollisionEnergyForSound = 9; const soundPitchMax = 4; const soundPitchMin = 0.5; @@ -451,8 +449,8 @@ class Game extends EventEmitter<{ } } else { const energy = pairs.collision.depth; - if (energy > minCollisionDepthForSound) { - const vol = (Math.min(maxCollisionDepthForSound, energy - minCollisionDepthForSound) / maxCollisionDepthForSound) / 4; + if (energy > minCollisionEnergyForSound) { + const vol = (Math.min(maxCollisionEnergyForSound, energy - minCollisionEnergyForSound) / maxCollisionEnergyForSound) / 4; const pan = ((((bodyA.position.x + bodyB.position.x) / 2) / GAME_WIDTH) - 0.5) * 2; const pitch = soundPitchMin + ((soundPitchMax - soundPitchMin) * (1 - (Math.min(10, energy) / 10))); sound.playRaw('syuilo/poi1', vol, pan, pitch); @@ -700,7 +698,6 @@ definePageMetadata({ width: 100%; // なんかiOSでちらつく //filter: drop-shadow(0 6px 16px #0007); - border-radius: 16px; pointer-events: none; user-select: none; } @@ -710,7 +707,8 @@ definePageMetadata({ display: block; z-index: 1; margin-top: -50px; - max-width: 100%; + width: 100% !important; + height: auto !important; pointer-events: none; user-select: none; } From 2a9db983fcd79e1993d5ea5b03e4979c1a578d7d Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Sun, 7 Jan 2024 02:35:58 +0100 Subject: [PATCH 040/311] feat: export clips (#12931) * feat: export clips * Update CHANGELOG.md --- CHANGELOG.md | 1 + locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + packages/backend/src/core/QueueService.ts | 10 + .../backend/src/queue/QueueProcessorModule.ts | 2 + .../src/queue/QueueProcessorService.ts | 3 + .../processors/ExportClipsProcessorService.ts | 206 ++++++++++++++++++ .../backend/src/server/api/EndpointsModule.ts | 4 + packages/backend/src/server/api/endpoints.ts | 4 +- .../server/api/endpoints/i/export-clips.ts | 35 +++ packages/backend/test/e2e/exports.ts | 194 +++++++++++++++++ packages/backend/test/utils.ts | 2 +- .../src/pages/settings/import-export.vue | 12 + 13 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 packages/backend/src/queue/processors/ExportClipsProcessorService.ts create mode 100644 packages/backend/src/server/api/endpoints/i/export-clips.ts create mode 100644 packages/backend/test/e2e/exports.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c27349f61db..0d2fb4ccd555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ ### Server - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました - Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916) +- Enhance: クリップをエクスポートできるように ## 2023.12.2 diff --git a/locales/index.d.ts b/locales/index.d.ts index 99bc0fc04f15..75517fa2ad46 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2256,6 +2256,7 @@ export interface Locale { "_exportOrImport": { "allNotes": string; "favoritedNotes": string; + "clips": string; "followingList": string; "muteList": string; "blockingList": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 7cf5663a7231..8b6b119d7ec6 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2159,6 +2159,7 @@ _profile: _exportOrImport: allNotes: "全てのノート" favoritedNotes: "お気に入りにしたノート" + clips: "クリップ" followingList: "フォロー" muteList: "ミュート" blockingList: "ブロック" diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 4f99dee64e07..dc3f248da4f7 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -182,6 +182,16 @@ export class QueueService { }); } + @bindThis + public createExportClipsJob(user: ThinUser) { + return this.dbQueue.add('exportClips', { + user: { id: user.id }, + }, { + removeOnComplete: true, + removeOnFail: true, + }); + } + @bindThis public createExportFavoritesJob(user: ThinUser) { return this.dbQueue.add('exportFavorites', { diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index e6327002c586..9c52c7d76a5f 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -24,6 +24,7 @@ import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmo import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js'; import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js'; import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js'; +import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js'; import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js'; import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js'; import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js'; @@ -53,6 +54,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor DeleteDriveFilesProcessorService, ExportCustomEmojisProcessorService, ExportNotesProcessorService, + ExportClipsProcessorService, ExportFavoritesProcessorService, ExportFollowingProcessorService, ExportMutingProcessorService, diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index b872dd65f7e8..bcc1a69f8098 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -16,6 +16,7 @@ import { InboxProcessorService } from './processors/InboxProcessorService.js'; import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js'; import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js'; import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js'; +import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js'; import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js'; import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js'; import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js'; @@ -91,6 +92,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService, private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService, private exportNotesProcessorService: ExportNotesProcessorService, + private exportClipsProcessorService: ExportClipsProcessorService, private exportFavoritesProcessorService: ExportFavoritesProcessorService, private exportFollowingProcessorService: ExportFollowingProcessorService, private exportMutingProcessorService: ExportMutingProcessorService, @@ -164,6 +166,7 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job); case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job); case 'exportNotes': return this.exportNotesProcessorService.process(job); + case 'exportClips': return this.exportClipsProcessorService.process(job); case 'exportFavorites': return this.exportFavoritesProcessorService.process(job); case 'exportFollowing': return this.exportFollowingProcessorService.process(job); case 'exportMuting': return this.exportMutingProcessorService.process(job); diff --git a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts new file mode 100644 index 000000000000..5221497bd396 --- /dev/null +++ b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts @@ -0,0 +1,206 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as fs from 'node:fs'; +import { Writable } from 'node:stream'; +import { Inject, Injectable, StreamableFile } from '@nestjs/common'; +import { MoreThan } from 'typeorm'; +import { format as dateFormat } from 'date-fns'; +import { DI } from '@/di-symbols.js'; +import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js'; +import type Logger from '@/logger.js'; +import { DriveService } from '@/core/DriveService.js'; +import { createTemp } from '@/misc/create-temp.js'; +import type { MiPoll } from '@/models/Poll.js'; +import type { MiNote } from '@/models/Note.js'; +import { bindThis } from '@/decorators.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { Packed } from '@/misc/json-schema.js'; +import { IdService } from '@/core/IdService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; +import type { DbJobDataWithUser } from '../types.js'; + +@Injectable() +export class ExportClipsProcessorService { + private logger: Logger; + + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, + + private driveService: DriveService, + private queueLoggerService: QueueLoggerService, + private idService: IdService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('export-clips'); + } + + @bindThis + public async process(job: Bull.Job): Promise { + this.logger.info(`Exporting clips of ${job.data.user.id} ...`); + + const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); + if (user == null) { + return; + } + + // Create temp file + const [path, cleanup] = await createTemp(); + + this.logger.info(`Temp file is ${path}`); + + try { + const stream = Writable.toWeb(fs.createWriteStream(path, { flags: 'a' })); + const writer = stream.getWriter(); + writer.closed.catch(this.logger.error); + + await writer.write('['); + + await this.processClips(writer, user, job); + + await writer.write(']'); + await writer.close(); + + this.logger.succ(`Exported to: ${path}`); + + const fileName = 'clips-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; + const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); + + this.logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); + } + } + + async processClips(writer: WritableStreamDefaultWriter, user: MiUser, job: Bull.Job) { + let exportedClipsCount = 0; + let cursor: MiClip['id'] | null = null; + + while (true) { + const clips = await this.clipsRepository.find({ + where: { + userId: user.id, + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + }); + + if (clips.length === 0) { + job.updateProgress(100); + break; + } + + cursor = clips.at(-1)?.id ?? null; + + for (const clip of clips) { + // Stringify but remove the last `]}` + const content = JSON.stringify(this.serializeClip(clip)).slice(0, -2); + const isFirst = exportedClipsCount === 0; + await writer.write(isFirst ? content : ',\n' + content); + + await this.processClipNotes(writer, clip.id); + + await writer.write(']}'); + exportedClipsCount++; + } + + const total = await this.clipsRepository.countBy({ + userId: user.id, + }); + + job.updateProgress(exportedClipsCount / total); + } + } + + async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string): Promise { + let exportedClipNotesCount = 0; + let cursor: MiClipNote['id'] | null = null; + + while (true) { + const clipNotes = await this.clipNotesRepository.find({ + where: { + clipId, + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + relations: ['note', 'note.user'], + }) as (MiClipNote & { note: MiNote & { user: MiUser } })[]; + + if (clipNotes.length === 0) { + break; + } + + cursor = clipNotes.at(-1)?.id ?? null; + + for (const clipNote of clipNotes) { + let poll: MiPoll | undefined; + if (clipNote.note.hasPoll) { + poll = await this.pollsRepository.findOneByOrFail({ noteId: clipNote.note.id }); + } + const content = JSON.stringify(this.serializeClipNote(clipNote, poll)); + const isFirst = exportedClipNotesCount === 0; + await writer.write(isFirst ? content : ',\n' + content); + + exportedClipNotesCount++; + } + } + } + + private serializeClip(clip: MiClip): Record { + return { + id: clip.id, + name: clip.name, + description: clip.description, + lastClippedAt: clip.lastClippedAt?.toISOString(), + clipNotes: [], + }; + } + + private serializeClipNote(clip: MiClipNote & { note: MiNote & { user: MiUser } }, poll: MiPoll | undefined): Record { + return { + id: clip.id, + createdAt: this.idService.parse(clip.id).date.toISOString(), + note: { + id: clip.note.id, + text: clip.note.text, + createdAt: this.idService.parse(clip.note.id).date.toISOString(), + fileIds: clip.note.fileIds, + replyId: clip.note.replyId, + renoteId: clip.note.renoteId, + poll: poll, + cw: clip.note.cw, + visibility: clip.note.visibility, + visibleUserIds: clip.note.visibleUserIds, + localOnly: clip.note.localOnly, + reactionAcceptance: clip.note.reactionAcceptance, + uri: clip.note.uri, + url: clip.note.url, + user: { + id: clip.note.user.id, + name: clip.note.user.name, + username: clip.note.user.username, + host: clip.note.user.host, + uri: clip.note.user.uri, + }, + }, + }; + } +} diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 86a64d71219a..a3a98054446a 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -208,6 +208,7 @@ import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js'; import * as ep___i_exportFollowing from './endpoints/i/export-following.js'; import * as ep___i_exportMute from './endpoints/i/export-mute.js'; import * as ep___i_exportNotes from './endpoints/i/export-notes.js'; +import * as ep___i_exportClips from './endpoints/i/export-clips.js'; import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js'; import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js'; import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js'; @@ -569,6 +570,7 @@ const $i_exportBlocking: Provider = { provide: 'ep:i/export-blocking', useClass: const $i_exportFollowing: Provider = { provide: 'ep:i/export-following', useClass: ep___i_exportFollowing.default }; const $i_exportMute: Provider = { provide: 'ep:i/export-mute', useClass: ep___i_exportMute.default }; const $i_exportNotes: Provider = { provide: 'ep:i/export-notes', useClass: ep___i_exportNotes.default }; +const $i_exportClips: Provider = { provide: 'ep:i/export-clips', useClass: ep___i_exportClips.default }; const $i_exportFavorites: Provider = { provide: 'ep:i/export-favorites', useClass: ep___i_exportFavorites.default }; const $i_exportUserLists: Provider = { provide: 'ep:i/export-user-lists', useClass: ep___i_exportUserLists.default }; const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass: ep___i_exportAntennas.default }; @@ -934,6 +936,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_exportFollowing, $i_exportMute, $i_exportNotes, + $i_exportClips, $i_exportFavorites, $i_exportUserLists, $i_exportAntennas, @@ -1293,6 +1296,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_exportFollowing, $i_exportMute, $i_exportNotes, + $i_exportClips, $i_exportFavorites, $i_exportUserLists, $i_exportAntennas, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 41232091c614..bd8aa4af724a 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { Schema } from '@/misc/json-schema.js'; import { permissions } from 'misskey-js'; +import type { Schema } from '@/misc/json-schema.js'; import { RolePolicies } from '@/core/RoleService.js'; import * as ep___admin_meta from './endpoints/admin/meta.js'; @@ -209,6 +209,7 @@ import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js'; import * as ep___i_exportFollowing from './endpoints/i/export-following.js'; import * as ep___i_exportMute from './endpoints/i/export-mute.js'; import * as ep___i_exportNotes from './endpoints/i/export-notes.js'; +import * as ep___i_exportClips from './endpoints/i/export-clips.js'; import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js'; import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js'; import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js'; @@ -568,6 +569,7 @@ const eps = [ ['i/export-following', ep___i_exportFollowing], ['i/export-mute', ep___i_exportMute], ['i/export-notes', ep___i_exportNotes], + ['i/export-clips', ep___i_exportClips], ['i/export-favorites', ep___i_exportFavorites], ['i/export-user-lists', ep___i_exportUserLists], ['i/export-antennas', ep___i_exportAntennas], diff --git a/packages/backend/src/server/api/endpoints/i/export-clips.ts b/packages/backend/src/server/api/endpoints/i/export-clips.ts new file mode 100644 index 000000000000..9435a2b23c3e --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/export-clips.ts @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; + +export const meta = { + secure: true, + requireCredential: true, + limit: { + duration: ms('1day'), + max: 1, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: {}, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportClipsJob(me); + }); + } +} diff --git a/packages/backend/test/e2e/exports.ts b/packages/backend/test/e2e/exports.ts new file mode 100644 index 000000000000..9686f2b7fdb6 --- /dev/null +++ b/packages/backend/test/e2e/exports.ts @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import { signup, api, startServer, startJobQueue, port, post } from '../utils.js'; +import type { INestApplicationContext } from '@nestjs/common'; +import type * as misskey from 'misskey-js'; + +describe('export-clips', () => { + let app: INestApplicationContext; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; + + // XXX: Any better way to get the result? + async function pollFirstDriveFile() { + while (true) { + const files = (await api('/drive/files', {}, alice)).body; + if (!files.length) { + await new Promise(r => setTimeout(r, 100)); + continue; + } + if (files.length > 1) { + throw new Error('Too many files?'); + } + const file = (await api('/drive/files/show', { fileId: files[0].id }, alice)).body; + const res = await fetch(new URL(new URL(file.url).pathname, `http://127.0.0.1:${port}`)); + return await res.json(); + } + } + + beforeAll(async () => { + app = await startServer(); + await startJobQueue(); + alice = await signup({ username: 'alice' }); + bob = await signup({ username: 'bob' }); + }, 1000 * 60 * 2); + + afterAll(async () => { + await app.close(); + }); + + beforeEach(async () => { + // Clean all clips and files of alice + const clips = (await api('/clips/list', {}, alice)).body; + for (const clip of clips) { + const res = await api('/clips/delete', { clipId: clip.id }, alice); + if (res.status !== 204) { + throw new Error('Failed to delete clip'); + } + } + const files = (await api('/drive/files', {}, alice)).body; + for (const file of files) { + const res = await api('/drive/files/delete', { fileId: file.id }, alice); + if (res.status !== 204) { + throw new Error('Failed to delete file'); + } + } + }); + + test('basic export', async () => { + let res = await api('/clips/create', { + name: 'foo', + description: 'bar', + }, alice); + assert.strictEqual(res.status, 200); + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'foo'); + assert.strictEqual(exported[0].description, 'bar'); + assert.strictEqual(exported[0].clipNotes.length, 0); + }); + + test('export with notes', async () => { + let res = await api('/clips/create', { + name: 'foo', + description: 'bar', + }, alice); + assert.strictEqual(res.status, 200); + const clip = res.body; + + const note1 = await post(alice, { + text: 'baz1', + }); + + const note2 = await post(alice, { + text: 'baz2', + poll: { + choices: ['sakura', 'izumi', 'ako'], + }, + }); + + for (const note of [note1, note2]) { + res = await api('/clips/add-note', { + clipId: clip.id, + noteId: note.id, + }, alice); + assert.strictEqual(res.status, 204); + } + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'foo'); + assert.strictEqual(exported[0].description, 'bar'); + assert.strictEqual(exported[0].clipNotes.length, 2); + assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz1'); + assert.strictEqual(exported[0].clipNotes[1].note.text, 'baz2'); + assert.deepStrictEqual(exported[0].clipNotes[1].note.poll.choices[0], 'sakura'); + }); + + test('multiple clips', async () => { + let res = await api('/clips/create', { + name: 'kawaii', + description: 'kawaii', + }, alice); + assert.strictEqual(res.status, 200); + const clip1 = res.body; + + res = await api('/clips/create', { + name: 'yuri', + description: 'yuri', + }, alice); + assert.strictEqual(res.status, 200); + const clip2 = res.body; + + const note1 = await post(alice, { + text: 'baz1', + }); + + const note2 = await post(alice, { + text: 'baz2', + }); + + res = await api('/clips/add-note', { + clipId: clip1.id, + noteId: note1.id, + }, alice); + assert.strictEqual(res.status, 204); + + res = await api('/clips/add-note', { + clipId: clip2.id, + noteId: note2.id, + }, alice); + assert.strictEqual(res.status, 204); + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'kawaii'); + assert.strictEqual(exported[0].clipNotes.length, 1); + assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz1'); + assert.strictEqual(exported[1].name, 'yuri'); + assert.strictEqual(exported[1].clipNotes.length, 1); + assert.strictEqual(exported[1].clipNotes[0].note.text, 'baz2'); + }); + + test('Clipping other user\'s note', async () => { + let res = await api('/clips/create', { + name: 'kawaii', + description: 'kawaii', + }, alice); + assert.strictEqual(res.status, 200); + const clip = res.body; + + const note = await post(bob, { + text: 'baz', + visibility: 'followers', + }); + + res = await api('/clips/add-note', { + clipId: clip.id, + noteId: note.id, + }, alice); + assert.strictEqual(res.status, 204); + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'kawaii'); + assert.strictEqual(exported[0].clipNotes.length, 1); + assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz'); + assert.strictEqual(exported[0].clipNotes[0].note.user.username, 'bob'); + }); +}); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 46b8ea9cdd1c..7c9428d47640 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -17,7 +17,7 @@ import { entities } from '../src/postgres.js'; import { loadConfig } from '../src/config.js'; import type * as misskey from 'misskey-js'; -export { server as startServer } from '@/boot/common.js'; +export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js'; interface UserToken { token: string; diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue index 990eff99c136..70d718f1abc3 100644 --- a/packages/frontend/src/pages/settings/import-export.vue +++ b/packages/frontend/src/pages/settings/import-export.vue @@ -21,6 +21,14 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.export }} + + + + + + {{ i18n.ts.export }} + +
@@ -157,6 +165,10 @@ const exportFavorites = () => { misskeyApi('i/export-favorites', {}).then(onExportSuccess).catch(onError); }; +const exportClips = () => { + misskeyApi('i/export-clips', {}).then(onExportSuccess).catch(onError); +}; + const exportFollowing = () => { misskeyApi('i/export-following', { excludeMuting: excludeMutingUsers.value, From 00e195f50bcc29ee28b6ae11f39b7661a10f2b16 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Jan 2024 13:19:10 +0900 Subject: [PATCH 041/311] tweak game --- locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + .../assets/drop-and-fusion/keycap_1.png | Bin 0 -> 29193 bytes .../assets/drop-and-fusion/keycap_10.png | Bin 0 -> 33717 bytes .../assets/drop-and-fusion/keycap_2.png | Bin 0 -> 32324 bytes .../assets/drop-and-fusion/keycap_3.png | Bin 0 -> 33127 bytes .../assets/drop-and-fusion/keycap_4.png | Bin 0 -> 31182 bytes .../assets/drop-and-fusion/keycap_5.png | Bin 0 -> 32745 bytes .../assets/drop-and-fusion/keycap_6.png | Bin 0 -> 32100 bytes .../assets/drop-and-fusion/keycap_7.png | Bin 0 -> 31318 bytes .../assets/drop-and-fusion/keycap_8.png | Bin 0 -> 32886 bytes .../assets/drop-and-fusion/keycap_9.png | Bin 0 -> 32483 bytes .../frontend/src/pages/drop-and-fusion.vue | 291 +++++++++++++++--- packages/frontend/src/router.ts | 2 +- packages/frontend/src/ui/_common_/common.ts | 4 +- 15 files changed, 245 insertions(+), 54 deletions(-) create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_1.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_10.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_2.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_3.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_4.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_5.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_6.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_7.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_8.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_9.png diff --git a/locales/index.d.ts b/locales/index.d.ts index 75517fa2ad46..8dfb81790ef1 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1192,6 +1192,7 @@ export interface Locale { "decorate": string; "addMfmFunction": string; "enableQuickAddMfmFunction": string; + "bubbleGame": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8b6b119d7ec6..d92c5f9a14c2 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1189,6 +1189,7 @@ seasonalScreenEffect: "季節に応じた画面の演出" decorate: "デコる" addMfmFunction: "装飾を追加" enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する" +bubbleGame: "バブルゲーム" _announcement: forExistingUsers: "既存ユーザーのみ" diff --git a/packages/frontend/assets/drop-and-fusion/keycap_1.png b/packages/frontend/assets/drop-and-fusion/keycap_1.png new file mode 100644 index 0000000000000000000000000000000000000000..d672f2854a8e22451f7329a1603c3241615c5936 GIT binary patch literal 29193 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 LRFbF>GYS3=P$`nK literal 0 HcmV?d00001 diff --git a/packages/frontend/assets/drop-and-fusion/keycap_10.png b/packages/frontend/assets/drop-and-fusion/keycap_10.png new file mode 100644 index 0000000000000000000000000000000000000000..32cf19354065bc8b720e46553c4da1ba5cedf4ea GIT binary patch literal 33717 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�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 a$jB1hdijoK?!Te$K+@t0VznZMLH`3BENuV) literal 0 HcmV?d00001 diff --git a/packages/frontend/assets/drop-and-fusion/keycap_3.png b/packages/frontend/assets/drop-and-fusion/keycap_3.png new file mode 100644 index 0000000000000000000000000000000000000000..424d8c123dd535754e606c2afcd04cf04b54fe76 GIT binary patch literal 33127 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 Qdd~m^p00i_>zopr0JZH>)Bpeg literal 0 HcmV?d00001 diff --git a/packages/frontend/assets/drop-and-fusion/keycap_4.png b/packages/frontend/assets/drop-and-fusion/keycap_4.png new file mode 100644 index 0000000000000000000000000000000000000000..ea6ae505316bb6b1419586e7ecaf9e2f837a924d GIT binary patch literal 31182 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 z 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

@@ -103,6 +104,7 @@ SPDX-License-Identifier: AGPL-3.0-only import * as Matter from 'matter-js'; import { Ref, onMounted, ref, shallowRef } from 'vue'; import { EventEmitter } from 'eventemitter3'; +import * as Misskey from 'misskey-js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import * as sound from '@/scripts/sound.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; @@ -115,6 +117,8 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { useInterval } from '@/scripts/use-interval.js'; import MkSelect from '@/components/MkSelect.vue'; +import { apiUrl } from '@/config.js'; +import { $i } from '@/account.js'; type Mono = { id: string; @@ -788,6 +792,46 @@ async function start() { game.start(); } +function getGameImageDriveFile() { + return new Promise(res => { + canvasEl.value?.toBlob(blob => { + if (!blob) return res(null); + if ($i == null) return res(null); + const formData = new FormData(); + formData.append('file', blob); + formData.append('name', `bubble-game-${Date.now()}.png`); + formData.append('isSensitive', 'false'); + formData.append('comment', 'null'); + formData.append('i', $i.token); + if (defaultStore.state.uploadFolder) { + formData.append('folderId', defaultStore.state.uploadFolder); + } + + window.fetch(apiUrl + '/drive/files/create', { + method: 'POST', + body: formData, + }) + .then(response => response.json()) + .then(f => { + res(f); + }); + }, 'image/png'); + }); +} + +async function share() { + const uploading = getGameImageDriveFile(); + os.promiseDialog(uploading); + const file = await uploading; + if (!file) return; + os.post({ + initialText: `#BubbleGame +MODE: ${gameMode.value} +SCORE: ${score.value}`, + initialFiles: [file], + }); +} + useInterval(() => { if (!canvasEl.value) return; const actualCanvasWidth = canvasEl.value.getBoundingClientRect().width; From c6a4caa8be576f9ac457bbb218eccb91455148aa Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Jan 2024 14:32:57 +0900 Subject: [PATCH 044/311] refactor --- .../frontend/src/pages/drop-and-fusion.vue | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 71d3f06192d2..a3be442d2186 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only :moveClass="$style.transition_stock_move" >
- +
HO5=2qx)cz@+_u!}f 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
+
+
+
{{ i18n.ts.bubbleGame }}
+ + + + + {{ i18n.ts.start }} +
+
+
-
SCORE:
-
HIGH SCORE: -
+ BUBBLE GAME +
- {{ gameMode }} -
@@ -33,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -67,7 +77,23 @@ SPDX-License-Identifier: AGPL-3.0-only
- Restart +
+
+
+
SCORE:
+
HIGH SCORE: -
+
+
+
+
+
+
+
+
+
+ Restart +
+
@@ -86,18 +112,35 @@ import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue'; import MkButton from '@/components/MkButton.vue'; import { defaultStore } from '@/store.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; +import { i18n } from '@/i18n.js'; +import { useInterval } from '@/scripts/use-interval.js'; +import MkSelect from '@/components/MkSelect.vue'; + +type Mono = { + id: string; + level: number; + size: number; + shape: 'circle' | 'rectangle'; + score: number; + dropCandidate: boolean; + sfxPitch: number; + img: string; + imgSize: number; + spriteScale: number; +}; const containerEl = shallowRef(); const canvasEl = shallowRef(); const mouseX = ref(0); -const BASE_SIZE = 30; -const FRUITS = [{ +const NORMAL_BASE_SIZE = 30; +const NORAML_MONOS: Mono[] = [{ id: '9377076d-c980-4d83-bdaf-175bc58275b7', level: 10, - size: BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + size: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'circle', score: 512, - available: false, + dropCandidate: false, sfxPitch: 0.25, img: '/client-assets/drop-and-fusion/exploding_head.png', imgSize: 256, @@ -105,9 +148,10 @@ const FRUITS = [{ }, { id: 'be9f38d2-b267-4b1a-b420-904e22e80568', level: 9, - size: BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + size: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'circle', score: 256, - available: false, + dropCandidate: false, sfxPitch: 0.5, img: '/client-assets/drop-and-fusion/face_with_symbols_on_mouth.png', imgSize: 256, @@ -115,9 +159,10 @@ const FRUITS = [{ }, { id: 'beb30459-b064-4888-926b-f572e4e72e0c', level: 8, - size: BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + size: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'circle', score: 128, - available: false, + dropCandidate: false, sfxPitch: 0.75, img: '/client-assets/drop-and-fusion/cold_face.png', imgSize: 256, @@ -125,9 +170,10 @@ const FRUITS = [{ }, { id: 'feab6426-d9d8-49ae-849c-048cdbb6cdf0', level: 7, - size: BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + size: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'circle', score: 64, - available: false, + dropCandidate: false, sfxPitch: 1, img: '/client-assets/drop-and-fusion/zany_face.png', imgSize: 256, @@ -135,9 +181,10 @@ const FRUITS = [{ }, { id: 'd6d8fed6-6d18-4726-81a1-6cf2c974df8a', level: 6, - size: BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + size: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'circle', score: 32, - available: false, + dropCandidate: false, sfxPitch: 1.5, img: '/client-assets/drop-and-fusion/pleading_face.png', imgSize: 256, @@ -145,9 +192,10 @@ const FRUITS = [{ }, { id: '249c728e-230f-4332-bbbf-281c271c75b2', level: 5, - size: BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25, + size: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'circle', score: 16, - available: true, + dropCandidate: true, sfxPitch: 2, img: '/client-assets/drop-and-fusion/face_with_open_mouth.png', imgSize: 256, @@ -155,9 +203,10 @@ const FRUITS = [{ }, { id: '23d67613-d484-4a93-b71e-3e81b19d6186', level: 4, - size: BASE_SIZE * 1.25 * 1.25 * 1.25, + size: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25, + shape: 'circle', score: 8, - available: true, + dropCandidate: true, sfxPitch: 2.5, img: '/client-assets/drop-and-fusion/smiling_face_with_sunglasses.png', imgSize: 256, @@ -165,9 +214,10 @@ const FRUITS = [{ }, { id: '3cbd0add-ad7d-4685-bad0-29f6dddc0b99', level: 3, - size: BASE_SIZE * 1.25 * 1.25, + size: NORMAL_BASE_SIZE * 1.25 * 1.25, + shape: 'circle', score: 4, - available: true, + dropCandidate: true, sfxPitch: 3, img: '/client-assets/drop-and-fusion/grinning_squinting_face.png', imgSize: 256, @@ -175,9 +225,10 @@ const FRUITS = [{ }, { id: '8f86d4f4-ee02-41bf-ad38-1ce0ae457fb5', level: 2, - size: BASE_SIZE * 1.25, + size: NORMAL_BASE_SIZE * 1.25, + shape: 'circle', score: 2, - available: true, + dropCandidate: true, sfxPitch: 3.5, img: '/client-assets/drop-and-fusion/smiling_face_with_hearts.png', imgSize: 256, @@ -185,14 +236,128 @@ const FRUITS = [{ }, { id: '64ec4add-ce39-42b4-96cb-33908f3f118d', level: 1, - size: BASE_SIZE, + size: NORMAL_BASE_SIZE, + shape: 'circle', score: 1, - available: true, + dropCandidate: true, sfxPitch: 4, img: '/client-assets/drop-and-fusion/heart_suit.png', imgSize: 256, spriteScale: 1.12, -}] as const; +}]; + +const SQUARE_BASE_SIZE = 28; +const SQUARE_MONOS: Mono[] = [{ + id: 'f75fd0ba-d3d4-40a4-9712-b470e45b0525', + level: 10, + size: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'rectangle', + score: 512, + dropCandidate: false, + sfxPitch: 0.25, + img: '/client-assets/drop-and-fusion/keycap_10.png', + imgSize: 256, + spriteScale: 1.12, +}, { + id: '7b70f4af-1c01-45fd-af72-61b1f01e03d1', + level: 9, + size: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'rectangle', + score: 256, + dropCandidate: false, + sfxPitch: 0.5, + img: '/client-assets/drop-and-fusion/keycap_9.png', + imgSize: 256, + spriteScale: 1.12, +}, { + id: '41607ef3-b6d6-4829-95b6-3737bf8bb956', + level: 8, + size: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'rectangle', + score: 128, + dropCandidate: false, + sfxPitch: 0.75, + img: '/client-assets/drop-and-fusion/keycap_8.png', + imgSize: 256, + spriteScale: 1.12, +}, { + id: '8a8310d2-0374-460f-bb50-ca9cd3ee3416', + level: 7, + size: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'rectangle', + score: 64, + dropCandidate: false, + sfxPitch: 1, + img: '/client-assets/drop-and-fusion/keycap_7.png', + imgSize: 256, + spriteScale: 1.12, +}, { + id: '1092e069-fe1a-450b-be97-b5d477ec398c', + level: 6, + size: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'rectangle', + score: 32, + dropCandidate: false, + sfxPitch: 1.5, + img: '/client-assets/drop-and-fusion/keycap_6.png', + imgSize: 256, + spriteScale: 1.12, +}, { + id: '2294734d-7bb8-4781-bb7b-ef3820abf3d0', + level: 5, + size: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25, + shape: 'rectangle', + score: 16, + dropCandidate: true, + sfxPitch: 2, + img: '/client-assets/drop-and-fusion/keycap_5.png', + imgSize: 256, + spriteScale: 1.12, +}, { + id: 'ea8a61af-e350-45f7-ba6a-366fcd65692a', + level: 4, + size: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25, + shape: 'rectangle', + score: 8, + dropCandidate: true, + sfxPitch: 2.5, + img: '/client-assets/drop-and-fusion/keycap_4.png', + imgSize: 256, + spriteScale: 1.12, +}, { + id: 'd0c74815-fc1c-4fbe-9953-c92e4b20f919', + level: 3, + size: SQUARE_BASE_SIZE * 1.25 * 1.25, + shape: 'rectangle', + score: 4, + dropCandidate: true, + sfxPitch: 3, + img: '/client-assets/drop-and-fusion/keycap_3.png', + imgSize: 256, + spriteScale: 1.12, +}, { + id: 'd8fbd70e-611d-402d-87da-1a7fd8cd2c8d', + level: 2, + size: SQUARE_BASE_SIZE * 1.25, + shape: 'rectangle', + score: 2, + dropCandidate: true, + sfxPitch: 3.5, + img: '/client-assets/drop-and-fusion/keycap_2.png', + imgSize: 256, + spriteScale: 1.12, +}, { + id: '35e476ee-44bd-4711-ad42-87be245d3efd', + level: 1, + size: SQUARE_BASE_SIZE, + shape: 'rectangle', + score: 1, + dropCandidate: true, + sfxPitch: 4, + img: '/client-assets/drop-and-fusion/keycap_1.png', + imgSize: 256, + spriteScale: 1.12, +}]; const GAME_WIDTH = 450; const GAME_HEIGHT = 600; @@ -200,12 +365,13 @@ const PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高い let viewScaleX = 1; let viewScaleY = 1; -const currentPick = shallowRef<{ id: string; fruit: typeof FRUITS[number] } | null>(null); -const stock = shallowRef<{ id: string; fruit: typeof FRUITS[number] }[]>([]); +const currentPick = shallowRef<{ id: string; fruit: Mono } | null>(null); +const stock = shallowRef<{ id: string; fruit: Mono }[]>([]); const score = ref(0); const combo = ref(0); const comboPrev = ref(0); const dropReady = ref(true); +const gameMode = ref<'normal' | 'square'>('normal'); const gameOver = ref(false); const gameStarted = ref(false); const highScore = ref(null); @@ -213,7 +379,7 @@ const highScore = ref(null); class Game extends EventEmitter<{ changeScore: (score: number) => void; changeCombo: (combo: number) => void; - changeStock: (stock: { id: string; fruit: typeof FRUITS[number] }[]) => void; + changeStock: (stock: { id: string; fruit: Mono }[]) => void; dropped: () => void; fusioned: (x: number, y: number, score: number) => void; gameOver: () => void; @@ -228,6 +394,8 @@ class Game extends EventEmitter<{ private overflowCollider: Matter.Body; private isGameOver = false; + private monoDefinitions: Mono[] = []; + /** * フィールドに出ていて、かつ合体の対象となるアイテム */ @@ -237,7 +405,7 @@ class Game extends EventEmitter<{ private latestDroppedAt = 0; private latestFusionedAt = 0; - private stock: { id: string; fruit: typeof FRUITS[number] }[] = []; + private stock: { id: string; fruit: Mono }[] = []; private _combo = 0; private get combo() { @@ -259,9 +427,13 @@ class Game extends EventEmitter<{ private comboIntervalId: number | null = null; - constructor() { + constructor(opts: { + monoDefinitions: Mono[]; + }) { super(); + this.monoDefinitions = opts.monoDefinitions; + this.engine = Matter.Engine.create({ constraintIterations: 2 * PHYSICS_QUALITY_FACTOR, positionIterations: 6 * PHYSICS_QUALITY_FACTOR, @@ -333,8 +505,8 @@ class Game extends EventEmitter<{ }); } - private createBody(fruit: typeof FRUITS[number], x: number, y: number) { - return Matter.Bodies.circle(x, y, fruit.size / 2, { + private createBody(fruit: Mono, x: number, y: number) { + const options: Matter.IBodyDefinition = { label: fruit.id, //density: 0.0005, density: fruit.size / 1000, @@ -351,7 +523,14 @@ class Game extends EventEmitter<{ yScale: (fruit.size / fruit.imgSize) * fruit.spriteScale, }, }, - }); + }; + if (fruit.shape === 'circle') { + return Matter.Bodies.circle(x, y, fruit.size / 2, options); + } else if (fruit.shape === 'rectangle') { + return Matter.Bodies.rectangle(x, y, fruit.size, fruit.size, options); + } else { + throw new Error('unrecognized shape'); + } } private fusion(bodyA: Matter.Body, bodyB: Matter.Body) { @@ -370,8 +549,8 @@ class Game extends EventEmitter<{ Matter.Composite.remove(this.engine.world, [bodyA, bodyB]); this.activeBodyIds = this.activeBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id); - const currentFruit = FRUITS.find(y => y.id === bodyA.label)!; - const nextFruit = FRUITS.find(x => x.level === currentFruit.level + 1); + const currentFruit = this.monoDefinitions.find(y => y.id === bodyA.label)!; + const nextFruit = this.monoDefinitions.find(x => x.level === currentFruit.level + 1); if (nextFruit) { const body = this.createBody(nextFruit, newX, newY); @@ -382,7 +561,8 @@ class Game extends EventEmitter<{ this.activeBodyIds.push(body.id); }, 100); - const additionalScore = Math.round(currentFruit.score * (1 + ((this.combo - 1) / 3))); + const comboBonus = 1 + ((this.combo - 1) / 5); + const additionalScore = Math.round(currentFruit.score * comboBonus); this.score += additionalScore; const pan = ((newX / GAME_WIDTH) - 0.5) * 2; @@ -413,7 +593,7 @@ class Game extends EventEmitter<{ for (let i = 0; i < this.STOCK_MAX; i++) { this.stock.push({ id: Math.random().toString(), - fruit: FRUITS.filter(x => x.available)[Math.floor(Math.random() * FRUITS.filter(x => x.available).length)], + fruit: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], }); } this.emit('changeStock', this.stock); @@ -474,7 +654,7 @@ class Game extends EventEmitter<{ const st = this.stock.shift()!; this.stock.push({ id: Math.random().toString(), - fruit: FRUITS.filter(x => x.available)[Math.floor(Math.random() * FRUITS.filter(x => x.available).length)], + fruit: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], }); this.emit('changeStock', this.stock); @@ -533,9 +713,7 @@ function restart() { score.value = 0; combo.value = 0; comboPrev.value = 0; - game = new Game(); - attachGame(); - game.start(); + gameStarted.value = false; } function attachGame() { @@ -584,36 +762,45 @@ function attachGame() { misskeyApi('i/registry/set', { scope: ['dropAndFusionGame'], - key: 'highScore', + key: 'highScore:' + gameMode.value, value: highScore.value, }); } }); } -onMounted(async () => { +async function start() { try { highScore.value = await misskeyApi('i/registry/get', { scope: ['dropAndFusionGame'], - key: 'highScore', + key: 'highScore:' + gameMode.value, }); } catch (err) { } - game = new Game(); - + gameStarted.value = true; + game = new Game(gameMode.value === 'normal' ? { + monoDefinitions: NORAML_MONOS, + } : { + monoDefinitions: SQUARE_MONOS, + }); attachGame(); - game.start(); +} +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; +}, 1000, { immediate: false, afterMounted: true }); + +onMounted(async () => { }); definePageMetadata({ - title: 'Drop & Fusion', + title: i18n.ts.bubbleGame, icon: 'ti ti-apple', }); @@ -666,6 +853,8 @@ definePageMetadata({ } .root { + margin: 0 auto; + max-width: 600px; user-select: none; * { diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 9cf4be778cd5..35478a35a96d 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -528,7 +528,7 @@ export const routes = [{ component: page(() => import('./pages/clicker.vue')), loginRequired: true, }, { - path: '/drop-and-fusion', + path: '/bubble-game', component: page(() => import('./pages/drop-and-fusion.vue')), loginRequired: true, }, { diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index e50002dc2cfa..9930b321f7eb 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -29,8 +29,8 @@ function toolsMenuItems(): MenuItem[] { icon: 'ti ti-cookie', }, { type: 'link', - to: '/drop-and-fusion', - text: 'Drop & Fusion', + to: '/bubble-game', + text: i18n.ts.bubbleGame, icon: 'ti ti-apple', }, ($i && ($i.isAdmin || $i.policies.canManageCustomEmojis)) ? { type: 'link', From 622a09f8ed2e94b5b89894f1f10745d53af45069 Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Sun, 7 Jan 2024 13:29:17 +0900 Subject: [PATCH 042/311] =?UTF-8?q?Fix:=20`Mk:C:mfm`=E3=81=AE`onClickEv`?= =?UTF-8?q?=E3=81=8C=E6=AD=A3=E5=B8=B8=E3=81=AB=E5=91=BC=E3=81=B3=E5=87=BA?= =?UTF-8?q?=E3=81=95=E3=82=8C=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(#12831)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix clickable api * Update CHANGELOG.md * revert CHANGELOG.md * Update CHANGELOG.md --- packages/frontend/src/scripts/aiscript/ui.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts index 08ba1e6d9bab..215ac4cc6959 100644 --- a/packages/frontend/src/scripts/aiscript/ui.ts +++ b/packages/frontend/src/scripts/aiscript/ui.ts @@ -218,7 +218,7 @@ function getTextOptions(def: values.Value | undefined): Omit { +function getMfmOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Omit { utils.assertObject(def); const text = def.value.get('text'); @@ -241,7 +241,7 @@ function getMfmOptions(def: values.Value | undefined): Omit { - if (onClickEv) call(onClickEv, values.STR(evId)); + if (onClickEv) call(onClickEv, [values.STR(evId)]); }, }; } From 1d1780081ee85ecc040e05a4ba18a4aa368c1490 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Jan 2024 14:21:19 +0900 Subject: [PATCH 043/311] =?UTF-8?q?enhance(frontend):=20=E3=82=B2=E3=83=BC?= =?UTF-8?q?=E3=83=A0=E3=81=AE=E3=82=B7=E3=82=A7=E3=82=A2=E6=A9=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/drop-and-fusion.vue | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 7f41be4c58dd..71d3f06192d2 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -74,6 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
GAME OVER!
SCORE:
+ Share

;BDp(T|y3v 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 literal 0 HcmV?d00001 diff --git a/packages/frontend/assets/drop-and-fusion/keycap_6.png b/packages/frontend/assets/drop-and-fusion/keycap_6.png new file mode 100644 index 0000000000000000000000000000000000000000..70c9522b4358aa3971d8d7187df98ce3dae4a6f4 GIT binary patch literal 32100 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 KB&x-J{Qe)zY2c3l literal 0 HcmV?d00001 diff --git a/packages/frontend/assets/drop-and-fusion/keycap_7.png b/packages/frontend/assets/drop-and-fusion/keycap_7.png new file mode 100644 index 0000000000000000000000000000000000000000..5a24307487e4a56d6bb53f39aea4f1568770bcdf GIT binary patch literal 31318 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�pr!q%q7zU$?tNZFV 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 literal 0 HcmV?d00001 diff --git a/packages/frontend/assets/drop-and-fusion/keycap_9.png b/packages/frontend/assets/drop-and-fusion/keycap_9.png new file mode 100644 index 0000000000000000000000000000000000000000..ac3f638841207751545ee904ef57fd327fd7b9e9 GIT binary patch literal 32483 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%0D$tOnjoISqNOyIAOf-_|v-y9Ls1Mcl2g8S6 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 literal 0 HcmV?d00001 diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 601493156237..7f41be4c58dd 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -7,12 +7,22 @@ SPDX-License-Identifier: AGPL-3.0-only -

@@ -65,10 +65,10 @@ SPDX-License-Identifier: AGPL-3.0-only :moveClass="$style.transition_picked_move" mode="out-in" > - +
@@ -369,8 +369,8 @@ const PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高い let viewScaleX = 1; let viewScaleY = 1; -const currentPick = shallowRef<{ id: string; fruit: Mono } | null>(null); -const stock = shallowRef<{ id: string; fruit: Mono }[]>([]); +const currentPick = shallowRef<{ id: string; mono: Mono } | null>(null); +const stock = shallowRef<{ id: string; mono: Mono }[]>([]); const score = ref(0); const combo = ref(0); const comboPrev = ref(0); @@ -383,7 +383,7 @@ const highScore = ref(null); class Game extends EventEmitter<{ changeScore: (score: number) => void; changeCombo: (combo: number) => void; - changeStock: (stock: { id: string; fruit: Mono }[]) => void; + changeStock: (stock: { id: string; mono: Mono }[]) => void; dropped: () => void; fusioned: (x: number, y: number, score: number) => void; gameOver: () => void; @@ -409,7 +409,7 @@ class Game extends EventEmitter<{ private latestDroppedAt = 0; private latestFusionedAt = 0; - private stock: { id: string; fruit: Mono }[] = []; + private stock: { id: string; mono: Mono }[] = []; private _combo = 0; private get combo() { @@ -509,11 +509,11 @@ class Game extends EventEmitter<{ }); } - private createBody(fruit: Mono, x: number, y: number) { + private createBody(mono: Mono, x: number, y: number) { const options: Matter.IBodyDefinition = { - label: fruit.id, + label: mono.id, //density: 0.0005, - density: fruit.size / 1000, + density: mono.size / 1000, restitution: 0.2, frictionAir: 0.01, friction: 0.7, @@ -522,16 +522,16 @@ class Game extends EventEmitter<{ //mass: 0, render: { sprite: { - texture: fruit.img, - xScale: (fruit.size / fruit.imgSize) * fruit.spriteScale, - yScale: (fruit.size / fruit.imgSize) * fruit.spriteScale, + texture: mono.img, + xScale: (mono.size / mono.imgSize) * mono.spriteScale, + yScale: (mono.size / mono.imgSize) * mono.spriteScale, }, }, }; - if (fruit.shape === 'circle') { - return Matter.Bodies.circle(x, y, fruit.size / 2, options); - } else if (fruit.shape === 'rectangle') { - return Matter.Bodies.rectangle(x, y, fruit.size, fruit.size, options); + if (mono.shape === 'circle') { + return Matter.Bodies.circle(x, y, mono.size / 2, options); + } else if (mono.shape === 'rectangle') { + return Matter.Bodies.rectangle(x, y, mono.size, mono.size, options); } else { throw new Error('unrecognized shape'); } @@ -553,11 +553,11 @@ class Game extends EventEmitter<{ Matter.Composite.remove(this.engine.world, [bodyA, bodyB]); this.activeBodyIds = this.activeBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id); - const currentFruit = this.monoDefinitions.find(y => y.id === bodyA.label)!; - const nextFruit = this.monoDefinitions.find(x => x.level === currentFruit.level + 1); + const currentMono = this.monoDefinitions.find(y => y.id === bodyA.label)!; + const nextMono = this.monoDefinitions.find(x => x.level === currentMono.level + 1); - if (nextFruit) { - const body = this.createBody(nextFruit, newX, newY); + if (nextMono) { + const body = this.createBody(nextMono, newX, newY); Matter.Composite.add(this.engine.world, body); // 連鎖してfusionした場合の分かりやすさのため少し間を置いてからfusion対象になるようにする @@ -566,11 +566,11 @@ class Game extends EventEmitter<{ }, 100); const comboBonus = 1 + ((this.combo - 1) / 5); - const additionalScore = Math.round(currentFruit.score * comboBonus); + const additionalScore = Math.round(currentMono.score * comboBonus); this.score += additionalScore; const pan = ((newX / GAME_WIDTH) - 0.5) * 2; - sound.playRaw('syuilo/bubble2', 1, pan, nextFruit.sfxPitch); + sound.playRaw('syuilo/bubble2', 1, pan, nextMono.sfxPitch); this.emit('fusioned', newX, newY, additionalScore); } else { @@ -597,7 +597,7 @@ class Game extends EventEmitter<{ for (let i = 0; i < this.STOCK_MAX; i++) { this.stock.push({ id: Math.random().toString(), - fruit: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], + mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], }); } this.emit('changeStock', this.stock); @@ -658,12 +658,12 @@ class Game extends EventEmitter<{ const st = this.stock.shift()!; this.stock.push({ id: Math.random().toString(), - fruit: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], + 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(GAME_WIDTH - this.PLAYAREA_MARGIN - (st.fruit.size / 2), Math.max(this.PLAYAREA_MARGIN + (st.fruit.size / 2), _x)); - const body = this.createBody(st.fruit, x, 50 + st.fruit.size / 2); + const x = Math.min(GAME_WIDTH - 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); Matter.Composite.add(this.engine.world, body); this.activeBodyIds.push(body.id); this.latestDroppedBodyId = body.id; @@ -970,7 +970,7 @@ definePageMetadata({ user-select: none; } -.currentFruit { +.currentMono { position: absolute; margin-top: 80px; z-index: 2; @@ -991,11 +991,11 @@ definePageMetadata({ user-select: none; } -.currentFruitArrow { +.currentMonoArrow { position: absolute; margin-top: 100px; z-index: 3; - animation: currentFruitArrow 2s ease infinite; + animation: currentMonoArrow 2s ease infinite; pointer-events: none; user-select: none; } @@ -1030,7 +1030,7 @@ definePageMetadata({ } } -@keyframes currentFruitArrow { +@keyframes currentMonoArrow { 0% { transform: translateY(0); } 25% { transform: translateY(-8px); } 50% { transform: translateY(0); } From 5e71418d5caca1cea333ee1b8629987cc69c4fbc Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Sun, 7 Jan 2024 08:02:53 +0100 Subject: [PATCH 045/311] fix(frontend/emoji) restore U+FE0F for simple emojis (#12866) * fix(frontend/emoji) restore U+FE0F for simple emojis * Update CHANGELOG.md --------- Co-authored-by: syuilo --- CHANGELOG.md | 1 + .../src/components/global/MkEmoji.vue | 10 ++--- packages/frontend/src/scripts/emojilist.ts | 7 +++- packages/frontend/test/emoji.test.ts | 41 +++++++++++++++++++ packages/frontend/test/init.ts | 24 ++++++----- 5 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 packages/frontend/test/emoji.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d2fb4ccd555..474fcad67431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Client - Feat: 新しいゲームを追加 - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように +- Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Enhance: チャンネルノートのピン留めをノートのメニューからできるよ diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index 76ca8688d1bc..f6b21343b66f 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -5,15 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only + + 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 067/311] Update CHANGELOG.md (#12953) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b56ff9fc90d..6963d45f6334 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 068/311] 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 9db93d153449..16fe87d97ade 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 069/311] 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 6963d45f6334..244fd724a9d6 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 895aa474193f..8c3ce30668fa 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 9d982248220a..400051bce79c 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 070/311] =?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 974daf35e4f3..d041a675f896 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 071/311] =?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 d041a675f896..f585519459e8 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 16fe87d97ade..a59eb271ec76 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 072/311] 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 f585519459e8..c5ab7a33f5fe 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 a59eb271ec76..342e8189053f 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 142ddf87c955..05c8977ecfb3 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 073/311] =?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 244fd724a9d6..13ad3a3508e2 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 49884c705fea..15eda4499fcd 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 f36d46506fe8..84424c58edc8 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 074/311] 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 aa74ba54b047..852cbdd27d06 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 4863bbe770ef..f85dc0fcf86e 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 c5ab7a33f5fe..9fb7ab2e23cb 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 075/311] 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 000000000000..acaebbadf753 --- /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 9fb7ab2e23cb..7bd0eef00049 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 342e8189053f..d64c6015a54d 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 076/311] enhance(drop-and-fusion): make game engine headless for server-side running --- .../src/pages/drop-and-fusion.game.vue | 438 ++++++++++++------ .../src/scripts/drop-and-fusion-engine.ts | 359 ++++---------- 2 files changed, 387 insertions(+), 410 deletions(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index acaebbadf753..3fefb49fae95 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 9930b321f7eb..b970ff1df46a 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 107/311] =?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 | 526 ++++++++++++++++-- packages/frontend/src/filters/hms.ts | 65 +++ packages/frontend/src/scripts/device-kind.ts | 7 + 10 files changed, 1173 insertions(+), 68 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 9f1fdcf9eedd..945b6ac1ad16 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 dafbdd35595c..71134544d943 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 58952894b37c..743a3ca38ea2 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 f59996ce17f5..7745a6cb78b1 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 000000000000..75b31b9a4916 --- /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 3f8fef6632eb..b21960a4900c 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 b7190f63355f..9eab85500400 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 4318694d4f33..fabbc1c05d17 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 d261055b0dd98ef6d8d50cda5da1952061cbff3f 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 118/311] =?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 75b31b9a4916..53fd3b2d558b 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 e6303a5c41d1..a150ae984367 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 977c9020c76d..0a113458a143 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 7b5da965ff6d..73db7becc21f 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 91a7fc66695492c691ad1bb1c68d87c0c6c347db 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 119/311] =?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 | 84 +++---- packages/frontend/src/pages/channels.vue | 77 +++---- packages/frontend/src/pages/drive.file.vue | 17 +- 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 | 200 +++++++++-------- .../frontend/src/pages/my-clips/index.vue | 28 ++- packages/frontend/src/pages/notifications.vue | 28 ++- packages/frontend/src/pages/pages.vue | 47 ++-- packages/frontend/src/pages/search.vue | 27 ++- .../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, 682 insertions(+), 411 deletions(-) create mode 100644 packages/frontend/src/components/MkHorizontalSwipe.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b433196147b..605b8af92c64 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 71134544d943..a659e790cc49 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 743a3ca38ea2..8749a5f49f09 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 000000000000..2c62aadbf4cd --- /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 f402b26ad8e7..4ba1b6da768d 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 5632bf7caf2b..c31c6d090326 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 971eca8cae2f..4cdf2eea7d65 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.thisChannelArchived }} - - + + - -
-
- -
-
-
-
- - - - {{ i18n.ts.search }} + +
+
+ +
+
+
+
+ + + + {{ 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 2c1e5d20a77e..6a9e9079637e 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 f068de888025..1b80014366c9 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 785201889403..53510ea232f8 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 0198ab9700f6..9749888fe952 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 c8a0eeeeaa0b..4211dc0d8734 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})` }} -
-
- - - - - - - - - - - + +
+
+ + {{ instance.name || `(${i18n.ts.unknown})` }} +
+
+ + + + + + + + + + + + +
+ + + -
- - - - - - -
- {{ i18n.ts.stopActivityDelivery }} - {{ i18n.ts.blockThisInstance }} - {{ i18n.ts.silenceThisInstance }} - Refresh metadata -
-
+ + +
+ {{ 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 }) }}
- + + + 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 850222708e37..468e46838b45 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 d57bda41b5ac..8913a89adbdf 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 22ab9ced092f..8b57b1af9f18 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 9d5e5697ceca..b68de805cfd9 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 607aaec52191..e52a5ee04fca 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 6fe8963f5162..666a9968b219 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 95869e7b8cf6..603f1bef33b0 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 e3a85377d852..21b796caa1aa 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 c6389de5a95d360a17845f38ddbd677569e1d021 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 120/311] 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 | 195 ++++++++++++++++++---- locales/index.d.ts | 229 +++++++++++++------------- packages/frontend/src/i18n.ts | 5 +- packages/frontend/src/scripts/i18n.ts | 113 +++++++++++-- 4 files changed, 380 insertions(+), 162 deletions(-) diff --git a/locales/generateDTS.js b/locales/generateDTS.js index d3afdd6e1502..6eb5bd630daf 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.createInterfaceDeclaration( - [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], - ts.factory.createIdentifier('Locale'), - undefined, - undefined, - members, - ), ts.factory.createVariableStatement( [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration( - ts.factory.createIdentifier('locales'), + [ + 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.createTypeLiteralNode([ts.factory.createIndexSignature( + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('T'), undefined, - [ts.factory.createParameterDeclaration( + ), + ), + ], + ), + 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('lang'), + 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('Locale'), + ts.factory.createIdentifier('ParameterizedString'), + [ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)], + ), + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('ILocale'), undefined, ), - )]), - undefined, - )], - ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags, + ]), + ), + ], + ), + ts.factory.createInterfaceDeclaration( + [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], + ts.factory.createIdentifier('Locale'), + 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( + 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 a659e790cc49..a22cb6350716 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 858db74dacf9..c5c4ccf82028 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 8e5f17f38ae0..55b537195096 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 (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 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 (args) { - for (const [k, v] of Object.entries(args)) { - str = str.replace(`{${k}}`, v.toString()); + if (_DEV_) { + if (typeof str === 'undefined') { + console.error(`Unexpected locale key: ${key}`); + return key; } } - return str; - } catch (err) { - console.warn(`missing localization '${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 972e6d57ed838f3e96e49eda4465b4e0f661d22a 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 121/311] 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 55b537195096..3366f3eac36b 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 acf75207d5a6b7d5aed5311d4820f7ed6d0efb1a 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 122/311] =?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 2c62aadbf4cd..a7d0d5a3e407 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 000000000000..301a177de13b --- /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 000000000000..dbbeb20f42a5 --- /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 000000000000..c483e36c24c2 --- /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 98fe0043c143..8cdc7b59c66c 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 f955cc5cc1e4..2b95e0153385 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 b60f449a7120..e4e7d13668fc 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 dc591a7046e2..671abd78ce62 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 dfe24ce0d8af..c14876c0e377 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 5c6bebf2fd02..78f14d2250c1 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 76e2b5309c9f..36facf6e28ae 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 000000000000..8d3ca3016625 --- /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 000000000000..55d0b84da735 --- /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 000000000000..20ed36f2083f --- /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 000000000000..85cf1a04852b --- /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 000000000000..f56b65e86802 --- /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 825a7ab860bb..31394eb0815d 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 3b2ecec7fd60..3a03a5825368 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 fb31f37743d1107666c918492a5727a9e9ccc9ed Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 20 Jan 2024 09:53:26 +0900 Subject: [PATCH 127/311] refactor: extract bubble-game engine as independent package --- packages/frontend/package.json | 2 +- .../src/pages/drop-and-fusion.game.vue | 2 +- packages/frontend/vite.config.ts | 4 +- packages/misskey-bubble-game/.eslintignore | 7 + packages/misskey-bubble-game/.eslintrc.cjs | 9 + packages/misskey-bubble-game/package.json | 31 ++ packages/misskey-bubble-game/src/game.ts | 495 +++++++++++++++++ packages/misskey-bubble-game/src/index.ts | 10 + .../src/monos.ts} | 498 +----------------- packages/misskey-bubble-game/tsconfig.json | 33 ++ packages/misskey-reversi/.eslintignore | 7 + packages/misskey-reversi/.eslintrc.cjs | 9 + packages/misskey-reversi/src/index.ts | 5 + packages/misskey-reversi/src/maps.ts | 5 + pnpm-lock.yaml | 147 ++++-- pnpm-workspace.yaml | 1 + 16 files changed, 718 insertions(+), 547 deletions(-) create mode 100644 packages/misskey-bubble-game/.eslintignore create mode 100644 packages/misskey-bubble-game/.eslintrc.cjs create mode 100644 packages/misskey-bubble-game/package.json create mode 100644 packages/misskey-bubble-game/src/game.ts create mode 100644 packages/misskey-bubble-game/src/index.ts rename packages/{frontend/src/scripts/drop-and-fusion-engine.ts => misskey-bubble-game/src/monos.ts} (51%) create mode 100644 packages/misskey-bubble-game/tsconfig.json create mode 100644 packages/misskey-reversi/.eslintignore create mode 100644 packages/misskey-reversi/.eslintrc.cjs diff --git a/packages/frontend/package.json b/packages/frontend/package.json index a9a68601fcac..6dd826d45941 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -55,12 +55,12 @@ "mfm-js": "0.24.0", "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", + "misskey-bubble-game": "workspace:*", "photoswipe": "5.4.3", "punycode": "2.3.1", "rollup": "4.9.1", "sanitize-html": "2.11.0", "sass": "1.69.5", - "seedrandom": "^3.0.5", "shiki": "0.14.7", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index 1fc0c7cd9cb6..51819fafd0f6 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -180,6 +180,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, onDeactivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'; import * as Matter from 'matter-js'; import * as Misskey from 'misskey-js'; +import { DropAndFusionGame, Mono } from 'misskey-bubble-game'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import * as os from '@/os.js'; @@ -193,7 +194,6 @@ import { i18n } from '@/i18n.js'; import { useInterval } from '@/scripts/use-interval.js'; import { apiUrl } from '@/config.js'; 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'; import copyToClipboard from '@/scripts/copy-to-clipboard.js'; diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 8cdc7b59c66c..84fe9e44df77 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', 'misskey-reversi'], + include: ['misskey-js', 'misskey-reversi', 'misskey-bubble-game'], }, 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/, /misskey-reversi/, /node_modules/], + include: [/misskey-js/, /misskey-reversi/, /misskey-bubble-game/, /node_modules/], }, }, diff --git a/packages/misskey-bubble-game/.eslintignore b/packages/misskey-bubble-game/.eslintignore new file mode 100644 index 000000000000..f22128f047fd --- /dev/null +++ b/packages/misskey-bubble-game/.eslintignore @@ -0,0 +1,7 @@ +node_modules +/built +/coverage +/.eslintrc.js +/jest.config.ts +/test +/test-d diff --git a/packages/misskey-bubble-game/.eslintrc.cjs b/packages/misskey-bubble-game/.eslintrc.cjs new file mode 100644 index 000000000000..e2e31e9e331e --- /dev/null +++ b/packages/misskey-bubble-game/.eslintrc.cjs @@ -0,0 +1,9 @@ +module.exports = { + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + extends: [ + '../shared/.eslintrc.js', + ], +}; diff --git a/packages/misskey-bubble-game/package.json b/packages/misskey-bubble-game/package.json new file mode 100644 index 000000000000..806d69367041 --- /dev/null +++ b/packages/misskey-bubble-game/package.json @@ -0,0 +1,31 @@ +{ + "name": "misskey-bubble-game", + "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/matter-js": "0.19.6", + "@types/node": "20.11.5", + "@types/seedrandom": "3.0.8", + "@typescript-eslint/eslint-plugin": "6.19.0", + "@typescript-eslint/parser": "6.19.0", + "eslint": "8.56.0", + "typescript": "5.3.3" + }, + "files": [ + "built" + ], + "dependencies": { + "eventemitter3": "5.0.1", + "matter-js": "0.19.0", + "seedrandom": "3.0.5" + } +} diff --git a/packages/misskey-bubble-game/src/game.ts b/packages/misskey-bubble-game/src/game.ts new file mode 100644 index 000000000000..e01a011eeeac --- /dev/null +++ b/packages/misskey-bubble-game/src/game.ts @@ -0,0 +1,495 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { EventEmitter } from 'eventemitter3'; +import * as Matter from 'matter-js'; +import seedrandom from 'seedrandom'; +import { NORAML_MONOS, SQUARE_MONOS, SWEETS_MONOS, YEN_MONOS } from './monos.js'; + +export type Mono = { + id: string; + level: number; + sizeX: number; + sizeY: number; + shape: 'circle' | 'rectangle' | 'custom'; + vertices?: Matter.Vector[][]; + verticesSize?: number; + score: number; + dropCandidate: boolean; +}; + +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; + changeStock: (newStock: { id: string; mono: Mono }[]) => void; + changeHolding: (newHolding: { id: string; mono: Mono } | null) => void; + dropped: (x: number) => void; + fusioned: (x: number, y: number, nextMono: Mono | null, scoreDelta: number) => void; + collision: (energy: number, bodyA: Matter.Body, bodyB: Matter.Body) => void; + monoAdded: (mono: Mono) => void; + gameOver: () => void; +}> { + private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる + private COMBO_INTERVAL = 60; // frame + public readonly GAME_VERSION = 3; + public readonly GAME_WIDTH = 450; + public readonly GAME_HEIGHT = 600; + public readonly DROP_COOLTIME = 30; // frame + public readonly PLAYAREA_MARGIN = 25; + private STOCK_MAX = 4; + private TICK_DELTA = 1000 / 60; // 60fps + + public frame = 0; + public engine: Matter.Engine; + private tickCallbackQueue: { frame: number; callback: () => void; }[] = []; + private overflowCollider: Matter.Body; + private isGameOver = false; + private gameMode: 'normal' | 'yen' | 'square' | 'sweets' | 'space'; + private rng: () => number; + private logs: Log[] = []; + + /** + * フィールドに出ていて、かつ合体の対象となるアイテム + */ + private fusionReadyBodyIds: Matter.Body['id'][] = []; + + private gameOverReadyBodyIds: Matter.Body['id'][] = []; + + /** + * fusion予約アイテムのペア + * TODO: これらのモノは光らせるなどの演出をすると視覚的に楽しそう + */ + private fusionReservedPairs: { bodyA: Matter.Body; bodyB: Matter.Body }[] = []; + + private latestDroppedAt = 0; // frame + private latestFusionedAt = 0; // frame + private stock: { id: string; mono: Mono }[] = []; + private holding: { id: string; mono: Mono } | null = null; + + public get monoDefinitions() { + switch (this.gameMode) { + case 'normal': return NORAML_MONOS; + case 'yen': return YEN_MONOS; + case 'square': return SQUARE_MONOS; + case 'sweets': return SWEETS_MONOS; + case 'space': return NORAML_MONOS; + } + } + + private _combo = 0; + private get combo() { + return this._combo; + } + private set combo(value: number) { + this._combo = value; + this.emit('changeCombo', value); + } + + private _score = 0; + private get score() { + return this._score; + } + private set score(value: number) { + this._score = value; + this.emit('changeScore', value); + } + + private getMonoRenderOptions: null | ((mono: Mono) => Partial) = null; + + public replayPlaybackRate = 1; + + constructor(env: { + seed: string; + gameMode: DropAndFusionGame['gameMode']; + getMonoRenderOptions?: (mono: Mono) => Partial; + }) { + super(); + + //#region BIND + this.tick = this.tick.bind(this); + //#endregion + + this.gameMode = env.gameMode; + this.getMonoRenderOptions = env.getMonoRenderOptions ?? null; + this.rng = seedrandom(env.seed); + + // sweetsモードは重いため + const physicsQualityFactor = this.gameMode === 'sweets' ? 4 : this.PHYSICS_QUALITY_FACTOR; + this.engine = Matter.Engine.create({ + constraintIterations: 2 * physicsQualityFactor, + positionIterations: 6 * physicsQualityFactor, + velocityIterations: 4 * physicsQualityFactor, + gravity: { + x: 0, + y: this.gameMode === 'space' ? 0.0125 : 1, + }, + timing: { + timeScale: 2, + }, + enableSleeping: false, + }); + + this.engine.world.bodies = []; + + //#region walls + const WALL_OPTIONS: Matter.IChamferableBodyDefinition = { + label: '_wall_', + isStatic: true, + friction: 0.7, + slop: this.gameMode === 'space' ? 0.01 : 0.7, + render: { + strokeStyle: 'transparent', + fillStyle: 'transparent', + }, + }; + + const thickness = 100; + Matter.Composite.add(this.engine.world, [ + Matter.Bodies.rectangle(this.GAME_WIDTH / 2, this.GAME_HEIGHT + (thickness / 2) - this.PLAYAREA_MARGIN, this.GAME_WIDTH, thickness, WALL_OPTIONS), + Matter.Bodies.rectangle(this.GAME_WIDTH + (thickness / 2) - this.PLAYAREA_MARGIN, this.GAME_HEIGHT / 2, thickness, this.GAME_HEIGHT, WALL_OPTIONS), + Matter.Bodies.rectangle(-((thickness / 2) - this.PLAYAREA_MARGIN), this.GAME_HEIGHT / 2, thickness, this.GAME_HEIGHT, WALL_OPTIONS), + ]); + //#endregion + + this.overflowCollider = Matter.Bodies.rectangle(this.GAME_WIDTH / 2, 0, this.GAME_WIDTH, 200, { + label: '_overflow_', + isStatic: true, + isSensor: true, + render: { + strokeStyle: 'transparent', + fillStyle: 'transparent', + }, + }); + Matter.Composite.add(this.engine.world, this.overflowCollider); + } + + public msToFrame(ms: number) { + return Math.round(ms / this.TICK_DELTA); + } + + public frameToMs(frame: number) { + return frame * this.TICK_DELTA; + } + + private createBody(mono: Mono, x: number, y: number) { + const options: Matter.IBodyDefinition = { + label: mono.id, + density: this.gameMode === 'space' ? 0.01 : ((mono.sizeX * mono.sizeY) / 10000), + restitution: this.gameMode === 'space' ? 0.5 : 0.2, + frictionAir: this.gameMode === 'space' ? 0 : 0.01, + friction: this.gameMode === 'space' ? 0.5 : 0.7, + frictionStatic: this.gameMode === 'space' ? 0 : 5, + slop: this.gameMode === 'space' ? 0.01 : 0.7, + //mass: 0, + render: this.getMonoRenderOptions ? this.getMonoRenderOptions(mono) : undefined, + }; + if (mono.shape === 'circle') { + return Matter.Bodies.circle(x, y, mono.sizeX / 2, options); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + } else if (mono.shape === 'rectangle') { + return Matter.Bodies.rectangle(x, y, mono.sizeX, mono.sizeY, options); + } else if (mono.shape === 'custom') { + return Matter.Bodies.fromVertices(x, y, mono.vertices!.map(i => i.map(j => ({ + x: (j.x / mono.verticesSize!) * mono.sizeX, + y: (j.y / mono.verticesSize!) * mono.sizeY, + }))), options); + } else { + throw new Error('unrecognized shape'); + } + } + + private fusion(bodyA: Matter.Body, bodyB: Matter.Body) { + if (this.latestFusionedAt > this.frame - this.COMBO_INTERVAL) { + this.combo++; + } else { + this.combo = 1; + } + this.latestFusionedAt = this.frame; + + const newX = (bodyA.position.x + bodyB.position.x) / 2; + const newY = (bodyA.position.y + bodyB.position.y) / 2; + + this.fusionReadyBodyIds = this.fusionReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id); + this.gameOverReadyBodyIds = this.gameOverReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id); + Matter.Composite.remove(this.engine.world, [bodyA, bodyB]); + + const currentMono = this.monoDefinitions.find(y => y.id === bodyA.label)!; + const nextMono = this.monoDefinitions.find(x => x.level === currentMono.level + 1) ?? null; + + if (nextMono) { + const body = this.createBody(nextMono, newX, newY); + Matter.Composite.add(this.engine.world, body); + + // 連鎖してfusionした場合の分かりやすさのため少し間を置いてからfusion対象になるようにする + this.tickCallbackQueue.push({ + frame: this.frame + this.msToFrame(100), + callback: () => { + this.fusionReadyBodyIds.push(body.id); + }, + }); + + this.emit('monoAdded', nextMono); + } + + const hasComboBonus = this.gameMode !== 'yen' && this.gameMode !== 'sweets'; + const comboBonus = hasComboBonus ? 1 + ((this.combo - 1) / 5) : 1; + const additionalScore = Math.round(currentMono.score * comboBonus); + this.score += additionalScore; + + this.emit('fusioned', newX, newY, nextMono, additionalScore); + } + + private onCollision(event: Matter.IEventCollision) { + for (const pairs of event.pairs) { + const { bodyA, bodyB } = pairs; + + const shouldFusion = (bodyA.label === bodyB.label) && + !this.fusionReservedPairs.some(x => + x.bodyA.id === bodyA.id || + x.bodyA.id === bodyB.id || + x.bodyB.id === bodyA.id || + x.bodyB.id === bodyB.id); + + if (shouldFusion) { + if (this.fusionReadyBodyIds.includes(bodyA.id) && this.fusionReadyBodyIds.includes(bodyB.id)) { + this.fusion(bodyA, bodyB); + } else { + this.fusionReservedPairs.push({ bodyA, bodyB }); + this.tickCallbackQueue.push({ + frame: this.frame + this.msToFrame(100), + callback: () => { + this.fusionReservedPairs = this.fusionReservedPairs.filter(x => x.bodyA.id !== bodyA.id && x.bodyB.id !== bodyB.id); + this.fusion(bodyA, bodyB); + }, + }); + } + } else { + const energy = pairs.collision.depth; + + if (bodyA.label === '_overflow_' || bodyB.label === '_overflow_') continue; + + if (bodyA.label !== '_wall_' && bodyB.label !== '_wall_') { + if (!this.gameOverReadyBodyIds.includes(bodyA.id)) this.gameOverReadyBodyIds.push(bodyA.id); + if (!this.gameOverReadyBodyIds.includes(bodyB.id)) this.gameOverReadyBodyIds.push(bodyB.id); + } + + this.emit('collision', energy, bodyA, bodyB); + } + } + } + + private onCollisionActive(event: Matter.IEventCollision) { + for (const pairs of event.pairs) { + const { bodyA, bodyB } = pairs; + + // ハコからあふれたかどうかの判定 + if (bodyA.id === this.overflowCollider.id || bodyB.id === this.overflowCollider.id) { + if (this.gameOverReadyBodyIds.includes(bodyA.id) || this.gameOverReadyBodyIds.includes(bodyB.id)) { + this.gameOver(); + break; + } + continue; + } + } + } + + public surrender() { + this.logs.push({ + frame: this.frame, + operation: 'surrender', + }); + + this.gameOver(); + } + + private gameOver() { + this.isGameOver = true; + this.emit('gameOver'); + } + + public start() { + for (let i = 0; i < this.STOCK_MAX; i++) { + this.stock.push({ + 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); + + Matter.Events.on(this.engine, 'collisionStart', this.onCollision.bind(this)); + Matter.Events.on(this.engine, 'collisionActive', this.onCollisionActive.bind(this)); + } + + public getLogs() { + return this.logs; + } + + public 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(); + return false; + } else { + return true; + } + }); + + Matter.Engine.update(this.engine, this.TICK_DELTA); + + const hasNextTick = !this.isGameOver; + + return hasNextTick; + } + + public getActiveMonos() { + return this.engine.world.bodies.map(x => this.monoDefinitions.find((mono) => mono.id === x.label)!).filter(x => x !== undefined); + } + + public drop(_x: number) { + if (this.isGameOver) return; + if (this.frame - this.latestDroppedAt < this.DROP_COOLTIME) return; + + const head = this.stock.shift()!; + this.stock.push({ + 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 inputX = Math.round(_x); + const x = Math.min(this.GAME_WIDTH - this.PLAYAREA_MARGIN - (head.mono.sizeX / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.sizeX / 2), inputX)); + const body = this.createBody(head.mono, x, 50 + head.mono.sizeY / 2); + this.logs.push({ + frame: this.frame, + operation: 'drop', + x: inputX, + }); + + // add force + if (this.gameMode === 'space') { + Matter.Body.applyForce(body, body.position, { + x: 0, + y: (Math.PI * head.mono.sizeX * head.mono.sizeY) / 65536, + }); + } + + Matter.Composite.add(this.engine.world, body); + + this.fusionReadyBodyIds.push(body.id); + this.latestDroppedAt = this.frame; + + this.emit('dropped', x); + this.emit('monoAdded', head.mono); + } + + 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); + 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: 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); + } + } + + public static serializeLogs(logs: Log[]) { + const _logs: number[][] = []; + + for (let i = 0; i < logs.length; i++) { + const log = logs[i]; + const frameDelta = i === 0 ? log.frame : log.frame - logs[i - 1].frame; + + switch (log.operation) { + case 'drop': + _logs.push([frameDelta, 0, log.x]); + break; + case 'hold': + _logs.push([frameDelta, 1]); + break; + case 'surrender': + _logs.push([frameDelta, 2]); + break; + } + } + + return _logs; + } + + public static deserializeLogs(logs: number[][]) { + const _logs: Log[] = []; + + let frame = 0; + + for (const log of logs) { + const frameDelta = log[0]; + frame += frameDelta; + + const operation = log[1]; + + switch (operation) { + case 0: + _logs.push({ + frame, + operation: 'drop', + x: log[2], + }); + break; + case 1: + _logs.push({ + frame, + operation: 'hold', + }); + break; + case 2: + _logs.push({ + frame, + operation: 'surrender', + }); + break; + } + } + + return _logs; + } + + public dispose() { + Matter.World.clear(this.engine.world, false); + Matter.Engine.clear(this.engine); + } +} diff --git a/packages/misskey-bubble-game/src/index.ts b/packages/misskey-bubble-game/src/index.ts new file mode 100644 index 000000000000..6df708763fb6 --- /dev/null +++ b/packages/misskey-bubble-game/src/index.ts @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { DropAndFusionGame, Mono } from './game.js'; + +export { + DropAndFusionGame, Mono, +}; diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/misskey-bubble-game/src/monos.ts similarity index 51% rename from packages/frontend/src/scripts/drop-and-fusion-engine.ts rename to packages/misskey-bubble-game/src/monos.ts index aef26130657c..d205c3cba5a0 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/misskey-bubble-game/src/monos.ts @@ -3,36 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { EventEmitter } from 'eventemitter3'; -import * as Matter from 'matter-js'; -import seedrandom from 'seedrandom'; - -export type Mono = { - id: string; - level: number; - sizeX: number; - sizeY: number; - shape: 'circle' | 'rectangle' | 'custom'; - vertices?: Matter.Vector[][]; - verticesSize?: number; - score: number; - dropCandidate: boolean; -}; - -type Log = { - frame: number; - operation: 'drop'; - x: number; -} | { - frame: number; - operation: 'hold'; -} | { - frame: number; - operation: 'surrender'; -}; +import { Mono } from './game.js'; const NORMAL_BASE_SIZE = 32; -const NORAML_MONOS: Mono[] = [{ +export const NORAML_MONOS: Mono[] = [{ id: '9377076d-c980-4d83-bdaf-175bc58275b7', level: 10, sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, @@ -116,7 +90,7 @@ const NORAML_MONOS: Mono[] = [{ const YEN_BASE_SIZE = 32; const YEN_SATSU_BASE_SIZE = 70; -const YEN_MONOS: Mono[] = [{ +export const YEN_MONOS: Mono[] = [{ id: '880f9bd9-802f-4135-a7e1-fd0e0331f726', level: 10, sizeX: (YEN_SATSU_BASE_SIZE * 2) * 1.25 * 1.25 * 1.25, @@ -199,7 +173,7 @@ const YEN_MONOS: Mono[] = [{ }]; const SQUARE_BASE_SIZE = 28; -const SQUARE_MONOS: Mono[] = [{ +export const SQUARE_MONOS: Mono[] = [{ id: 'f75fd0ba-d3d4-40a4-9712-b470e45b0525', level: 10, sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, @@ -282,7 +256,7 @@ const SQUARE_MONOS: Mono[] = [{ }]; const SWEETS_BASE_SIZE = 40; -const SWEETS_MONOS: Mono[] = [{ +export const SWEETS_MONOS: Mono[] = [{ id: '77f724c0-88be-4aeb-8e1a-a00ed18e3844', level: 10, sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25, @@ -976,465 +950,3 @@ const SWEETS_MONOS: Mono[] = [{ score: 30, dropCandidate: true, }]; - -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: (x: number) => void; - fusioned: (x: number, y: number, nextMono: Mono | null, scoreDelta: number) => void; - collision: (energy: number, bodyA: Matter.Body, bodyB: Matter.Body) => void; - monoAdded: (mono: Mono) => void; - gameOver: () => void; -}> { - private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる - private COMBO_INTERVAL = 60; // frame - public readonly GAME_VERSION = 3; - public readonly GAME_WIDTH = 450; - public readonly GAME_HEIGHT = 600; - public readonly DROP_COOLTIME = 30; // frame - public readonly PLAYAREA_MARGIN = 25; - private STOCK_MAX = 4; - private TICK_DELTA = 1000 / 60; // 60fps - - public frame = 0; - public engine: Matter.Engine; - private tickCallbackQueue: { frame: number; callback: () => void; }[] = []; - private overflowCollider: Matter.Body; - private isGameOver = false; - private gameMode: 'normal' | 'yen' | 'square' | 'sweets' | 'space'; - private rng: () => number; - private logs: Log[] = []; - - /** - * フィールドに出ていて、かつ合体の対象となるアイテム - */ - private fusionReadyBodyIds: Matter.Body['id'][] = []; - - private gameOverReadyBodyIds: Matter.Body['id'][] = []; - - /** - * fusion予約アイテムのペア - * TODO: これらのモノは光らせるなどの演出をすると視覚的に楽しそう - */ - private fusionReservedPairs: { bodyA: Matter.Body; bodyB: Matter.Body }[] = []; - - private latestDroppedAt = 0; // frame - private latestFusionedAt = 0; // frame - private stock: { id: string; mono: Mono }[] = []; - private holding: { id: string; mono: Mono } | null = null; - - public get monoDefinitions() { - switch (this.gameMode) { - case 'normal': return NORAML_MONOS; - case 'yen': return YEN_MONOS; - case 'square': return SQUARE_MONOS; - case 'sweets': return SWEETS_MONOS; - case 'space': return NORAML_MONOS; - } - } - - private _combo = 0; - private get combo() { - return this._combo; - } - private set combo(value: number) { - this._combo = value; - this.emit('changeCombo', value); - } - - private _score = 0; - private get score() { - return this._score; - } - private set score(value: number) { - this._score = value; - this.emit('changeScore', value); - } - - private getMonoRenderOptions: null | ((mono: Mono) => Partial) = null; - - public replayPlaybackRate = 1; - - constructor(env: { - seed: string; - gameMode: DropAndFusionGame['gameMode']; - getMonoRenderOptions?: (mono: Mono) => Partial; - }) { - super(); - - //#region BIND - this.tick = this.tick.bind(this); - //#endregion - - this.gameMode = env.gameMode; - this.getMonoRenderOptions = env.getMonoRenderOptions ?? null; - this.rng = seedrandom(env.seed); - - // sweetsモードは重いため - const physicsQualityFactor = this.gameMode === 'sweets' ? 4 : this.PHYSICS_QUALITY_FACTOR; - this.engine = Matter.Engine.create({ - constraintIterations: 2 * physicsQualityFactor, - positionIterations: 6 * physicsQualityFactor, - velocityIterations: 4 * physicsQualityFactor, - gravity: { - x: 0, - y: this.gameMode === 'space' ? 0.0125 : 1, - }, - timing: { - timeScale: 2, - }, - enableSleeping: false, - }); - - this.engine.world.bodies = []; - - //#region walls - const WALL_OPTIONS: Matter.IChamferableBodyDefinition = { - label: '_wall_', - isStatic: true, - friction: 0.7, - slop: this.gameMode === 'space' ? 0.01 : 0.7, - render: { - strokeStyle: 'transparent', - fillStyle: 'transparent', - }, - }; - - const thickness = 100; - Matter.Composite.add(this.engine.world, [ - Matter.Bodies.rectangle(this.GAME_WIDTH / 2, this.GAME_HEIGHT + (thickness / 2) - this.PLAYAREA_MARGIN, this.GAME_WIDTH, thickness, WALL_OPTIONS), - Matter.Bodies.rectangle(this.GAME_WIDTH + (thickness / 2) - this.PLAYAREA_MARGIN, this.GAME_HEIGHT / 2, thickness, this.GAME_HEIGHT, WALL_OPTIONS), - Matter.Bodies.rectangle(-((thickness / 2) - this.PLAYAREA_MARGIN), this.GAME_HEIGHT / 2, thickness, this.GAME_HEIGHT, WALL_OPTIONS), - ]); - //#endregion - - this.overflowCollider = Matter.Bodies.rectangle(this.GAME_WIDTH / 2, 0, this.GAME_WIDTH, 200, { - label: '_overflow_', - isStatic: true, - isSensor: true, - render: { - strokeStyle: 'transparent', - fillStyle: 'transparent', - }, - }); - Matter.Composite.add(this.engine.world, this.overflowCollider); - } - - public msToFrame(ms: number) { - return Math.round(ms / this.TICK_DELTA); - } - - public frameToMs(frame: number) { - return frame * this.TICK_DELTA; - } - - private createBody(mono: Mono, x: number, y: number) { - const options: Matter.IBodyDefinition = { - label: mono.id, - density: this.gameMode === 'space' ? 0.01 : ((mono.sizeX * mono.sizeY) / 10000), - restitution: this.gameMode === 'space' ? 0.5 : 0.2, - frictionAir: this.gameMode === 'space' ? 0 : 0.01, - friction: this.gameMode === 'space' ? 0.5 : 0.7, - frictionStatic: this.gameMode === 'space' ? 0 : 5, - slop: this.gameMode === 'space' ? 0.01 : 0.7, - //mass: 0, - render: this.getMonoRenderOptions ? this.getMonoRenderOptions(mono) : undefined, - }; - if (mono.shape === 'circle') { - return Matter.Bodies.circle(x, y, mono.sizeX / 2, options); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - } else if (mono.shape === 'rectangle') { - return Matter.Bodies.rectangle(x, y, mono.sizeX, mono.sizeY, options); - } else if (mono.shape === 'custom') { - return Matter.Bodies.fromVertices(x, y, mono.vertices!.map(i => i.map(j => ({ - x: (j.x / mono.verticesSize!) * mono.sizeX, - y: (j.y / mono.verticesSize!) * mono.sizeY, - }))), options); - } else { - throw new Error('unrecognized shape'); - } - } - - private fusion(bodyA: Matter.Body, bodyB: Matter.Body) { - if (this.latestFusionedAt > this.frame - this.COMBO_INTERVAL) { - this.combo++; - } else { - this.combo = 1; - } - this.latestFusionedAt = this.frame; - - const newX = (bodyA.position.x + bodyB.position.x) / 2; - const newY = (bodyA.position.y + bodyB.position.y) / 2; - - this.fusionReadyBodyIds = this.fusionReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id); - this.gameOverReadyBodyIds = this.gameOverReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id); - Matter.Composite.remove(this.engine.world, [bodyA, bodyB]); - - const currentMono = this.monoDefinitions.find(y => y.id === bodyA.label)!; - const nextMono = this.monoDefinitions.find(x => x.level === currentMono.level + 1) ?? null; - - if (nextMono) { - const body = this.createBody(nextMono, newX, newY); - Matter.Composite.add(this.engine.world, body); - - // 連鎖してfusionした場合の分かりやすさのため少し間を置いてからfusion対象になるようにする - this.tickCallbackQueue.push({ - frame: this.frame + this.msToFrame(100), - callback: () => { - this.fusionReadyBodyIds.push(body.id); - }, - }); - - this.emit('monoAdded', nextMono); - } - - const hasComboBonus = this.gameMode !== 'yen' && this.gameMode !== 'sweets'; - const comboBonus = hasComboBonus ? 1 + ((this.combo - 1) / 5) : 1; - const additionalScore = Math.round(currentMono.score * comboBonus); - this.score += additionalScore; - - this.emit('fusioned', newX, newY, nextMono, additionalScore); - } - - private onCollision(event: Matter.IEventCollision) { - for (const pairs of event.pairs) { - const { bodyA, bodyB } = pairs; - - const shouldFusion = (bodyA.label === bodyB.label) && - !this.fusionReservedPairs.some(x => - x.bodyA.id === bodyA.id || - x.bodyA.id === bodyB.id || - x.bodyB.id === bodyA.id || - x.bodyB.id === bodyB.id); - - if (shouldFusion) { - if (this.fusionReadyBodyIds.includes(bodyA.id) && this.fusionReadyBodyIds.includes(bodyB.id)) { - this.fusion(bodyA, bodyB); - } else { - this.fusionReservedPairs.push({ bodyA, bodyB }); - this.tickCallbackQueue.push({ - frame: this.frame + this.msToFrame(100), - callback: () => { - this.fusionReservedPairs = this.fusionReservedPairs.filter(x => x.bodyA.id !== bodyA.id && x.bodyB.id !== bodyB.id); - this.fusion(bodyA, bodyB); - }, - }); - } - } else { - const energy = pairs.collision.depth; - - if (bodyA.label === '_overflow_' || bodyB.label === '_overflow_') continue; - - if (bodyA.label !== '_wall_' && bodyB.label !== '_wall_') { - if (!this.gameOverReadyBodyIds.includes(bodyA.id)) this.gameOverReadyBodyIds.push(bodyA.id); - if (!this.gameOverReadyBodyIds.includes(bodyB.id)) this.gameOverReadyBodyIds.push(bodyB.id); - } - - this.emit('collision', energy, bodyA, bodyB); - } - } - } - - private onCollisionActive(event: Matter.IEventCollision) { - for (const pairs of event.pairs) { - const { bodyA, bodyB } = pairs; - - // ハコからあふれたかどうかの判定 - if (bodyA.id === this.overflowCollider.id || bodyB.id === this.overflowCollider.id) { - if (this.gameOverReadyBodyIds.includes(bodyA.id) || this.gameOverReadyBodyIds.includes(bodyB.id)) { - this.gameOver(); - break; - } - continue; - } - } - } - - public surrender() { - this.logs.push({ - frame: this.frame, - operation: 'surrender', - }); - - this.gameOver(); - } - - private gameOver() { - this.isGameOver = true; - this.emit('gameOver'); - } - - public start() { - for (let i = 0; i < this.STOCK_MAX; i++) { - this.stock.push({ - 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); - - Matter.Events.on(this.engine, 'collisionStart', this.onCollision.bind(this)); - Matter.Events.on(this.engine, 'collisionActive', this.onCollisionActive.bind(this)); - } - - public getLogs() { - return this.logs; - } - - public 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(); - return false; - } else { - return true; - } - }); - - Matter.Engine.update(this.engine, this.TICK_DELTA); - - const hasNextTick = !this.isGameOver; - - return hasNextTick; - } - - public getActiveMonos() { - return this.engine.world.bodies.map(x => this.monoDefinitions.find((mono) => mono.id === x.label)!).filter(x => x !== undefined); - } - - public drop(_x: number) { - if (this.isGameOver) return; - if (this.frame - this.latestDroppedAt < this.DROP_COOLTIME) return; - - const head = this.stock.shift()!; - this.stock.push({ - 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 inputX = Math.round(_x); - const x = Math.min(this.GAME_WIDTH - this.PLAYAREA_MARGIN - (head.mono.sizeX / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.sizeX / 2), inputX)); - const body = this.createBody(head.mono, x, 50 + head.mono.sizeY / 2); - this.logs.push({ - frame: this.frame, - operation: 'drop', - x: inputX, - }); - - // add force - if (this.gameMode === 'space') { - Matter.Body.applyForce(body, body.position, { - x: 0, - y: (Math.PI * head.mono.sizeX * head.mono.sizeY) / 65536, - }); - } - - Matter.Composite.add(this.engine.world, body); - - this.fusionReadyBodyIds.push(body.id); - this.latestDroppedAt = this.frame; - - this.emit('dropped', x); - this.emit('monoAdded', head.mono); - } - - 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); - 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: 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); - } - } - - public static serializeLogs(logs: Log[]) { - const _logs: number[][] = []; - - for (let i = 0; i < logs.length; i++) { - const log = logs[i]; - const frameDelta = i === 0 ? log.frame : log.frame - logs[i - 1].frame; - - switch (log.operation) { - case 'drop': - _logs.push([frameDelta, 0, log.x]); - break; - case 'hold': - _logs.push([frameDelta, 1]); - break; - case 'surrender': - _logs.push([frameDelta, 2]); - break; - } - } - - return _logs; - } - - public static deserializeLogs(logs: number[][]) { - const _logs: Log[] = []; - - let frame = 0; - - for (const log of logs) { - const frameDelta = log[0]; - frame += frameDelta; - - const operation = log[1]; - - switch (operation) { - case 0: - _logs.push({ - frame, - operation: 'drop', - x: log[2], - }); - break; - case 1: - _logs.push({ - frame, - operation: 'hold', - }); - break; - case 2: - _logs.push({ - frame, - operation: 'surrender', - }); - break; - } - } - - return _logs; - } - - public dispose() { - Matter.World.clear(this.engine.world, false); - Matter.Engine.clear(this.engine); - } -} diff --git a/packages/misskey-bubble-game/tsconfig.json b/packages/misskey-bubble-game/tsconfig.json new file mode 100644 index 000000000000..f56b65e86802 --- /dev/null +++ b/packages/misskey-bubble-game/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/packages/misskey-reversi/.eslintignore b/packages/misskey-reversi/.eslintignore new file mode 100644 index 000000000000..f22128f047fd --- /dev/null +++ b/packages/misskey-reversi/.eslintignore @@ -0,0 +1,7 @@ +node_modules +/built +/coverage +/.eslintrc.js +/jest.config.ts +/test +/test-d diff --git a/packages/misskey-reversi/.eslintrc.cjs b/packages/misskey-reversi/.eslintrc.cjs new file mode 100644 index 000000000000..e2e31e9e331e --- /dev/null +++ b/packages/misskey-reversi/.eslintrc.cjs @@ -0,0 +1,9 @@ +module.exports = { + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + extends: [ + '../shared/.eslintrc.js', + ], +}; diff --git a/packages/misskey-reversi/src/index.ts b/packages/misskey-reversi/src/index.ts index 20ed36f2083f..28964413b703 100644 --- a/packages/misskey-reversi/src/index.ts +++ b/packages/misskey-reversi/src/index.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { Game } from './game.js'; export { diff --git a/packages/misskey-reversi/src/maps.ts b/packages/misskey-reversi/src/maps.ts index 85cf1a04852b..b47a996c7c50 100644 --- a/packages/misskey-reversi/src/maps.ts +++ b/packages/misskey-reversi/src/maps.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /** * 組み込みマップ定義 * diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31394eb0815d..0a8040372523 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -778,6 +778,9 @@ importers: mfm-js: specifier: 0.24.0 version: 0.24.0 + misskey-bubble-game: + specifier: workspace:* + version: link:../misskey-bubble-game misskey-js: specifier: workspace:* version: link:../misskey-js @@ -799,9 +802,6 @@ 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 @@ -1026,6 +1026,43 @@ importers: specifier: 1.8.27 version: 1.8.27(typescript@5.3.3) + packages/misskey-bubble-game: + dependencies: + eventemitter3: + specifier: 5.0.1 + version: 5.0.1 + matter-js: + specifier: 0.19.0 + version: 0.19.0 + seedrandom: + specifier: 3.0.5 + version: 3.0.5 + 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/matter-js': + specifier: 0.19.6 + version: 0.19.6 + '@types/node': + specifier: 20.11.5 + version: 20.11.5 + '@types/seedrandom': + specifier: 3.0.8 + version: 3.0.8 + '@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/misskey-js: dependencies: '@swc/cli': @@ -1845,7 +1882,7 @@ packages: '@babel/traverse': 7.22.11 '@babel/types': 7.22.17 convert-source-map: 1.9.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1868,7 +1905,7 @@ packages: '@babel/traverse': 7.23.5 '@babel/types': 7.23.5 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1970,7 +2007,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@5.5.0) + debug: 4.3.4(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -3369,7 +3406,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@5.5.0) + debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3387,7 +3424,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@5.5.0) + debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -4266,7 +4303,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) espree: 9.6.1 globals: 13.19.0 ignore: 5.2.4 @@ -4283,7 +4320,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) espree: 9.6.1 globals: 13.19.0 ignore: 5.2.4 @@ -4548,7 +4585,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 2.0.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5150,7 +5187,7 @@ packages: '@open-draft/until': 1.0.3 '@types/debug': 4.1.7 '@xmldom/xmldom': 0.8.6 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) headers-polyfill: 3.2.5 outvariant: 1.4.0 strict-event-emitter: 0.2.8 @@ -8289,6 +8326,10 @@ packages: resolution: {integrity: sha512-pTVB5krRGb01hr8L6BJqWGoSriqUbbvJ9Fd0Qp0eAOE//w/lFvkaVHkVB8J3wXr9U3lZDzmAjJPPQn7x4wzbWg==} dev: true + /@types/matter-js@0.19.6: + resolution: {integrity: sha512-ffk6tqJM5scla+ThXmnox+mdfCo3qYk6yMjQsNcrbo6eQ5DqorVdtnaL+1agCoYzxUjmHeiNB7poBMAmhuLY7w==} + dev: true + /@types/mdx@2.0.3: resolution: {integrity: sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ==} dev: true @@ -8479,6 +8520,10 @@ packages: requiresBuild: true dev: false + /@types/seedrandom@3.0.8: + resolution: {integrity: sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==} + dev: true + /@types/semver@7.5.6: resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} dev: true @@ -8621,7 +8666,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@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.53.0 graphemer: 1.4.0 ignore: 5.2.4 @@ -8650,7 +8695,7 @@ 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@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 graphemer: 1.4.0 ignore: 5.2.4 @@ -8679,7 +8724,7 @@ packages: '@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) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 graphemer: 1.4.0 ignore: 5.2.4 @@ -8708,7 +8753,7 @@ packages: '@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) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 graphemer: 1.4.0 ignore: 5.2.4 @@ -8734,7 +8779,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@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.53.0 typescript: 5.3.3 transitivePeerDependencies: @@ -8755,7 +8800,7 @@ 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@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 typescript: 5.3.3 transitivePeerDependencies: @@ -8776,7 +8821,7 @@ packages: '@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) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 typescript: 5.3.3 transitivePeerDependencies: @@ -8819,7 +8864,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@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.53.0 ts-api-utils: 1.0.1(typescript@5.3.3) typescript: 5.3.3 @@ -8839,7 +8884,7 @@ 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@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 ts-api-utils: 1.0.1(typescript@5.3.3) typescript: 5.3.3 @@ -8859,7 +8904,7 @@ packages: 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) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 ts-api-utils: 1.0.1(typescript@5.3.3) typescript: 5.3.3 @@ -8893,7 +8938,7 @@ packages: dependencies: '@typescript-eslint/types': 6.11.0 '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -8914,7 +8959,7 @@ packages: dependencies: '@typescript-eslint/types': 6.14.0 '@typescript-eslint/visitor-keys': 6.14.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -8935,7 +8980,7 @@ packages: dependencies: '@typescript-eslint/types': 6.19.0 '@typescript-eslint/visitor-keys': 6.19.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -9420,7 +9465,7 @@ packages: engines: {node: '>= 6.0.0'} requiresBuild: true dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -9428,7 +9473,7 @@ packages: resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} engines: {node: '>= 14'} dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: false @@ -9814,7 +9859,7 @@ packages: resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==} dependencies: archy: 1.0.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) fastq: 1.15.0 transitivePeerDependencies: - supports-color @@ -11263,6 +11308,7 @@ 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==} @@ -11275,7 +11321,6 @@ 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==} @@ -11492,7 +11537,7 @@ packages: hasBin: true dependencies: address: 1.2.2 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: true @@ -11816,7 +11861,7 @@ packages: peerDependencies: esbuild: '>=0.12 <1' dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) esbuild: 0.18.20 transitivePeerDependencies: - supports-color @@ -12218,7 +12263,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -12265,7 +12310,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -12895,7 +12940,7 @@ packages: debug: optional: true dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -13451,6 +13496,7 @@ 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==} @@ -13588,7 +13634,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: false @@ -13650,7 +13696,7 @@ packages: engines: {node: '>= 6.0.0'} dependencies: agent-base: 5.1.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: true @@ -13660,7 +13706,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -13669,7 +13715,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: false @@ -13679,7 +13725,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: false @@ -13839,7 +13885,7 @@ packages: dependencies: '@ioredis/commands': 1.2.0 cluster-key-slot: 1.1.2 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -14280,7 +14326,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -14997,7 +15043,7 @@ packages: resolution: {integrity: sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) rfdc: 1.3.0 uri-js: 4.4.1 transitivePeerDependencies: @@ -17608,7 +17654,7 @@ packages: engines: {node: '>=8.16.0'} dependencies: '@types/mime-types': 2.1.4 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 @@ -18605,7 +18651,7 @@ packages: dependencies: '@hapi/hoek': 10.0.1 '@hapi/wreck': 18.0.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) joi: 17.7.0 transitivePeerDependencies: - supports-color @@ -18805,7 +18851,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -18958,7 +19004,7 @@ packages: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -19222,6 +19268,7 @@ 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==} @@ -19844,7 +19891,7 @@ packages: chalk: 4.1.2 cli-highlight: 2.1.11 date-fns: 2.30.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) dotenv: 16.0.3 glob: 8.1.0 ioredis: 5.3.2 @@ -20209,7 +20256,7 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) mlly: 1.4.0 pathe: 1.1.1 picocolors: 1.0.0 @@ -20321,7 +20368,7 @@ packages: acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.10 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) happy-dom: 10.0.3 local-pkg: 0.4.3 magic-string: 0.30.3 @@ -20403,7 +20450,7 @@ packages: peerDependencies: eslint: '>=6.0.0' dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) 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 3a03a5825368..193669e7a402 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,3 +5,4 @@ packages: - 'packages/misskey-js' - 'packages/misskey-js/generator' - 'packages/misskey-reversi' + - 'packages/misskey-bubble-game' From d114507eff4641874fa951d0ff93dba1fbb978c0 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 128/311] 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 6eb5bd630daf..49807144ec64 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 85e0c6b244ce..f7f952175fad 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 bdb145b39aa6..c99118f9b23c 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 c649e69cd0bb..54cbbe18c23f 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 4a6d2dfba240..ca19a2122d93 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 0a71b689fed8..7b9a05286898 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 3c1f83d33558..3fc9f0e3577b 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 dbf98cd62282..560d5502d4e1 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 78c4fb3cd27a..6bc96be1b9a1 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 9c4354ef5f17..d7bb64661bf5 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 e941827d7425..1f5b283cfe39 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 ce8b054b3957..92be20c6f639 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 6725776f43c8..0e77d2a6aa6f 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 4cac1fe9c30e..7c58b586975d 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 43e576d1ab28..34f6c72b6e8b 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 a42767e1b63f..28f2f29b7d94 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 963e78a1ff9f..9c3b46e133e4 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 af094a8e8c9f..9e3a78fe225c 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 37aa677b440f..f0828338385a 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 05b55f77a755..4b0c540829df 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 ee4e29dd8fcf..b8b253de065c 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 000000000000..162aa2bcf811 --- /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 0eeefa48597a..0e7f6a9bdf35 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 e11db9dc311b..2b0bf246ad38 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 2f4d7edabd96..000000000000 --- 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 a3e13c3a5067..f3b476b15c3c 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 4ba1b6da768d..69cb6ef647cf 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 4a9c659a977d..84f3d1f3f11c 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 85417f0ecb2e..530bcca04acb 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 eb9aef0e488e..fe55fe3a023b 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 f941d512b36f..44552fb88c1e 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 72b47949e7d8..dbbb3941d88b 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 6811a8eba5bc..847c8bc1d4c7 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 ff29f4ec1f96..ad582555769e 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 c31c6d090326..e3c0ea574a06 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 39a7924f946f..50fd696af313 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