From 872cefcfb80eb8f0185f5b28e9aed0a2a49fc544 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 22 Nov 2024 09:15:37 +0000 Subject: [PATCH 001/448] [skip ci] Update CHANGELOG.md (prepend template) --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa756c0f0..8f097d16fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## Unreleased + +### General +- + +### Client +- + +### Server +- + + ## 2024.11.0 ### Note From 0e90589290de7948683131b356f91265c521d280 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 Nov 2024 21:19:12 +0900 Subject: [PATCH 002/448] Update CHANGELOG.md (typo) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f097d16fd..a0ea1127c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,7 +49,7 @@ - Fix: デッキのタイムラインカラムで「センシティブなファイルを含むノートを表示」設定が使用できなかった問題を修正 - Fix: Encode RSS urls with escape sequences before fetching allowing query parameters to be used - Fix: リンク切れを修正 -= Fix: ノート投稿ボタンにホバー時のスタイルが適用されていないのを修正 +- Fix: ノート投稿ボタンにホバー時のスタイルが適用されていないのを修正 (Cherry-picked from https://github.com/taiyme/misskey/pull/305) - Fix: メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正 - Fix: 画面幅が狭い環境でデザインが崩れる問題を修正 From 04b221409cac8e54e9f392b1a50094799c866441 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sat, 23 Nov 2024 04:44:33 +0900 Subject: [PATCH 003/448] fix(backend): use atomic command to improve security --- packages/backend/src/core/WebAuthnService.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index ad53192f18..ed75e4f467 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -189,14 +189,12 @@ export class WebAuthnService { */ @bindThis public async verifySignInWithPasskeyAuthentication(context: string, response: AuthenticationResponseJSON): Promise { - const challenge = await this.redisClient.get(`webauthn:challenge:${context}`); + const challenge = await this.redisClient.getdel(`webauthn:challenge:${context}`); if (!challenge) { throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', `challenge '${context}' not found`); } - await this.redisClient.del(`webauthn:challenge:${context}`); - const key = await this.userSecurityKeysRepository.findOneBy({ id: response.id, }); From d91a1be56201cba91d61265bc928f70ac21a2fad 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: Sat, 23 Nov 2024 15:35:06 +0900 Subject: [PATCH 004/448] =?UTF-8?q?fix(frontend):=20=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=82=B5=E3=82=A4=E3=82=BA=E3=81=8C=E5=A4=89=E3=82=8F=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E9=9A=9B=E3=81=ABnavbar=E3=81=8C=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E3=81=A7=E6=8A=98=E3=82=8A=E3=81=9F=E3=81=9F=E3=81=BE=E3=82=8C?= =?UTF-8?q?=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=20(#15042)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): 画面サイズが変わった際にnavbarが自動で折りたたまれない問題を修正 * Update Changelog * fix --- CHANGELOG.md | 2 +- packages/frontend/src/ui/_common_/navbar.vue | 22 +++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0ea1127c3..3ce54e4d1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - ### Client -- +- 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 ### Server - diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index 8fc76741e3..9724905e02 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- + {{ i18n.ts.controlPanel }} - @@ -83,8 +83,12 @@ import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; +import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js'; -const iconOnly = ref(false); +const forceIconOnly = ref(window.innerWidth <= 1279); +const iconOnly = computed(() => { + return forceIconOnly.value || (defaultStore.reactiveState.menuDisplay.value === 'sideIcon'); +}); const menu = computed(() => defaultStore.state.menu); const otherMenuItemIndicated = computed(() => { @@ -95,14 +99,10 @@ const otherMenuItemIndicated = computed(() => { return false; }); -const forceIconOnly = window.innerWidth <= 1279; - function calcViewState() { - iconOnly.value = forceIconOnly || (defaultStore.state.menuDisplay === 'sideIcon'); + forceIconOnly.value = window.innerWidth <= 1279; } -calcViewState(); - window.addEventListener('resize', calcViewState); watch(defaultStore.reactiveState.menuDisplay, () => { @@ -120,8 +120,10 @@ function openAccountMenu(ev: MouseEvent) { } function more(ev: MouseEvent) { + const target = getHTMLElementOrNull(ev.currentTarget ?? ev.target); + if (!target) return; const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), { - src: ev.currentTarget ?? ev.target, + src: target, }, { closed: () => dispose(), }); From 00301ed04f144c57e2ed54b5103292ef573e8c02 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: Sat, 23 Nov 2024 16:05:10 +0900 Subject: [PATCH 005/448] =?UTF-8?q?Update=20CHANGELOG.md=20(=E6=9B=B8?= =?UTF-8?q?=E3=81=8D=E6=96=B9=E3=82=92=E6=8F=83=E3=81=88=E3=82=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ce54e4d1d..762ecafbdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - ### Client -- 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 +- Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 ### Server - From a77ad7a16ba3f5c4845fe0716a72c7f01c285e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:45:05 +0900 Subject: [PATCH 006/448] =?UTF-8?q?fix(backend):=20=E3=82=A2=E3=83=89?= =?UTF-8?q?=E3=83=AC=E3=82=B9=E5=85=A5=E5=8A=9B=E3=81=A7=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=81=AE=E3=83=97=E3=83=AD=E3=83=95?= =?UTF-8?q?=E3=82=A3=E3=83=BC=E3=83=AB=E3=83=9A=E3=83=BC=E3=82=B8=E3=82=92?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=97=E3=81=9F=E9=9A=9B=E3=80=81=E5=89=8D?= =?UTF-8?q?=E6=8F=90=E3=83=87=E3=83=BC=E3=82=BF=E3=81=8C=E8=B6=B3=E3=82=8A?= =?UTF-8?q?=E3=81=9A=E6=8F=8F=E7=94=BB=E3=81=AB=E5=A4=B1=E6=95=97=E3=81=99?= =?UTF-8?q?=E3=82=8B=20(#15033)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): アドレス入力で直接ユーザのプロフィールページを表示した際、前提データが足りず描画に失敗する * fix CHANGELOG.md --- CHANGELOG.md | 2 +- packages/backend/src/server/web/ClientServerService.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 762ecafbdb..7896a42883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 ### Server -- +- Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) ## 2024.11.0 diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 1b8873214b..e356e0d76c 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -585,7 +585,10 @@ export class ClientServerService { reply.header('X-Robots-Tag', 'noai'); } - const _user = await this.userEntityService.pack(user); + const _user = await this.userEntityService.pack(user, null, { + schema: 'UserDetailed', + userProfile: profile, + }); return await reply.view('user', { user, profile, me, From ae1d0b08eb7d95a79d23d1f2c1d9d69cc0650e0a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 23 Nov 2024 17:42:55 +0900 Subject: [PATCH 007/448] ci: do not run chromatic on fork repositories (#15041) --- .github/workflows/storybook.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index c02f38ee0b..efcf0291d8 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -15,6 +15,8 @@ on: jobs: build: + # chromatic is not likely to be available for fork repositories, so we disable for fork repositories. + if: github.repository == 'misskey-dev/misskey' runs-on: ubuntu-latest env: From d176db517fd11b63e83daa7c1f45ecd25bd0750e 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: Sun, 24 Nov 2024 15:23:07 +0900 Subject: [PATCH 008/448] =?UTF-8?q?fix(backend/misskey-js):=20=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=9D=E4=BF=AE=E6=AD=A3=20(#15046)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/server/api/openapi/gen-spec.ts | 2 +- packages/misskey-js/src/autogen/types.ts | 110 +++++++++--------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index efa47a6986..3b20ec1321 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -183,7 +183,7 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) { }, ...(endpoint.meta.limit ? { '429': { - description: 'To many requests', + description: 'Too many requests', content: { 'application/json': { schema: { diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 280abba727..42ca05e057 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -10593,7 +10593,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -11112,7 +11112,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -11179,7 +11179,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -11573,7 +11573,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -11633,7 +11633,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -11756,7 +11756,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -13351,7 +13351,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -14184,7 +14184,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -14531,7 +14531,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -14656,7 +14656,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15151,7 +15151,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15624,7 +15624,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15684,7 +15684,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15747,7 +15747,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15806,7 +15806,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15866,7 +15866,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -16373,7 +16373,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -16648,7 +16648,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -17908,7 +17908,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -17969,7 +17969,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18020,7 +18020,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18071,7 +18071,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18122,7 +18122,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18173,7 +18173,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18224,7 +18224,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18275,7 +18275,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18512,7 +18512,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18572,7 +18572,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18631,7 +18631,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18690,7 +18690,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18749,7 +18749,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18817,7 +18817,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18885,7 +18885,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -19877,7 +19877,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -20114,7 +20114,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -20174,7 +20174,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -20544,7 +20544,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -21023,7 +21023,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -21191,7 +21191,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -21688,7 +21688,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -21746,7 +21746,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -21804,7 +21804,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -22464,7 +22464,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -22898,7 +22898,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23142,7 +23142,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23278,7 +23278,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23416,7 +23416,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23550,7 +23550,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23882,7 +23882,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23949,7 +23949,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -24279,7 +24279,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -24829,7 +24829,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -26108,7 +26108,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -27398,7 +27398,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -27512,7 +27512,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; From eddf6a23197e1a14b20423c7da08206034d198aa 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: Sun, 24 Nov 2024 15:23:21 +0900 Subject: [PATCH 009/448] =?UTF-8?q?fix(frontend):=20=E3=82=B5=E3=83=BC?= =?UTF-8?q?=E3=83=90=E3=83=BC=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=A8Misskey=E9=96=A2=E9=80=A3=E3=83=AA=E3=82=BD?= =?UTF-8?q?=E3=83=BC=E3=82=B9=E3=81=A8=E3=81=AE=E9=96=93=E3=81=ABdivider?= =?UTF-8?q?=E3=81=8C=E5=85=A5=E3=82=89=E3=81=AA=E3=81=84=E3=81=93=E3=81=A8?= =?UTF-8?q?=E3=81=8C=E3=81=82=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20(#15044)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): サーバードキュメントとMisskey関連リソースとの間にdividerが入らないことがある問題を修正 * Update Changelog --- CHANGELOG.md | 1 + packages/frontend/src/ui/_common_/common.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7896a42883..b8eca4c355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Client - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 +- Fix: サーバー情報メニューに区切り線が不足していたのを修正 ### Server - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index f908803f01..b31254196a 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -124,7 +124,7 @@ export function openInstanceMenu(ev: MouseEvent) { }); } - if (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) { + if (instance.impressumUrl != null || instance.tosUrl != null || instance.privacyPolicyUrl != null) { menuItems.push({ type: 'divider' }); } From a0e91b5882379b31b379ec366ed09e7253c788bc 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: Sun, 24 Nov 2024 20:43:47 +0900 Subject: [PATCH 010/448] =?UTF-8?q?fix(backend):=20=E8=B5=B7=E5=8B=95?= =?UTF-8?q?=E5=89=8D=E3=81=AE=E7=96=8E=E9=80=9A=E3=83=81=E3=82=A7=E3=83=83?= =?UTF-8?q?=E3=82=AF=E3=81=8C=E6=A9=9F=E8=83=BD=E3=81=97=E3=81=AA=E3=81=8F?= =?UTF-8?q?=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=20(#15043)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * check harder for connectibility `allSettled` does not throw if a promise is rejected, so `check_connect` never actually failed * Update Changelog --------- Co-authored-by: dakkar --- CHANGELOG.md | 2 ++ packages/backend/scripts/check_connect.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8eca4c355..badcf1cebb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ ### Server - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) +- Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) ## 2024.11.0 diff --git a/packages/backend/scripts/check_connect.js b/packages/backend/scripts/check_connect.js index bb149444b5..96c4549ccb 100644 --- a/packages/backend/scripts/check_connect.js +++ b/packages/backend/scripts/check_connect.js @@ -53,4 +53,4 @@ const promises = Array connectToPostgres() ]); -await Promise.allSettled(promises); +await Promise.all(promises); From dd56623cded7cc7a355d1dc59e46e2d8f1ed3660 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 24 Nov 2024 20:44:59 +0900 Subject: [PATCH 011/448] fix: unable to upload to local object storage (#15040) --- packages/backend/src/core/S3Service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts index bb2a463354..37721d2bf1 100644 --- a/packages/backend/src/core/S3Service.ts +++ b/packages/backend/src/core/S3Service.ts @@ -28,7 +28,7 @@ export class S3Service { ? `${meta.objectStorageUseSSL ? 'https' : 'http'}://${meta.objectStorageEndpoint}` : `${meta.objectStorageUseSSL ? 'https' : 'http'}://example.net`; // dummy url to select http(s) agent - const agent = this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy); + const agent = this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy, true); const handlerOption: NodeHttpHandlerOptions = {}; if (meta.objectStorageUseSSL) { handlerOption.httpsAgent = agent as https.Agent; From 8076f78d06786dd60db121b588289be54731b13e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 25 Nov 2024 22:18:00 +0000 Subject: [PATCH 012/448] Bump version to 2024.11.1-alpha.0 --- CHANGELOG.md | 2 +- package.json | 2 +- packages/misskey-js/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index badcf1cebb..8f2c36ffc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## 2024.11.1 ### General - diff --git a/package.json b/package.json index 60de6f4e15..b91f044ff7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.11.0", + "version": "2024.11.1-alpha.0", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 7994dac1e4..6a2b45150d 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2024.11.0", + "version": "2024.11.1-alpha.0", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", From fa271cf84e7797358703d4cda0e8f48b26bdbd87 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sat, 30 Nov 2024 13:20:49 +0900 Subject: [PATCH 013/448] Update about-misskey.vue --- packages/frontend/src/pages/about-misskey.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index f2becfd8f5..1a86e34969 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -384,6 +384,7 @@ const patrons = [ 'こまつぶり', 'まゆつな空高', 'asata', + 'ruru', ]; const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure')); From 020c191e2c7457c8eaa6b7bbbd7892de32b9de24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:29:40 +0900 Subject: [PATCH 014/448] =?UTF-8?q?fix(frontend):=20MiAuth=E8=AA=8D?= =?UTF-8?q?=E5=8F=AF=E7=94=BB=E9=9D=A2=E3=81=AE=E3=83=87=E3=82=B6=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E4=BF=AE=E6=AD=A3=20(#15106)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/pages/miauth.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue index e85d2c29c1..7fb6653c13 100644 --- a/packages/frontend/src/pages/miauth.vue +++ b/packages/frontend/src/pages/miauth.vue @@ -117,5 +117,6 @@ definePageMetadata(() => ({ border-radius: var(--MI-radius); background-color: var(--MI_THEME-panel); overflow-x: scroll; + white-space: nowrap; } From 074b7b0bee82ea692ba675bf4b35734b540b7752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:36:03 +0900 Subject: [PATCH 015/448] =?UTF-8?q?fix(frontend):=20=E5=85=AC=E9=96=8B?= =?UTF-8?q?=E7=AF=84=E5=9B=B2=E3=81=8C=E3=83=9B=E3=83=BC=E3=83=A0=E3=81=AE?= =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=88=E3=81=AE=E5=9F=8B=E3=82=81=E8=BE=BC?= =?UTF-8?q?=E3=81=BF=E3=82=A6=E3=82=A3=E3=82=B8=E3=82=A7=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=81=8C=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=81=BE=E3=82=8C=E3=81=AA?= =?UTF-8?q?=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=20(#1510?= =?UTF-8?q?2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Resolve frontend/backend contradiction for home visibility embeds This now uses the same check from `packages/frontend/src/scripts/get-note-menu.ts` * Update Changelog --------- Co-authored-by: CenTdemeern1 --- CHANGELOG.md | 2 ++ packages/backend/src/server/web/ClientServerService.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f2c36ffc3..595252d3e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Client - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 - Fix: サーバー情報メニューに区切り線が不足していたのを修正 +- Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803) ### Server - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index e356e0d76c..1a75096c4e 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -871,7 +871,7 @@ export class ClientServerService { }); if (note == null) return; - if (note.visibility !== 'public') return; + if (['specified', 'followers'].includes(note.visibility)) return; if (note.userHost != null) return; const _note = await this.noteEntityService.pack(note, null, { detail: true }); From e8bf6285cb023bd2031e85de3a3eb495bf4afb2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:42:12 +0900 Subject: [PATCH 016/448] =?UTF-8?q?fix(frontend):=20=E3=83=8E=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=8C=E3=83=AD=E3=82=B0=E3=82=A4=E3=83=B3=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=82=8B=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC?= =?UTF-8?q?=E3=81=97=E3=81=8B=E8=A6=8B=E3=82=8C=E3=81=AA=E3=81=84=E5=A0=B4?= =?UTF-8?q?=E5=90=88=E3=81=AB=E3=83=AD=E3=82=B0=E3=82=A4=E3=83=B3=E3=82=92?= =?UTF-8?q?=E3=82=AD=E3=83=A3=E3=83=B3=E3=82=BB=E3=83=AB=E3=81=97=E3=81=9F?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=9D=E3=81=AE=E5=BE=8C=E3=81=AE=E5=8B=95?= =?UTF-8?q?=E7=B7=9A=E3=81=8C=E3=81=AA=E3=81=8F=E3=81=AA=E3=82=8B=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=20(#15101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): ノートがログインしているユーザーしか見れない場合にログインをキャンセルすると一切の処理が停止する問題を修正 * Update Changelog --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + packages/frontend/src/pages/note.vue | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 595252d3e4..6b9ae480af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Client - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 - Fix: サーバー情報メニューに区切り線が不足していたのを修正 +- Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正 - Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803) diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index 3e1d04bd6d..5cf8f56776 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -50,6 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + From 3e0fcaeca89b9adf42a89f82e30fb2ad3cc6d5ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 16 Dec 2024 09:02:38 +0900 Subject: [PATCH 019/448] =?UTF-8?q?fix(frontend):=20=E7=B5=B5=E6=96=87?= =?UTF-8?q?=E5=AD=97=E7=AE=A1=E7=90=86=E7=94=BB=E9=9D=A2=E3=81=A7=E7=B5=B5?= =?UTF-8?q?=E6=96=87=E5=AD=97=E3=81=8C=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=81=93=E3=81=A8=E3=81=8C=E3=81=82=E3=82=8B?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=20(#15128)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): 絵文字管理画面で絵文字が表示されないことがある問題を修正 * Update Changelog * optimize --- CHANGELOG.md | 1 + packages/frontend/src/pages/custom-emojis-manager.vue | 5 +++-- packages/frontend/src/pages/emoji-edit-dialog.vue | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4293c94376..ee82834de7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正 - Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803) +- Fix: 絵文字管理画面で一部の絵文字が表示されない問題を修正 ### Server - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index cae3f3ede9..789a63eb90 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -239,5 +241,9 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar margin: 0; padding: 0; font-size: 90%; + + &.exceeded { + color: var(--MI_THEME-error); + } } From 4120c9ab10bd2974b95006656a424f1428348f6c 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: Sat, 4 Jan 2025 15:44:31 +0900 Subject: [PATCH 048/448] =?UTF-8?q?fix(frontend):=20=E3=82=A2=E3=82=AB?= =?UTF-8?q?=E3=82=A6=E3=83=B3=E3=83=88=E4=B8=80=E8=A6=A7=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=A7=E3=80=81=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E6=83=85?= =?UTF-8?q?=E5=A0=B1=E3=81=AE=E5=8F=96=E5=BE=97=E3=81=AB=E5=A4=B1=E6=95=97?= =?UTF-8?q?=E3=81=97=E3=81=9F=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=81=8C=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=20(#15183)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): アカウント一覧画面で、ユーザー情報の取得に失敗したアカウントが表示されない問題を修正 * Update Changelog * :art: --- CHANGELOG.md | 1 + .../frontend/src/pages/settings/accounts.vue | 107 ++++++++++++++---- 2 files changed, 88 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a4576a76c..875d14ef68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Fix: MiAuth認可画面で、認可処理に失敗した場合でもコールバックURLに遷移してしまう問題を修正 (Cherry-picked from https://github.com/TeamNijimiss/misskey/commit/800359623e41a662551d774de15b0437b6849bb4) - Fix: ノート作成画面でファイルの添付可能個数を超えてもノートボタンが押せていた問題を修正 +- Fix: 「アカウントを管理」画面で、ユーザー情報の取得に失敗したアカウント(削除されたアカウントなど)が表示されない問題を修正 ### Server - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue index 97e960675f..c2588736b3 100644 --- a/packages/frontend/src/pages/settings/accounts.vue +++ b/packages/frontend/src/pages/settings/accounts.vue @@ -12,7 +12,16 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.reloadAccountsList }} - + @@ -29,9 +38,10 @@ import { getAccounts, removeAccount as _removeAccount, login, $i, getAccountWith import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; +import { MenuItem } from '@/types/menu'; const storedAccounts = ref<{ id: string, token: string }[] | null>(null); -const accounts = ref([]); +const accounts = ref(new Map()); const init = async () => { getAccounts().then(accounts => { @@ -41,21 +51,35 @@ const init = async () => { userIds: storedAccounts.value.map(x => x.id), }); }).then(response => { - accounts.value = response; + if (storedAccounts.value == null) return; + accounts.value = new Map(storedAccounts.value.map(x => [x.id, response.find((y: Misskey.entities.UserDetailed) => y.id === x.id) ?? null])); }); }; -function menu(account: Misskey.entities.UserDetailed, ev: MouseEvent) { - os.popupMenu([{ - text: i18n.ts.switch, - icon: 'ti ti-switch-horizontal', - action: () => switchAccount(account), - }, { - text: i18n.ts.logout, - icon: 'ti ti-trash', - danger: true, - action: () => removeAccount(account), - }], ev.currentTarget ?? ev.target); +function menu(account: Misskey.entities.UserDetailed | string, ev: MouseEvent) { + let menu: MenuItem[]; + + if (typeof account === 'string') { + menu = [{ + text: i18n.ts.logout, + icon: 'ti ti-trash', + danger: true, + action: () => removeAccount(account), + }]; + } else { + menu = [{ + text: i18n.ts.switch, + icon: 'ti ti-switch-horizontal', + action: () => switchAccount(account.id), + }, { + text: i18n.ts.logout, + icon: 'ti ti-trash', + danger: true, + action: () => removeAccount(account.id), + }]; + } + + os.popupMenu(menu, ev.currentTarget ?? ev.target); } function addAccount(ev: MouseEvent) { @@ -68,9 +92,9 @@ function addAccount(ev: MouseEvent) { }], ev.currentTarget ?? ev.target); } -async function removeAccount(account: Misskey.entities.UserDetailed) { - await _removeAccount(account.id); - accounts.value = accounts.value.filter(x => x.id !== account.id); +async function removeAccount(id: string) { + await _removeAccount(id); + accounts.value.delete(id); } function addExistingAccount() { @@ -90,9 +114,9 @@ function createAccount() { }); } -async function switchAccount(account: Misskey.entities.UserDetailed) { +async function switchAccount(id: string) { const fetchedAccounts = await getAccounts(); - const token = fetchedAccounts.find(x => x.id === account.id)!.token; + const token = fetchedAccounts.find(x => x.id === id)!.token; switchAccountWithToken(token); } @@ -112,6 +136,49 @@ definePageMetadata(() => ({ From 6c9eea2c0f3cda05101060bd2b85d76f6af23ceb 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: Sat, 4 Jan 2025 15:55:22 +0900 Subject: [PATCH 049/448] =?UTF-8?q?[ci=20skip]=20Update=20CHANGELOG.md=20(?= =?UTF-8?q?=E6=9B=B8=E3=81=8D=E6=96=B9=E3=82=92=E6=8F=83=E3=81=88=E3=82=8B?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 875d14ef68..f7ba1afc52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,10 +22,10 @@ - Fix: 「アカウントを管理」画面で、ユーザー情報の取得に失敗したアカウント(削除されたアカウントなど)が表示されない問題を修正 ### Server +- Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) -- Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように ## 2024.11.0 From 8ad97e5ede6b1cdfe9472073352ede46e585e7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Tue, 7 Jan 2025 21:19:59 +0900 Subject: [PATCH 050/448] =?UTF-8?q?fix(backend):=20disableClustering?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E6=99=82=E3=81=AE=E5=88=9D=E6=9C=9F=E5=8C=96?= =?UTF-8?q?=E3=83=AD=E3=82=B8=E3=83=83=E3=82=AF=E3=82=92=E8=AA=BF=E6=95=B4?= =?UTF-8?q?=20(#15224)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): disableClustering設定時の初期化ロジックを調整 * onlyServer かつ enableCluster な場合、メインプロセスでlistenするとワーカープロセス側のlistenと衝突するため、メインプロセスはforkのみに制限する(listenしない) * ログの追加 * fix CHANGELOG.md * fix comment --- CHANGELOG.md | 2 +- packages/backend/src/boot/entry.ts | 20 +++++++++++------- packages/backend/src/boot/master.ts | 32 ++++++++++++++++++++--------- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7ba1afc52..f065ed307f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) - +- Fix: disableClustering設定時の初期化ロジックを調整( #15223 ) ## 2024.11.0 diff --git a/packages/backend/src/boot/entry.ts b/packages/backend/src/boot/entry.ts index 25375c3015..da585ad68d 100644 --- a/packages/backend/src/boot/entry.ts +++ b/packages/backend/src/boot/entry.ts @@ -68,16 +68,22 @@ process.on('exit', code => { //#endregion -if (cluster.isPrimary || envOption.disableClustering) { - await masterMain(); - +if (!envOption.disableClustering) { if (cluster.isPrimary) { + logger.info(`Start main process... pid: ${process.pid}`); + await masterMain(); ev.mount(); + } else if (cluster.isWorker) { + logger.info(`Start worker process... pid: ${process.pid}`); + await workerMain(); + } else { + throw new Error('Unknown process type'); } -} - -if (cluster.isWorker || envOption.disableClustering) { - await workerMain(); +} else { + // 非clusterの場合はMasterのみが起動するため、Workerの処理は行わない(cluster.isWorker === trueの状態でこのブロックに来ることはない) + logger.info(`Start main process... pid: ${process.pid}`); + await masterMain(); + ev.mount(); } readyRef.value = true; diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 4bc5c799cf..2b181af675 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -91,25 +91,37 @@ export async function masterMain() { }); } - if (envOption.disableClustering) { + bootLogger.info( + `mode: [disableClustering: ${envOption.disableClustering}, onlyServer: ${envOption.onlyServer}, onlyQueue: ${envOption.onlyQueue}]` + ); + + if (!envOption.disableClustering) { + // clusterモジュール有効時 + if (envOption.onlyServer) { - await server(); + // onlyServer かつ enableCluster な場合、メインプロセスはforkのみに制限する(listenしない)。 + // ワーカープロセス側でlistenすると、メインプロセスでポートへの着信を受け入れてワーカープロセスへの分配を行う動作をする。 + // そのため、メインプロセスでも直接listenするとポートの競合が発生して起動に失敗してしまう。 + // see: https://nodejs.org/api/cluster.html#cluster } else if (envOption.onlyQueue) { await jobQueue(); } else { await server(); await jobQueue(); } - } else { - if (envOption.onlyServer) { - // nop - } else if (envOption.onlyQueue) { - // nop - } else { - await server(); - } await spawnWorkers(config.clusterLimit); + } else { + // clusterモジュール無効時 + + if (envOption.onlyServer) { + await server(); + } else if (envOption.onlyQueue) { + await jobQueue(); + } else { + await server(); + await jobQueue(); + } } if (envOption.onlyQueue) { From 99ba7ebaa2ea60c4077e0f5af9638ab569314bfc Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Tue, 7 Jan 2025 21:21:05 +0900 Subject: [PATCH 051/448] =?UTF-8?q?fix(frontend-shared):=20nodemon?= =?UTF-8?q?=E3=82=92devDependencies=E3=81=AB=E8=BF=BD=E5=8A=A0=20(#15225)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend-shared/package.json | 1 + pnpm-lock.yaml | 123 +++++++++++--------------- 2 files changed, 52 insertions(+), 72 deletions(-) diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index f8770f33f2..4e681393c6 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -26,6 +26,7 @@ "@typescript-eslint/parser": "7.17.0", "esbuild": "0.24.0", "eslint-plugin-vue": "9.31.0", + "nodemon": "3.1.7", "typescript": "5.6.3", "vue-eslint-parser": "9.4.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0ec950181..f50c635bf0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -142,7 +142,7 @@ importers: version: 10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/testing': specifier: 10.4.7 - version: 10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)) + version: 10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7) '@peertube/http-signature': specifier: 1.7.0 version: 1.7.0 @@ -1163,7 +1163,7 @@ importers: version: 7.17.0(eslint@9.14.0)(typescript@5.6.3) '@vitest/coverage-v8': specifier: 1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.4)(terser@5.36.0)) + version: 1.6.0(vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.4)(terser@5.36.0)) '@vue/runtime-core': specifier: 3.5.12 version: 3.5.12 @@ -1240,6 +1240,9 @@ importers: eslint-plugin-vue: specifier: 9.31.0 version: 9.31.0(eslint@9.14.0) + nodemon: + specifier: 3.1.7 + version: 3.1.7 typescript: specifier: 5.6.3 version: 5.6.3 @@ -10942,6 +10945,9 @@ packages: vue-component-type-helpers@2.1.10: resolution: {integrity: sha512-lfgdSLQKrUmADiSV6PbBvYgQ33KF3Ztv6gP85MfGaGaSGMTXORVaHT1EHfsqCgzRNBstPKYDmvAV9Do5CmJ07A==} + vue-component-type-helpers@2.2.0: + resolution: {integrity: sha512-cYrAnv2me7bPDcg9kIcGwjJiSB6Qyi08+jLDo9yuvoFQjzHiPTzML7RnkJB1+3P6KMsX/KbCD4QE3Tv/knEllw==} + vue-demi@0.14.7: resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} engines: {node: '>=12'} @@ -11780,7 +11786,7 @@ snapshots: '@babel/traverse': 7.23.5 '@babel/types': 7.24.7 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -11800,7 +11806,7 @@ snapshots: '@babel/traverse': 7.24.7 '@babel/types': 7.24.7 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -12059,7 +12065,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.25.6 '@babel/types': 7.24.7 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -12074,7 +12080,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.25.6 '@babel/types': 7.25.6 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -12465,7 +12471,7 @@ snapshots: '@eslint/config-array@0.18.0': dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -12475,7 +12481,7 @@ snapshots: '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) espree: 10.3.0 globals: 14.0.0 ignore: 5.3.1 @@ -12985,7 +12991,7 @@ snapshots: nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.6.0 + semver: 7.6.3 tar: 6.2.1 transitivePeerDependencies: - encoding @@ -13180,7 +13186,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/testing@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7))': + '@nestjs/testing@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7)': dependencies: '@nestjs/common': 10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -14554,7 +14560,7 @@ snapshots: ts-dedent: 2.2.0 type-fest: 2.19.0 vue: 3.5.12(typescript@5.6.3) - vue-component-type-helpers: 2.1.10 + vue-component-type-helpers: 2.2.0 '@swc/cli@0.3.12(@swc/core@1.9.2)(chokidar@3.5.3)': dependencies: @@ -15264,7 +15270,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.6.3) '@typescript-eslint/utils': 7.1.0(eslint@9.14.0)(typescript@5.6.3) - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) eslint: 9.14.0 ts-api-utils: 1.0.1(typescript@5.6.3) optionalDependencies: @@ -15276,7 +15282,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.3) '@typescript-eslint/utils': 7.17.0(eslint@9.14.0)(typescript@5.6.3) - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) eslint: 9.14.0 ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: @@ -15292,11 +15298,11 @@ snapshots: dependencies: '@typescript-eslint/types': 7.1.0 '@typescript-eslint/visitor-keys': 7.1.0 - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.0 + semver: 7.6.3 ts-api-utils: 1.0.1(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 @@ -15307,11 +15313,11 @@ snapshots: dependencies: '@typescript-eslint/types': 7.17.0 '@typescript-eslint/visitor-keys': 7.17.0 - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 - semver: 7.6.0 + semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 @@ -15327,7 +15333,7 @@ snapshots: '@typescript-eslint/types': 7.1.0 '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.6.3) eslint: 9.14.0 - semver: 7.6.0 + semver: 7.6.3 transitivePeerDependencies: - supports-color - typescript @@ -15384,7 +15390,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.4)(terser@5.36.0))': + '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.4)(terser@5.36.0))': dependencies: '@ampproject/remapping': 2.2.1 '@bcoe/v8-coverage': 0.2.3 @@ -15399,7 +15405,7 @@ snapshots: std-env: 3.7.0 strip-literal: 2.1.0 test-exclude: 6.0.0 - vitest: 1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.4)(terser@5.36.0) + vitest: 1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.4)(terser@5.36.0) transitivePeerDependencies: - supports-color @@ -15637,14 +15643,14 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color optional: true agent-base@7.1.0: dependencies: - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -17248,7 +17254,7 @@ snapshots: esbuild-register@3.5.0(esbuild@0.24.0): dependencies: - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) esbuild: 0.24.0 transitivePeerDependencies: - supports-color @@ -17490,7 +17496,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint-scope: 8.2.0 eslint-visitor-keys: 4.2.0 @@ -17935,7 +17941,7 @@ snapshots: follow-redirects@1.15.9(debug@4.3.7): optionalDependencies: - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) for-each@0.3.3: dependencies: @@ -18138,7 +18144,7 @@ snapshots: dependencies: foreground-child: 3.1.1 jackspeak: 2.3.6 - minimatch: 9.0.3 + minimatch: 9.0.4 minipass: 7.0.4 path-scurry: 1.10.1 @@ -18405,7 +18411,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -18444,7 +18450,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color optional: true @@ -18452,14 +18458,14 @@ snapshots: https-proxy-agent@7.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.0 - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -18805,7 +18811,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -18814,7 +18820,7 @@ snapshots: istanbul-lib-source-maps@5.0.4: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -19108,7 +19114,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.0 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -19215,35 +19221,6 @@ snapshots: jsdoc-type-pratt-parser@4.1.0: {} - jsdom@24.1.1: - dependencies: - cssstyle: 4.0.1 - data-urls: 5.0.0 - decimal.js: 10.4.3 - form-data: 4.0.1 - html-encoding-sniffer: 4.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.12 - parse5: 7.2.1 - rrweb-cssom: 0.7.1 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 4.1.4 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 3.1.1 - whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 - ws: 8.18.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) - xml-name-validator: 5.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - optional: true - jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3): dependencies: cssstyle: 4.0.1 @@ -19936,7 +19913,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.0 @@ -20276,7 +20253,7 @@ snapshots: make-fetch-happen: 13.0.0 nopt: 7.2.0 proc-log: 4.2.0 - semver: 7.6.0 + semver: 7.6.3 tar: 6.2.1 which: 4.0.0 transitivePeerDependencies: @@ -21396,7 +21373,7 @@ snapshots: require-in-the-middle@7.3.0: dependencies: - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: @@ -21732,7 +21709,7 @@ snapshots: simple-update-notifier@2.0.0: dependencies: - semver: 7.6.0 + semver: 7.6.3 sinon@16.1.3: dependencies: @@ -21821,7 +21798,7 @@ snapshots: socks-proxy-agent@8.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -21930,7 +21907,7 @@ snapshots: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -22677,7 +22654,7 @@ snapshots: vite-node@1.6.0(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0): dependencies: cac: 6.7.14 - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.0.1 vite: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0) @@ -22695,7 +22672,7 @@ snapshots: vite-node@1.6.0(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0): dependencies: cac: 6.7.14 - debug: 4.3.5 + debug: 4.3.7(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.0.1 vite: 5.4.11(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0) @@ -22777,7 +22754,7 @@ snapshots: - supports-color - terser - vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.4)(terser@5.36.0): + vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.4)(terser@5.36.0): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -22802,7 +22779,7 @@ snapshots: optionalDependencies: '@types/node': 22.9.0 happy-dom: 10.0.3 - jsdom: 24.1.1 + jsdom: 24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4) transitivePeerDependencies: - less - lightningcss @@ -22853,6 +22830,8 @@ snapshots: vue-component-type-helpers@2.1.10: {} + vue-component-type-helpers@2.2.0: {} + vue-demi@0.14.7(vue@3.5.12(typescript@5.6.3)): dependencies: vue: 3.5.12(typescript@5.6.3) From f7da2bad6f0b25652ded11e6a9f86efc40872200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Tue, 7 Jan 2025 21:23:05 +0900 Subject: [PATCH 052/448] =?UTF-8?q?fix(frontend):=20frontend=20/=20fronten?= =?UTF-8?q?d-embed=E3=81=AB=E3=81=82=E3=82=8Btsconfig.json=E3=81=AEmodule?= =?UTF-8?q?=E3=82=92ES2022=E3=81=AB=E3=81=99=E3=82=8B=20(#15215)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): frontend / frontend-embedにあるtsconfig.jsonのmoduleをES2022にする * fixed errors * fixed errors * fixed errors --- packages/frontend-embed/tsconfig.json | 4 ++-- packages/frontend/tsconfig.json | 4 ++-- packages/misskey-js/etc/misskey-js.api.md | 4 ++-- packages/misskey-js/src/streaming.ts | 12 +++++++----- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/frontend-embed/tsconfig.json b/packages/frontend-embed/tsconfig.json index 45f933dc28..60164a7e3d 100644 --- a/packages/frontend-embed/tsconfig.json +++ b/packages/frontend-embed/tsconfig.json @@ -10,8 +10,8 @@ "declaration": false, "sourceMap": false, "target": "ES2022", - "module": "nodenext", - "moduleResolution": "nodenext", + "module": "ES2022", + "moduleResolution": "Bundler", "removeComments": false, "noLib": false, "strict": true, diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index 4e5ca7f559..68b6651a56 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -10,8 +10,8 @@ "declaration": false, "sourceMap": false, "target": "ES2022", - "module": "nodenext", - "moduleResolution": "nodenext", + "module": "ES2022", + "moduleResolution": "Bundler", "removeComments": false, "noLib": false, "strict": true, diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 01a3dbbb30..50002f1983 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -6,8 +6,8 @@ import type { AuthenticationResponseJSON } from '@simplewebauthn/types'; import { EventEmitter } from 'eventemitter3'; +import { Options } from 'reconnecting-websocket'; import type { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types'; -import _ReconnectingWebsocket from 'reconnecting-websocket'; // Warning: (ae-forgotten-export) The symbol "components" needs to be exported by the entry point index.d.ts // @@ -3150,7 +3150,7 @@ export class Stream extends EventEmitter implements IStream { constructor(origin: string, user: { token: string; } | null, options?: { - WebSocket?: _ReconnectingWebsocket.Options['WebSocket']; + WebSocket?: Options['WebSocket']; }); // (undocumented) close(): void; diff --git a/packages/misskey-js/src/streaming.ts b/packages/misskey-js/src/streaming.ts index 6e34ec1508..fec6114cca 100644 --- a/packages/misskey-js/src/streaming.ts +++ b/packages/misskey-js/src/streaming.ts @@ -1,8 +1,10 @@ import { EventEmitter } from 'eventemitter3'; -import _ReconnectingWebsocket from 'reconnecting-websocket'; +import _ReconnectingWebSocket, { Options } from 'reconnecting-websocket'; import type { BroadcastEvents, Channels } from './streaming.types.js'; -const ReconnectingWebsocket = _ReconnectingWebsocket as unknown as typeof _ReconnectingWebsocket['default']; +// コンストラクタとクラスそのものの定義が上手く解決出来ないため再定義 +const ReconnectingWebSocketConstructor = _ReconnectingWebSocket as unknown as typeof _ReconnectingWebSocket.default; +type ReconnectingWebSocket = _ReconnectingWebSocket.default; export function urlQuery(obj: Record): string { const params = Object.entries(obj) @@ -43,7 +45,7 @@ export interface IStream extends EventEmitter { */ // eslint-disable-next-line import/no-default-export export default class Stream extends EventEmitter implements IStream { - private stream: _ReconnectingWebsocket.default; + private stream: ReconnectingWebSocket; public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing'; private sharedConnectionPools: Pool[] = []; private sharedConnections: SharedConnection[] = []; @@ -51,7 +53,7 @@ export default class Stream extends EventEmitter implements IStrea private idCounter = 0; constructor(origin: string, user: { token: string; } | null, options?: { - WebSocket?: _ReconnectingWebsocket.Options['WebSocket']; + WebSocket?: Options['WebSocket']; }) { super(); @@ -80,7 +82,7 @@ export default class Stream extends EventEmitter implements IStrea const wsOrigin = origin.replace('http://', 'ws://').replace('https://', 'wss://'); - this.stream = new ReconnectingWebsocket(`${wsOrigin}/streaming?${query}`, '', { + this.stream = new ReconnectingWebSocketConstructor(`${wsOrigin}/streaming?${query}`, '', { minReconnectionDelay: 1, // https://github.com/pladaria/reconnecting-websocket/issues/91 WebSocket: options.WebSocket, }); From bbe80af1dde195ff0ac6713db967b556acebb30c Mon Sep 17 00:00:00 2001 From: Take-John Date: Tue, 7 Jan 2025 21:28:48 +0900 Subject: [PATCH 053/448] =?UTF-8?q?Fix:=20aiscript=E3=83=87=E3=82=A3?= =?UTF-8?q?=E3=83=AC=E3=82=AF=E3=83=88=E3=83=AA=E5=86=85=E3=81=AE=E5=9E=8B?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E8=A7=A3=E6=B6=88=E3=81=A8=E5=8D=98?= =?UTF-8?q?=E4=BD=93=E3=83=86=E3=82=B9=E3=83=88=20(#15191)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * AiScript APIの型エラーに対処 * AiScript UI APIのテスト作成 * onInputなどがPromiseを返すように * AiScript共通APIのテスト作成 * CHANGELOG記載 * 定数のテストをconcurrentに * vi.mockを使用 * misskeyApiをmisskeyApiUntypedのエイリアスとする * 期待されるエラーメッセージを修正 * Mk:removeのテスト * misskeyApiの型を変更 --- CHANGELOG.md | 1 + packages/frontend/src/scripts/aiscript/api.ts | 38 +- .../frontend/src/scripts/aiscript/common.ts | 15 + packages/frontend/src/scripts/aiscript/ui.ts | 139 +-- packages/frontend/src/scripts/misskey-api.ts | 18 +- packages/frontend/test/aiscript/api.test.ts | 401 +++++++++ .../frontend/test/aiscript/common.test.ts | 23 + packages/frontend/test/aiscript/ui.test.ts | 825 ++++++++++++++++++ 8 files changed, 1396 insertions(+), 64 deletions(-) create mode 100644 packages/frontend/src/scripts/aiscript/common.ts create mode 100644 packages/frontend/test/aiscript/api.test.ts create mode 100644 packages/frontend/test/aiscript/common.test.ts create mode 100644 packages/frontend/test/aiscript/ui.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f065ed307f..10de8b5fc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ (Cherry-picked from https://github.com/TeamNijimiss/misskey/commit/800359623e41a662551d774de15b0437b6849bb4) - Fix: ノート作成画面でファイルの添付可能個数を超えてもノートボタンが押せていた問題を修正 - Fix: 「アカウントを管理」画面で、ユーザー情報の取得に失敗したアカウント(削除されたアカウントなど)が表示されない問題を修正 +- Enhance: AiScriptの拡張API関数において引数の型チェックをより厳格に ### Server - Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts index 8afe88eec6..e203c51bba 100644 --- a/packages/frontend/src/scripts/aiscript/api.ts +++ b/packages/frontend/src/scripts/aiscript/api.ts @@ -3,14 +3,24 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { utils, values } from '@syuilo/aiscript'; +import { errors, utils, values } from '@syuilo/aiscript'; import * as Misskey from 'misskey-js'; +import { url, lang } from '@@/js/config.js'; +import { assertStringAndIsIn } from './common.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { $i } from '@/account.js'; import { miLocalStorage } from '@/local-storage.js'; import { customEmojis } from '@/custom-emojis.js'; -import { url, lang } from '@@/js/config.js'; + +const DIALOG_TYPES = [ + 'error', + 'info', + 'success', + 'warning', + 'waiting', + 'question', +] as const; export function aiScriptReadline(q: string): Promise { return new Promise(ok => { @@ -22,15 +32,20 @@ export function aiScriptReadline(q: string): Promise { }); } -export function createAiScriptEnv(opts) { +export function createAiScriptEnv(opts: { storageKey: string, token?: string }) { return { USER_ID: $i ? values.STR($i.id) : values.NULL, - USER_NAME: $i ? values.STR($i.name) : values.NULL, + USER_NAME: $i?.name ? values.STR($i.name) : values.NULL, USER_USERNAME: $i ? values.STR($i.username) : values.NULL, CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value), LOCALE: values.STR(lang), SERVER_URL: values.STR(url), 'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => { + utils.assertString(title); + utils.assertString(text); + if (type != null) { + assertStringAndIsIn(type, DIALOG_TYPES); + } await os.alert({ type: type ? type.value : 'info', title: title.value, @@ -39,6 +54,11 @@ export function createAiScriptEnv(opts) { return values.NULL; }), 'Mk:confirm': values.FN_NATIVE(async ([title, text, type]) => { + utils.assertString(title); + utils.assertString(text); + if (type != null) { + assertStringAndIsIn(type, DIALOG_TYPES); + } const confirm = await os.confirm({ type: type ? type.value : 'question', title: title.value, @@ -48,14 +68,20 @@ export function createAiScriptEnv(opts) { }), 'Mk:api': values.FN_NATIVE(async ([ep, param, token]) => { utils.assertString(ep); - if (ep.value.includes('://')) throw new Error('invalid endpoint'); + if (ep.value.includes('://')) { + throw new errors.AiScriptRuntimeError('invalid endpoint'); + } if (token) { utils.assertString(token); // バグがあればundefinedもあり得るため念のため if (typeof token.value !== 'string') throw new Error('invalid token'); } const actualToken: string|null = token?.value ?? opts.token ?? null; - return misskeyApi(ep.value, utils.valToJs(param), actualToken).then(res => { + if (param == null) { + throw new errors.AiScriptRuntimeError('expected param'); + } + utils.assertObject(param); + return misskeyApi(ep.value, utils.valToJs(param) as object, actualToken).then(res => { return utils.jsToVal(res); }, err => { return values.ERROR('request_failed', utils.jsToVal(err)); diff --git a/packages/frontend/src/scripts/aiscript/common.ts b/packages/frontend/src/scripts/aiscript/common.ts new file mode 100644 index 0000000000..de6fa1d633 --- /dev/null +++ b/packages/frontend/src/scripts/aiscript/common.ts @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { errors, utils, type values } from '@syuilo/aiscript'; + +export function assertStringAndIsIn(value: values.Value | undefined, expects: A): asserts value is values.VStr & { value: A[number] } { + utils.assertString(value); + const str = value.value; + if (!expects.includes(str)) { + const expected = expects.map((expect) => `"${expect}"`).join(', '); + throw new errors.AiScriptRuntimeError(`"${value.value}" is not in ${expected}`); + } +} diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts index 2b386bebb8..ca92b27ff5 100644 --- a/packages/frontend/src/scripts/aiscript/ui.ts +++ b/packages/frontend/src/scripts/aiscript/ui.ts @@ -7,6 +7,15 @@ import { utils, values } from '@syuilo/aiscript'; import { v4 as uuid } from 'uuid'; import { ref, Ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { assertStringAndIsIn } from './common.js'; + +const ALIGNS = ['left', 'center', 'right'] as const; +const FONTS = ['serif', 'sans-serif', 'monospace'] as const; +const BORDER_STYLES = ['hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'] as const; + +type Align = (typeof ALIGNS)[number]; +type Font = (typeof FONTS)[number]; +type BorderStyle = (typeof BORDER_STYLES)[number]; export type AsUiComponentBase = { id: string; @@ -21,13 +30,13 @@ export type AsUiRoot = AsUiComponentBase & { export type AsUiContainer = AsUiComponentBase & { type: 'container'; children?: AsUiComponent['id'][]; - align?: 'left' | 'center' | 'right'; + align?: Align; bgColor?: string; fgColor?: string; - font?: 'serif' | 'sans-serif' | 'monospace'; + font?: Font; borderWidth?: number; borderColor?: string; - borderStyle?: 'hidden' | 'dotted' | 'dashed' | 'solid' | 'double' | 'groove' | 'ridge' | 'inset' | 'outset'; + borderStyle?: BorderStyle; borderRadius?: number; padding?: number; rounded?: boolean; @@ -40,7 +49,7 @@ export type AsUiText = AsUiComponentBase & { size?: number; bold?: boolean; color?: string; - font?: 'serif' | 'sans-serif' | 'monospace'; + font?: Font; }; export type AsUiMfm = AsUiComponentBase & { @@ -49,14 +58,14 @@ export type AsUiMfm = AsUiComponentBase & { size?: number; bold?: boolean; color?: string; - font?: 'serif' | 'sans-serif' | 'monospace'; - onClickEv?: (evId: string) => void + font?: Font; + onClickEv?: (evId: string) => Promise; }; export type AsUiButton = AsUiComponentBase & { type: 'button'; text?: string; - onClick?: () => void; + onClick?: () => Promise; primary?: boolean; rounded?: boolean; disabled?: boolean; @@ -69,7 +78,7 @@ export type AsUiButtons = AsUiComponentBase & { export type AsUiSwitch = AsUiComponentBase & { type: 'switch'; - onChange?: (v: boolean) => void; + onChange?: (v: boolean) => Promise; default?: boolean; label?: string; caption?: string; @@ -77,7 +86,7 @@ export type AsUiSwitch = AsUiComponentBase & { export type AsUiTextarea = AsUiComponentBase & { type: 'textarea'; - onInput?: (v: string) => void; + onInput?: (v: string) => Promise; default?: string; label?: string; caption?: string; @@ -85,7 +94,7 @@ export type AsUiTextarea = AsUiComponentBase & { export type AsUiTextInput = AsUiComponentBase & { type: 'textInput'; - onInput?: (v: string) => void; + onInput?: (v: string) => Promise; default?: string; label?: string; caption?: string; @@ -93,7 +102,7 @@ export type AsUiTextInput = AsUiComponentBase & { export type AsUiNumberInput = AsUiComponentBase & { type: 'numberInput'; - onInput?: (v: number) => void; + onInput?: (v: number) => Promise; default?: number; label?: string; caption?: string; @@ -105,7 +114,7 @@ export type AsUiSelect = AsUiComponentBase & { text: string; value: string; }[]; - onChange?: (v: string) => void; + onChange?: (v: string) => Promise; default?: string; label?: string; caption?: string; @@ -140,11 +149,15 @@ export type AsUiPostForm = AsUiComponentBase & { export type AsUiComponent = AsUiRoot | AsUiContainer | AsUiText | AsUiMfm | AsUiButton | AsUiButtons | AsUiSwitch | AsUiTextarea | AsUiTextInput | AsUiNumberInput | AsUiSelect | AsUiFolder | AsUiPostFormButton | AsUiPostForm; +type Options = T extends AsUiButtons + ? Omit & { 'buttons'?: Options[] } + : Omit; + export function patch(id: string, def: values.Value, call: (fn: values.VFn, args: values.Value[]) => Promise) { // TODO } -function getRootOptions(def: values.Value | undefined): Omit { +function getRootOptions(def: values.Value | undefined): Options { utils.assertObject(def); const children = def.value.get('children'); @@ -153,30 +166,32 @@ function getRootOptions(def: values.Value | undefined): Omit { utils.assertObject(v); - return v.value.get('id').value; + const id = v.value.get('id'); + utils.assertString(id); + return id.value; }), }; } -function getContainerOptions(def: values.Value | undefined): Omit { +function getContainerOptions(def: values.Value | undefined): Options { utils.assertObject(def); const children = def.value.get('children'); if (children) utils.assertArray(children); const align = def.value.get('align'); - if (align) utils.assertString(align); + if (align) assertStringAndIsIn(align, ALIGNS); const bgColor = def.value.get('bgColor'); if (bgColor) utils.assertString(bgColor); const fgColor = def.value.get('fgColor'); if (fgColor) utils.assertString(fgColor); const font = def.value.get('font'); - if (font) utils.assertString(font); + if (font) assertStringAndIsIn(font, FONTS); const borderWidth = def.value.get('borderWidth'); if (borderWidth) utils.assertNumber(borderWidth); const borderColor = def.value.get('borderColor'); if (borderColor) utils.assertString(borderColor); const borderStyle = def.value.get('borderStyle'); - if (borderStyle) utils.assertString(borderStyle); + if (borderStyle) assertStringAndIsIn(borderStyle, BORDER_STYLES); const borderRadius = def.value.get('borderRadius'); if (borderRadius) utils.assertNumber(borderRadius); const padding = def.value.get('padding'); @@ -189,7 +204,9 @@ function getContainerOptions(def: values.Value | undefined): Omit { utils.assertObject(v); - return v.value.get('id').value; + const id = v.value.get('id'); + utils.assertString(id); + return id.value; }) : [], align: align?.value, fgColor: fgColor?.value, @@ -205,7 +222,7 @@ function getContainerOptions(def: values.Value | undefined): Omit { +function getTextOptions(def: values.Value | undefined): Options { utils.assertObject(def); const text = def.value.get('text'); @@ -217,7 +234,7 @@ function getTextOptions(def: values.Value | undefined): Omit Promise): Omit { +function getMfmOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Options { utils.assertObject(def); const text = def.value.get('text'); @@ -240,7 +257,7 @@ function getMfmOptions(def: values.Value | undefined, call: (fn: values.VFn, arg const color = def.value.get('color'); if (color) utils.assertString(color); const font = def.value.get('font'); - if (font) utils.assertString(font); + if (font) assertStringAndIsIn(font, FONTS); const onClickEv = def.value.get('onClickEv'); if (onClickEv) utils.assertFunction(onClickEv); @@ -250,13 +267,13 @@ function getMfmOptions(def: values.Value | undefined, call: (fn: values.VFn, arg bold: bold?.value, color: color?.value, font: font?.value, - onClickEv: (evId: string) => { - if (onClickEv) call(onClickEv, [values.STR(evId)]); + onClickEv: async (evId: string) => { + if (onClickEv) await call(onClickEv, [values.STR(evId)]); }, }; } -function getTextInputOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Omit { +function getTextInputOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Options { utils.assertObject(def); const onInput = def.value.get('onInput'); @@ -269,8 +286,8 @@ function getTextInputOptions(def: values.Value | undefined, call: (fn: values.VF if (caption) utils.assertString(caption); return { - onInput: (v) => { - if (onInput) call(onInput, [utils.jsToVal(v)]); + onInput: async (v) => { + if (onInput) await call(onInput, [utils.jsToVal(v)]); }, default: defaultValue?.value, label: label?.value, @@ -278,7 +295,7 @@ function getTextInputOptions(def: values.Value | undefined, call: (fn: values.VF }; } -function getTextareaOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Omit { +function getTextareaOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Options { utils.assertObject(def); const onInput = def.value.get('onInput'); @@ -291,8 +308,8 @@ function getTextareaOptions(def: values.Value | undefined, call: (fn: values.VFn if (caption) utils.assertString(caption); return { - onInput: (v) => { - if (onInput) call(onInput, [utils.jsToVal(v)]); + onInput: async (v) => { + if (onInput) await call(onInput, [utils.jsToVal(v)]); }, default: defaultValue?.value, label: label?.value, @@ -300,7 +317,7 @@ function getTextareaOptions(def: values.Value | undefined, call: (fn: values.VFn }; } -function getNumberInputOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Omit { +function getNumberInputOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Options { utils.assertObject(def); const onInput = def.value.get('onInput'); @@ -313,8 +330,8 @@ function getNumberInputOptions(def: values.Value | undefined, call: (fn: values. if (caption) utils.assertString(caption); return { - onInput: (v) => { - if (onInput) call(onInput, [utils.jsToVal(v)]); + onInput: async (v) => { + if (onInput) await call(onInput, [utils.jsToVal(v)]); }, default: defaultValue?.value, label: label?.value, @@ -322,7 +339,7 @@ function getNumberInputOptions(def: values.Value | undefined, call: (fn: values. }; } -function getButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Omit { +function getButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Options { utils.assertObject(def); const text = def.value.get('text'); @@ -338,8 +355,8 @@ function getButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, return { text: text?.value, - onClick: () => { - if (onClick) call(onClick, []); + onClick: async () => { + if (onClick) await call(onClick, []); }, primary: primary?.value, rounded: rounded?.value, @@ -347,7 +364,7 @@ function getButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, }; } -function getButtonsOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Omit { +function getButtonsOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Options { utils.assertObject(def); const buttons = def.value.get('buttons'); @@ -369,8 +386,8 @@ function getButtonsOptions(def: values.Value | undefined, call: (fn: values.VFn, return { text: text.value, - onClick: () => { - call(onClick, []); + onClick: async () => { + await call(onClick, []); }, primary: primary?.value, rounded: rounded?.value, @@ -380,7 +397,7 @@ function getButtonsOptions(def: values.Value | undefined, call: (fn: values.VFn, }; } -function getSwitchOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Omit { +function getSwitchOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Options { utils.assertObject(def); const onChange = def.value.get('onChange'); @@ -393,8 +410,8 @@ function getSwitchOptions(def: values.Value | undefined, call: (fn: values.VFn, if (caption) utils.assertString(caption); return { - onChange: (v) => { - if (onChange) call(onChange, [utils.jsToVal(v)]); + onChange: async (v) => { + if (onChange) await call(onChange, [utils.jsToVal(v)]); }, default: defaultValue?.value, label: label?.value, @@ -402,7 +419,7 @@ function getSwitchOptions(def: values.Value | undefined, call: (fn: values.VFn, }; } -function getSelectOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Omit { +function getSelectOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Options { utils.assertObject(def); const items = def.value.get('items'); @@ -428,8 +445,8 @@ function getSelectOptions(def: values.Value | undefined, call: (fn: values.VFn, value: value ? value.value : text.value, }; }) : [], - onChange: (v) => { - if (onChange) call(onChange, [utils.jsToVal(v)]); + onChange: async (v) => { + if (onChange) await call(onChange, [utils.jsToVal(v)]); }, default: defaultValue?.value, label: label?.value, @@ -437,7 +454,7 @@ function getSelectOptions(def: values.Value | undefined, call: (fn: values.VFn, }; } -function getFolderOptions(def: values.Value | undefined): Omit { +function getFolderOptions(def: values.Value | undefined): Options { utils.assertObject(def); const children = def.value.get('children'); @@ -450,7 +467,9 @@ function getFolderOptions(def: values.Value | undefined): Omit { utils.assertObject(v); - return v.value.get('id').value; + const id = v.value.get('id'); + utils.assertString(id); + return id.value; }) : [], title: title?.value ?? '', opened: opened?.value ?? true, @@ -475,7 +494,7 @@ function getPostFormProps(form: values.VObj): PostFormPropsForAsUi { }; } -function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Omit { +function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Options { utils.assertObject(def); const text = def.value.get('text'); @@ -497,7 +516,7 @@ function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: valu }; } -function getPostFormOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Omit { +function getPostFormOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise): Options { utils.assertObject(def); const form = def.value.get('form'); @@ -511,18 +530,26 @@ function getPostFormOptions(def: values.Value | undefined, call: (fn: values.VFn } export function registerAsUiLib(components: Ref[], done: (root: Ref) => void) { + type OptionsConverter = (def: values.Value | undefined, call: C) => Options; + const instances = {}; - function createComponentInstance(type: AsUiComponent['type'], def: values.Value | undefined, id: values.Value | undefined, getOptions: (def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise) => any, call: (fn: values.VFn, args: values.Value[]) => Promise) { + function createComponentInstance( + type: T['type'], + def: values.Value | undefined, + id: values.Value | undefined, + getOptions: OptionsConverter, + call: C, + ) { if (id) utils.assertString(id); const _id = id?.value ?? uuid(); const component = ref({ ...getOptions(def, call), type, id: _id, - }); + } as T); components.push(component); - const instance = values.OBJ(new Map([ + const instance = values.OBJ(new Map([ ['id', values.STR(_id)], ['update', values.FN_NATIVE(([def], opts) => { utils.assertObject(def); @@ -547,7 +574,7 @@ export function registerAsUiLib(components: Ref[], done: (root: R 'Ui:patch': values.FN_NATIVE(([id, val], opts) => { utils.assertString(id); utils.assertArray(val); - patch(id.value, val.value, opts.call); + // patch(id.value, val.value, opts.call); // TODO }), 'Ui:get': values.FN_NATIVE(([id], opts) => { @@ -566,7 +593,9 @@ export function registerAsUiLib(components: Ref[], done: (root: R rootComponent.value.children = children.value.map(v => { utils.assertObject(v); - return v.value.get('id').value; + const id = v.value.get('id'); + utils.assertString(id); + return id.value; }); }), diff --git a/packages/frontend/src/scripts/misskey-api.ts b/packages/frontend/src/scripts/misskey-api.ts index e7a92e2d5c..dc07ad477b 100644 --- a/packages/frontend/src/scripts/misskey-api.ts +++ b/packages/frontend/src/scripts/misskey-api.ts @@ -9,12 +9,24 @@ import { apiUrl } from '@@/js/config.js'; import { $i } from '@/account.js'; export const pendingApiRequestsCount = ref(0); +export type Endpoint = keyof Misskey.Endpoints; + +export type Request = Misskey.Endpoints[E]['req']; + +export type AnyRequest = + (E extends Endpoint ? Request : never) | object; + +export type Response> = + E extends Endpoint + ? P extends Request ? Misskey.api.SwitchCaseResponseType : never + : object; + // Implements Misskey.api.ApiClient.request export function misskeyApi< ResT = void, - E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints, - P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'], - _ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType : ResT, + E extends Endpoint | NonNullable = Endpoint, + P extends AnyRequest = E extends Endpoint ? Request : never, + _ResT = ResT extends void ? Response : ResT, >( endpoint: E, data: P & { i?: string | null; } = {} as any, diff --git a/packages/frontend/test/aiscript/api.test.ts b/packages/frontend/test/aiscript/api.test.ts new file mode 100644 index 0000000000..2a15a74249 --- /dev/null +++ b/packages/frontend/test/aiscript/api.test.ts @@ -0,0 +1,401 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { miLocalStorage } from '@/local-storage.js'; +import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js'; +import { errors, Interpreter, Parser, values } from '@syuilo/aiscript'; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi +} from 'vitest'; + +async function exe(script: string): Promise { + const outputs: values.Value[] = []; + const interpreter = new Interpreter( + createAiScriptEnv({ storageKey: 'widget' }), + { + in: aiScriptReadline, + out: (value) => { + outputs.push(value); + } + } + ); + const ast = Parser.parse(script); + await interpreter.exec(ast); + return outputs; +} + +let $iMock = vi.hoisted | null >( + () => null +); + +vi.mock('@/account.js', () => { + return { + get $i() { + return $iMock; + }, + }; +}); + +const osMock = vi.hoisted(() => { + return { + inputText: vi.fn(), + alert: vi.fn(), + confirm: vi.fn(), + }; +}); + +vi.mock('@/os.js', () => { + return osMock; +}); + +const misskeyApiMock = vi.hoisted(() => vi.fn()); + +vi.mock('@/scripts/misskey-api.js', () => { + return { misskeyApi: misskeyApiMock }; +}); + +describe('AiScript common API', () => { + afterAll(() => { + vi.unstubAllGlobals(); + }); + + describe('readline', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + test.sequential('ok', async () => { + osMock.inputText.mockImplementationOnce(async ({ title }) => { + expect(title).toBe('question'); + return { + canceled: false, + result: 'Hello', + }; + }); + const [res] = await exe(` + <: readline('question') + `); + expect(res).toStrictEqual(values.STR('Hello')); + expect(osMock.inputText).toHaveBeenCalledOnce(); + }); + + test.sequential('cancelled', async () => { + osMock.inputText.mockImplementationOnce(async ({ title }) => { + expect(title).toBe('question'); + return { + canceled: true, + result: undefined, + }; + }); + const [res] = await exe(` + <: readline('question') + `); + expect(res).toStrictEqual(values.STR('')); + expect(osMock.inputText).toHaveBeenCalledOnce(); + }); + }); + + describe('user constants', () => { + describe.sequential('logged in', () => { + beforeAll(() => { + $iMock = { + id: 'xxxxxxxx', + name: '藍', + username: 'ai', + }; + }); + + test.concurrent('USER_ID', async () => { + const [res] = await exe(` + <: USER_ID + `); + expect(res).toStrictEqual(values.STR('xxxxxxxx')); + }); + + test.concurrent('USER_NAME', async () => { + const [res] = await exe(` + <: USER_NAME + `); + expect(res).toStrictEqual(values.STR('藍')); + }); + + test.concurrent('USER_USERNAME', async () => { + const [res] = await exe(` + <: USER_USERNAME + `); + expect(res).toStrictEqual(values.STR('ai')); + }); + }); + + describe.sequential('not logged in', () => { + beforeAll(() => { + $iMock = null; + }); + + test.concurrent('USER_ID', async () => { + const [res] = await exe(` + <: USER_ID + `); + expect(res).toStrictEqual(values.NULL); + }); + + test.concurrent('USER_NAME', async () => { + const [res] = await exe(` + <: USER_NAME + `); + expect(res).toStrictEqual(values.NULL); + }); + + test.concurrent('USER_USERNAME', async () => { + const [res] = await exe(` + <: USER_USERNAME + `); + expect(res).toStrictEqual(values.NULL); + }); + }); + }); + + describe('dialog', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + test.sequential('ok', async () => { + osMock.alert.mockImplementationOnce(async ({ type, title, text }) => { + expect(type).toBe('success'); + expect(title).toBe('Hello'); + expect(text).toBe('world'); + }); + const [res] = await exe(` + <: Mk:dialog('Hello', 'world', 'success') + `); + expect(res).toStrictEqual(values.NULL); + expect(osMock.alert).toHaveBeenCalledOnce(); + }); + + test.sequential('omit type', async () => { + osMock.alert.mockImplementationOnce(async ({ type, title, text }) => { + expect(type).toBe('info'); + expect(title).toBe('Hello'); + expect(text).toBe('world'); + }); + const [res] = await exe(` + <: Mk:dialog('Hello', 'world') + `); + expect(res).toStrictEqual(values.NULL); + expect(osMock.alert).toHaveBeenCalledOnce(); + }); + + test.sequential('invalid type', async () => { + await expect(() => exe(` + <: Mk:dialog('Hello', 'world', 'invalid') + `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError); + expect(osMock.alert).not.toHaveBeenCalled(); + }); + }); + + describe('confirm', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + test.sequential('ok', async () => { + osMock.confirm.mockImplementationOnce(async ({ type, title, text }) => { + expect(type).toBe('success'); + expect(title).toBe('Hello'); + expect(text).toBe('world'); + return { canceled: false }; + }); + const [res] = await exe(` + <: Mk:confirm('Hello', 'world', 'success') + `); + expect(res).toStrictEqual(values.TRUE); + expect(osMock.confirm).toHaveBeenCalledOnce(); + }); + + test.sequential('omit type', async () => { + osMock.confirm + .mockImplementationOnce(async ({ type, title, text }) => { + expect(type).toBe('question'); + expect(title).toBe('Hello'); + expect(text).toBe('world'); + return { canceled: false }; + }); + const [res] = await exe(` + <: Mk:confirm('Hello', 'world') + `); + expect(res).toStrictEqual(values.TRUE); + expect(osMock.confirm).toHaveBeenCalledOnce(); + }); + + test.sequential('canceled', async () => { + osMock.confirm.mockImplementationOnce(async ({ type, title, text }) => { + expect(type).toBe('question'); + expect(title).toBe('Hello'); + expect(text).toBe('world'); + return { canceled: true }; + }); + const [res] = await exe(` + <: Mk:confirm('Hello', 'world') + `); + expect(res).toStrictEqual(values.FALSE); + expect(osMock.confirm).toHaveBeenCalledOnce(); + }); + + test.sequential('invalid type', async () => { + const confirm = osMock.confirm; + await expect(() => exe(` + <: Mk:confirm('Hello', 'world', 'invalid') + `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError); + expect(confirm).not.toHaveBeenCalled(); + }); + }); + + describe('api', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + test.sequential('successful', async () => { + misskeyApiMock.mockImplementationOnce( + async (endpoint, data, token) => { + expect(endpoint).toBe('ping'); + expect(data).toStrictEqual({}); + expect(token).toBeNull(); + return { pong: 1735657200000 }; + } + ); + const [res] = await exe(` + <: Mk:api('ping', {}) + `); + expect(res).toStrictEqual(values.OBJ(new Map([ + ['pong', values.NUM(1735657200000)], + ]))); + expect(misskeyApiMock).toHaveBeenCalledOnce(); + }); + + test.sequential('with token', async () => { + misskeyApiMock.mockImplementationOnce( + async (endpoint, data, token) => { + expect(endpoint).toBe('ping'); + expect(data).toStrictEqual({}); + expect(token).toStrictEqual('xxxxxxxx'); + return { pong: 1735657200000 }; + } + ); + const [res] = await exe(` + <: Mk:api('ping', {}, 'xxxxxxxx') + `); + expect(res).toStrictEqual(values.OBJ(new Map([ + ['pong', values.NUM(1735657200000 )], + ]))); + expect(misskeyApiMock).toHaveBeenCalledOnce(); + }); + + test.sequential('request failed', async () => { + misskeyApiMock.mockRejectedValueOnce('Not Found'); + const [res] = await exe(` + <: Mk:api('this/endpoint/should/not/be/found', {}) + `); + expect(res).toStrictEqual( + values.ERROR('request_failed', values.STR('Not Found')) + ); + expect(misskeyApiMock).toHaveBeenCalledOnce(); + }); + + test.sequential('invalid endpoint', async () => { + await expect(() => exe(` + Mk:api('https://example.com/api/ping', {}) + `)).rejects.toStrictEqual( + new errors.AiScriptRuntimeError('invalid endpoint'), + ); + expect(misskeyApiMock).not.toHaveBeenCalled(); + }); + + test.sequential('missing param', async () => { + await expect(() => exe(` + Mk:api('ping') + `)).rejects.toStrictEqual( + new errors.AiScriptRuntimeError('expected param'), + ); + expect(misskeyApiMock).not.toHaveBeenCalled(); + }); + }); + + describe('save and load', () => { + beforeEach(() => { + miLocalStorage.removeItem('aiscript:widget:key'); + }); + + afterEach(() => { + miLocalStorage.removeItem('aiscript:widget:key'); + }); + + test.sequential('successful', async () => { + const [res] = await exe(` + Mk:save('key', 'value') + <: Mk:load('key') + `); + expect(miLocalStorage.getItem('aiscript:widget:key')).toBe('"value"'); + expect(res).toStrictEqual(values.STR('value')); + }); + + test.sequential('missing value to save', async () => { + await expect(() => exe(` + Mk:save('key') + `)).rejects.toStrictEqual( + new errors.AiScriptRuntimeError('Expect anything, but got nothing.'), + ); + }); + + test.sequential('not value found to load', async () => { + const [res] = await exe(` + <: Mk:load('key') + `); + expect(res).toStrictEqual(values.NULL); + }); + + test.sequential('remove existing', async () => { + const res = await exe(` + Mk:save('key', 'value') + <: Mk:load('key') + <: Mk:remove('key') + <: Mk:load('key') + `); + expect(res).toStrictEqual([values.STR('value'), values.NULL, values.NULL]); + }); + + test.sequential('remove nothing', async () => { + const res = await exe(` + <: Mk:load('key') + <: Mk:remove('key') + <: Mk:load('key') + `); + expect(res).toStrictEqual([values.NULL, values.NULL, values.NULL]); + }); + }); + + test.concurrent('url', async () => { + vi.stubGlobal('location', { href: 'https://example.com/' }); + const [res] = await exe(` + <: Mk:url() + `); + expect(res).toStrictEqual(values.STR('https://example.com/')); + }); + + test.concurrent('nyaize', async () => { + const [res] = await exe(` + <: Mk:nyaize('な') + `); + expect(res).toStrictEqual(values.STR('にゃ')); + }); +}); diff --git a/packages/frontend/test/aiscript/common.test.ts b/packages/frontend/test/aiscript/common.test.ts new file mode 100644 index 0000000000..acc48826ea --- /dev/null +++ b/packages/frontend/test/aiscript/common.test.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { assertStringAndIsIn } from "@/scripts/aiscript/common.js"; +import { values } from "@syuilo/aiscript"; +import { describe, expect, test } from "vitest"; + +describe('AiScript common script', () => { + test('assertStringAndIsIn', () => { + expect( + () => assertStringAndIsIn(values.STR('a'), ['a', 'b']) + ).not.toThrow(); + expect( + () => assertStringAndIsIn(values.STR('c'), ['a', 'b']) + ).toThrow('"c" is not in "a", "b"'); + expect(() => assertStringAndIsIn( + values.STR('invalid'), + ['left', 'center', 'right'] + )).toThrow('"invalid" is not in "left", "center", "right"'); + }); +}); diff --git a/packages/frontend/test/aiscript/ui.test.ts b/packages/frontend/test/aiscript/ui.test.ts new file mode 100644 index 0000000000..5f77edbb49 --- /dev/null +++ b/packages/frontend/test/aiscript/ui.test.ts @@ -0,0 +1,825 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { registerAsUiLib } from '@/scripts/aiscript/ui.js'; +import { errors, Interpreter, Parser, values } from '@syuilo/aiscript'; +import { describe, expect, test } from 'vitest'; +import { type Ref, ref } from 'vue'; +import type { + AsUiButton, + AsUiButtons, + AsUiComponent, + AsUiMfm, + AsUiNumberInput, + AsUiRoot, + AsUiSelect, + AsUiSwitch, + AsUiText, + AsUiTextarea, + AsUiTextInput, +} from '@/scripts/aiscript/ui.js'; + +type ExeResult = { + root: AsUiRoot; + get: (id: string) => AsUiComponent; + outputs: values.Value[]; +} +async function exe(script: string): Promise { + const rootRef = ref(); + const componentRefs = ref[]>([]); + const outputs: values.Value[] = []; + + const interpreter = new Interpreter( + registerAsUiLib(componentRefs.value, (root) => { + rootRef.value = root.value; + }), + { + out: (value) => { + outputs.push(value); + } + } + ); + const ast = Parser.parse(script); + await interpreter.exec(ast); + + const root = rootRef.value; + if (root === undefined) { + expect.unreachable('root must not be undefined'); + } + const components = componentRefs.value.map( + (componentRef) => componentRef.value, + ); + expect(root).toBe(components[0]); + expect(root.type).toBe('root'); + const get = (id: string) => { + const component = componentRefs.value.find( + (componentRef) => componentRef.value.id === id, + ); + if (component === undefined) { + expect.unreachable(`component "${id}" is not defined`); + } + return component.value; + }; + return { root, get, outputs }; +} + +describe('AiScript UI API', () => { + test.concurrent('root', async () => { + const { root } = await exe(''); + expect(root.children).toStrictEqual([]); + }); + + describe('get', () => { + test.concurrent('some', async () => { + const { outputs } = await exe(` + Ui:C:text({}, 'id') + <: Ui:get('id') + `); + const output = outputs[0] as values.VObj; + expect(output.type).toBe('obj'); + expect(output.value.size).toBe(2); + expect(output.value.get('id')).toStrictEqual(values.STR('id')); + expect(output.value.get('update')!.type).toBe('fn'); + }); + + test.concurrent('none', async () => { + const { outputs } = await exe(` + <: Ui:get('id') + `); + expect(outputs).toStrictEqual([values.NULL]); + }); + }); + + describe('update', () => { + test.concurrent('normal', async () => { + const { get } = await exe(` + let text = Ui:C:text({ text: 'a' }, 'id') + text.update({ text: 'b' }) + `); + const text = get('id') as AsUiText; + expect(text.text).toBe('b'); + }); + + test.concurrent('skip unknown key', async () => { + const { get } = await exe(` + let text = Ui:C:text({ text: 'a' }, 'id') + text.update({ + text: 'b' + unknown: null + }) + `); + const text = get('id') as AsUiText; + expect(text.text).toBe('b'); + expect('unknown' in text).toBeFalsy(); + }); + }); + + describe('container', () => { + test.concurrent('all options', async () => { + const { root, get } = await exe(` + let text = Ui:C:text({ + text: 'text' + }, 'id1') + let container = Ui:C:container({ + children: [text] + align: 'left' + bgColor: '#fff' + fgColor: '#000' + font: 'sans-serif' + borderWidth: 1 + borderColor: '#f00' + borderStyle: 'hidden' + borderRadius: 2 + padding: 3 + rounded: true + hidden: false + }, 'id2') + Ui:render([container]) + `); + expect(root.children).toStrictEqual(['id2']); + expect(get('id2')).toStrictEqual({ + type: 'container', + id: 'id2', + children: ['id1'], + align: 'left', + bgColor: '#fff', + fgColor: '#000', + font: 'sans-serif', + borderColor: '#f00', + borderWidth: 1, + borderStyle: 'hidden', + borderRadius: 2, + padding: 3, + rounded: true, + hidden: false, + }); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:container({}, 'id') + `); + expect(get('id')).toStrictEqual({ + type: 'container', + id: 'id', + children: [], + align: undefined, + fgColor: undefined, + bgColor: undefined, + font: undefined, + borderWidth: undefined, + borderColor: undefined, + borderStyle: undefined, + borderRadius: undefined, + padding: undefined, + rounded: undefined, + hidden: undefined, + }); + }); + + test.concurrent('invalid children', async () => { + await expect(() => exe(` + Ui:C:container({ + children: 0 + }) + `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError); + }); + + test.concurrent('invalid align', async () => { + await expect(() => exe(` + Ui:C:container({ + align: 'invalid' + }) + `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError); + }); + + test.concurrent('invalid font', async () => { + await expect(() => exe(` + Ui:C:container({ + font: 'invalid' + }) + `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError); + }); + + test.concurrent('invalid borderStyle', async () => { + await expect(() => exe(` + Ui:C:container({ + borderStyle: 'invalid' + }) + `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError); + }); + }); + + describe('text', () => { + test.concurrent('all options', async () => { + const { root, get } = await exe(` + let text = Ui:C:text({ + text: 'a' + size: 1 + bold: true + color: '#000' + font: 'sans-serif' + }, 'id') + Ui:render([text]) + `); + expect(root.children).toStrictEqual(['id']); + expect(get('id')).toStrictEqual({ + type: 'text', + id: 'id', + text: 'a', + size: 1, + bold: true, + color: '#000', + font: 'sans-serif', + }); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:text({}, 'id') + `); + expect(get('id')).toStrictEqual({ + type: 'text', + id: 'id', + text: undefined, + size: undefined, + bold: undefined, + color: undefined, + font: undefined, + }); + }); + + test.concurrent('invalid font', async () => { + await expect(() => exe(` + Ui:C:text({ + font: 'invalid' + }) + `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError); + }); + }); + + describe('mfm', () => { + test.concurrent('all options', async () => { + const { root, get, outputs } = await exe(` + let mfm = Ui:C:mfm({ + text: 'text' + size: 1 + bold: true + color: '#000' + font: 'sans-serif' + onClickEv: print + }, 'id') + Ui:render([mfm]) + `); + expect(root.children).toStrictEqual(['id']); + const { onClickEv, ...mfm } = get('id') as AsUiMfm; + expect(mfm).toStrictEqual({ + type: 'mfm', + id: 'id', + text: 'text', + size: 1, + bold: true, + color: '#000', + font: 'sans-serif', + }); + await onClickEv!('a'); + expect(outputs).toStrictEqual([values.STR('a')]); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:mfm({}, 'id') + `); + const { onClickEv, ...mfm } = get('id') as AsUiMfm; + expect(onClickEv).toBeTypeOf('function'); + expect(mfm).toStrictEqual({ + type: 'mfm', + id: 'id', + text: undefined, + size: undefined, + bold: undefined, + color: undefined, + font: undefined, + }); + }); + + test.concurrent('invalid font', async () => { + await expect(() => exe(` + Ui:C:mfm({ + font: 'invalid' + }) + `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError); + }); + }); + + describe('textInput', () => { + test.concurrent('all options', async () => { + const { root, get, outputs } = await exe(` + let text_input = Ui:C:textInput({ + onInput: print + default: 'a' + label: 'b' + caption: 'c' + }, 'id') + Ui:render([text_input]) + `); + expect(root.children).toStrictEqual(['id']); + const { onInput, ...textInput } = get('id') as AsUiTextInput; + expect(textInput).toStrictEqual({ + type: 'textInput', + id: 'id', + default: 'a', + label: 'b', + caption: 'c', + }); + await onInput!('d'); + expect(outputs).toStrictEqual([values.STR('d')]); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:textInput({}, 'id') + `); + const { onInput, ...textInput } = get('id') as AsUiTextInput; + expect(onInput).toBeTypeOf('function'); + expect(textInput).toStrictEqual({ + type: 'textInput', + id: 'id', + default: undefined, + label: undefined, + caption: undefined, + }); + }); + }); + + describe('textarea', () => { + test.concurrent('all options', async () => { + const { root, get, outputs } = await exe(` + let textarea = Ui:C:textarea({ + onInput: print + default: 'a' + label: 'b' + caption: 'c' + }, 'id') + Ui:render([textarea]) + `); + expect(root.children).toStrictEqual(['id']); + const { onInput, ...textarea } = get('id') as AsUiTextarea; + expect(textarea).toStrictEqual({ + type: 'textarea', + id: 'id', + default: 'a', + label: 'b', + caption: 'c', + }); + await onInput!('d'); + expect(outputs).toStrictEqual([values.STR('d')]); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:textarea({}, 'id') + `); + const { onInput, ...textarea } = get('id') as AsUiTextarea; + expect(onInput).toBeTypeOf('function'); + expect(textarea).toStrictEqual({ + type: 'textarea', + id: 'id', + default: undefined, + label: undefined, + caption: undefined, + }); + }); + }); + + describe('numberInput', () => { + test.concurrent('all options', async () => { + const { root, get, outputs } = await exe(` + let number_input = Ui:C:numberInput({ + onInput: print + default: 1 + label: 'a' + caption: 'b' + }, 'id') + Ui:render([number_input]) + `); + expect(root.children).toStrictEqual(['id']); + const { onInput, ...numberInput } = get('id') as AsUiNumberInput; + expect(numberInput).toStrictEqual({ + type: 'numberInput', + id: 'id', + default: 1, + label: 'a', + caption: 'b', + }); + await onInput!(2); + expect(outputs).toStrictEqual([values.NUM(2)]); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:numberInput({}, 'id') + `); + const { onInput, ...numberInput } = get('id') as AsUiNumberInput; + expect(onInput).toBeTypeOf('function'); + expect(numberInput).toStrictEqual({ + type: 'numberInput', + id: 'id', + default: undefined, + label: undefined, + caption: undefined, + }); + }); + }); + + describe('button', () => { + test.concurrent('all options', async () => { + const { root, get, outputs } = await exe(` + let button = Ui:C:button({ + text: 'a' + onClick: @() { <: 'clicked' } + primary: true + rounded: false + disabled: false + }, 'id') + Ui:render([button]) + `); + expect(root.children).toStrictEqual(['id']); + const { onClick, ...button } = get('id') as AsUiButton; + expect(button).toStrictEqual({ + type: 'button', + id: 'id', + text: 'a', + primary: true, + rounded: false, + disabled: false, + }); + await onClick!(); + expect(outputs).toStrictEqual([values.STR('clicked')]); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:button({}, 'id') + `); + const { onClick, ...button } = get('id') as AsUiButton; + expect(onClick).toBeTypeOf('function'); + expect(button).toStrictEqual({ + type: 'button', + id: 'id', + text: undefined, + primary: undefined, + rounded: undefined, + disabled: undefined, + }); + }); + }); + + describe('buttons', () => { + test.concurrent('all options', async () => { + const { root, get } = await exe(` + let buttons = Ui:C:buttons({ + buttons: [] + }, 'id') + Ui:render([buttons]) + `); + expect(root.children).toStrictEqual(['id']); + expect(get('id')).toStrictEqual({ + type: 'buttons', + id: 'id', + buttons: [], + }); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:buttons({}, 'id') + `); + expect(get('id')).toStrictEqual({ + type: 'buttons', + id: 'id', + buttons: [], + }); + }); + + test.concurrent('some buttons', async () => { + const { root, get, outputs } = await exe(` + let buttons = Ui:C:buttons({ + buttons: [ + { + text: 'a' + onClick: @() { <: 'clicked a' } + primary: true + rounded: false + disabled: false + } + { + text: 'b' + onClick: @() { <: 'clicked b' } + primary: true + rounded: false + disabled: false + } + ] + }, 'id') + Ui:render([buttons]) + `); + expect(root.children).toStrictEqual(['id']); + const { buttons, ...buttonsOptions } = get('id') as AsUiButtons; + expect(buttonsOptions).toStrictEqual({ + type: 'buttons', + id: 'id', + }); + expect(buttons!.length).toBe(2); + const { onClick: onClickA, ...buttonA } = buttons![0]; + expect(buttonA).toStrictEqual({ + text: 'a', + primary: true, + rounded: false, + disabled: false, + }); + const { onClick: onClickB, ...buttonB } = buttons![1]; + expect(buttonB).toStrictEqual({ + text: 'b', + primary: true, + rounded: false, + disabled: false, + }); + await onClickA!(); + await onClickB!(); + expect(outputs).toStrictEqual( + [values.STR('clicked a'), values.STR('clicked b')] + ); + }); + }); + + describe('switch', () => { + test.concurrent('all options', async () => { + const { root, get, outputs } = await exe(` + let switch = Ui:C:switch({ + onChange: print + default: false + label: 'a' + caption: 'b' + }, 'id') + Ui:render([switch]) + `); + expect(root.children).toStrictEqual(['id']); + const { onChange, ...switchOptions } = get('id') as AsUiSwitch; + expect(switchOptions).toStrictEqual({ + type: 'switch', + id: 'id', + default: false, + label: 'a', + caption: 'b', + }); + await onChange!(true); + expect(outputs).toStrictEqual([values.TRUE]); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:switch({}, 'id') + `); + const { onChange, ...switchOptions } = get('id') as AsUiSwitch; + expect(onChange).toBeTypeOf('function'); + expect(switchOptions).toStrictEqual({ + type: 'switch', + id: 'id', + default: undefined, + label: undefined, + caption: undefined, + }); + }); + }); + + describe('select', () => { + test.concurrent('all options', async () => { + const { root, get, outputs } = await exe(` + let select = Ui:C:select({ + items: [ + { text: 'A', value: 'a' } + { text: 'B', value: 'b' } + ] + onChange: print + default: 'a' + label: 'c' + caption: 'd' + }, 'id') + Ui:render([select]) + `); + expect(root.children).toStrictEqual(['id']); + const { onChange, ...select } = get('id') as AsUiSelect; + expect(select).toStrictEqual({ + type: 'select', + id: 'id', + items: [ + { text: 'A', value: 'a' }, + { text: 'B', value: 'b' }, + ], + default: 'a', + label: 'c', + caption: 'd', + }); + await onChange!('b'); + expect(outputs).toStrictEqual([values.STR('b')]); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:select({}, 'id') + `); + const { onChange, ...select } = get('id') as AsUiSelect; + expect(onChange).toBeTypeOf('function'); + expect(select).toStrictEqual({ + type: 'select', + id: 'id', + items: [], + default: undefined, + label: undefined, + caption: undefined, + }); + }); + + test.concurrent('omit item values', async () => { + const { get } = await exe(` + let select = Ui:C:select({ + items: [ + { text: 'A' } + { text: 'B' } + ] + }, 'id') + `); + const { onChange, ...select } = get('id') as AsUiSelect; + expect(onChange).toBeTypeOf('function'); + expect(select).toStrictEqual({ + type: 'select', + id: 'id', + items: [ + { text: 'A', value: 'A' }, + { text: 'B', value: 'B' }, + ], + default: undefined, + label: undefined, + caption: undefined, + }); + }); + }); + + describe('folder', () => { + test.concurrent('all options', async () => { + const { root, get } = await exe(` + let folder = Ui:C:folder({ + children: [] + title: 'a' + opened: true + }, 'id') + Ui:render([folder]) + `); + expect(root.children).toStrictEqual(['id']); + expect(get('id')).toStrictEqual({ + type: 'folder', + id: 'id', + children: [], + title: 'a', + opened: true, + }); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:folder({}, 'id') + `); + expect(get('id')).toStrictEqual({ + type: 'folder', + id: 'id', + children: [], + title: '', + opened: true, + }); + }); + + test.concurrent('some children', async () => { + const { get } = await exe(` + let text = Ui:C:text({ + text: 'text' + }, 'id1') + Ui:C:folder({ + children: [text] + }, 'id2') + `); + expect(get('id2')).toStrictEqual({ + type: 'folder', + id: 'id2', + children: ['id1'], + title: '', + opened: true, + }); + }); + }); + + describe('postFormButton', () => { + test.concurrent('all options', async () => { + const { root, get } = await exe(` + let post_form_button = Ui:C:postFormButton({ + text: 'a' + primary: true + rounded: false + form: { + text: 'b' + cw: 'c' + visibility: 'public' + localOnly: true + } + }, 'id') + Ui:render([post_form_button]) + `); + expect(root.children).toStrictEqual(['id']); + expect(get('id')).toStrictEqual({ + type: 'postFormButton', + id: 'id', + text: 'a', + primary: true, + rounded: false, + form: { + text: 'b', + cw: 'c', + visibility: 'public', + localOnly: true, + }, + }); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:postFormButton({}, 'id') + `); + expect(get('id')).toStrictEqual({ + type: 'postFormButton', + id: 'id', + text: undefined, + primary: undefined, + rounded: undefined, + form: { text: '' }, + }); + }); + }); + + describe('postForm', () => { + test.concurrent('all options', async () => { + const { root, get } = await exe(` + let post_form = Ui:C:postForm({ + form: { + text: 'a' + cw: 'b' + visibility: 'public' + localOnly: true + } + }, 'id') + Ui:render([post_form]) + `); + expect(root.children).toStrictEqual(['id']); + expect(get('id')).toStrictEqual({ + type: 'postForm', + id: 'id', + form: { + text: 'a', + cw: 'b', + visibility: 'public', + localOnly: true, + }, + }); + }); + + test.concurrent('minimum options', async () => { + const { get } = await exe(` + Ui:C:postForm({}, 'id') + `); + expect(get('id')).toStrictEqual({ + type: 'postForm', + id: 'id', + form: { text: '' }, + }); + }); + + test.concurrent('minimum options for form', async () => { + const { get } = await exe(` + Ui:C:postForm({ + form: { text: '' } + }, 'id') + `); + expect(get('id')).toStrictEqual({ + type: 'postForm', + id: 'id', + form: { + text: '', + cw: undefined, + visibility: undefined, + localOnly: undefined, + }, + }); + }); + }); +}); From 79b851fe562c3e6be601b5b25a744d86798d4747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 7 Jan 2025 22:38:43 +0900 Subject: [PATCH 054/448] =?UTF-8?q?Update=20CHANGELOG.md=20(=E6=9B=B8?= =?UTF-8?q?=E3=81=8D=E6=96=B9=E3=82=92=E3=81=9D=E3=82=8D=E3=81=88=E3=82=8B?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10de8b5fc5..4e34049011 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ (Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13) - Enhance: 照会に失敗した場合、その理由を表示するように - Enhance: AiScriptのセーブデータを明示的に削除する関数`Mk:remove`を追加 +- Enhance: AiScriptの拡張API関数において引数の型チェックをより厳格に - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 - Fix: サーバー情報メニューに区切り線が不足していたのを修正 - Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正 @@ -20,7 +21,6 @@ (Cherry-picked from https://github.com/TeamNijimiss/misskey/commit/800359623e41a662551d774de15b0437b6849bb4) - Fix: ノート作成画面でファイルの添付可能個数を超えてもノートボタンが押せていた問題を修正 - Fix: 「アカウントを管理」画面で、ユーザー情報の取得に失敗したアカウント(削除されたアカウントなど)が表示されない問題を修正 -- Enhance: AiScriptの拡張API関数において引数の型チェックをより厳格に ### Server - Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように From d7835313c35be8565d2cfb1a7cc724f95ddf3db7 Mon Sep 17 00:00:00 2001 From: taichan <40626578+tai-cha@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:33:08 +0900 Subject: [PATCH 055/448] =?UTF-8?q?fix(backend):=20=E3=83=AD=E3=83=83?= =?UTF-8?q?=E3=82=AF=E3=83=80=E3=82=A6=E3=83=B3=E3=81=95=E3=82=8C=E3=81=9F?= =?UTF-8?q?=E6=9C=9F=E9=96=93=E6=8C=87=E5=AE=9A=E3=81=AE=E3=83=8E=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=8CStreaming=E7=B5=8C=E7=94=B1=E3=81=A7LTL?= =?UTF-8?q?=E3=81=AB=E5=87=BA=E7=8F=BE=E3=81=99=E3=82=8B=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#15200)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): skipHideなときにもロックダウンされたノートのprivate化をするように * fix linting * Update packages/backend/src/core/entities/NoteEntityService.ts * Fix: type error * Remove unneeded await * Fix: typo * Remove skipTreatVisibillity --- packages/backend/src/core/entities/NoteEntityService.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 96cc6b028e..97f1c3d739 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -102,8 +102,7 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise { - // FIXME: このvisibility変更処理が当関数にあるのは若干不自然かもしれない(関数名を treatVisibility とかに変える手もある) + private treatVisibility(packedNote: Packed<'Note'>): Packed<'Note'>['visibility'] { if (packedNote.visibility === 'public' || packedNote.visibility === 'home') { const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore; if ((followersOnlyBefore != null) @@ -115,7 +114,11 @@ export class NoteEntityService implements OnModuleInit { packedNote.visibility = 'followers'; } } + return packedNote.visibility; + } + @bindThis + private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise { if (meId === packedNote.userId) return; // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) @@ -458,6 +461,8 @@ export class NoteEntityService implements OnModuleInit { } : {}), }); + this.treatVisibility(packed); + if (!opts.skipHide) { await this.hideNote(packed, meId); } From f6808711af282ad62ff2f7adedf96431586ca077 Mon Sep 17 00:00:00 2001 From: taichan <40626578+tai-cha@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:52:08 +0900 Subject: [PATCH 056/448] update changelog (#15236) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e34049011..174f1282cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) +- Fix: ロックダウンされた期間指定のノートがStreaming経由でLTLに出現するのを修正 ( #15200 ) - Fix: disableClustering設定時の初期化ロジックを調整( #15223 ) ## 2024.11.0 From 8652ce7cc030cf5d21b86da9cee453dd82667978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B4=87=E5=B3=B0=20=E6=9C=94=E8=8F=AF?= <160555157+sakuhanight@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:58:29 +0900 Subject: [PATCH 057/448] =?UTF-8?q?fix(frontend):=20=E8=87=AA=E5=88=86?= =?UTF-8?q?=E4=BB=A5=E5=A4=96=E3=81=AE=E3=83=8E=E3=83=BC=E3=83=88=E3=82=92?= =?UTF-8?q?=E6=B6=88=E3=81=97=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AB=E5=AE=9F?= =?UTF-8?q?=E7=B8=BE=E3=82=92=E8=A7=A3=E9=99=A4=E3=81=97=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3=20(#15071)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/scripts/get-note-menu.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index c1846b0589..e131cf5156 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -5,19 +5,19 @@ import { defineAsyncComponent, Ref, ShallowRef } from 'vue'; import * as Misskey from 'misskey-js'; +import { url } from '@@/js/config.js'; import { claimAchievement } from './achievements.js'; +import type { MenuItem } from '@/types/menu.js'; import { $i } from '@/account.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { url } from '@@/js/config.js'; import { defaultStore, noteActions } from '@/store.js'; import { miLocalStorage } from '@/local-storage.js'; import { getUserMenu } from '@/scripts/get-user-menu.js'; import { clipsCache, favoritedChannelsCache } from '@/cache.js'; -import type { MenuItem } from '@/types/menu.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { isSupportShare } from '@/scripts/navigator.js'; import { getAppearNote } from '@/scripts/get-appear-note.js'; @@ -194,7 +194,7 @@ export function getNoteMenu(props: { noteId: appearNote.id, }); - if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60) { + if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60 && appearNote.userId === $i.id) { claimAchievement('noteDeletedWithin1min'); } }); @@ -213,7 +213,7 @@ export function getNoteMenu(props: { os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel }); - if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60) { + if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60 && appearNote.userId === $i.id) { claimAchievement('noteDeletedWithin1min'); } }); From c49a13de65a678e56a6350c6a8562d73a7caa282 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, 8 Jan 2025 19:33:43 +0900 Subject: [PATCH 058/448] =?UTF-8?q?fix(frontend-embed):=20locale=E3=81=AE?= =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=83=81=E3=82=A7?= =?UTF-8?q?=E3=83=83=E3=82=AF=E3=81=8C=E6=8A=9C=E3=81=91=E3=81=A6=E3=81=8A?= =?UTF-8?q?=E3=82=8A=E8=B5=B7=E5=8B=95=E3=81=AB=E5=A4=B1=E6=95=97=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=81=93=E3=81=A8=E3=81=8C=E3=81=82=E3=82=8B=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=20(#15212)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend-embed): localeのバージョンチェックが抜けており起動に失敗することがある問題を修正 * Update Changelog --- CHANGELOG.md | 1 + packages/frontend-embed/src/boot.ts | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 174f1282cb..24b5f6e661 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ (Cherry-picked from https://github.com/TeamNijimiss/misskey/commit/800359623e41a662551d774de15b0437b6849bb4) - Fix: ノート作成画面でファイルの添付可能個数を超えてもノートボタンが押せていた問題を修正 - Fix: 「アカウントを管理」画面で、ユーザー情報の取得に失敗したアカウント(削除されたアカウントなど)が表示されない問題を修正 +- Fix: 言語データのキャッシュ状況によっては、埋め込みウィジェットが正しく起動しない問題を修正 ### Server - Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts index 8ab4ab32e6..c1b2b58beb 100644 --- a/packages/frontend-embed/src/boot.ts +++ b/packages/frontend-embed/src/boot.ts @@ -17,11 +17,11 @@ import { applyTheme, assertIsTheme } from '@/theme.js'; import { fetchCustomEmojis } from '@/custom-emojis.js'; import { DI } from '@/di.js'; import { serverMetadata } from '@/server-metadata.js'; -import { url } from '@@/js/config.js'; +import { url, version, locale, lang, updateLocale } from '@@/js/config.js'; import { parseEmbedParams } from '@@/js/embed-page.js'; import { postMessageToParentWindow, setIframeId } from '@/post-message.js'; import { serverContext } from '@/server-context.js'; -import { i18n } from '@/i18n.js'; +import { i18n, updateI18n } from '@/i18n.js'; import type { Theme } from '@/theme.js'; @@ -71,6 +71,22 @@ if (embedParams.colorMode === 'dark') { } //#endregion +//#region Detect language & fetch translations +const localeVersion = localStorage.getItem('localeVersion'); +const localeOutdated = (localeVersion == null || localeVersion !== version || locale == null); +if (localeOutdated) { + const res = await window.fetch(`/assets/locales/${lang}.${version}.json`); + if (res.status === 200) { + const newLocale = await res.text(); + const parsedNewLocale = JSON.parse(newLocale); + localStorage.setItem('locale', newLocale); + localStorage.setItem('localeVersion', version); + updateLocale(parsedNewLocale); + updateI18n(parsedNewLocale); + } +} +//#endregion + // サイズの制限 document.documentElement.style.maxWidth = '500px'; From 55713fcd657983add090a7788c6ffd984cbbd15f 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, 8 Jan 2025 19:35:09 +0900 Subject: [PATCH 059/448] =?UTF-8?q?fix(backend):=20apOrHtml=20Constraint?= =?UTF-8?q?=E3=81=8C=E6=AD=A3=E3=81=97=E3=81=8F=E8=A9=95=E4=BE=A1=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20(#15213)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend/ActivityPubServerService): apOrHtml Constraintが正しく評価されない問題を修正 (MisskeyIO#869) * Update Changelog * indent --------- Co-authored-by: あわわわとーにゅ <17376330+u1-liquid@users.noreply.github.com> --- CHANGELOG.md | 2 ++ packages/backend/src/server/ActivityPubServerService.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24b5f6e661..fd8eaf00e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) - Fix: ロックダウンされた期間指定のノートがStreaming経由でLTLに出現するのを修正 ( #15200 ) - Fix: disableClustering設定時の初期化ロジックを調整( #15223 ) +- Fix: ActivityPubリクエストかどうかの判定が正しくない問題を修正 + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/869) ## 2024.11.0 diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index f34f6583d3..8c4b13a40a 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -519,8 +519,8 @@ export class ActivityPubServerService { }, deriveConstraint(request: IncomingMessage) { const accepted = accepts(request).type(['html', ACTIVITY_JSON, LD_JSON]); - const isAp = typeof accepted === 'string' && !accepted.match(/html/); - return isAp ? 'ap' : 'html'; + if (accepted === false) return null; + return accepted !== 'html' ? 'ap' : 'html'; }, }); From bb4457266dc6f51831bf54b5071dd84928366f4f Mon Sep 17 00:00:00 2001 From: Rsplwe Date: Wed, 8 Jan 2025 18:51:23 +0800 Subject: [PATCH 060/448] feat(frontend): Do not display blocked instances on the welcome page (#15178) --- packages/frontend/src/pages/welcome.entrance.a.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue index f0e4a852c9..68938f0bbf 100644 --- a/packages/frontend/src/pages/welcome.entrance.a.vue +++ b/packages/frontend/src/pages/welcome.entrance.a.vue @@ -59,6 +59,7 @@ function getInstanceIcon(instance: Misskey.entities.FederationInstance): string misskeyApiGet('federation/instances', { sort: '+pubSub', limit: 20, + blocked: 'false', }).then(_instances => { instances.value = _instances; }); From 13439e04c426d48c1700b0fd476dcf453965318a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:00:02 +0900 Subject: [PATCH 061/448] =?UTF-8?q?fix(frontend-embed):=20=E5=9E=8B?= =?UTF-8?q?=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=20(#15216)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): frontend / frontend-embedにあるtsconfig.jsonのmoduleをES2022にする * fixed errors * fixed errors * fixed errors * fix(frontend-embed): 型チェックエラーを修正 --- .../frontend-embed/src/components/EmMfm.ts | 2 -- .../frontend-embed/src/components/EmNotes.vue | 3 ++- packages/frontend-embed/src/theme.ts | 23 +++++++++++-------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts index cae2feb8fb..e84fd6f679 100644 --- a/packages/frontend-embed/src/components/EmMfm.ts +++ b/packages/frontend-embed/src/components/EmMfm.ts @@ -415,8 +415,6 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext
- +
@@ -24,6 +24,7 @@ import { useTemplateRef } from 'vue'; import EmNote from '@/components/EmNote.vue'; import EmPagination, { Paging } from '@/components/EmPagination.vue'; import { i18n } from '@/i18n.js'; +import * as Misskey from 'misskey-js'; withDefaults(defineProps<{ pagination: Paging; diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts index 4664ad4880..680ab80167 100644 --- a/packages/frontend-embed/src/theme.ts +++ b/packages/frontend-embed/src/theme.ts @@ -75,16 +75,21 @@ function compile(theme: Theme): Record { return getColor(theme.props[val]); } else if (val[0] === ':') { // func const parts = val.split('<'); - const func = parts.shift().substring(1); - const arg = parseFloat(parts.shift()); - const color = getColor(parts.join('<')); + const funcTxt = parts.shift(); + const argTxt = parts.shift(); - switch (func) { - case 'darken': return color.darken(arg); - case 'lighten': return color.lighten(arg); - case 'alpha': return color.setAlpha(arg); - case 'hue': return color.spin(arg); - case 'saturate': return color.saturate(arg); + if (funcTxt && argTxt) { + const func = funcTxt.substring(1); + const arg = parseFloat(argTxt); + const color = getColor(parts.join('<')); + + switch (func) { + case 'darken': return color.darken(arg); + case 'lighten': return color.lighten(arg); + case 'alpha': return color.setAlpha(arg); + case 'hue': return color.spin(arg); + case 'saturate': return color.saturate(arg); + } } } From 5fc9c1c8cdebec868440cd8ed4b3ad84f214e117 Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 8 Jan 2025 12:51:46 -0300 Subject: [PATCH 062/448] shallow clone --- .../backend/src/server/api/stream/channel.ts | 37 ++++++++++++++++--- .../api/stream/channels/bubble-timeline.ts | 19 ++-------- .../api/stream/channels/global-timeline.ts | 19 ++-------- .../api/stream/channels/home-timeline.ts | 19 ++-------- .../api/stream/channels/hybrid-timeline.ts | 19 ++-------- .../api/stream/channels/local-timeline.ts | 19 ++-------- 6 files changed, 47 insertions(+), 85 deletions(-) diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 93d5046902..ed6e41c4bb 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -9,8 +9,8 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { Packed } from '@/misc/json-schema.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; -import type Connection from './Connection.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import type Connection from './Connection.js'; /** * Stream channel @@ -103,14 +103,41 @@ export default abstract class Channel { public onMessage?(type: string, body: JsonValue): void; - public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService) { - if (this.user && Object.keys(note.reactions).length > 0) { - const myReaction = await noteEntityService.populateMyReaction(note, this.user.id); - note.myReaction = myReaction; + public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { + let changed = false; + const clonedNote = { ...note }; + if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.renote, this.user.id); + if (myReaction) { + changed = true; + clonedNote.renote = { ...note.renote }; + clonedNote.renote.myReaction = myReaction; + } + } } + if (this.user && note.renote?.reply && Object.keys(note.renote.reply.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.renote.reply, this.user.id); + if (myReaction) { + changed = true; + clonedNote.renote = { ...note.renote }; + clonedNote.renote.reply = { ...note.renote.reply }; + clonedNote.renote.reply.myReaction = myReaction; + } + } + if (this.user && note.reply && Object.keys(note.reply.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.reply, this.user.id); + if (myReaction) { + changed = true; + clonedNote.reply = { ...note.reply }; + clonedNote.reply.myReaction = myReaction; + } + } + return changed ? clonedNote : note; } } + export type MiChannelService = { shouldShare: boolean; requireCredential: T; diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts index b2745db92d..98ecf16a83 100644 --- a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts @@ -65,24 +65,11 @@ class BubbleTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - await Promise.all(reactionsToFetch); + this.connection.cacheNote(clonedNote); - this.connection.cacheNote(note); - - this.send('note', note); + this.send('note', clonedNote); } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 8df59d906d..4443b20bed 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -60,24 +60,11 @@ class GlobalTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - await Promise.all(reactionsToFetch); + this.connection.cacheNote(clonedNote); - this.connection.cacheNote(note); - - this.send('note', note); + this.send('note', clonedNote); } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index f48eff85c9..af1b17b533 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -81,24 +81,11 @@ class HomeTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - await Promise.all(reactionsToFetch); + this.connection.cacheNote(clonedNote); - this.connection.cacheNote(note); - - this.send('note', note); + this.send('note', clonedNote); } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 8c58b2518e..7c604c0b58 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -98,24 +98,11 @@ class HybridTimelineChannel extends Channel { } } - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - await Promise.all(reactionsToFetch); + this.connection.cacheNote(clonedNote); - this.connection.cacheNote(note); - - this.send('note', note); + this.send('note', clonedNote); } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index cb832bd3c2..2d48b6ecfb 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -70,24 +70,11 @@ class LocalTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - await Promise.all(reactionsToFetch); + this.connection.cacheNote(clonedNote); - this.connection.cacheNote(note); - - this.send('note', note); + this.send('note', clonedNote); } @bindThis From e76e6cd08f69ddd7f8c62039b9d3e64a7a6189ca Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 8 Jan 2025 12:58:57 -0300 Subject: [PATCH 063/448] small refactor --- .../backend/src/server/api/stream/channel.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index ed6e41c4bb..6589a7af8d 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -115,14 +115,14 @@ export default abstract class Channel { clonedNote.renote.myReaction = myReaction; } } - } - if (this.user && note.renote?.reply && Object.keys(note.renote.reply.reactions).length > 0) { - const myReaction = await noteEntityService.populateMyReaction(note.renote.reply, this.user.id); - if (myReaction) { - changed = true; - clonedNote.renote = { ...note.renote }; - clonedNote.renote.reply = { ...note.renote.reply }; - clonedNote.renote.reply.myReaction = myReaction; + if (note.renote?.reply && Object.keys(note.renote.reply.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.renote.reply, this.user.id); + if (myReaction) { + changed = true; + clonedNote.renote = { ...note.renote }; + clonedNote.renote.reply = { ...note.renote.reply }; + clonedNote.renote.reply.myReaction = myReaction; + } } } if (this.user && note.reply && Object.keys(note.reply.reactions).length > 0) { From a3fc9a1085c7ad1fedf64fd6417c04cdcc936887 Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 8 Jan 2025 13:10:20 -0300 Subject: [PATCH 064/448] comment code --- packages/backend/src/server/api/stream/channel.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 6589a7af8d..7a044296fe 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -105,6 +105,8 @@ export default abstract class Channel { public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { let changed = false; + // cloning here seems like the best solution for a race condition + // where multiple users shared the same myReaction. (Sharkey #877) const clonedNote = { ...note }; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { From f1d9bb2cf1d9045ec13cdeb231656054b9e5bfde Mon Sep 17 00:00:00 2001 From: piuvas Date: Fri, 10 Jan 2025 22:10:18 -0300 Subject: [PATCH 065/448] requested changes --- packages/backend/src/server/api/stream/channel.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 7a044296fe..cfac4b30ce 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -9,8 +9,8 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { Packed } from '@/misc/json-schema.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; -import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import type Connection from './Connection.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; /** * Stream channel @@ -105,7 +105,7 @@ export default abstract class Channel { public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { let changed = false; - // cloning here seems like the best solution for a race condition + // cloning here seems like the best solution for not sharing changes with other users. // where multiple users shared the same myReaction. (Sharkey #877) const clonedNote = { ...note }; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { @@ -139,7 +139,6 @@ export default abstract class Channel { } } - export type MiChannelService = { shouldShare: boolean; requireCredential: T; From fa64112f9ff4052bd71475b9005938786eb52d29 Mon Sep 17 00:00:00 2001 From: dakkar Date: Sat, 11 Jan 2025 13:03:02 +0000 Subject: [PATCH 066/448] make widgets wider in classic view the style of widgets was changed by @Insert5StarName (@marie?) with commit e14fd33140f4ff076e141540559d4540fb9368b8 as part of !178 I'm not sure if it was intentional or accidental, and I'm also not sure why nobody noticed before 2024.11 (maybe something else kept the widgets wider?) Anyway, this change seems to be enough --- packages/frontend/src/ui/classic.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue index ded945dda1..f8cd5fa8be 100644 --- a/packages/frontend/src/ui/classic.vue +++ b/packages/frontend/src/ui/classic.vue @@ -260,6 +260,7 @@ onMounted(() => { position: sticky; top: 0; height: 100%; + width: 300px; padding-top: 16px; box-sizing: border-box; overflow: auto; From 531f7198d3b44503ceb742e1c7e06bb517bd10f1 Mon Sep 17 00:00:00 2001 From: dakkar Date: Sat, 11 Jan 2025 13:26:28 +0000 Subject: [PATCH 067/448] manual save search engine "custom url" - fixes #882 auto-saving would hit the rate-limit for `/api/i/registry/set` --- packages/frontend/src/pages/settings/general.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index b42c2f1503..cb0451c8b4 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -86,7 +86,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- + From c4192e81ed30292a06b85e7b00578ca87c97d076 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: Sat, 11 Jan 2025 22:43:42 +0900 Subject: [PATCH 068/448] =?UTF-8?q?enhance(backend):=20=E3=83=81=E3=83=A3?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E4=B8=80?= =?UTF-8?q?=E3=81=A4=E3=81=9A=E3=81=A4=E8=A1=8C=E3=81=86=E3=81=93=E3=81=A8?= =?UTF-8?q?=E3=81=A7DB=E3=81=AE=E5=90=8C=E6=99=82=E6=8E=A5=E7=B6=9A?= =?UTF-8?q?=E3=81=A8=E3=82=BF=E3=82=A4=E3=83=A0=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E3=82=92=E5=89=8A=E6=B8=9B=20(#15239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sync charts one-at-a-time to reduce database contention and timeouts * fix merge resolve failure * Update Changelog * update changelog * add comments --------- Co-authored-by: Hazelnoot --- CHANGELOG.md | 2 ++ .../src/core/chart/ChartManagementService.ts | 10 +++---- .../processors/CleanChartsProcessorService.ts | 27 +++++++++---------- .../ResyncChartsProcessorService.ts | 9 +++---- .../processors/TickChartsProcessorService.ts | 27 +++++++++---------- 5 files changed, 37 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd8eaf00e8..75dd37bc24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ ### Server - Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように +- Enhance: チャート更新時にDBに同時接続しないように + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/830) - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts index 79681370a1..f04c561063 100644 --- a/packages/backend/src/core/chart/ChartManagementService.ts +++ b/packages/backend/src/core/chart/ChartManagementService.ts @@ -58,9 +58,9 @@ export class ChartManagementService implements OnApplicationShutdown { @bindThis public async start() { // 20分おきにメモリ情報をDBに書き込み - this.saveIntervalId = setInterval(() => { + this.saveIntervalId = setInterval(async () => { for (const chart of this.charts) { - chart.save(); + await chart.save(); } }, 1000 * 60 * 20); } @@ -69,9 +69,9 @@ export class ChartManagementService implements OnApplicationShutdown { public async dispose(): Promise { clearInterval(this.saveIntervalId); if (process.env.NODE_ENV !== 'test') { - await Promise.all( - this.charts.map(chart => chart.save()), - ); + for (const chart of this.charts) { + await chart.save(); + } } } diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts index 110468801c..8c5faa8d07 100644 --- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts @@ -48,20 +48,19 @@ export class CleanChartsProcessorService { public async process(): Promise { this.logger.info('Clean charts...'); - await Promise.all([ - this.federationChart.clean(), - this.notesChart.clean(), - this.usersChart.clean(), - this.activeUsersChart.clean(), - this.instanceChart.clean(), - this.perUserNotesChart.clean(), - this.perUserPvChart.clean(), - this.driveChart.clean(), - this.perUserReactionsChart.clean(), - this.perUserFollowingChart.clean(), - this.perUserDriveChart.clean(), - this.apRequestChart.clean(), - ]); + // DBへの同時接続を避けるためにPromise.allを使わずひとつずつ実行する + await this.federationChart.clean(); + await this.notesChart.clean(); + await this.usersChart.clean(); + await this.activeUsersChart.clean(); + await this.instanceChart.clean(); + await this.perUserNotesChart.clean(); + await this.perUserPvChart.clean(); + await this.driveChart.clean(); + await this.perUserReactionsChart.clean(); + await this.perUserFollowingChart.clean(); + await this.perUserDriveChart.clean(); + await this.apRequestChart.clean(); this.logger.succ('All charts successfully cleaned.'); } diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts index 570cdf9a75..0c47fdedb3 100644 --- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts @@ -29,13 +29,12 @@ export class ResyncChartsProcessorService { public async process(): Promise { this.logger.info('Resync charts...'); + // DBへの同時接続を避けるためにPromise.allを使わずひとつずつ実行する // TODO: ユーザーごとのチャートも更新する // TODO: インスタンスごとのチャートも更新する - await Promise.all([ - this.driveChart.resync(), - this.notesChart.resync(), - this.usersChart.resync(), - ]); + await this.driveChart.resync(); + await this.notesChart.resync(); + await this.usersChart.resync(); this.logger.succ('All charts successfully resynced.'); } diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts index 93ec34162d..fc8856a271 100644 --- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts @@ -48,20 +48,19 @@ export class TickChartsProcessorService { public async process(): Promise { this.logger.info('Tick charts...'); - await Promise.all([ - this.federationChart.tick(false), - this.notesChart.tick(false), - this.usersChart.tick(false), - this.activeUsersChart.tick(false), - this.instanceChart.tick(false), - this.perUserNotesChart.tick(false), - this.perUserPvChart.tick(false), - this.driveChart.tick(false), - this.perUserReactionsChart.tick(false), - this.perUserFollowingChart.tick(false), - this.perUserDriveChart.tick(false), - this.apRequestChart.tick(false), - ]); + // DBへの同時接続を避けるためにPromise.allを使わずひとつずつ実行する + await this.federationChart.tick(false); + await this.notesChart.tick(false); + await this.usersChart.tick(false); + await this.activeUsersChart.tick(false); + await this.instanceChart.tick(false); + await this.perUserNotesChart.tick(false); + await this.perUserPvChart.tick(false); + await this.driveChart.tick(false); + await this.perUserReactionsChart.tick(false); + await this.perUserFollowingChart.tick(false); + await this.perUserDriveChart.tick(false); + await this.apRequestChart.tick(false); this.logger.succ('All charts successfully ticked.'); } From d60c307c4e4c3eaba2a40b46ba41c4d684d5d370 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: Sat, 11 Jan 2025 22:47:15 +0900 Subject: [PATCH 069/448] =?UTF-8?q?refactor/deps(frontend):=20shiki?= =?UTF-8?q?=E3=81=AEdeprecated=E8=A1=A8=E7=8F=BE=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20(#15169)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): shikiのdeprecated表現を修正 * update aiscript-vscode * :v: * fix * remove unused imports * bump aiscript-vscode to 0.1.15 --- packages/frontend/package.json | 2 +- packages/frontend/src/scripts/code-highlighter.ts | 6 +++--- packages/frontend/src/scripts/merge.ts | 8 ++++---- pnpm-lock.yaml | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 4f132ab500..843e4373ca 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -29,7 +29,7 @@ "@twemoji/parser": "15.1.1", "@vitejs/plugin-vue": "5.2.0", "@vue/compiler-sfc": "3.5.12", - "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11", + "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "astring": "1.9.0", "broadcast-channel": "7.0.0", "buraha": "0.0.1", diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts index 6710d9826e..4d57dcd944 100644 --- a/packages/frontend/src/scripts/code-highlighter.ts +++ b/packages/frontend/src/scripts/code-highlighter.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { createHighlighterCore, loadWasm } from 'shiki/core'; +import { createHighlighterCore } from 'shiki/core'; +import { createOnigurumaEngine } from 'shiki/engine/oniguruma'; import darkPlus from 'shiki/themes/dark-plus.mjs'; import { bundledThemesInfo } from 'shiki/themes'; import { bundledLanguagesInfo } from 'shiki/langs'; @@ -60,8 +61,6 @@ export async function getHighlighter(): Promise { } async function initHighlighter() { - await loadWasm(import('shiki/onig.wasm?init')); - // テーマの重複を消す const themes = unique([ darkPlus, @@ -70,6 +69,7 @@ async function initHighlighter() { const jsLangInfo = bundledLanguagesInfo.find(t => t.id === 'javascript'); const highlighter = await createHighlighterCore({ + engine: createOnigurumaEngine(() => import('shiki/onig.wasm?init')), themes, langs: [ ...(jsLangInfo ? [async () => await jsLangInfo.import()] : []), diff --git a/packages/frontend/src/scripts/merge.ts b/packages/frontend/src/scripts/merge.ts index 9794a300da..004b6d42a4 100644 --- a/packages/frontend/src/scripts/merge.ts +++ b/packages/frontend/src/scripts/merge.ts @@ -7,10 +7,10 @@ import { deepClone } from './clone.js'; import type { Cloneable } from './clone.js'; export type DeepPartial = { - [P in keyof T]?: T[P] extends Record ? DeepPartial : T[P]; + [P in keyof T]?: T[P] extends Record ? DeepPartial : T[P]; }; -function isPureObject(value: unknown): value is Record { +function isPureObject(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } @@ -18,14 +18,14 @@ function isPureObject(value: unknown): value is Record>(value: DeepPartial, def: X): X { +export function deepMerge>(value: DeepPartial, def: X): X { if (isPureObject(value) && isPureObject(def)) { const result = deepClone(value as Cloneable) as X; for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) { if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) { result[k] = v; } else if (isPureObject(v) && isPureObject(result[k])) { - const child = deepClone(result[k] as Cloneable) as DeepPartial>; + const child = deepClone(result[k] as Cloneable) as DeepPartial>; result[k] = deepMerge(child, v); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f50c635bf0..d9464ba600 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -728,8 +728,8 @@ importers: specifier: 3.5.12 version: 3.5.12 aiscript-vscode: - specifier: github:aiscript-dev/aiscript-vscode#v0.1.11 - version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9 + specifier: github:aiscript-dev/aiscript-vscode#v0.1.15 + version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7 astring: specifier: 1.9.0 version: 1.9.0 @@ -4938,9 +4938,9 @@ packages: resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} engines: {node: '>=18'} - aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9: - resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9} - version: 0.1.11 + aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7: + resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7} + version: 0.1.15 engines: {vscode: ^1.83.0} ajv-draft-04@1.0.0: @@ -15664,7 +15664,7 @@ snapshots: clean-stack: 5.2.0 indent-string: 5.0.0 - aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9: + aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7: dependencies: '@aiscript-dev/aiscript-languageserver': https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.6/aiscript-dev-aiscript-languageserver-0.1.6.tgz vscode-languageclient: 9.0.1 From b9e03339fd5409dee836f3c8dbeda529c762954a Mon Sep 17 00:00:00 2001 From: dakkar Date: Sat, 11 Jan 2025 14:06:21 +0000 Subject: [PATCH 070/448] don't double-url-encode rss urls `url.searchParams.set()` already encodes the values passed! (this is a partial revert of 0472d43ee97f1ac0fd13969b2111d67b322a947f, the change in `statusbar-rss.vue` was correct) --- packages/frontend/src/widgets/WidgetRss.vue | 2 +- packages/frontend/src/widgets/WidgetRssTicker.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue index 92dc6d148e..3e43687709 100644 --- a/packages/frontend/src/widgets/WidgetRss.vue +++ b/packages/frontend/src/widgets/WidgetRss.vue @@ -70,7 +70,7 @@ const items = computed(() => rawItems.value.slice(0, widgetProps.maxEntries)); const fetching = ref(true); const fetchEndpoint = computed(() => { const url = new URL('/api/fetch-rss', base); - url.searchParams.set('url', encodeURIComponent(widgetProps.url)); + url.searchParams.set('url', widgetProps.url); return url; }); const intervalClear = ref<(() => void) | undefined>(); diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue index 6957878572..4f594b720f 100644 --- a/packages/frontend/src/widgets/WidgetRssTicker.vue +++ b/packages/frontend/src/widgets/WidgetRssTicker.vue @@ -99,7 +99,7 @@ const items = computed(() => { const fetching = ref(true); const fetchEndpoint = computed(() => { const url = new URL('/api/fetch-rss', base); - url.searchParams.set('url', encodeURIComponent(widgetProps.url)); + url.searchParams.set('url', widgetProps.url); return url; }); const intervalClear = ref<(() => void) | undefined>(); From 3ceac893c942b0baad2f8a0c6b51baa2b8477b5e Mon Sep 17 00:00:00 2001 From: dakkar Date: Sun, 12 Jan 2025 12:33:08 +0000 Subject: [PATCH 071/448] attribute invite codes to admins/moderators when a regular user (who has the appropriate permissions) creates an invite, we record that user's id in the `createdById` column but when an admin/mod creates an invite via the control panel, we didn't now we do --- .../backend/src/server/api/endpoints/admin/invite/create.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/backend/src/server/api/endpoints/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts index 5ecae3161a..e52b177e2b 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts @@ -68,6 +68,8 @@ export default class extends Endpoint { // eslint- for (let i = 0; i < ps.count; i++) { ticketsPromises.push(this.registrationTicketsRepository.insertOne({ id: this.idService.gen(), + createdBy: me, + createdById: me.id, expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, code: generateInviteCode(), })); From 3fc377839c6155d1eb3c55ae7eb24e85e4ad90c7 Mon Sep 17 00:00:00 2001 From: piuvas Date: Sun, 12 Jan 2025 21:11:36 -0300 Subject: [PATCH 072/448] comment :3 --- packages/backend/src/server/api/stream/channel.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index cfac4b30ce..047dedd5ce 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -105,8 +105,11 @@ export default abstract class Channel { public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { let changed = false; - // cloning here seems like the best solution for not sharing changes with other users. - // where multiple users shared the same myReaction. (Sharkey #877) + // StreamingApiServerService creates a single EventEmitter per server process, + // so a new note arriving from redis gets de-serialised once per server process, + // and then that single object is passed to all active channels on each connection. + // If we didn't clone the notes here, different connections would asynchronously write + // different values to the same object, resulting in a random value being sent to each frontend. -- Dakkar const clonedNote = { ...note }; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { From d86c77260e59cc684d8bf1b993e3aaf75e90eb2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:13:57 +0900 Subject: [PATCH 073/448] =?UTF-8?q?fix(frontend):=20RSS=E3=82=A6=E3=82=A3?= =?UTF-8?q?=E3=82=B8=E3=82=A7=E3=83=83=E3=83=88=E3=81=A7URL=E3=82=A8?= =?UTF-8?q?=E3=83=B3=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E4=BA=8C=E9=87=8D?= =?UTF-8?q?=E3=81=AB=E8=A1=8C=E3=81=A3=E3=81=A6=E3=81=84=E3=82=8B=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=20(#15272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * don't double-url-encode rss urls `url.searchParams.set()` already encodes the values passed! (this is a partial revert of 0472d43ee97f1ac0fd13969b2111d67b322a947f, the change in `statusbar-rss.vue` was correct) * Update Changelog --------- Co-authored-by: dakkar --- CHANGELOG.md | 2 ++ packages/frontend/src/widgets/WidgetRss.vue | 2 +- packages/frontend/src/widgets/WidgetRssTicker.vue | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75dd37bc24..0c63226c58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ - Fix: ノート作成画面でファイルの添付可能個数を超えてもノートボタンが押せていた問題を修正 - Fix: 「アカウントを管理」画面で、ユーザー情報の取得に失敗したアカウント(削除されたアカウントなど)が表示されない問題を修正 - Fix: 言語データのキャッシュ状況によっては、埋め込みウィジェットが正しく起動しない問題を修正 +- Fix: RSSウィジェットが正しく表示されない問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/857) ### Server - Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue index 92dc6d148e..3e43687709 100644 --- a/packages/frontend/src/widgets/WidgetRss.vue +++ b/packages/frontend/src/widgets/WidgetRss.vue @@ -70,7 +70,7 @@ const items = computed(() => rawItems.value.slice(0, widgetProps.maxEntries)); const fetching = ref(true); const fetchEndpoint = computed(() => { const url = new URL('/api/fetch-rss', base); - url.searchParams.set('url', encodeURIComponent(widgetProps.url)); + url.searchParams.set('url', widgetProps.url); return url; }); const intervalClear = ref<(() => void) | undefined>(); diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue index 6957878572..4f594b720f 100644 --- a/packages/frontend/src/widgets/WidgetRssTicker.vue +++ b/packages/frontend/src/widgets/WidgetRssTicker.vue @@ -99,7 +99,7 @@ const items = computed(() => { const fetching = ref(true); const fetchEndpoint = computed(() => { const url = new URL('/api/fetch-rss', base); - url.searchParams.set('url', encodeURIComponent(widgetProps.url)); + url.searchParams.set('url', widgetProps.url); return url; }); const intervalClear = ref<(() => void) | undefined>(); From b161601863662e3c40571fd9958d4b43a202974a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:35:19 +0900 Subject: [PATCH 074/448] =?UTF-8?q?fix(frontend):=20=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=81=AE=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=88=E5=BF=98=E3=82=8C=20(#1527?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/pages/settings/mute-block.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue index 797f0ec106..19dd2468f8 100644 --- a/packages/frontend/src/pages/settings/mute-block.vue +++ b/packages/frontend/src/pages/settings/mute-block.vue @@ -144,6 +144,7 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { infoImageUrl } from '@/instance.js'; import { signinRequired } from '@/account.js'; +import MkInfo from '@/components/MkInfo.vue'; import MkFolder from '@/components/MkFolder.vue'; const $i = signinRequired(); From 6820878676f5f531bc596076ed5c5ea022051631 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 14 Jan 2025 19:36:35 +0900 Subject: [PATCH 075/448] fix: unable to use AiService on arm64 (#15261) --- CHANGELOG.md | 1 + packages/backend/src/core/AiService.ts | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c63226c58..b9f0d6495a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - Fix: disableClustering設定時の初期化ロジックを調整( #15223 ) - Fix: ActivityPubリクエストかどうかの判定が正しくない問題を修正 (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/869) +- Fix: AIセンシティブ判定が arm64 環境で動作しない問題を修正 ## 2024.11.0 diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index ad852fdd6e..33ddabb5e0 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -15,7 +15,7 @@ import { bindThis } from '@/decorators.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); -const REQUIRED_CPU_FLAGS = ['avx2', 'fma']; +const REQUIRED_CPU_FLAGS_X64 = ['avx2', 'fma']; let isSupportedCpu: undefined | boolean = undefined; @Injectable() @@ -31,8 +31,7 @@ export class AiService { public async detectSensitive(path: string): Promise { try { if (isSupportedCpu === undefined) { - const cpuFlags = await this.getCpuFlags(); - isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required)); + isSupportedCpu = await this.computeIsSupportedCpu(); } if (!isSupportedCpu) { @@ -64,6 +63,22 @@ export class AiService { } } + private async computeIsSupportedCpu(): Promise { + switch (process.arch) { + case 'x64': { + const cpuFlags = await this.getCpuFlags(); + return REQUIRED_CPU_FLAGS_X64.every(required => cpuFlags.includes(required)); + } + case 'arm64': { + // As far as I know, no required CPU flags for ARM64. + return true; + } + default: { + return false; + } + } + } + @bindThis private async getCpuFlags(): Promise { const str = await si.cpuFlags(); From 759b9f4cf133dfa6b06c6fcebf22aefc017363bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:37:41 +0900 Subject: [PATCH 076/448] =?UTF-8?q?feat(backend):=20config(default.yml)?= =?UTF-8?q?=E3=81=8B=E3=82=89SQL=E3=83=AD=E3=82=B0=E5=85=A8=E6=96=87?= =?UTF-8?q?=E3=82=92=E5=87=BA=E5=8A=9B=E3=81=99=E3=82=8B=E3=81=8B=E5=90=A6?= =?UTF-8?q?=E3=81=8B=E3=82=92=E8=A8=AD=E5=AE=9A=E5=8F=AF=E8=83=BD=E3=81=AB?= =?UTF-8?q?=20(#15268)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feature(backend): config(default.yml)からSQLログ全文を出力するか否かを設定可能に * disableHighlightやめる * refactor --- .config/docker_example.yml | 10 ++++++ .config/example.yml | 10 ++++++ CHANGELOG.md | 1 + packages/backend/src/config.ts | 14 ++++++++ packages/backend/src/postgres.ts | 59 +++++++++++++++++++++++++++----- 5 files changed, 86 insertions(+), 8 deletions(-) diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 3f8e5734ce..0f5ba9696d 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -219,3 +219,13 @@ signToActivityPubGet: true # Upload or download file size limits (bytes) #maxFileSize: 262144000 + +# Log settings +# logging: +# sql: +# # Outputs query parameters during SQL execution to the log. +# # default: false +# enableQueryParamLogging: false +# # Disable query truncation. If set to true, the full text of the query will be output to the log. +# # default: false +# disableQueryTruncation: false diff --git a/.config/example.yml b/.config/example.yml index 60a6a0aa71..ea29cedd10 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -321,3 +321,13 @@ signToActivityPubGet: true # PID File of master process #pidFile: /tmp/misskey.pid + +# Log settings +# logging: +# sql: +# # Outputs query parameters during SQL execution to the log. +# # default: false +# enableQueryParamLogging: false +# # Disable query truncation. If set to true, the full text of the query will be output to the log. +# # default: false +# disableQueryTruncation: false diff --git a/CHANGELOG.md b/CHANGELOG.md index b9f0d6495a..071cf22a5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように - Enhance: チャート更新時にDBに同時接続しないように (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/830) +- Enhance: config(default.yml)からSQLログ全文を出力するか否かを設定可能に ( #15266 ) - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 42f1033b9d..a5dc61db13 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -99,6 +99,13 @@ type Source = { perUserNotificationsMaxCount?: number; deactivateAntennaThreshold?: number; pidFile: string; + + logging?: { + sql?: { + disableQueryTruncation? : boolean, + enableQueryParamLogging? : boolean, + } + } }; export type Config = { @@ -151,6 +158,12 @@ export type Config = { inboxJobMaxAttempts: number | undefined; proxyRemoteFiles: boolean | undefined; signToActivityPubGet: boolean | undefined; + logging?: { + sql?: { + disableQueryTruncation? : boolean, + enableQueryParamLogging? : boolean, + } + } version: string; publishTarballInsteadOfProvideRepositoryUrl: boolean; @@ -293,6 +306,7 @@ export function loadConfig(): Config { perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500, deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7), pidFile: config.pidFile, + logging: config.logging, }; } diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 251a03c303..d09240eba1 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -89,27 +89,65 @@ export const dbLogger = new MisskeyLogger('db'); const sqlLogger = dbLogger.createSubLogger('sql', 'gray'); +export type LoggerProps = { + disableQueryTruncation?: boolean; + enableQueryParamLogging?: boolean; +} + +function highlightSql(sql: string) { + return highlight.highlight(sql, { + language: 'sql', ignoreIllegals: true, + }); +} + +function truncateSql(sql: string) { + return sql.length > 100 ? `${sql.substring(0, 100)}...` : sql; +} + +function stringifyParameter(param: any) { + if (param instanceof Date) { + return param.toISOString(); + } else { + return param; + } +} + class MyCustomLogger implements Logger { + constructor(private props: LoggerProps = {}) { + } + @bindThis - private highlight(sql: string) { - return highlight.highlight(sql, { - language: 'sql', ignoreIllegals: true, - }); + private transformQueryLog(sql: string) { + let modded = sql; + if (!this.props.disableQueryTruncation) { + modded = truncateSql(modded); + } + + return highlightSql(modded); + } + + @bindThis + private transformParameters(parameters?: any[]) { + if (this.props.enableQueryParamLogging && parameters && parameters.length > 0) { + return parameters.map(stringifyParameter); + } + + return undefined; } @bindThis public logQuery(query: string, parameters?: any[]) { - sqlLogger.info(this.highlight(query).substring(0, 100)); + sqlLogger.info(this.transformQueryLog(query), this.transformParameters(parameters)); } @bindThis public logQueryError(error: string, query: string, parameters?: any[]) { - sqlLogger.error(this.highlight(query)); + sqlLogger.error(this.transformQueryLog(query), this.transformParameters(parameters)); } @bindThis public logQuerySlow(time: number, query: string, parameters?: any[]) { - sqlLogger.warn(this.highlight(query)); + sqlLogger.warn(this.transformQueryLog(query), this.transformParameters(parameters)); } @bindThis @@ -247,7 +285,12 @@ export function createPostgresDataSource(config: Config) { }, } : false, logging: log, - logger: log ? new MyCustomLogger() : undefined, + logger: log + ? new MyCustomLogger({ + disableQueryTruncation: config.logging?.sql?.disableQueryTruncation, + enableQueryParamLogging: config.logging?.sql?.enableQueryParamLogging, + }) + : undefined, maxQueryExecutionTime: 300, entities: entities, migrations: ['../../migration/*.js'], From d082a1dd34fa84a0a6c5afa99a984437d9d1e686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:40:14 +0900 Subject: [PATCH 077/448] =?UTF-8?q?fix(frontend/dev):=20=E3=83=90=E3=83=83?= =?UTF-8?q?=E3=82=AF=E3=82=A8=E3=83=B3=E3=83=89=E7=B5=8C=E7=94=B1=E3=81=A7?= =?UTF-8?q?=E3=81=AE=E9=96=8B=E7=99=BA=E6=99=82=E3=81=ABHMR=E3=81=8C?= =?UTF-8?q?=E5=8A=B9=E3=81=8B=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(#15255)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend-embed/vite.config.ts | 6 ++++++ packages/frontend/vite.config.ts | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts index 2dbee488c5..151d316190 100644 --- a/packages/frontend-embed/vite.config.ts +++ b/packages/frontend-embed/vite.config.ts @@ -63,6 +63,12 @@ export function getConfig(): UserConfig { server: { port: 5174, + hmr: { + // バックエンド経由での起動時、Viteは5174経由でアセットを参照していると思い込んでいるが実際は3000から配信される + // そのため、バックエンドのWSサーバーにHMRのWSリクエストが吸収されてしまい、正しくHMRが機能しない + // クライアント側のWSポートをViteサーバーのポートに強制させることで、正しくHMRが機能するようになる + clientPort: 5174, + }, }, plugins: [ diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 504562a91e..3c4b19a571 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -65,6 +65,12 @@ export function getConfig(): UserConfig { server: { port: 5173, + hmr: { + // バックエンド経由での起動時、Viteは5173経由でアセットを参照していると思い込んでいるが実際は3000から配信される + // そのため、バックエンドのWSサーバーにHMRのWSリクエストが吸収されてしまい、正しくHMRが機能しない + // クライアント側のWSポートをViteサーバーのポートに強制させることで、正しくHMRが機能するようになる + clientPort: 5173, + }, headers: { // なんか効かない 'X-Frame-Options': 'DENY', }, From dd6743dda44abd27740421b47e27604a02f55c2c Mon Sep 17 00:00:00 2001 From: taichan <40626578+tai-cha@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:46:57 +0900 Subject: [PATCH 078/448] =?UTF-8?q?Fix(frontend):=20=E5=89=8A=E9=99=A4?= =?UTF-8?q?=E3=81=97=E3=81=A6=E7=B7=A8=E9=9B=86=E3=81=A7=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E3=81=82=E3=82=8A=E3=82=92=E6=B6=88=E3=81=9B=E3=81=AA=E3=81=84?= =?UTF-8?q?=20(#15249)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix(frontend): 削除して編集で引用ありを消せない * docs(changelog): update CHANGELOG.md * rename noteToRenote -> renoteTargetNote with type fix * Update Changelog --------- Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + .../frontend/src/components/MkPostForm.vue | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 071cf22a5a..f5fd739232 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Fix: ノート作成画面でファイルの添付可能個数を超えてもノートボタンが押せていた問題を修正 - Fix: 「アカウントを管理」画面で、ユーザー情報の取得に失敗したアカウント(削除されたアカウントなど)が表示されない問題を修正 - Fix: 言語データのキャッシュ状況によっては、埋め込みウィジェットが正しく起動しない問題を修正 +- Fix: 「削除して編集」でノートの引用を解除出来なかった問題を修正( #14476 ) - Fix: RSSウィジェットが正しく表示されない問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/857) diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index ba988ee715..07b6869bf6 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -46,14 +46,14 @@ SPDX-License-Identifier: AGPL-3.0-only - +
- -
{{ i18n.ts.quoteAttached }}
+ +
{{ i18n.ts.quoteAttached }}
{{ i18n.ts.recipient }}
@@ -100,12 +100,13 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 50002f1983..3ff8fdc844 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -136,6 +136,12 @@ type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations // @public (undocumented) type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json']; +// @public (undocumented) +type AdminCaptchaCurrentResponse = operations['admin___captcha___current']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json']; + // @public (undocumented) type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json']; @@ -1261,6 +1267,8 @@ declare namespace entities { AdminAvatarDecorationsListRequest, AdminAvatarDecorationsListResponse, AdminAvatarDecorationsUpdateRequest, + AdminCaptchaCurrentResponse, + AdminCaptchaSaveRequest, AdminDeleteAllFilesOfAUserRequest, AdminUnsetUserAvatarRequest, AdminUnsetUserBannerRequest, diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 1837f3db4f..3bcdae6a4a 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -250,6 +250,28 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index cb1f4dbe96..b016d5bbcf 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -36,6 +36,8 @@ import type { AdminAvatarDecorationsListRequest, AdminAvatarDecorationsListResponse, AdminAvatarDecorationsUpdateRequest, + AdminCaptchaCurrentResponse, + AdminCaptchaSaveRequest, AdminDeleteAllFilesOfAUserRequest, AdminUnsetUserAvatarRequest, AdminUnsetUserBannerRequest, @@ -604,6 +606,8 @@ export type Endpoints = { 'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse }; 'admin/avatar-decorations/list': { req: AdminAvatarDecorationsListRequest; res: AdminAvatarDecorationsListResponse }; 'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse }; + 'admin/captcha/current': { req: EmptyRequest; res: AdminCaptchaCurrentResponse }; + 'admin/captcha/save': { req: AdminCaptchaSaveRequest; res: EmptyResponse }; 'admin/delete-all-files-of-a-user': { req: AdminDeleteAllFilesOfAUserRequest; res: EmptyResponse }; 'admin/unset-user-avatar': { req: AdminUnsetUserAvatarRequest; res: EmptyResponse }; 'admin/unset-user-banner': { req: AdminUnsetUserBannerRequest; res: EmptyResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index a8f474c25c..02be4848c7 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -39,6 +39,8 @@ export type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-dec export type AdminAvatarDecorationsListRequest = operations['admin___avatar-decorations___list']['requestBody']['content']['application/json']; export type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations___list']['responses']['200']['content']['application/json']; export type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json']; +export type AdminCaptchaCurrentResponse = operations['admin___captcha___current']['responses']['200']['content']['application/json']; +export type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json']; export type AdminDeleteAllFilesOfAUserRequest = operations['admin___delete-all-files-of-a-user']['requestBody']['content']['application/json']; export type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json']; export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 42ca05e057..e6a9df3f5a 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -215,6 +215,24 @@ export type paths = { */ post: operations['admin___avatar-decorations___update']; }; + '/admin/captcha/current': { + /** + * admin/captcha/current + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + */ + post: operations['admin___captcha___current']; + }; + '/admin/captcha/save': { + /** + * admin/captcha/save + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + */ + post: operations['admin___captcha___save']; + }; '/admin/delete-all-files-of-a-user': { /** * admin/delete-all-files-of-a-user @@ -6564,6 +6582,128 @@ export type operations = { }; }; }; + /** + * admin/captcha/current + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + */ + admin___captcha___current: { + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + /** @enum {string} */ + provider: 'none' | 'hcaptcha' | 'mcaptcha' | 'recaptcha' | 'turnstile' | 'testcaptcha'; + hcaptcha: { + siteKey: string | null; + secretKey: string | null; + }; + mcaptcha: { + siteKey: string | null; + secretKey: string | null; + instanceUrl: string | null; + }; + recaptcha: { + siteKey: string | null; + secretKey: string | null; + }; + turnstile: { + siteKey: string | null; + secretKey: string | null; + }; + }; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * admin/captcha/save + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + */ + admin___captcha___save: { + requestBody: { + content: { + 'application/json': { + /** @enum {string} */ + provider: 'none' | 'hcaptcha' | 'mcaptcha' | 'recaptcha' | 'turnstile' | 'testcaptcha'; + captchaResult?: string | null; + sitekey?: string | null; + secret?: string | null; + instanceUrl?: string | null; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * admin/delete-all-files-of-a-user * @description No description provided. From 5445b023e5cedb7228710637c895c63328e3db74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 14 Jan 2025 20:08:54 +0900 Subject: [PATCH 081/448] =?UTF-8?q?enhance:=20=E9=80=A3=E5=90=88=E3=83=A2?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=81=AB=E3=81=82=E3=82=8F=E3=81=9B=E3=81=A6?= =?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?=E3=82=92=E5=A4=89=E5=8C=96=E3=81=95=E3=81=9B=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=20(#15112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(backend): metaにfederation modeに関する情報を公開 * enhance(frontend): 登録画面の注意書きを追加 * enhance(frontend): aboutページ・サーバー情報 * enhance(frontend): サーバー統計 * enhance(frontend): みつけるページ * enhance(frontend): 検索 * enhance(frontend): ユーザー選択 * enhance(frontend): 設定画面 * enhance(frontend): ウィジェット * enhance(frontend): リモートで開くオプション * Update Changelog * enhance(frontend): ステータスバー * i18n --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + locales/index.d.ts | 8 ++++ locales/ja-JP.yml | 2 + .../src/core/entities/MetaEntityService.ts | 1 + .../backend/src/models/json-schema/meta.ts | 5 ++ .../src/components/MkInstanceStats.vue | 18 ++++--- .../src/components/MkSignupDialog.rules.vue | 6 ++- .../src/components/MkUserSelectDialog.vue | 11 +++-- .../src/components/MkVisitorDashboard.vue | 6 ++- .../frontend/src/components/MkWidgets.vue | 19 ++++++-- packages/frontend/src/pages/about.vue | 47 ++++++++++++------- packages/frontend/src/pages/explore.users.vue | 3 +- packages/frontend/src/pages/search.note.vue | 22 +++++---- packages/frontend/src/pages/search.user.vue | 5 +- .../frontend/src/pages/settings/general.vue | 3 +- .../src/pages/settings/mute-block.vue | 4 +- .../frontend/src/pages/settings/privacy.vue | 9 ++-- .../pages/settings/statusbar.statusbar.vue | 3 +- packages/frontend/src/scripts/please-login.ts | 12 ++++- packages/frontend/src/ui/_common_/common.ts | 18 ++++--- .../frontend/src/ui/_common_/statusbars.vue | 3 +- packages/frontend/src/widgets/index.ts | 10 +++- packages/misskey-js/src/autogen/types.ts | 2 + 23 files changed, 150 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af5d333927..287d390453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Enhance: PC画面でチャンネルが複数列で表示されるように (Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13) - Enhance: 照会に失敗した場合、その理由を表示するように +- Enhance: 連合がホワイトリスト化・無効化されているサーバー向けのデザイン修正 - Enhance: AiScriptのセーブデータを明示的に削除する関数`Mk:remove`を追加 - Enhance: AiScriptの拡張API関数において引数の型チェックをより厳格に - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index 7c3ef5d93c..453d40feea 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5230,6 +5230,14 @@ export interface Locale extends ILocale { * 注意事項を理解した上でオンにします。 */ "acknowledgeNotesAndEnable": string; + /** + * このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。 + */ + "federationSpecified": string; + /** + * このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。 + */ + "federationDisabled": string; "_accountSettings": { /** * コンテンツの表示にログインを必須にする diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 57a88062c1..a3cb9d052a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1303,6 +1303,8 @@ lockdown: "ロックダウン" pleaseSelectAccount: "アカウントを選択してください" availableRoles: "利用可能なロール" acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。" +federationSpecified: "このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。" +federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。" _accountSettings: requireSigninToViewContents: "コンテンツの表示にログインを必須にする" diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index 409dca3426..ec0b5360f4 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -132,6 +132,7 @@ export class MetaEntityService { enableUrlPreview: instance.urlPreviewEnabled, noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local', maxFileSize: this.config.maxFileSize, + federation: this.meta.federation, }; return packed; diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index e3fd63464a..e7ae2ee8e5 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -261,6 +261,11 @@ export const packedMetaLiteSchema = { type: 'number', optional: false, nullable: false, }, + federation: { + type: 'string', + enum: ['all', 'specified', 'none'], + optional: false, nullable: false, + }, }, } as const; diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue index 8ccbf61e48..d8066857fe 100644 --- a/packages/frontend/src/components/MkInstanceStats.vue +++ b/packages/frontend/src/components/MkInstanceStats.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- + @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -46,9 +46,9 @@ SPDX-License-Identifier: AGPL-3.0-only - - - + + +
@@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
@@ -84,13 +84,15 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -101,4 +111,8 @@ const isThumbnailAvailable = computed(() => { font-size: 32px; color: #777; } + +.large .icon { + font-size: 40px; +} diff --git a/packages/frontend/src/components/MkNoteMediaGrid.vue b/packages/frontend/src/components/MkNoteMediaGrid.vue new file mode 100644 index 0000000000..520421bfb7 --- /dev/null +++ b/packages/frontend/src/components/MkNoteMediaGrid.vue @@ -0,0 +1,99 @@ + + + + + + + diff --git a/packages/frontend/src/pages/user/files.vue b/packages/frontend/src/pages/user/files.vue new file mode 100644 index 0000000000..b6c7c1c777 --- /dev/null +++ b/packages/frontend/src/pages/user/files.vue @@ -0,0 +1,56 @@ + + + + + + + diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 2794db2821..a6a49f0ab9 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -136,7 +136,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.userPagePinTip }} @@ -109,4 +110,9 @@ function emitOrder(sortOrders: SortOrder[]) { border-radius: 9999px; background-color: var(--MI_THEME-buttonBg); } + +.sortOrderTag { + user-select: none; + cursor: pointer; +} diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 0caaed6f39..fa0e40d8f9 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -47,7 +47,7 @@ export type SuperMenuDef = { active?: boolean; action: (ev: MouseEvent) => void; } | { - type: 'link'; + type?: 'link'; to: string; icon?: string; text: string; diff --git a/packages/frontend/src/components/MkTagItem.vue b/packages/frontend/src/components/MkTagItem.vue index 98f2411392..8b7460f3a3 100644 --- a/packages/frontend/src/components/MkTagItem.vue +++ b/packages/frontend/src/components/MkTagItem.vue @@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index aa4be69b2c..a2e70a5cad 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -48,13 +48,16 @@ import { scrollToTop } from '@@/js/scroll.js'; import { globalEvents } from '@/events.js'; import { injectReactiveMetadata } from '@/scripts/page-metadata.js'; import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js'; -import { PageHeaderItem } from '@/types/page-header.js'; +import type { PageHeaderItem } from '@/types/page-header.js'; +import type { PageMetadata } from '@/scripts/page-metadata.js'; const props = withDefaults(defineProps<{ + overridePageMetadata?: PageMetadata; tabs?: Tab[]; tab?: string; actions?: PageHeaderItem[] | null; thin?: boolean; + hideTitle?: boolean; displayMyAvatar?: boolean; }>(), { tabs: () => ([] as Tab[]), @@ -64,9 +67,10 @@ const emit = defineEmits<{ (ev: 'update:tab', key: string); }>(); -const pageMetadata = injectReactiveMetadata(); +const injectedPageMetadata = injectReactiveMetadata(); +const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value); -const hideTitle = inject('shouldOmitHeaderTitle', false); +const hideTitle = computed(() => inject('shouldOmitHeaderTitle', false) || props.hideTitle); const thin_ = props.thin || inject('shouldHeaderThin', false); const el = shallowRef(undefined); @@ -75,7 +79,7 @@ const narrow = ref(false); const hasTabs = computed(() => props.tabs.length > 0); const hasActions = computed(() => props.actions && props.actions.length > 0); const show = computed(() => { - return !hideTitle || hasTabs.value || hasActions.value; + return !hideTitle.value || hasTabs.value || hasActions.value; }); const preventDrag = (ev: TouchEvent) => { diff --git a/packages/frontend/src/components/grid/MkDataCell.vue b/packages/frontend/src/components/grid/MkDataCell.vue index 0ffd42abda..e473b7c1af 100644 --- a/packages/frontend/src/components/grid/MkDataCell.vue +++ b/packages/frontend/src/components/grid/MkDataCell.vue @@ -39,13 +39,15 @@ SPDX-License-Identifier: AGPL-3.0-only {{ cell.value }}
- - +
@@ -375,6 +377,31 @@ $cellHeight: 28px; object-fit: cover; } +.bool { + position: relative; + width: 18px; + height: 18px; + background: var(--MI_THEME-panel); + border: solid 2px var(--MI_THEME-divider); + border-radius: 4px; + box-sizing: border-box; + + &.boolTrue { + border-color: var(--MI_THEME-accent); + background: var(--MI_THEME-accent); + + &::before { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: var(--MI_THEME-fgOnAccent); + font-size: 12px; + line-height: 18px; + } + } +} + .editingInput { padding: 0 8px; width: 100%; diff --git a/packages/frontend/src/components/grid/MkGrid.vue b/packages/frontend/src/components/grid/MkGrid.vue index 60738365fb..4dbd4ebcae 100644 --- a/packages/frontend/src/components/grid/MkGrid.vue +++ b/packages/frontend/src/components/grid/MkGrid.vue @@ -7,7 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
(); const props = defineProps<{ - settings: GridSetting, - data: DataSource[] + settings: GridSetting; + data: DataSource[]; }>(); +const rootSetting: Required = { + noOverflowStyle: false, + rounded: true, + outerBorder: true, + ...props.settings.root, +}; + // non-reactive // eslint-disable-next-line vue/no-setup-props-reactivity-loss const rowSetting: Required = { @@ -1277,32 +1288,48 @@ onMounted(() => { overflow-x: scroll; // firefoxだとスクロールバーがセルに重なって見づらくなってしまうのでスペースを空けておく padding-bottom: 8px; + + &.noOverflowHandling { + overflow-x: revert; + padding-bottom: 0; + } } diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue index 55f9632ce4..c4ea3b93e3 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue @@ -5,137 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only + + +const headerTabs = computed(() => [{ + key: 'list', + title: i18n.ts._customEmojisManager._local.tabTitleList, +}, { + key: 'register', + title: i18n.ts._customEmojisManager._local.tabTitleRegister, +}]); + diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.logs-folder.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.logs.vue similarity index 62% rename from packages/frontend/src/pages/admin/custom-emojis-manager.logs-folder.vue rename to packages/frontend/src/pages/admin/custom-emojis-manager.logs.vue index f75f6c0da5..eef55a9f7e 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-manager.logs-folder.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.logs.vue @@ -4,47 +4,38 @@ SPDX-License-Identifier: AGPL-3.0-only --> - - diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue index 14a3b71e53..eecf8d7390 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue @@ -64,6 +64,8 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+ @@ -74,6 +76,14 @@ SPDX-License-Identifier: AGPL-3.0-only /> + + + +
{{ i18n.ts.search }} @@ -85,7 +95,14 @@ SPDX-License-Identifier: AGPL-3.0-only
- + + + + + + @@ -253,6 +255,9 @@ SPDX-License-Identifier: AGPL-3.0-only +