From bdfe70931995e7ec79c2ec399b2a00c095692b21 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:57:20 +0900 Subject: [PATCH 001/654] =?UTF-8?q?fix(frontend):=20=E8=AA=AD=E3=81=BF?= =?UTF-8?q?=E8=BE=BC=E3=81=BF=E7=9B=B4=E5=BE=8C=E3=81=AB=E3=83=97=E3=83=A9?= =?UTF-8?q?=E3=82=B0=E3=82=A4=E3=83=B3=E3=81=AB=E3=82=88=E3=82=8B=E3=83=8E?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=81=AE=E6=9B=B8=E3=81=8D=E6=8F=9B=E3=81=88?= =?UTF-8?q?=E3=81=8C=E8=A1=8C=E3=82=8F=E3=82=8C=E3=81=AA=E3=81=84=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ブート時にプラグインがロードされるまで待機 Fix #16428 --- packages/frontend/src/boot/common.ts | 7 +++++++ packages/frontend/src/boot/main-boot.ts | 5 +---- packages/frontend/src/plugin.ts | 10 ++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index 395d1e5e7e..574012ff78 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -29,6 +29,7 @@ import { miLocalStorage } from '@/local-storage.js'; import { fetchCustomEmojis } from '@/custom-emojis.js'; import { prefer } from '@/preferences.js'; import { $i } from '@/i.js'; +import { launchPlugins } from '@/plugin.js'; export async function common(createVue: () => Promise>) { console.info(`Misskey v${version}`); @@ -338,6 +339,12 @@ export async function common(createVue: () => Promise>) { }); } + try { + await launchPlugins(); + } catch (error) { + console.error('Failed to launch plugins:', error); + } + app.mount(rootEl); // boot.jsのやつを解除 diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 46e690a55f..6ae8379801 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -26,7 +26,6 @@ import { mainRouter } from '@/router.js'; import { makeHotkey } from '@/utility/hotkey.js'; import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js'; import { prefer } from '@/preferences.js'; -import { launchPlugins } from '@/plugin.js'; import { updateCurrentAccountPartial } from '@/accounts.js'; import { migrateOldSettings } from '@/pref-migrate.js'; import { unisonReload } from '@/utility/unison-reload.js'; @@ -79,8 +78,6 @@ export async function mainBoot() { } } - launchPlugins(); - try { if (prefer.s.enableSeasonalScreenEffect) { const month = new Date().getMonth() + 1; @@ -421,7 +418,7 @@ export async function mainBoot() { } }, allowRepeat: true, - } + }, } as const satisfies Keymap; window.document.addEventListener('keydown', makeHotkey(keymap), { passive: false }); diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts index 632c913ad7..346e275575 100644 --- a/packages/frontend/src/plugin.ts +++ b/packages/frontend/src/plugin.ts @@ -233,11 +233,13 @@ function addPluginHandler(installId: Plugin['install } export function launchPlugins() { - for (const plugin of prefer.s.plugins) { + return Promise.all(prefer.s.plugins.map(plugin => { if (plugin.active) { - launchPlugin(plugin.installId); + return launchPlugin(plugin.installId); + } else { + return Promise.resolve(); } - } + })); } async function launchPlugin(id: Plugin['installId']): Promise { @@ -292,7 +294,7 @@ async function launchPlugin(id: Plugin['installId']): Promise { pluginContexts.set(plugin.installId, aiscript); const parser = await getParser(); - aiscript.exec(parser.parse(plugin.src)).then( + await aiscript.exec(parser.parse(plugin.src)).then( () => { console.info('Plugin installed:', plugin.name, 'v' + plugin.version); systemLog('Plugin started'); From b07bf838e36dd5cc4b4d2533229d88bb3ba85481 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:35:26 +0900 Subject: [PATCH 002/654] =?UTF-8?q?=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=20(#1588?= =?UTF-8?q?2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * Update cli.ts * Update cli.ts * wip * Update CHANGELOG.md * Delete cli.mjs --- CHANGELOG.md | 4 ++ package.json | 1 + packages/backend/package.json | 1 + packages/backend/src/boot/cli.ts | 49 ++++++++++++++++++++++ packages/backend/src/cli/CommandModule.ts | 23 ++++++++++ packages/backend/src/cli/CommandService.ts | 49 ++++++++++++++++++++++ 6 files changed, 127 insertions(+) create mode 100644 packages/backend/src/boot/cli.ts create mode 100644 packages/backend/src/cli/CommandModule.ts create mode 100644 packages/backend/src/cli/CommandService.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5664b94b81..6875cdda23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,10 @@ - Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正 ### Server +- Feat: サーバー管理コマンド + - `pnpm cli foo` の形式で実行可能です + - 現在以下のコマンドが利用可能です + - `reset-captcha` - CAPTCHA設定をリセットします - Enhance: ノートの削除処理の効率化 - Enhance: 全体的なパフォーマンスの向上 - Enhance: 依存ソフトウェアの更新 diff --git a/package.json b/package.json index dc92be543f..5ab8c64ef1 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && 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": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js", + "cli": "cd packages/backend && pnpm cli", "init": "pnpm migrate", "migrate": "cd packages/backend && pnpm migrate", "revert": "cd packages/backend && pnpm revert", diff --git a/packages/backend/package.json b/packages/backend/package.json index 0d1ae4ac3d..39ea07125d 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -11,6 +11,7 @@ "start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js", "migrate": "pnpm typeorm migration:run -d ormconfig.js", "revert": "pnpm typeorm migration:revert -d ormconfig.js", + "cli": "node ./built/boot/cli.js", "check:connect": "node ./scripts/check_connect.js", "build": "swc src -d built -D --strip-leading-paths", "build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc --strip-leading-paths", diff --git a/packages/backend/src/boot/cli.ts b/packages/backend/src/boot/cli.ts new file mode 100644 index 0000000000..a5618f8152 --- /dev/null +++ b/packages/backend/src/boot/cli.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import 'reflect-metadata'; +import { EventEmitter } from 'node:events'; +import { NestFactory } from '@nestjs/core'; +import { CommandModule } from '@/cli/CommandModule.js'; +import { NestLogger } from '@/NestLogger.js'; +import { CommandService } from '@/cli/CommandService.js'; + +process.title = 'Misskey Cli'; + +Error.stackTraceLimit = Infinity; +EventEmitter.defaultMaxListeners = 128; + +const app = await NestFactory.createApplicationContext(CommandModule, { + logger: new NestLogger(), +}); + +const commandService = app.get(CommandService); + +const command = process.argv[2] ?? 'help'; + +switch (command) { + case 'help': { + console.log('Available commands:'); + console.log(' help - Displays this help message'); + console.log(' reset-captcha - Resets the captcha'); + break; + } + case 'ping': { + await commandService.ping(); + break; + } + case 'reset-captcha': { + await commandService.resetCaptcha(); + console.log('Captcha has been reset.'); + break; + } + default: { + console.error(`Unrecognized command: ${command}`); + console.error('Use "help" to see available commands.'); + process.exit(1); + } +} + +process.exit(0); diff --git a/packages/backend/src/cli/CommandModule.ts b/packages/backend/src/cli/CommandModule.ts new file mode 100644 index 0000000000..f4b1d25c18 --- /dev/null +++ b/packages/backend/src/cli/CommandModule.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Module } from '@nestjs/common'; +import { CoreModule } from '@/core/CoreModule.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { CommandService } from './CommandService.js'; + +@Module({ + imports: [ + GlobalModule, + CoreModule, + ], + providers: [ + CommandService, + ], + exports: [ + CommandService, + ], +}) +export class CommandModule {} diff --git a/packages/backend/src/cli/CommandService.ts b/packages/backend/src/cli/CommandService.ts new file mode 100644 index 0000000000..cdb2a9f382 --- /dev/null +++ b/packages/backend/src/cli/CommandService.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import type { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; + +@Injectable() +export class CommandService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + private metaService: MetaService, + ) { + } + + @bindThis + public async ping() { + console.log('pong'); + } + + @bindThis + public async resetCaptcha() { + await this.metaService.update({ + enableHcaptcha: false, + hcaptchaSiteKey: null, + hcaptchaSecretKey: null, + enableMcaptcha: false, + mcaptchaSitekey: null, + mcaptchaSecretKey: null, + mcaptchaInstanceUrl: null, + enableRecaptcha: false, + recaptchaSiteKey: null, + recaptchaSecretKey: null, + enableTurnstile: false, + turnstileSiteKey: null, + turnstileSecretKey: null, + enableTestcaptcha: false, + }); + } +} From 3a856b785d0ced1956d855b42914372bc25dde14 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:35:50 +0900 Subject: [PATCH 003/654] New Crowdin updates (#16416) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Korean) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (English) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Thai) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Korean) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Italian) --- locales/ca-ES.yml | 6 ++++++ locales/en-US.yml | 2 ++ locales/it-IT.yml | 8 +++++++- locales/ko-KR.yml | 6 ++++++ locales/th-TH.yml | 3 +++ locales/zh-CN.yml | 6 ++++++ locales/zh-TW.yml | 6 ++++++ 7 files changed, 36 insertions(+), 1 deletion(-) diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 2eb8ab33c9..ae9de0f173 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1054,6 +1054,7 @@ permissionDeniedError: "Operació no permesa " permissionDeniedErrorDescription: "Aquest compte no té suficients permisos per dur a terme aquesta acció " preset: "Predefinit" selectFromPresets: "Escull des dels predefinits" +custom: "Personalitzat" achievements: "Assoliments" gotInvalidResponseError: "Resposta del servidor invàlida " gotInvalidResponseErrorDescription: "No es pot contactar amb el servidor o potser es troba fora de línia per manteniment. Provar-ho de nou més tard." @@ -1375,6 +1376,7 @@ safeModeEnabled: "Mode segur activat" pluginsAreDisabledBecauseSafeMode: "Els afegits no estan activats perquè el mode segur està activat." customCssIsDisabledBecauseSafeMode: "El CSS personalitzat no s'aplica perquè el mode segur es troba activat." themeIsDefaultBecauseSafeMode: "El tema predeterminat es farà servir mentre el mode segur estigui activat. Una vegada es desactivi el mode segur es restablirà el tema escollit." +thankYouForTestingBeta: "Gràcies per ajudar-nos a provar la versió beta!" _order: newest: "Més recent" oldest: "Antigues primer" @@ -1664,6 +1666,9 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor_description2: "La publicació incondicional de tots els continguts del servidor a internet, incloent-hi els continguts remots rebuts pel servidor, comporta riscos. Això és extremadament important per els espectadors que desconeixen el caràcter descentralitzat dels continguts, ja que poden percebre erroneament els continguts remots com contingut generat per el propi servidor." restartServerSetupWizardConfirm_title: "Vols tornar a executar l'assistent de configuració inicial del servidor?" restartServerSetupWizardConfirm_text: "Algunes configuracions actuals seran restablertes." + entrancePageStyle: "Estil de la pàgina d'inici" + showTimelineForVisitor: "Mostrar la línia de temps" + showActivityiesForVisitor: "Mostrar les activitats" _userGeneratedContentsVisibilityForVisitor: all: "Tot obert al públic " localOnly: "Només es publiquen els continguts locals, el contingut remot es manté privat" @@ -2273,6 +2278,7 @@ _time: minute: "Minut(s)" hour: "Hor(a)(es)" day: "Di(a)(es)" + month: "Mes(os)" _2fa: alreadyRegistered: "J has registrat un dispositiu d'autenticació de doble factor." registerTOTP: "Registrar una aplicació autenticadora" diff --git a/locales/en-US.yml b/locales/en-US.yml index 395540ea01..4a797e0fc2 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1054,6 +1054,7 @@ permissionDeniedError: "Operation denied" permissionDeniedErrorDescription: "This account does not have the permission to perform this action." preset: "Preset" selectFromPresets: "Choose from presets" +custom: "Custom" achievements: "Achievements" gotInvalidResponseError: "Invalid server response" gotInvalidResponseErrorDescription: "The server may be unreachable or undergoing maintenance. Please try again later." @@ -2273,6 +2274,7 @@ _time: minute: "Minute(s)" hour: "Hour(s)" day: "Day(s)" + month: "Month(s)" _2fa: alreadyRegistered: "You have already registered a 2-factor authentication device." registerTOTP: "Register authenticator app" diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 81487c4d79..5d1b9dc924 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1054,6 +1054,7 @@ permissionDeniedError: "Errore, attività non autorizzata" permissionDeniedErrorDescription: "Non si dispone dell'autorizzazione per eseguire questa operazione." preset: "Preimpostato" selectFromPresets: "Seleziona preimpostato" +custom: "Personalizzato" achievements: "Conquiste" gotInvalidResponseError: "Risposta del server non valida" gotInvalidResponseErrorDescription: "Il server potrebbe essere irraggiungibile o in manutenzione. Riprova più tardi." @@ -1375,6 +1376,7 @@ safeModeEnabled: "La modalità sicura è attiva" pluginsAreDisabledBecauseSafeMode: "Tutti i plugin sono disattivati, poiché la modalità sicura è attiva." customCssIsDisabledBecauseSafeMode: "Il CSS personalizzato non è stato applicato, poiché la modalità sicura è attiva." themeIsDefaultBecauseSafeMode: "Quando la modalità sicura è attiva, viene utilizzato il tema predefinito. Quando la modalità sicura viene disattivata, il tema torna a essere quello precedente." +thankYouForTestingBeta: "Grazie per la tua collaborazione nella verifica delle versioni beta!" _order: newest: "Prima i più recenti" oldest: "Meno recenti prima" @@ -1664,6 +1666,9 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor_description2: "Esistono dei rischi nell'esporre incondizionatamente su internet tutto il contenuto del tuo server, incluso il contenuto remoto ricevuto da altri server. In particolare, occorre prestare attenzione, perché le persone non consapevoli della federazione potrebbero erroneamente credere che il contenuto remoto sia stato invece creato all'interno del proprio server." restartServerSetupWizardConfirm_title: "Vuoi ripetere la procedura guidata di configurazione iniziale del server?" restartServerSetupWizardConfirm_text: "Verranno ripristinate alcune tue impostazioni personalizzate." + entrancePageStyle: "Stile della pagina di ingresso" + showTimelineForVisitor: "Mostra la Timeline a visitatori non autenticati" + showActivityiesForVisitor: "Mostra le attività a visitatori non autenticati" _userGeneratedContentsVisibilityForVisitor: all: "Tutto pubblico" localOnly: "Pubblica solo contenuti locali, mantieni privati ​​i contenuti remoti" @@ -2273,6 +2278,7 @@ _time: minute: "min" hour: "ore" day: "giorni" + month: "Mese" _2fa: alreadyRegistered: "La configurazione è stata già completata." registerTOTP: "Registra una App di autenticazione a due fattori (2FA/MFA)" @@ -3231,7 +3237,7 @@ _imageEffector: zoomLinesThreshold: "Limite delle linee zoom" zoomLinesMaskSize: "Ampiezza del diametro" zoomLinesBlack: "Bande nere" -drafts: "Bozza" +drafts: "Bozze" _drafts: select: "Selezionare bozza" cannotCreateDraftAnymore: "Hai superato il numero massimo di bozze ammissibili." diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index ff9bb32788..382a506726 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1054,6 +1054,7 @@ permissionDeniedError: "작업이 거부되었습니다" permissionDeniedErrorDescription: "이 작업을 수행할 권한이 없습니다." preset: "프리셋" selectFromPresets: "프리셋에서 선택" +custom: "커스텀" achievements: "도전 과제" gotInvalidResponseError: "서버의 응답이 올바르지 않습니다" gotInvalidResponseErrorDescription: " 서버가 다운되었거나 점검중일 가능성이 있습니다. 잠시후에 다시 시도해 주십시오." @@ -1375,6 +1376,7 @@ safeModeEnabled: "세이프 모드가 활성화돼있습니다" pluginsAreDisabledBecauseSafeMode: "세이프 모드가 활성화돼있기에 플러그인은 전부 비활성화됩니다." customCssIsDisabledBecauseSafeMode: "세이프 모드가 활성화돼있기에 커스텀 CSS는 적용되지 않습니다." themeIsDefaultBecauseSafeMode: "세이프 모드가 활성화돼있는 동안에는 기본 테마가 사용됩니다. 세이프 모드를 끄면 원래대로 돌아옵니다." +thankYouForTestingBeta: "베타 버전의 검증에 협력해 주셔서 감사합니다!" _order: newest: "최신 순" oldest: "오래된 순" @@ -1664,6 +1666,9 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor_description2: "서버에서 받은 리모트 콘텐츠를 포함해 서버 내의 모든 콘텐츠를 무조건 인터넷에 공개하는 것에는 위험이 따릅니다. 특히, 분산형 특성에 대해 모르는 열람자에게는 리모트 콘텐츠여도 서버 내에서 작성된 콘텐츠라고 잘못 인식할 수 있기에 주의가 필요합니다." restartServerSetupWizardConfirm_title: "서버의 초기 설정 위자드를 재시도하시겠습니까?" restartServerSetupWizardConfirm_text: "현재 일부 설정은 리셋됩니다." + entrancePageStyle: "입구 페이지의 스타일" + showTimelineForVisitor: "타임라인 표시" + showActivityiesForVisitor: "활동 표시" _userGeneratedContentsVisibilityForVisitor: all: "모두 공개" localOnly: "로컬 콘텐츠만 공개하고 리모트 콘텐츠는 비공개" @@ -2273,6 +2278,7 @@ _time: minute: "분" hour: "시간" day: "일" + month: "개월" _2fa: alreadyRegistered: "이미 설정이 완료되었습니다." registerTOTP: "인증 앱 설정 시작" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 7ed6a3dbdc..0835902642 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1054,6 +1054,7 @@ permissionDeniedError: "การดำเนินถูกปฏิเสธ" permissionDeniedErrorDescription: "บัญชีนี้ไม่มีสิทธิ์อนุญาตในการดำเนินการนี้" preset: "พรีเซ็ต" selectFromPresets: "เลือกจากการพรีเซ็ต" +custom: "แบบกำหนดเอง" achievements: "ความสำเร็จ" gotInvalidResponseError: "การตอบสนองเซิร์ฟเวอร์ไม่ถูกต้อง" gotInvalidResponseErrorDescription: "เซิร์ฟเวอร์อาจไม่สามารถเข้าถึงได้หรืออาจจะกำลังอยู่ในระหว่างปรับปรุง กรุณาลองใหม่อีกครั้งในภายหลังนะคะ" @@ -1375,6 +1376,7 @@ safeModeEnabled: "โหมดปลอดภัยถูกเปิดใช pluginsAreDisabledBecauseSafeMode: "เนื่องจากโหมดปลอดภัยถูกเปิดใช้งาน ปลั๊กอินทั้งหมดจึงถูกปิดใช้งาน" customCssIsDisabledBecauseSafeMode: "เนื่องจากโหมดปลอดภัยถูกเปิดใช้งาน CSS แบบกำหนดเองจึงไม่ได้ถูกนำมาใช้" themeIsDefaultBecauseSafeMode: "ในระหว่างที่โหมดปลอดภัยถูกเปิดใช้งาน จะใช้ธีมเริ่มต้น เมื่อปิดโหมดปลอดภัยจะกลับคืนดังเดิม" +thankYouForTestingBeta: "ขอบคุณที่ให้ความร่วมมือในการทดสอบเวอร์ชันเบต้า!" _order: newest: "เรียงจากใหม่ไปเก่า" oldest: "เรียงจากเก่าไปใหม่" @@ -2273,6 +2275,7 @@ _time: minute: "นาที" hour: "ชั่วโมง" day: "วัน" + month: "เดือน" _2fa: alreadyRegistered: "คุณได้ลงทะเบียนอุปกรณ์ยืนยันตัวตนแบบ 2 ชั้นแล้ว" registerTOTP: "ลงทะเบียนแอพตัวตรวจสอบสิทธิ์" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index b095374e4d..7fbb907783 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1054,6 +1054,7 @@ permissionDeniedError: "操作被拒绝" permissionDeniedErrorDescription: "本账户没有执行该操作的权限。" preset: "预设值" selectFromPresets: "从预设值中选择" +custom: "自定义" achievements: "成就" gotInvalidResponseError: "服务器无应答" gotInvalidResponseErrorDescription: "您的网络连接可能出现了问题, 或是远程服务器暂时不可用. 请稍后重试。" @@ -1375,6 +1376,7 @@ safeModeEnabled: "已启用安全模式" pluginsAreDisabledBecauseSafeMode: "因启用了安全模式,所有插件均已被禁用。" customCssIsDisabledBecauseSafeMode: "因启用了安全模式,无法应用自定义 CSS。" themeIsDefaultBecauseSafeMode: "启用安全模式时将使用默认主题。关闭安全模式后将还原。" +thankYouForTestingBeta: "感谢您协助测试 beta 版!" _order: newest: "从新到旧" oldest: "从旧到新" @@ -1664,6 +1666,9 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor_description2: "包含服务器接收到的远程内容在内,无条件将服务器上的所有内容公开在互联网上存在风险。特别是对去中心化的特性不是很了解的访问者有可能将远程服务器上的内容误认为是在此服务器内生成的,需要特别留意。" restartServerSetupWizardConfirm_title: "要重新开始服务器初始设定向导吗?" restartServerSetupWizardConfirm_text: "现有的部分设定将重置。" + entrancePageStyle: "入口页面样式" + showTimelineForVisitor: "显示时间线" + showActivityiesForVisitor: "显示活动" _userGeneratedContentsVisibilityForVisitor: all: "全部公开" localOnly: "仅公开本地内容,隐藏远程内容" @@ -2273,6 +2278,7 @@ _time: minute: "分" hour: "小时" day: "日" + month: "个月" _2fa: alreadyRegistered: "此设备已被注册" registerTOTP: "开始设置验证器" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index b9f5f9a6c9..ead814b7bb 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1054,6 +1054,7 @@ permissionDeniedError: "操作被拒絕" permissionDeniedErrorDescription: "此帳戶沒有執行這個操作的權限。" preset: "預設值" selectFromPresets: "從預設值中選擇" +custom: "自訂" achievements: "成就" gotInvalidResponseError: "伺服器的回應無效" gotInvalidResponseErrorDescription: "伺服器可能已關閉或者在維護中,請稍後再試。" @@ -1375,6 +1376,7 @@ safeModeEnabled: "啟用安全模式" pluginsAreDisabledBecauseSafeMode: "由於啟用安全模式,所有的外掛都被停用。" customCssIsDisabledBecauseSafeMode: "由於啟用安全模式,所有的客製 CSS 都被停用。" themeIsDefaultBecauseSafeMode: "在安全模式啟用期間將使用預設主題。關閉安全模式後會恢復原本的設定。" +thankYouForTestingBeta: "感謝您協助驗證 beta 版!" _order: newest: "最新的在前" oldest: "最舊的在前" @@ -1664,6 +1666,9 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor_description2: "包括伺服器接收到的遠端內容在內,無條件地將伺服器內所有內容公開到網際網路上是具有風險的。特別是對於不了解分散式架構特性的瀏覽者來說,他們可能會誤以為這些遠端內容是由該伺服器所創建的,因此需要特別留意。" restartServerSetupWizardConfirm_title: "要重新執行伺服器的初始設定精靈嗎?" restartServerSetupWizardConfirm_text: "當前的部分設定將會被重設。" + entrancePageStyle: "入口頁面的樣式" + showTimelineForVisitor: "顯示時間軸" + showActivityiesForVisitor: "顯示活動" _userGeneratedContentsVisibilityForVisitor: all: "全部公開\n" localOnly: "僅公開本地內容,遠端內容則不公開\n" @@ -2273,6 +2278,7 @@ _time: minute: "分鐘" hour: "小時" day: "日" + month: "個月" _2fa: alreadyRegistered: "此裝置已被註冊過了" registerTOTP: "開始設定驗證應用程式" From 8c433d27061267c71a58168e8062feba75b1d3b8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 20 Aug 2025 07:41:12 +0000 Subject: [PATCH 004/654] Bump version to 2025.8.0-beta.2 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5ab8c64ef1..85d3137563 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.8.0-beta.1", + "version": "2025.8.0-beta.2", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index c15c4146ce..73cc0851ba 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2025.8.0-beta.1", + "version": "2025.8.0-beta.2", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", From 7f6ba2e50128182258b92a8572700d14a05a9f8b 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, 21 Aug 2025 16:52:30 +0900 Subject: [PATCH 005/654] =?UTF-8?q?enhance:=20verify-email=E3=81=AB?= =?UTF-8?q?=E3=83=95=E3=83=AD=E3=83=B3=E3=83=88=E3=82=A8=E3=83=B3=E3=83=89?= =?UTF-8?q?UI=E3=82=92=E5=AE=9F=E8=A3=85=20(#16431)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance: メールのverifyをAPIに変更 * enhance(frontend): メールのVerifyページを追加 * fix * :art: * :art: * Update Changelog * lint --- CHANGELOG.md | 1 + locales/index.d.ts | 2 +- locales/ja-JP.yml | 2 +- packages/backend/src/server/ServerService.ts | 24 ---- .../backend/src/server/api/endpoint-list.ts | 1 + .../src/server/api/endpoints/verify-email.ts | 66 ++++++++++ packages/frontend/src/_boot_.ts | 2 +- packages/frontend/src/components/MkButton.vue | 3 +- .../frontend/src/pages/signup-complete.vue | 2 +- packages/frontend/src/pages/verify-email.vue | 122 ++++++++++++++++++ packages/frontend/src/router.definition.ts | 3 + packages/misskey-js/etc/misskey-js.api.md | 4 + .../misskey-js/src/autogen/apiClientJSDoc.ts | 11 ++ packages/misskey-js/src/autogen/endpoint.ts | 2 + packages/misskey-js/src/autogen/entities.ts | 1 + packages/misskey-js/src/autogen/types.ts | 71 ++++++++++ 16 files changed, 288 insertions(+), 29 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/verify-email.ts create mode 100644 packages/frontend/src/pages/verify-email.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 6875cdda23..02f07be69c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ - Enhance: 画像エフェクトのパラメータ名の多言語対応 - Enhance: 依存ソフトウェアの更新 - Enhance: ノートを非表示にする相対期間を1ヶ月単位で自由に指定できるように +- Enhance: メールアドレス確認画面のUIを改善 - Fix: 投稿フォームでファイルのアップロードが中止または失敗した際のハンドリングを修正 - Fix: 一部の設定検索結果が存在しないパスになる問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1171) diff --git a/locales/index.d.ts b/locales/index.d.ts index eefd8a5ecb..c31a3f4e83 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5001,7 +5001,7 @@ export interface Locale extends ILocale { /** * メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。 */ - "signupPendingError": string; + "emailVerificationFailedError": string; /** * 「内容を隠す」がオンの場合は注釈の記述が必要です。 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d04445282a..522f53ce4d 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1245,7 +1245,7 @@ releaseToRefresh: "離してリロード" refreshing: "リロード中" pullDownToRefresh: "引っ張ってリロード" useGroupedNotifications: "通知をグルーピング" -signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" +emailVerificationFailedError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。" doReaction: "リアクションする" code: "コード" diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 23c085ee27..7325c53df0 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -238,30 +238,6 @@ export class ServerService implements OnApplicationShutdown { } }); - fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => { - const profile = await this.userProfilesRepository.findOneBy({ - emailVerifyCode: request.params.code, - }); - - if (profile != null) { - await this.userProfilesRepository.update({ userId: profile.userId }, { - emailVerified: true, - emailVerifyCode: null, - }); - - this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { - schema: 'MeDetailed', - includeSecrets: true, - })); - - reply.code(200).send('Verification succeeded! メールアドレスの認証に成功しました。'); - return; - } else { - reply.code(404).send('Verification failed. Please try again. メールアドレスの認証に失敗しました。もう一度お試しください'); - return; - } - }); - fastify.register(this.clientServerService.createServer); this.streamingApiServerService.attach(fastify.server); diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index c0c43dd5c9..f4aa07875d 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -412,6 +412,7 @@ export * as 'users/search' from './endpoints/users/search.js'; export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js'; export * as 'users/show' from './endpoints/users/show.js'; export * as 'users/update-memo' from './endpoints/users/update-memo.js'; +export * as 'verify-email' from './endpoints/verify-email.js'; export * as 'chat/messages/create-to-user' from './endpoints/chat/messages/create-to-user.js'; export * as 'chat/messages/create-to-room' from './endpoints/chat/messages/create-to-room.js'; export * as 'chat/messages/delete' from './endpoints/chat/messages/delete.js'; diff --git a/packages/backend/src/server/api/endpoints/verify-email.ts b/packages/backend/src/server/api/endpoints/verify-email.ts new file mode 100644 index 0000000000..e069ed59f2 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/verify-email.ts @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UserProfilesRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ApiError } from '../error.js'; + +export const meta = { + requireCredential: false, + + tags: ['account'], + + errors: { + noSuchCode: { + message: 'No such code.', + code: 'NO_SUCH_CODE', + id: '97c1f576-e4b8-4b8a-a6dc-9cb65e7f6f85', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + code: { type: 'string' }, + }, + required: ['code'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps) => { + const profile = await this.userProfilesRepository.findOneBy({ + emailVerifyCode: ps.code, + }); + + if (profile == null) { + throw new ApiError(meta.errors.noSuchCode); + } + + await this.userProfilesRepository.update({ userId: profile.userId }, { + emailVerified: true, + emailVerifyCode: null, + }); + + this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { + schema: 'MeDetailed', + includeSecrets: true, + })); + }); + } +} + diff --git a/packages/frontend/src/_boot_.ts b/packages/frontend/src/_boot_.ts index 354fb95544..111a4abbfd 100644 --- a/packages/frontend/src/_boot_.ts +++ b/packages/frontend/src/_boot_.ts @@ -16,7 +16,7 @@ import '@/style.scss'; import { mainBoot } from '@/boot/main-boot.js'; import { subBoot } from '@/boot/sub-boot.js'; -const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete', '/install-extensions']; +const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete', '/verify-email', '/install-extensions']; if (subBootPaths.some(i => window.location.pathname === i || window.location.pathname.startsWith(i + '/'))) { subBoot(); diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index a77ebd6ac5..b729128a21 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -36,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/router.definition.ts b/packages/frontend/src/router.definition.ts index 57d9a860d6..e25e0fe161 100644 --- a/packages/frontend/src/router.definition.ts +++ b/packages/frontend/src/router.definition.ts @@ -202,6 +202,9 @@ export const ROUTE_DEF = [{ }, { path: '/signup-complete/:code', component: page(() => import('@/pages/signup-complete.vue')), +}, { + path: '/verify-email/:code', + component: page(() => import('@/pages/verify-email.vue')), }, { path: '/announcements', component: page(() => import('@/pages/announcements.vue')), diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index ae12547f35..0416e46cdc 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2129,6 +2129,7 @@ declare namespace entities { UsersUpdateMemoRequest, V2AdminEmojiListRequest, V2AdminEmojiListResponse, + VerifyEmailRequest, Error_2 as Error, UserLite, UserDetailedNotMeOnly, @@ -3807,6 +3808,9 @@ type V2AdminEmojiListRequest = operations['v2___admin___emoji___list']['requestB // @public (undocumented) type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['responses']['200']['content']['application/json']; +// @public (undocumented) +type VerifyEmailRequest = operations['verify-email']['requestBody']['content']['application/json']; + // Warnings were encountered during analysis: // // src/entities.ts:55:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 5407b7a653..c4428efcc2 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -4762,5 +4762,16 @@ declare module '../api.js' { params: P, credential?: string | null, ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *No* + */ + 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 d7cb2a46eb..5e9fc936b5 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -652,6 +652,7 @@ import type { UsersUpdateMemoRequest, V2AdminEmojiListRequest, V2AdminEmojiListResponse, + VerifyEmailRequest, } from './entities.js'; export type Endpoints = { @@ -1083,6 +1084,7 @@ export type Endpoints = { 'users/show': { req: UsersShowRequest; res: UsersShowResponse }; 'users/update-memo': { req: UsersUpdateMemoRequest; res: EmptyResponse }; 'v2/admin/emoji/list': { req: V2AdminEmojiListRequest; res: V2AdminEmojiListResponse }; + 'verify-email': { req: VerifyEmailRequest; res: EmptyResponse }; }; /** diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index a14febb6e6..73e460c50a 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -655,3 +655,4 @@ export type UsersShowResponse = operations['users___show']['responses']['200'][' export type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody']['content']['application/json']; export type V2AdminEmojiListRequest = operations['v2___admin___emoji___list']['requestBody']['content']['application/json']; export type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['responses']['200']['content']['application/json']; +export type VerifyEmailRequest = operations['verify-email']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 05ac143762..472b2f9c9e 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -3906,6 +3906,15 @@ export type paths = { */ post: operations['v2___admin___emoji___list']; }; + '/verify-email': { + /** + * verify-email + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['verify-email']; + }; }; export type webhooks = Record; export type components = { @@ -36387,5 +36396,67 @@ export interface operations { }; }; }; + 'verify-email': { + requestBody: { + content: { + 'application/json': { + code: string; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + headers: { + [name: string]: unknown; + }; + }; + /** @description Client error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; } From 1eabb21d69fc365970a03eabde20d54d05966f64 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, 21 Aug 2025 19:02:21 +0900 Subject: [PATCH 006/654] =?UTF-8?q?fix(backend):=20=E3=82=AF=E3=83=AA?= =?UTF-8?q?=E3=83=83=E3=83=97=E4=B8=80=E8=A6=A7API=E3=82=92=E3=83=9A?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=83=8D=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E3=81=AB=E5=AF=BE=E5=BF=9C=E3=81=95=E3=81=9B=E3=82=8B=20(#1643?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): クリップ一覧APIをページネーションに対応させる * Update Changelog --- CHANGELOG.md | 1 + .../src/server/api/endpoints/clips/list.ts | 17 +++++++++++++---- packages/misskey-js/etc/misskey-js.api.md | 4 ++++ packages/misskey-js/src/autogen/endpoint.ts | 3 ++- packages/misskey-js/src/autogen/entities.ts | 1 + packages/misskey-js/src/autogen/types.ts | 14 ++++++++++++++ 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02f07be69c..f9b250a1a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ - Enhance: ノートの削除処理の効率化 - Enhance: 全体的なパフォーマンスの向上 - Enhance: 依存ソフトウェアの更新 +- Enhance: `clips/list` APIがページネーションに対応しました - Fix: SystemWebhook設定でsecretを空に出来ない問題を修正 diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 2e4a3ff820..af20ea9f8d 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; import type { ClipsRepository } from '@/models/_.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -29,7 +30,13 @@ export const meta = { export const paramDef = { type: 'object', - properties: {}, + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + }, required: [], } as const; @@ -39,12 +46,14 @@ export default class extends Endpoint { // eslint- @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, + private queryService: QueryService, private clipEntityService: ClipEntityService, ) { super(meta, paramDef, async (ps, me) => { - const clips = await this.clipsRepository.findBy({ - userId: me.id, - }); + const query = this.queryService.makePaginationQuery(this.clipsRepository.createQueryBuilder('clip'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('clip.userId = :userId', { userId: me.id }); + + const clips = await query.limit(ps.limit).getMany(); return await this.clipEntityService.packMany(clips, me); }); diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 0416e46cdc..170c20f163 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1196,6 +1196,9 @@ type ClipsDeleteRequest = operations['clips___delete']['requestBody']['content'] // @public (undocumented) type ClipsFavoriteRequest = operations['clips___favorite']['requestBody']['content']['application/json']; +// @public (undocumented) +type ClipsListRequest = operations['clips___list']['requestBody']['content']['application/json']; + // @public (undocumented) type ClipsListResponse = operations['clips___list']['responses']['200']['content']['application/json']; @@ -1741,6 +1744,7 @@ declare namespace entities { ClipsCreateResponse, ClipsDeleteRequest, ClipsFavoriteRequest, + ClipsListRequest, ClipsListResponse, ClipsMyFavoritesResponse, ClipsNotesRequest, diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index 5e9fc936b5..4b83a9dd9b 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -264,6 +264,7 @@ import type { ClipsCreateResponse, ClipsDeleteRequest, ClipsFavoriteRequest, + ClipsListRequest, ClipsListResponse, ClipsMyFavoritesResponse, ClipsNotesRequest, @@ -830,7 +831,7 @@ export type Endpoints = { 'clips/create': { req: ClipsCreateRequest; res: ClipsCreateResponse }; 'clips/delete': { req: ClipsDeleteRequest; res: EmptyResponse }; 'clips/favorite': { req: ClipsFavoriteRequest; res: EmptyResponse }; - 'clips/list': { req: EmptyRequest; res: ClipsListResponse }; + 'clips/list': { req: ClipsListRequest; res: ClipsListResponse }; 'clips/my-favorites': { req: EmptyRequest; res: ClipsMyFavoritesResponse }; 'clips/notes': { req: ClipsNotesRequest; res: ClipsNotesResponse }; 'clips/remove-note': { req: ClipsRemoveNoteRequest; res: EmptyResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 73e460c50a..4ebe9a5155 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -267,6 +267,7 @@ export type ClipsCreateRequest = operations['clips___create']['requestBody']['co export type ClipsCreateResponse = operations['clips___create']['responses']['200']['content']['application/json']; export type ClipsDeleteRequest = operations['clips___delete']['requestBody']['content']['application/json']; export type ClipsFavoriteRequest = operations['clips___favorite']['requestBody']['content']['application/json']; +export type ClipsListRequest = operations['clips___list']['requestBody']['content']['application/json']; export type ClipsListResponse = operations['clips___list']['responses']['200']['content']['application/json']; export type ClipsMyFavoritesResponse = operations['clips___my-favorites']['responses']['200']['content']['application/json']; export type ClipsNotesRequest = operations['clips___notes']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 472b2f9c9e..3525d082d5 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -18263,6 +18263,20 @@ export interface operations { }; }; clips___list: { + requestBody: { + content: { + 'application/json': { + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + sinceDate?: number; + untilDate?: number; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { From 8cbbb80e3f08f94f7235d40bd7ae4d4c6781011e Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 21 Aug 2025 19:10:16 +0900 Subject: [PATCH 007/654] =?UTF-8?q?fix(backend):=20`notes/mentions`=20?= =?UTF-8?q?=E3=81=A7=E5=A0=B4=E5=90=88=E3=81=AB=E3=82=88=E3=81=A3=E3=81=A6?= =?UTF-8?q?=E3=81=AF=E4=B8=A6=E3=81=B3=E9=A0=86=E3=81=8C=E6=AD=A3=E3=81=97?= =?UTF-8?q?=E3=81=8F=E8=BF=94=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #16398 --- CHANGELOG.md | 1 + packages/backend/src/server/api/endpoints/notes/mentions.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9b250a1a7..59c3e7481b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ - Enhance: 全体的なパフォーマンスの向上 - Enhance: 依存ソフトウェアの更新 - Enhance: `clips/list` APIがページネーションに対応しました +- Fix: `notes/mentions` で場合によっては並び順が正しく返されない問題を修正 - Fix: SystemWebhook設定でsecretを空に出来ない問題を修正 diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 05ffdc1f97..e775bdb7fd 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -66,7 +66,7 @@ export default class extends Endpoint { // eslint- .orWhere(':meIdAsList <@ note.visibleUserIds'); })) // Avoid scanning primary key index - .orderBy('CONCAT(note.id)', 'DESC') + .orderBy('CONCAT(note.id)', (ps.sinceDate || ps.sinceId) ? 'ASC' : 'DESC') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') From 20d81696e1facad04eca01b616a674c87ade3aed 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, 22 Aug 2025 18:26:19 +0900 Subject: [PATCH 008/654] fix(backend): fix test (#16441) * fix(backend): fix test * fix * fix --- packages/backend/test/e2e/clips.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts index 570cc61c4b..fe9a217ee8 100644 --- a/packages/backend/test/e2e/clips.ts +++ b/packages/backend/test/e2e/clips.ts @@ -363,14 +363,11 @@ describe('クリップ', () => { const clipLimit = DEFAULT_POLICIES.clipLimit; const clips = await createMany({}, clipLimit); const res = await list({ - parameters: { limit: 1 }, // FIXME: 無視されて11全部返ってくる + parameters: { limit: clips.length }, }); - // 返ってくる配列には順序保障がないのでidでソートして厳密比較 - assert.deepStrictEqual( - res.sort(compareBy(s => s.id)), - clips.sort(compareBy(s => s.id)), - ); + // 作成responseの配列には順序保障がないのでidでソートして厳密比較 + assert.deepStrictEqual(res.toReversed(), clips.sort(compareBy(s => s.id))); }); test('の一覧が取得できる(空)', async () => { From 4d215bde1052d952a01b8ab7584d5e05f4ea10f7 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, 22 Aug 2025 19:31:27 +0900 Subject: [PATCH 009/654] fix(frontend): follow-up of #16380 --- packages/frontend-shared/js/const.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index 5c33c38f44..b2d83fff8b 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -88,6 +88,7 @@ export const ROLE_POLICIES = [ 'canManageCustomEmojis', 'canManageAvatarDecorations', 'canSearchNotes', + 'canSearchUsers', 'canUseTranslator', 'canHideAds', 'driveCapacityMb', From ade603ff7a6e2985a236949ed3d737ddab095aa1 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, 22 Aug 2025 19:34:20 +0900 Subject: [PATCH 010/654] =?UTF-8?q?fix(frontend):=20=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=83=8D=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AE?= =?UTF-8?q?=E9=80=B2=E8=A1=8C=E6=96=B9=E5=90=91=E3=82=92=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#1643?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): ページネーションの進行方向を指定できるように * Update Changelog * fix lint * fix: directionをMkPaginationに移動 * fix * fix * fix --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + .../src/components/MkNotesTimeline.vue | 5 +- .../frontend/src/components/MkPagination.vue | 51 ++++++++++++++++--- packages/frontend/src/pages/note.vue | 4 +- packages/frontend/src/utility/paginator.ts | 44 ++++++++++++---- 5 files changed, 86 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59c3e7481b..c6d674b4f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ - Fix: カラムの名前が正しくリスト/チャンネルの名前にならない問題を修正 - Fix: 複数のメンションを1行に記述した場合に、サジェストが正しく表示されない問題を修正 - Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正 +- Fix: ユーザーの前後ノートを閲覧する機能が壊れている問題を修正 ### Server - Feat: サーバー管理コマンド diff --git a/packages/frontend/src/components/MkNotesTimeline.vue b/packages/frontend/src/components/MkNotesTimeline.vue index 42d44dffdb..d94cf3924c 100644 --- a/packages/frontend/src/components/MkNotesTimeline.vue +++ b/packages/frontend/src/components/MkNotesTimeline.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/theme.ts b/packages/frontend/src/theme.ts index 036b86cff8..b715426917 100644 --- a/packages/frontend/src/theme.ts +++ b/packages/frontend/src/theme.ts @@ -23,6 +23,7 @@ export type Theme = { author: string; desc?: string; base?: 'dark' | 'light'; + kind?: 'dark' | 'light'; // legacy props: Record; codeHighlighter?: { base: BundledTheme; From dbb6c71c5c7098c33824b6070b6526416d3bdd69 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 26 Aug 2025 09:39:23 +0900 Subject: [PATCH 047/654] refactor --- .../frontend/src/pages/channel-editor.vue | 37 +++++++++++-------- packages/frontend/src/pages/gallery/edit.vue | 6 +-- packages/frontend/src/pages/registry.vue | 2 +- .../frontend/src/pages/settings/privacy.vue | 28 +++++++++----- packages/frontend/src/pages/tag.vue | 2 +- packages/frontend/src/utility/chart-vline.ts | 5 ++- packages/frontend/src/utility/popout.ts | 4 +- .../frontend/src/utility/sticky-sidebar.ts | 2 + 8 files changed, 51 insertions(+), 35 deletions(-) diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue index 80dfb8e84e..ce26a26109 100644 --- a/packages/frontend/src/pages/channel-editor.vue +++ b/packages/frontend/src/pages/channel-editor.vue @@ -92,7 +92,7 @@ const props = defineProps<{ }>(); const channel = ref(null); -const name = ref(null); +const name = ref(''); const description = ref(null); const bannerUrl = ref(null); const bannerId = ref(null); @@ -114,20 +114,22 @@ watch(() => bannerId.value, async () => { async function fetchChannel() { if (props.channelId == null) return; - channel.value = await misskeyApi('channels/show', { + const result = await misskeyApi('channels/show', { channelId: props.channelId, }); - name.value = channel.value.name; - description.value = channel.value.description; - bannerId.value = channel.value.bannerId; - bannerUrl.value = channel.value.bannerUrl; - isSensitive.value = channel.value.isSensitive; - pinnedNotes.value = channel.value.pinnedNoteIds.map(id => ({ + name.value = result.name; + description.value = result.description; + bannerId.value = result.bannerId; + bannerUrl.value = result.bannerUrl; + isSensitive.value = result.isSensitive; + pinnedNotes.value = result.pinnedNoteIds.map(id => ({ id, })); - color.value = channel.value.color; - allowRenoteToExternal.value = channel.value.allowRenoteToExternal; + color.value = result.color; + allowRenoteToExternal.value = result.allowRenoteToExternal; + + channel.value = result; } fetchChannel(); @@ -154,15 +156,17 @@ function save() { name: name.value, description: description.value, bannerId: bannerId.value, - pinnedNoteIds: pinnedNotes.value.map(x => x.id), color: color.value, isSensitive: isSensitive.value, allowRenoteToExternal: allowRenoteToExternal.value, - }; + } satisfies Misskey.entities.ChannelsCreateRequest; - if (props.channelId) { - params.channelId = props.channelId; - os.apiWithDialog('channels/update', params); + if (props.channelId != null) { + os.apiWithDialog('channels/update', { + ...params, + channelId: props.channelId, + pinnedNoteIds: pinnedNotes.value.map(x => x.id), + }); } else { os.apiWithDialog('channels/create', params).then(created => { router.push('/channels/:channelId', { @@ -175,12 +179,13 @@ function save() { } async function archive() { + if (props.channelId == null) return; + const { canceled } = await os.confirm({ type: 'warning', title: i18n.tsx.channelArchiveConfirmTitle({ name: name.value }), text: i18n.ts.channelArchiveConfirmDescription, }); - if (canceled) return; misskeyApi('channels/update', { diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue index cf0d700962..3fd462e0b9 100644 --- a/packages/frontend/src/pages/gallery/edit.vue +++ b/packages/frontend/src/pages/gallery/edit.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
{{ file.name }}
@@ -88,7 +88,7 @@ async function save() { router.push('/gallery/:postId', { params: { postId: props.postId, - } + }, }); } else { const created = await os.apiWithDialog('gallery/posts/create', { @@ -100,7 +100,7 @@ async function save() { router.push('/gallery/:postId', { params: { postId: created.id, - } + }, }); } } diff --git a/packages/frontend/src/pages/registry.vue b/packages/frontend/src/pages/registry.vue index 3762dadd12..389438242e 100644 --- a/packages/frontend/src/pages/registry.vue +++ b/packages/frontend/src/pages/registry.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._registry.createKey }}
- +
{{ scope.length === 0 ? '(root)' : scope.join('/') }} diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue index ab012841dc..54a6c0af82 100644 --- a/packages/frontend/src/pages/settings/privacy.vue +++ b/packages/frontend/src/pages/settings/privacy.vue @@ -160,10 +160,18 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - - + @@ -262,7 +270,7 @@ const makeNotesFollowersOnlyBefore_presets = [ const makeNotesFollowersOnlyBefore_isCustomMode = ref( makeNotesFollowersOnlyBefore.value != null && makeNotesFollowersOnlyBefore.value < 0 && - !makeNotesFollowersOnlyBefore_presets.some((preset) => preset.value === makeNotesFollowersOnlyBefore.value) + !makeNotesFollowersOnlyBefore_presets.some((preset) => preset.value === makeNotesFollowersOnlyBefore.value), ); const makeNotesFollowersOnlyBefore_selection = computed({ @@ -270,14 +278,14 @@ const makeNotesFollowersOnlyBefore_selection = computed({ set(value) { makeNotesFollowersOnlyBefore_isCustomMode.value = value === 'custom'; if (value !== 'custom') makeNotesFollowersOnlyBefore.value = value; - } + }, }); const makeNotesFollowersOnlyBefore_customMonths = computed({ get: () => makeNotesFollowersOnlyBefore.value ? Math.abs(makeNotesFollowersOnlyBefore.value) / (30 * 24 * 60 * 60) : null, set(value) { if (value != null && value > 0) makeNotesFollowersOnlyBefore.value = -Math.abs(Math.floor(Number(value))) * 30 * 24 * 60 * 60; - } + }, }); const makeNotesHiddenBefore_type = computed(() => { @@ -303,7 +311,7 @@ const makeNotesHiddenBefore_presets = [ const makeNotesHiddenBefore_isCustomMode = ref( makeNotesHiddenBefore.value != null && makeNotesHiddenBefore.value < 0 && - !makeNotesHiddenBefore_presets.some((preset) => preset.value === makeNotesHiddenBefore.value) + !makeNotesHiddenBefore_presets.some((preset) => preset.value === makeNotesHiddenBefore.value), ); const makeNotesHiddenBefore_selection = computed({ @@ -311,14 +319,14 @@ const makeNotesHiddenBefore_selection = computed({ set(value) { makeNotesHiddenBefore_isCustomMode.value = value === 'custom'; if (value !== 'custom') makeNotesHiddenBefore.value = value; - } + }, }); const makeNotesHiddenBefore_customMonths = computed({ get: () => makeNotesHiddenBefore.value ? Math.abs(makeNotesHiddenBefore.value) / (30 * 24 * 60 * 60) : null, set(value) { if (value != null && value > 0) makeNotesHiddenBefore.value = -Math.abs(Math.floor(Number(value))) * 30 * 24 * 60 * 60; - } + }, }); watch([makeNotesFollowersOnlyBefore, makeNotesHiddenBefore], () => { diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue index b5a4503b68..047e68f583 100644 --- a/packages/frontend/src/pages/tag.vue +++ b/packages/frontend/src/pages/tag.vue @@ -52,7 +52,7 @@ async function post() { const headerActions = computed(() => [{ icon: 'ti ti-dots', - label: i18n.ts.more, + text: i18n.ts.more, handler: (ev: MouseEvent) => { os.popupMenu([{ text: i18n.ts.embed, diff --git a/packages/frontend/src/utility/chart-vline.ts b/packages/frontend/src/utility/chart-vline.ts index 465ca591c6..2fe4bdb83b 100644 --- a/packages/frontend/src/utility/chart-vline.ts +++ b/packages/frontend/src/utility/chart-vline.ts @@ -8,9 +8,10 @@ import type { Plugin } from 'chart.js'; export const chartVLine = (vLineColor: string) => ({ id: 'vLine', beforeDraw(chart, args, options) { - if (chart.tooltip?._active?.length) { + const tooltip = chart.tooltip as any; + if (tooltip?._active?.length) { const ctx = chart.ctx; - const xs = chart.tooltip._active.map(a => a.element.x); + const xs = tooltip._active.map(a => a.element.x); const x = xs.reduce((a, b) => a + b, 0) / xs.length; const topY = chart.scales.y.top; const bottomY = chart.scales.y.bottom; diff --git a/packages/frontend/src/utility/popout.ts b/packages/frontend/src/utility/popout.ts index 5b141222e8..7e0222c459 100644 --- a/packages/frontend/src/utility/popout.ts +++ b/packages/frontend/src/utility/popout.ts @@ -20,8 +20,8 @@ export function popout(path: string, w?: HTMLElement) { } else { const width = 400; const height = 500; - const x = window.top.outerHeight / 2 + window.top.screenY - (height / 2); - const y = window.top.outerWidth / 2 + window.top.screenX - (width / 2); + const x = window.top == null ? 0 : window.top.outerHeight / 2 + window.top.screenY - (height / 2); + const y = window.top == null ? 0 : window.top.outerWidth / 2 + window.top.screenX - (width / 2); window.open(url, url, `width=${width}, height=${height}, top=${x}, left=${y}`); } diff --git a/packages/frontend/src/utility/sticky-sidebar.ts b/packages/frontend/src/utility/sticky-sidebar.ts index 867c9b8324..435555896f 100644 --- a/packages/frontend/src/utility/sticky-sidebar.ts +++ b/packages/frontend/src/utility/sticky-sidebar.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/* export class StickySidebar { private lastScrollTop = 0; private container: HTMLElement; @@ -53,3 +54,4 @@ export class StickySidebar { this.lastScrollTop = scrollTop <= 0 ? 0 : scrollTop; } } +*/ From eb9915baf880146007bf035a8fe770acee016358 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:56:09 +0900 Subject: [PATCH 048/654] refactor and fix --- CHANGELOG.md | 1 + packages/frontend/src/os.ts | 21 +----------------- .../src/pages/settings/avatar-decoration.vue | 22 ++++++++++++++----- .../pages/settings/drive.WatermarkItem.vue | 2 +- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd9b4ff183..770b37a206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ - Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正 - Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正 - Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正 +- Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正 ### Server - Feat: サーバー管理コマンド diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index bf0e5e1b37..6ba3af72b9 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -9,7 +9,7 @@ import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue'; import { EventEmitter } from 'eventemitter3'; import * as Misskey from 'misskey-js'; import type { Component, Ref } from 'vue'; -import type { ComponentProps as CP } from 'vue-component-type-helpers'; +import type { ComponentEmit, ComponentProps as CP } from 'vue-component-type-helpers'; import type { Form, GetFormResultType } from '@/utility/form.js'; import type { MenuItem } from '@/types/menu.js'; import type { PostFormProps } from '@/types/post-form.js'; @@ -157,28 +157,9 @@ export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number { return zIndexes[priority]; } -// InstanceType['$emit'] だとインターセクション型が返ってきて -// 使い物にならないので、代わりに ['$props'] から色々省くことで emit の型を生成する -// FIXME: 何故か *.ts ファイルからだと型がうまく取れない?ことがあるのをなんとかしたい -type ComponentEmit = T extends new () => { $props: infer Props } - ? [keyof Pick>] extends [never] - ? Record // *.ts ファイルから型がうまく取れないとき用(これがないと {} になって型エラーがうるさい) - : EmitsExtractor - : T extends (...args: any) => any - ? ReturnType extends { [x: string]: any; __ctx?: { [x: string]: any; props: infer Props } } - ? [keyof Pick>] extends [never] - ? Record - : EmitsExtractor - : never - : never; - // props に ref を許可するようにする type ComponentProps = { [K in keyof CP]: CP[K] | Ref[K]> }; -type EmitsExtractor = { - [K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize : K extends string ? never : K]: T[K]; -}; - export function popup( component: T, props: ComponentProps, diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue index c58cd57c65..4b8ac9a26c 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.vue @@ -17,13 +17,13 @@ SPDX-License-Identifier: AGPL-3.0-only
@@ -50,6 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, defineAsyncComponent, computed } from 'vue'; import * as Misskey from 'misskey-js'; import XDecoration from './avatar-decoration.decoration.vue'; +import XDialog from './avatar-decoration.dialog.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; @@ -68,14 +69,24 @@ misskeyApi('get-avatar-decorations').then(_avatarDecorations => { loading.value = false; }); -async function openDecoration(avatarDecoration, index?: number) { - const { dispose } = await os.popupAsyncWithDialog(import('./avatar-decoration.dialog.vue').then(x => x.default), { +function openAttachedDecoration(index: number) { + openDecoration(avatarDecorations.value.find(d => d.id === $i.avatarDecorations[index].id) ?? { id: '', url: '', name: '?', roleIdsThatCanBeUsedThisDecoration: [] }, index); +} + +async function openDecoration(avatarDecoration: { + id: string; + url: string; + name: string; + roleIdsThatCanBeUsedThisDecoration: string[]; +}, index?: number) { + const { dispose } = os.popup(XDialog, { decoration: avatarDecoration, - usingIndex: index, + usingIndex: index ?? null, }, { 'attach': async (payload) => { const decoration = { id: avatarDecoration.id, + url: avatarDecoration.url, angle: payload.angle, flipH: payload.flipH, offsetX: payload.offsetX, @@ -90,6 +101,7 @@ async function openDecoration(avatarDecoration, index?: number) { 'update': async (payload) => { const decoration = { id: avatarDecoration.id, + url: avatarDecoration.url, angle: payload.angle, flipH: payload.flipH, offsetX: payload.offsetX, diff --git a/packages/frontend/src/pages/settings/drive.WatermarkItem.vue b/packages/frontend/src/pages/settings/drive.WatermarkItem.vue index b466f35fc5..bb91d5e212 100644 --- a/packages/frontend/src/pages/settings/drive.WatermarkItem.vue +++ b/packages/frontend/src/pages/settings/drive.WatermarkItem.vue @@ -43,7 +43,7 @@ async function edit() { const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkWatermarkEditorDialog.vue')), { preset: deepClone(props.preset), }, { - ok: (preset: WatermarkPreset) => { + ok: (preset) => { emit('updatePreset', preset); }, closed: () => dispose(), From d6a1046361d3d38726f2a86588960c3614f72a9f Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 26 Aug 2025 13:34:41 +0900 Subject: [PATCH 049/654] refactor --- .../entities/NoteReactionEntityService.ts | 50 +++++++++++++---- packages/backend/src/misc/json-schema.ts | 3 +- .../src/models/json-schema/note-reaction.ts | 31 ++++++++++- .../api/endpoints/admin/announcements/list.ts | 28 ++++++++++ .../server/api/endpoints/users/lists/show.ts | 10 ++++ .../server/api/endpoints/users/reactions.ts | 4 +- packages/frontend/src/boot/main-boot.ts | 5 -- .../src/components/MkAchievements.vue | 2 +- packages/frontend/src/components/MkChart.vue | 55 +++++++++++++++---- .../src/components/MkCropperDialog.vue | 44 +++++++++------ .../frontend/src/components/MkEmojiPicker.vue | 2 +- .../src/components/MkEmojiPickerDialog.vue | 4 +- packages/frontend/src/components/MkModal.vue | 4 +- .../components/MkReactionsViewer.reaction.vue | 27 ++++++--- .../components/global/MkPageHeader.tabs.vue | 2 +- packages/frontend/src/pages/admin-user.vue | 4 +- .../frontend/src/pages/drive.file.info.vue | 24 +++++--- packages/frontend/src/pages/explore.vue | 6 -- packages/frontend/src/pages/list.vue | 1 + packages/frontend/src/pages/note.vue | 2 +- .../frontend/src/pages/registry.value.vue | 4 +- .../src/pages/settings/avatar-decoration.vue | 4 +- .../src/pages/settings/notifications.vue | 11 ++-- .../frontend/src/pages/settings/security.vue | 4 +- .../frontend/src/pages/settings/theme.vue | 3 +- .../src/pages/settings/webhook.edit.vue | 2 - packages/frontend/src/pages/timeline.vue | 2 +- .../src/pages/user/activity.following.vue | 2 + .../src/pages/user/activity.notes.vue | 4 +- .../frontend/src/pages/user/activity.pv.vue | 4 +- packages/frontend/src/preferences/def.ts | 4 +- packages/frontend/src/preferences/manager.ts | 7 ++- .../src/ui/_common_/announcements.vue | 2 +- packages/frontend/src/ui/_common_/common.ts | 34 ++++++++---- .../src/ui/_common_/statusbar-rss.vue | 6 +- packages/frontend/src/ui/deck.vue | 2 +- packages/frontend/src/utility/admin-lookup.ts | 2 +- packages/frontend/src/utility/chart-legend.ts | 2 +- packages/frontend/src/utility/clicker-game.ts | 16 +----- .../frontend/src/utility/get-note-menu.ts | 2 +- .../src/widgets/WidgetInstanceInfo.vue | 4 +- 41 files changed, 289 insertions(+), 140 deletions(-) diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 46ec13704c..54ce4d472a 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -49,15 +49,12 @@ export class NoteReactionEntityService implements OnModuleInit { public async pack( src: MiNoteReaction['id'] | MiNoteReaction, me?: { id: MiUser['id'] } | null | undefined, - options?: { - withNote: boolean; - }, + options?: object, hints?: { packedUser?: Packed<'UserLite'> }, ): Promise> { const opts = Object.assign({ - withNote: false, }, options); const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src }); @@ -67,9 +64,6 @@ export class NoteReactionEntityService implements OnModuleInit { createdAt: this.idService.parse(reaction.id).date.toISOString(), user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me), type: this.reactionService.convertLegacyReaction(reaction.reaction), - ...(opts.withNote ? { - note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me), - } : {}), }; } @@ -77,16 +71,50 @@ export class NoteReactionEntityService implements OnModuleInit { public async packMany( reactions: MiNoteReaction[], me?: { id: MiUser['id'] } | null | undefined, - options?: { - withNote: boolean; - }, + options?: object, ): Promise[]> { const opts = Object.assign({ - withNote: false, }, options); const _users = reactions.map(({ user, userId }) => user ?? userId); const _userMap = await this.userEntityService.packMany(_users, me) .then(users => new Map(users.map(u => [u.id, u]))); return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) }))); } + + @bindThis + public async packWithNote( + src: MiNoteReaction['id'] | MiNoteReaction, + me?: { id: MiUser['id'] } | null | undefined, + options?: object, + hints?: { + packedUser?: Packed<'UserLite'> + }, + ): Promise> { + const opts = Object.assign({ + }, options); + + const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src }); + + return { + id: reaction.id, + createdAt: this.idService.parse(reaction.id).date.toISOString(), + user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me), + type: this.reactionService.convertLegacyReaction(reaction.reaction), + note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me), + }; + } + + @bindThis + public async packManyWithNote( + reactions: MiNoteReaction[], + me?: { id: MiUser['id'] } | null | undefined, + options?: object, + ): Promise[]> { + const opts = Object.assign({ + }, options); + const _users = reactions.map(({ user, userId }) => user ?? userId); + const _userMap = await this.userEntityService.packMany(_users, me) + .then(users => new Map(users.map(u => [u.id, u]))); + return Promise.all(reactions.map(reaction => this.packWithNote(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) }))); + } } diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index ed47edff9b..dca92e1037 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -22,7 +22,7 @@ import { packedFollowingSchema } from '@/models/json-schema/following.js'; import { packedMutingSchema } from '@/models/json-schema/muting.js'; import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js'; import { packedBlockingSchema } from '@/models/json-schema/blocking.js'; -import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js'; +import { packedNoteReactionSchema, packedNoteReactionWithNoteSchema } from '@/models/json-schema/note-reaction.js'; import { packedHashtagSchema } from '@/models/json-schema/hashtag.js'; import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js'; import { packedPageBlockSchema, packedPageSchema } from '@/models/json-schema/page.js'; @@ -92,6 +92,7 @@ export const refs = { Note: packedNoteSchema, NoteDraft: packedNoteDraftSchema, NoteReaction: packedNoteReactionSchema, + NoteReactionWithNote: packedNoteReactionWithNoteSchema, NoteFavorite: packedNoteFavoriteSchema, Notification: packedNotificationSchema, DriveFile: packedDriveFileSchema, diff --git a/packages/backend/src/models/json-schema/note-reaction.ts b/packages/backend/src/models/json-schema/note-reaction.ts index 95658ace1f..04c9f34232 100644 --- a/packages/backend/src/models/json-schema/note-reaction.ts +++ b/packages/backend/src/models/json-schema/note-reaction.ts @@ -10,7 +10,6 @@ export const packedNoteReactionSchema = { type: 'string', optional: false, nullable: false, format: 'id', - example: 'xxxxxxxxxx', }, createdAt: { type: 'string', @@ -28,3 +27,33 @@ export const packedNoteReactionSchema = { }, }, } as const; + +export const packedNoteReactionWithNoteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + type: { + type: 'string', + optional: false, nullable: false, + }, + note: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + }, +} as const; diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 81a788de2b..804bd5d9b9 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -49,6 +49,34 @@ export const meta = { type: 'string', optional: false, nullable: false, }, + icon: { + type: 'string', + optional: false, nullable: true, + }, + display: { + type: 'string', + optional: false, nullable: false, + }, + isActive: { + type: 'boolean', + optional: false, nullable: false, + }, + forExistingUsers: { + type: 'boolean', + optional: false, nullable: false, + }, + silence: { + type: 'boolean', + optional: false, nullable: false, + }, + needConfirmationToRead: { + type: 'boolean', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: true, + }, imageUrl: { type: 'string', optional: false, nullable: true, diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 8756801fe4..ed5952d4c5 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -23,6 +23,16 @@ export const meta = { type: 'object', optional: false, nullable: false, ref: 'UserList', + properties: { + likedCount: { + type: 'number', + optional: true, nullable: false, + }, + isLiked: { + type: 'boolean', + optional: true, nullable: false, + }, + }, }, errors: { diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index d6f1ecd8ed..d84a191f7a 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -28,7 +28,7 @@ export const meta = { items: { type: 'object', optional: false, nullable: false, - ref: 'NoteReaction', + ref: 'NoteReactionWithNote', }, }, @@ -120,7 +120,7 @@ export default class extends Endpoint { // eslint- return true; }); - return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true }); + return await this.noteReactionEntityService.packManyWithNote(reactions, me); }); } } diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 6ae8379801..18817d3f79 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -368,11 +368,6 @@ export async function mainBoot() { }); }); - main.on('unreadAntenna', () => { - updateCurrentAccountPartial({ hasUnreadAntenna: true }); - sound.playMisskeySfx('antenna'); - }); - main.on('newChatMessage', () => { updateCurrentAccountPartial({ hasUnreadChatMessages: true }); sound.playMisskeySfx('chatMessage'); diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index 3b7b59b4d3..bf39c1e983 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only [$style.iconFrame_platinum]: ACHIEVEMENT_BADGES[achievement.name].frame === 'platinum', }]" > -
+
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index 4d67bba70d..c54081ad42 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -589,7 +589,10 @@ const fetchDriveFilesChart = async (): Promise => { }; const fetchInstanceRequestsChart = async (): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'In', @@ -611,7 +614,10 @@ const fetchInstanceRequestsChart = async (): Promise => { }; const fetchInstanceUsersChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Users', @@ -626,7 +632,10 @@ const fetchInstanceUsersChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Notes', @@ -641,7 +650,10 @@ const fetchInstanceNotesChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Following', @@ -664,7 +676,10 @@ const fetchInstanceFfChart = async (total: boolean): Promise = }; const fetchInstanceDriveUsageChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { bytes: true, series: [{ @@ -680,7 +695,10 @@ const fetchInstanceDriveUsageChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Drive files', @@ -695,7 +713,10 @@ const fetchInstanceDriveFilesChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/user/notes', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/notes', { userId: userId, limit: props.limit, span: props.span }); return { series: [...(props.args?.withoutAll ? [] : [{ name: 'All', @@ -727,7 +748,10 @@ const fetchPerUserNotesChart = async (): Promise => { }; const fetchPerUserPvChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/pv', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/pv', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Unique PV (user)', @@ -754,7 +778,10 @@ const fetchPerUserPvChart = async (): Promise => { }; const fetchPerUserFollowingChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/following', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Local', @@ -769,7 +796,10 @@ const fetchPerUserFollowingChart = async (): Promise => { }; const fetchPerUserFollowersChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/following', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Local', @@ -784,7 +814,10 @@ const fetchPerUserFollowersChart = async (): Promise => { }; const fetchPerUserDriveChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/drive', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/drive', { userId: userId, limit: props.limit, span: props.span }); return { bytes: true, series: [{ diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 7f592fba79..6c07eac47a 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue index 052829ffe2..63d3640f9c 100644 --- a/packages/frontend/src/pages/admin-file.vue +++ b/packages/frontend/src/pages/admin-file.vue @@ -4,197 +4,43 @@ SPDX-License-Identifier: AGPL-3.0-only --> - - From 959e72b2b34968d9b3188776cf2843a2f69bf8b2 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:02:14 +0900 Subject: [PATCH 120/654] refactor --- packages/backend/src/core/RoleService.ts | 1 + packages/backend/src/models/Notification.ts | 1 + packages/frontend-shared/js/const.ts | 62 ------------------- .../components/MkNotificationSelectWindow.vue | 2 +- .../src/components/MkServerSetupWizard.vue | 1 - .../MkStreamingNotificationsTimeline.vue | 2 +- .../frontend/src/pages/admin/roles.editor.vue | 5 +- packages/frontend/src/pages/admin/roles.vue | 6 +- packages/frontend/src/pages/notifications.vue | 2 +- .../src/pages/settings/notifications.vue | 2 +- .../src/widgets/WidgetNotifications.vue | 2 +- packages/misskey-js/src/consts.ts | 62 ++++++++++++++++++- packages/misskey-js/src/index.ts | 1 + 13 files changed, 75 insertions(+), 74 deletions(-) diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 3df7ee69ee..7dc07ef4dd 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -31,6 +31,7 @@ import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { NotificationService } from '@/core/NotificationService.js'; import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; +// misskey-js の rolePolicies と同期すべし export type RolePolicies = { gtlAvailable: boolean; ltlAvailable: boolean; diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts index 5764a307b0..0b4eeb3455 100644 --- a/packages/backend/src/models/Notification.ts +++ b/packages/backend/src/models/Notification.ts @@ -10,6 +10,7 @@ import { MiAccessToken } from './AccessToken.js'; import { MiRole } from './Role.js'; import { MiDriveFile } from './DriveFile.js'; +// misskey-js の notificationTypes と同期すべし export type MiNotification = { type: 'note'; id: string; diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index b2d83fff8b..c8c437afe9 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -54,68 +54,6 @@ https://github.com/sindresorhus/file-type/blob/main/core.js https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers */ -export const notificationTypes = [ - 'note', - 'follow', - 'mention', - 'reply', - 'renote', - 'quote', - 'reaction', - 'pollEnded', - 'receiveFollowRequest', - 'followRequestAccepted', - 'roleAssigned', - 'chatRoomInvitationReceived', - 'achievementEarned', - 'exportCompleted', - 'login', - 'createToken', - 'test', - 'app', -] as const; -export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const; - -export const ROLE_POLICIES = [ - 'gtlAvailable', - 'ltlAvailable', - 'canPublicNote', - 'mentionLimit', - 'canInvite', - 'inviteLimit', - 'inviteLimitCycle', - 'inviteExpirationTime', - 'canManageCustomEmojis', - 'canManageAvatarDecorations', - 'canSearchNotes', - 'canSearchUsers', - 'canUseTranslator', - 'canHideAds', - 'driveCapacityMb', - 'maxFileSizeMb', - 'alwaysMarkNsfw', - 'canUpdateBioMedia', - 'pinLimit', - 'antennaLimit', - 'wordMuteLimit', - 'webhookLimit', - 'clipLimit', - 'noteEachClipsLimit', - 'userListLimit', - 'userEachUserListsLimit', - 'rateLimitFactor', - 'avatarDecorationLimit', - 'canImportAntennas', - 'canImportBlocking', - 'canImportFollowing', - 'canImportMuting', - 'canImportUserLists', - 'chatAvailability', - 'uploadableFileTypes', - 'noteDraftLimit', - 'watermarkAvailable', -] as const; - export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime']; export const MFM_PARAMS: Record = { tada: ['speed=', 'delay='], diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue index bb01a008bd..7205e516d2 100644 --- a/packages/frontend/src/components/MkNotificationSelectWindow.vue +++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue @@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue index 09bc6375ac..12d1a37390 100644 --- a/packages/frontend/src/pages/gallery/edit.vue +++ b/packages/frontend/src/pages/gallery/edit.vue @@ -4,161 +4,35 @@ SPDX-License-Identifier: AGPL-3.0-only --> - - From 2ccf4f94cb85f7732bc884792cdbc631c468a873 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Mon, 1 Sep 2025 16:51:58 +0900 Subject: [PATCH 125/654] refactor --- .../src/pages/page-editor/els/page-editor.el.section.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue index 11f83b6ec6..cf5712a8e5 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue @@ -24,8 +24,8 @@ SPDX-License-Identifier: AGPL-3.0-only import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue'; import * as Misskey from 'misskey-js'; -import { genId } from '@/utility/id.js'; import XContainer from '../page-editor.container.vue'; +import { genId } from '@/utility/id.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { deepClone } from '@/utility/clone.js'; @@ -35,11 +35,11 @@ import { getPageBlockList } from '@/pages/page-editor/common.js'; const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue')); const props = defineProps<{ - modelValue: Misskey.entities.PageBlock & { type: 'section'; }, + modelValue: Extract, }>(); const emit = defineEmits<{ - (ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'section' }): void; + (ev: 'update:modelValue', value: Extract): void; (ev: 'remove'): void; }>(); @@ -59,7 +59,7 @@ async function rename() { title: i18n.ts._pages.enterSectionTitle, default: props.modelValue.title, }); - if (canceled) return; + if (canceled || title == null) return; emit('update:modelValue', { ...props.modelValue, title, From ffc481a99450cd8ff3222c8679816f00fbfee548 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 2 Sep 2025 10:11:50 +0900 Subject: [PATCH 126/654] =?UTF-8?q?fix:=20=E3=80=8C=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E3=81=A7=E3=82=82=E3=81=A3=E3=81=A8=E8=A6=8B=E3=82=8B=E3=80=8D?= =?UTF-8?q?=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=81=8C=E3=81=A7=E3=81=8D=E3=81=AA?= =?UTF-8?q?=E3=81=84=E5=95=8F=E9=A1=8C=20(#16500)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/pages/settings/preferences.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index fdf2373bfc..ba35dd7f43 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -110,7 +110,6 @@ SPDX-License-Identifier: AGPL-3.0-only -
From 842670e10084b98a09acd195be566d43e8cab485 Mon Sep 17 00:00:00 2001 From: yukineko <27853966+hideki0403@users.noreply.github.com> Date: Tue, 2 Sep 2025 10:29:25 +0900 Subject: [PATCH 127/654] =?UTF-8?q?fix(frontend):=20RSS=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=83=83=E3=82=AB=E3=83=BC=E3=82=A6=E3=82=A3=E3=82=B8=E3=82=A7?= =?UTF-8?q?=E3=83=83=E3=83=88=E3=81=8C=E6=AD=A3=E3=81=97=E3=81=8F=E5=8B=95?= =?UTF-8?q?=E4=BD=9C=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(#16498)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: RSSティッカーウィジェットが正しく機能しない問題を修正 * chore: update CHANGELOG.md --- CHANGELOG.md | 2 +- packages/frontend/src/widgets/WidgetRssTicker.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc9526ec88..f15a8c2bea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - ### Client -- +- Fix: RSSティッカーウィジェットが正しく動作しない問題を修正 ### Server - diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue index 9d4feb784c..95f82f7d7b 100644 --- a/packages/frontend/src/widgets/WidgetRssTicker.vue +++ b/packages/frontend/src/widgets/WidgetRssTicker.vue @@ -31,7 +31,7 @@ import { ref, watch, computed } from 'vue'; import * as Misskey from 'misskey-js'; import { useWidgetPropsManager } from './widget.js'; import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; -import MarqueeText from '@/components/MkMarqueeText.vue'; +import MkMarqueeText from '@/components/MkMarqueeText.vue'; import type { FormWithDefault, GetFormResultType } from '@/utility/form.js'; import MkContainer from '@/components/MkContainer.vue'; import { shuffle } from '@/utility/shuffle.js'; From 047773341d88065eda604a8f59f87e6f34258695 Mon Sep 17 00:00:00 2001 From: takaion <3522531+takaion@users.noreply.github.com> Date: Tue, 2 Sep 2025 16:40:57 +0900 Subject: [PATCH 128/654] =?UTF-8?q?fix(frontend):=20=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E7=94=BB=E5=83=8F=E3=81=8C=E6=A8=AA=E3=81=AB=E5=BC=95?= =?UTF-8?q?=E3=81=8D=E4=BC=B8=E3=81=B0=E3=81=95=E3=82=8C=E3=81=A6=E3=81=97?= =?UTF-8?q?=E3=81=BE=E3=81=86=E5=95=8F=E9=A1=8C=E3=81=AB=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=20(#16502)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): エラー画像が横に引き伸ばされてしまう問題に対応 Fix misskey-dev#15982 * Update CHANGELOG.md --- CHANGELOG.md | 1 + packages/frontend/src/components/global/MkResult.vue | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f15a8c2bea..89e52f5cc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Client - Fix: RSSティッカーウィジェットが正しく動作しない問題を修正 +- Fix: エラー画像が横に引き伸ばされてしまう問題に対応 ### Server - diff --git a/packages/frontend/src/components/global/MkResult.vue b/packages/frontend/src/components/global/MkResult.vue index fc8206f814..2071859e57 100644 --- a/packages/frontend/src/components/global/MkResult.vue +++ b/packages/frontend/src/components/global/MkResult.vue @@ -41,8 +41,7 @@ const props = defineProps<{ .img { vertical-align: bottom; height: 128px; - aspect-ratio: 1; - margin-bottom: 16px; + margin: auto auto 16px; border-radius: 16px; } From a92fd8856a77e8a80e8e9294a091e08f12f86c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A5=BA=E5=AD=90w=20=28Yumechi=29?= <35571479+eternal-flame-AD@users.noreply.github.com> Date: Thu, 4 Sep 2025 23:55:37 -0500 Subject: [PATCH 129/654] feat(backend): Send Clear-Site-Data header on /flush (#16517) * feat(backend): Send Clear-Site-Data header on /flush Signed-off-by: eternal-flame-AD * simplify check on flush.pug Signed-off-by: eternal-flame-AD --------- Signed-off-by: eternal-flame-AD --- CHANGELOG.md | 1 + .../src/server/web/ClientServerService.ts | 19 +++++- .../backend/src/server/web/views/flush.pug | 64 ++++++++++--------- 3 files changed, 53 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e52f5cc8..fb2212ebd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Client - Fix: RSSティッカーウィジェットが正しく動作しない問題を修正 - Fix: エラー画像が横に引き伸ばされてしまう問題に対応 +- Enhance: /flushページでサイトキャッシュをクリアできるようになりました ### Server - diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index b515a0c0c8..3cd83efa1a 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -201,6 +201,8 @@ export class ClientServerService { @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + const configUrl = new URL(this.config.url); + fastify.register(fastifyView, { root: _dirname + '/views', engine: { @@ -239,7 +241,6 @@ export class ClientServerService { done(); }); } else { - const configUrl = new URL(this.config.url); const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, ''); const port = (process.env.VITE_PORT ?? '5173'); @@ -887,6 +888,22 @@ export class ClientServerService { [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); fastify.get('/flush', async (request, reply) => { + let sendHeader = true; + + if (request.headers['origin']) { + const originURL = new URL(request.headers['origin']); + if (originURL.protocol !== 'https:') { // Clear-Site-Data only supports https + sendHeader = false; + } + if (originURL.host !== configUrl.host) { + sendHeader = false; + } + } + + if (sendHeader) { + reply.header('Clear-Site-Data', '"*"'); + } + reply.header('Set-Cookie', 'http-flush-failed=1; Path=/flush; Max-Age=60'); return await reply.view('flush'); }); diff --git a/packages/backend/src/server/web/views/flush.pug b/packages/backend/src/server/web/views/flush.pug index a73a45212f..7884495d08 100644 --- a/packages/backend/src/server/web/views/flush.pug +++ b/packages/backend/src/server/web/views/flush.pug @@ -6,41 +6,45 @@ html const msg = document.getElementById('msg'); const successText = `\nSuccess Flush! Back to Misskey\n成功しました。Misskeyを開き直してください。`; - message('Start flushing.'); + if (!document.cookie) { + message('Your site data is fully cleared by your browser.'); + message(successText); + } else { + message('Your browser does not support Clear-Site-Data header. Start opportunistic flushing.'); + (async function() { + try { + localStorage.clear(); + message('localStorage cleared.'); - (async function() { - try { - localStorage.clear(); - message('localStorage cleared.'); + const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => { + const delidb = indexedDB.deleteDatabase(name); + delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`)); + delidb.onerror = e => rej(e) + })); - const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => { - const delidb = indexedDB.deleteDatabase(name); - delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`)); - delidb.onerror = e => rej(e) - })); + await Promise.all(idbPromises); - await Promise.all(idbPromises); + if (navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage('clear'); + await navigator.serviceWorker.getRegistrations() + .then(registrations => { + return Promise.all(registrations.map(registration => registration.unregister())); + }) + .catch(e => { throw new Error(e) }); + } - if (navigator.serviceWorker.controller) { - navigator.serviceWorker.controller.postMessage('clear'); - await navigator.serviceWorker.getRegistrations() - .then(registrations => { - return Promise.all(registrations.map(registration => registration.unregister())); - }) - .catch(e => { throw new Error(e) }); + message(successText); + } catch (e) { + message(`\n${e}\n\nFlush Failed. Please retry.\n失敗しました。もう一度試してみてください。`); + message(`\nIf you retry more than 3 times, try manually clearing the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを手動で消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`) + + console.error(e); + setTimeout(() => { + location = '/'; + }, 10000) } - - message(successText); - } catch (e) { - message(`\n${e}\n\nFlush Failed. Please retry.\n失敗しました。もう一度試してみてください。`); - message(`\nIf you retry more than 3 times, clear the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`) - - console.error(e); - setTimeout(() => { - location = '/'; - }, 10000) - } - })(); + })(); + } function message(text) { msg.insertAdjacentHTML('beforeend', `

[${(new Date()).toString()}] ${text.replace(/\n/g,'
')}

`) From 9b565728e792e4eba53b9755b92e3ab92a76fa9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=AA=E3=81=A3=E3=81=8B=E3=81=82?= <10798675+nakkaa@users.noreply.github.com> Date: Fri, 5 Sep 2025 15:26:39 +0900 Subject: [PATCH 130/654] fix #16494 (#16509) --- packages/frontend/src/pages/welcome.entrance.simple.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/welcome.entrance.simple.vue b/packages/frontend/src/pages/welcome.entrance.simple.vue index c2a2420e50..edbd312249 100644 --- a/packages/frontend/src/pages/welcome.entrance.simple.vue +++ b/packages/frontend/src/pages/welcome.entrance.simple.vue @@ -34,7 +34,7 @@ import { instance as meta } from '@/instance.js'; position: fixed; top: 0; right: 0; - width: 80vw; // 100%からshapeの幅を引いている + width: 100vw; height: 100vh; } From de1b2223ffd0b3f97e1073f61d0f0699f437294c Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Fri, 5 Sep 2025 19:44:11 +0900 Subject: [PATCH 131/654] =?UTF-8?q?enhance(frontend):=20AiScriptApp?= =?UTF-8?q?=E3=82=A6=E3=82=A3=E3=82=B8=E3=82=A7=E3=83=83=E3=83=88=E3=81=A7?= =?UTF-8?q?=E6=A7=8B=E6=96=87=E3=82=A8=E3=83=A9=E3=83=BC=E3=82=92=E6=A4=9C?= =?UTF-8?q?=E7=9F=A5=E3=81=97=E3=81=A6=E3=82=82=E3=83=80=E3=82=A4=E3=82=A2?= =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F=E3=82=A6?= =?UTF-8?q?=E3=82=A3=E3=82=B8=E3=82=A7=E3=83=83=E3=83=88=E5=86=85=E3=81=AB?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++- .../frontend/src/widgets/WidgetAiscriptApp.vue | 15 ++++++++------- packages/frontend/src/widgets/WidgetButton.vue | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb2212ebd2..0d877e449c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,10 @@ - ### Client +- Enhance: AiScriptAppウィジェットで構文エラーを検知してもダイアログではなくウィジェット内にエラーを表示するように +- Enhance: /flushページでサイトキャッシュをクリアできるようになりました - Fix: RSSティッカーウィジェットが正しく動作しない問題を修正 - Fix: エラー画像が横に引き伸ばされてしまう問題に対応 -- Enhance: /flushページでサイトキャッシュをクリアできるようになりました ### Server - diff --git a/packages/frontend/src/widgets/WidgetAiscriptApp.vue b/packages/frontend/src/widgets/WidgetAiscriptApp.vue index fdd4eaae06..18acd966fd 100644 --- a/packages/frontend/src/widgets/WidgetAiscriptApp.vue +++ b/packages/frontend/src/widgets/WidgetAiscriptApp.vue @@ -7,25 +7,26 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
Syntax error :(
+
diff --git a/packages/frontend-embed/src/components/EmPagination.vue b/packages/frontend-embed/src/components/EmPagination.vue index 94a91305f4..bd49d127a9 100644 --- a/packages/frontend-embed/src/components/EmPagination.vue +++ b/packages/frontend-embed/src/components/EmPagination.vue @@ -134,7 +134,7 @@ const isBackTop = ref(false); const empty = computed(() => items.value.size === 0); const error = ref(false); -const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : document.body); +const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : window.document.body); const visibility = useDocumentVisibility(); @@ -353,7 +353,7 @@ watch(visibility, () => { BACKGROUND_PAUSE_WAIT_SEC * 1000); } else { // 'visible' if (timerForSetPause) { - clearTimeout(timerForSetPause); + window.clearTimeout(timerForSetPause); timerForSetPause = null; } else { isPausingUpdate = false; @@ -447,11 +447,11 @@ onBeforeMount(() => { init().then(() => { if (props.pagination.reversed) { nextTick(() => { - setTimeout(toBottom, 800); + window.setTimeout(toBottom, 800); // scrollToBottomでmoreFetchingボタンが画面外まで出るまで // more = trueを遅らせる - setTimeout(() => { + window.setTimeout(() => { moreFetching.value = false; }, 2000); }); @@ -461,11 +461,11 @@ onBeforeMount(() => { onBeforeUnmount(() => { if (timerForSetPause) { - clearTimeout(timerForSetPause); + window.clearTimeout(timerForSetPause); timerForSetPause = null; } if (preventAppearFetchMoreTimer.value) { - clearTimeout(preventAppearFetchMoreTimer.value); + window.clearTimeout(preventAppearFetchMoreTimer.value); preventAppearFetchMoreTimer.value = null; } scrollObserver.value?.disconnect(); diff --git a/packages/frontend-embed/src/server-context.ts b/packages/frontend-embed/src/server-context.ts index a84a1a726a..c061d5a6f1 100644 --- a/packages/frontend-embed/src/server-context.ts +++ b/packages/frontend-embed/src/server-context.ts @@ -4,7 +4,7 @@ */ import * as Misskey from 'misskey-js'; -const providedContextEl = document.getElementById('misskey_embedCtx'); +const providedContextEl = window.document.getElementById('misskey_embedCtx'); export type ServerContext = { clip?: Misskey.entities.Clip; diff --git a/packages/frontend-embed/src/server-metadata.ts b/packages/frontend-embed/src/server-metadata.ts index 6c94aacd48..ad9b5a1a91 100644 --- a/packages/frontend-embed/src/server-metadata.ts +++ b/packages/frontend-embed/src/server-metadata.ts @@ -6,7 +6,7 @@ import * as Misskey from 'misskey-js'; import { misskeyApi } from '@/misskey-api.js'; -const providedMetaEl = document.getElementById('misskey_meta'); +const providedMetaEl = window.document.getElementById('misskey_meta'); const _serverMetadata: Misskey.entities.MetaDetailed | null = (providedMetaEl && providedMetaEl.textContent) ? JSON.parse(providedMetaEl.textContent) : null; diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts index c9b1c0d0c6..c7bc5df85d 100644 --- a/packages/frontend-embed/src/theme.ts +++ b/packages/frontend-embed/src/theme.ts @@ -35,15 +35,15 @@ export function assertIsTheme(theme: Record): theme is Theme { export function applyTheme(theme: Theme, persist = true) { if (timeout) window.clearTimeout(timeout); - document.documentElement.classList.add('_themeChanging_'); + window.document.documentElement.classList.add('_themeChanging_'); timeout = window.setTimeout(() => { - document.documentElement.classList.remove('_themeChanging_'); + window.document.documentElement.classList.remove('_themeChanging_'); }, 1000); const colorScheme = theme.base === 'dark' ? 'dark' : 'light'; - document.documentElement.dataset.colorScheme = colorScheme; + window.document.documentElement.dataset.colorScheme = colorScheme; // Deep copy const _theme = JSON.parse(JSON.stringify(theme)); @@ -55,7 +55,7 @@ export function applyTheme(theme: Theme, persist = true) { const props = compile(_theme); - for (const tag of document.head.children) { + for (const tag of window.document.head.children) { if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { tag.setAttribute('content', props['htmlThemeColor']); break; @@ -63,7 +63,7 @@ export function applyTheme(theme: Theme, persist = true) { } for (const [k, v] of Object.entries(props)) { - document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); + window.document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); } // iframeを正常に透過させるために、cssのcolor-schemeは `light dark;` 固定にしてある。style.scss参照 diff --git a/packages/frontend-embed/src/ui.vue b/packages/frontend-embed/src/ui.vue index 4ba5968a91..711d0eae6d 100644 --- a/packages/frontend-embed/src/ui.vue +++ b/packages/frontend-embed/src/ui.vue @@ -52,8 +52,8 @@ function safeURIDecode(str: string): string { } } -const page = location.pathname.split('/')[2]; -const contentId = safeURIDecode(location.pathname.split('/')[3]); +const page = window.location.pathname.split('/')[2]; +const contentId = safeURIDecode(window.location.pathname.split('/')[3]); if (_DEV_) console.log(page, contentId); const embedParams = inject(DI.embedParams, defaultEmbedParams); diff --git a/packages/frontend-shared/eslint.config.js b/packages/frontend-shared/eslint.config.js index 6453be0042..b972cfdb27 100644 --- a/packages/frontend-shared/eslint.config.js +++ b/packages/frontend-shared/eslint.config.js @@ -51,9 +51,71 @@ export default [ allowSingleExtends: true, }], 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], - // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため - // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため - 'id-denylist': ['error', 'window', 'e'], + // window ... グローバルスコープと衝突し、予期せぬ結果を招くため + // e ... error や event など、複数のキーワードの頭文字であり分かりにくいため + // close ... window.closeと衝突 or 紛らわしい + // open ... window.openと衝突 or 紛らわしい + // fetch ... window.fetchと衝突 or 紛らわしい + // location ... window.locationと衝突 or 紛らわしい + // document ... window.documentと衝突 or 紛らわしい + // history ... window.historyと衝突 or 紛らわしい + // scroll ... window.scrollと衝突 or 紛らわしい + // setTimeout ... window.setTimeoutと衝突 or 紛らわしい + // setInterval ... window.setIntervalと衝突 or 紛らわしい + // clearTimeout ... window.clearTimeoutと衝突 or 紛らわしい + // clearInterval ... window.clearIntervalと衝突 or 紛らわしい + 'id-denylist': ['error', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history', 'scroll', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'], + 'no-restricted-globals': [ + 'error', + { + 'name': 'open', + 'message': 'Use `window.open`.', + }, + { + 'name': 'close', + 'message': 'Use `window.close`.', + }, + { + 'name': 'fetch', + 'message': 'Use `window.fetch`.', + }, + { + 'name': 'location', + 'message': 'Use `window.location`.', + }, + { + 'name': 'document', + 'message': 'Use `window.document`.', + }, + { + 'name': 'history', + 'message': 'Use `window.history`.', + }, + { + 'name': 'scroll', + 'message': 'Use `window.scroll`.', + }, + { + 'name': 'setTimeout', + 'message': 'Use `window.setTimeout`.', + }, + { + 'name': 'setInterval', + 'message': 'Use `window.setInterval`.', + }, + { + 'name': 'clearTimeout', + 'message': 'Use `window.clearTimeout`.', + }, + { + 'name': 'clearInterval', + 'message': 'Use `window.clearInterval`.', + }, + { + 'name': 'name', + 'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている', + }, + ], 'no-shadow': ['warn'], 'vue/attributes-order': ['error', { alphabetical: false, diff --git a/packages/frontend-shared/js/config.ts b/packages/frontend-shared/js/config.ts index ac5c5629f3..6272d3f6b9 100644 --- a/packages/frontend-shared/js/config.ts +++ b/packages/frontend-shared/js/config.ts @@ -4,15 +4,15 @@ */ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -const address = new URL(document.querySelector('meta[property="instance_url"]')?.content || location.href); -const siteName = document.querySelector('meta[property="og:site_name"]')?.content; +const address = new URL(window.document.querySelector('meta[property="instance_url"]')?.content || window.location.href); +const siteName = window.document.querySelector('meta[property="og:site_name"]')?.content; export const host = address.host; export const hostname = address.hostname; export const url = address.origin; export const port = address.port; -export const apiUrl = location.origin + '/api'; -export const wsOrigin = location.origin; +export const apiUrl = window.location.origin + '/api'; +export const wsOrigin = window.location.origin; export const lang = localStorage.getItem('lang') ?? 'en-US'; export const langs = _LANGS_; export const version = _VERSION_; diff --git a/packages/frontend-shared/js/scroll.ts b/packages/frontend-shared/js/scroll.ts index 9057b896c6..5578cffdec 100644 --- a/packages/frontend-shared/js/scroll.ts +++ b/packages/frontend-shared/js/scroll.ts @@ -51,7 +51,7 @@ export function onScrollTop(el: HTMLElement, cb: (topVisible: boolean) => unknow // - toleranceの範囲内に収まる程度の微量なスクロールが発生した let prevTopVisible = firstTopVisible; const onScroll = () => { - if (!document.body.contains(el)) return; + if (!window.document.body.contains(el)) return; const topVisible = isHeadVisible(el, tolerance); if (topVisible !== prevTopVisible) { @@ -78,7 +78,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1 const containerOrWindow = container ?? window; const onScroll = () => { - if (!document.body.contains(el)) return; + if (!window.document.body.contains(el)) return; if (isTailVisible(el, 1, container)) { cb(); if (once) removeListener(); @@ -145,8 +145,8 @@ export function isTailVisible(el: HTMLElement, tolerance = 1, container = getScr // https://ja.javascript.info/size-and-scroll-window#ref-932 export function getBodyScrollHeight() { return Math.max( - document.body.scrollHeight, document.documentElement.scrollHeight, - document.body.offsetHeight, document.documentElement.offsetHeight, - document.body.clientHeight, document.documentElement.clientHeight, + window.document.body.scrollHeight, window.document.documentElement.scrollHeight, + window.document.body.offsetHeight, window.document.documentElement.offsetHeight, + window.document.body.clientHeight, window.document.documentElement.clientHeight, ); } diff --git a/packages/frontend-shared/js/use-document-visibility.ts b/packages/frontend-shared/js/use-document-visibility.ts index b1197e68da..a87c1f1bab 100644 --- a/packages/frontend-shared/js/use-document-visibility.ts +++ b/packages/frontend-shared/js/use-document-visibility.ts @@ -7,18 +7,18 @@ import { onMounted, onUnmounted, ref } from 'vue'; import type { Ref } from 'vue'; export function useDocumentVisibility(): Ref { - const visibility = ref(document.visibilityState); + const visibility = ref(window.document.visibilityState); const onChange = (): void => { - visibility.value = document.visibilityState; + visibility.value = window.document.visibilityState; }; onMounted(() => { - document.addEventListener('visibilitychange', onChange); + window.document.addEventListener('visibilitychange', onChange); }); onUnmounted(() => { - document.removeEventListener('visibilitychange', onChange); + window.document.removeEventListener('visibilitychange', onChange); }); return visibility; From f60b6291d7df798dae071b545138f9df2478b189 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: Wed, 10 Sep 2025 10:01:25 +0900 Subject: [PATCH 161/654] chore(gh): add frontend-builder to renovate --- renovate.json5 | 1 + 1 file changed, 1 insertion(+) diff --git a/renovate.json5 b/renovate.json5 index ded6b987c5..faafae92c7 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -37,6 +37,7 @@ 'packages/frontend/**/package.json', 'packages/frontend-embed/**/package.json', 'packages/frontend-shared/**/package.json', + 'packages/frontend-builder/**/package.json', 'packages/misskey-bubble-game/**/package.json', 'packages/misskey-reversi/**/package.json', 'packages/sw/**/package.json', From aebc3f781e32c40faf2ee7fa5a23042e6382ce2e 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, 12 Sep 2025 17:12:50 +0900 Subject: [PATCH 162/654] =?UTF-8?q?perf(frontend):=20=E4=BD=8E=E7=B2=BE?= =?UTF-8?q?=E5=BA=A6=E3=81=AA=E7=8F=BE=E5=9C=A8=E6=99=82=E5=88=BB=E3=82=92?= =?UTF-8?q?=E4=B8=80=E3=81=8B=E6=89=80=E3=81=A7=E7=AE=A1=E7=90=86=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#16479)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(frontend): 低精度な現在時刻を一か所で管理するように * lint * fix * remove unused imports * fix * Update Changelog * [ci skip] typo * enhance: カレンダーウィジェットの日付変更は時間通りに行うように * [ci skip] fix --- CHANGELOG.md | 1 + packages/frontend/src/components/MkPoll.vue | 37 +++++++++-------- .../frontend/src/components/global/MkTime.vue | 30 +++----------- .../src/composables/use-lowres-time.ts | 34 ++++++++++++++++ .../frontend/src/widgets/WidgetCalendar.vue | 40 +++++++++++++++---- 5 files changed, 90 insertions(+), 52 deletions(-) create mode 100644 packages/frontend/src/composables/use-lowres-time.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d1ec15de5c..38c93de869 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Enhance: クリップ/リスト/アンテナ/ロール追加系メニュー項目において、表示件数を拡張 - Enhance: 「キャッシュを削除」ボタンでブラウザの内部キャッシュの削除も行えるように - Enhance: Ctrlキー(Commandキー)を押下しながらリンクをクリックすると新しいタブで開くように +- Enhance: 時刻計算のための基準値を一か所で管理するようにし、パフォーマンスを向上 - Fix: プッシュ通知を有効にできない問題を修正 - Fix: RSSティッカーウィジェットが正しく動作しない問題を修正 - Fix: プロファイルを復元後アカウントの切り替えができない問題を修正 diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 359ee08812..76c65397ae 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -27,16 +27,16 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkPositionSelector.vue b/packages/frontend/src/components/MkPositionSelector.vue index 739f55125b..6f12aada30 100644 --- a/packages/frontend/src/components/MkPositionSelector.vue +++ b/packages/frontend/src/components/MkPositionSelector.vue @@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue b/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue index 11ae091d90..288293db3f 100644 --- a/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue +++ b/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue @@ -18,6 +18,18 @@ SPDX-License-Identifier: AGPL-3.0-only > + + + + + + + + + +