From f13bb06c29380d54f02afdf645983cf47ca96dd6 Mon Sep 17 00:00:00 2001 From: Camilla Ett Date: Fri, 24 Nov 2023 08:37:27 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat(frontend):=20=E3=83=AA=E3=82=A2?= =?UTF-8?q?=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AE=E6=A8=AA=E5=B9=85?= =?UTF-8?q?=E3=82=92150px=E3=81=AB=E5=88=B6=E9=99=90=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=8B=E3=81=A9=E3=81=86=E3=81=8B=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=81=8C=E9=81=B8=E3=81=B9=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=20(#12416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(frontend): リアクションの横幅を150pxに制限するかどうかユーザーが選べるように * localesの変更をjs-JP.ymlのみに修正し、日本語をより分かりやすく * クラス名を.icon から .limitWidthに変更 --- locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + .../frontend/src/components/MkReactionsViewer.reaction.vue | 4 ++-- packages/frontend/src/pages/settings/general.vue | 3 +++ packages/frontend/src/store.ts | 4 ++++ 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 422f371fe609..51896daba22a 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1038,6 +1038,7 @@ export interface Locale { "enableChartsForFederatedInstances": string; "showClipButtonInNoteFooter": string; "reactionsDisplaySize": string; + "limitWidthOfReaction": string; "noteIdOrUrl": string; "video": string; "videos": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 67b5cfc0e196..40e2d5453fcf 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1035,6 +1035,7 @@ enableChartsForRemoteUser: "リモートユーザーのチャートを生成" enableChartsForFederatedInstances: "リモートサーバーのチャートを生成" showClipButtonInNoteFooter: "ノートのアクションにクリップを追加" reactionsDisplaySize: "リアクションの表示サイズ" +limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小して表示する" noteIdOrUrl: "ノートIDまたはURL" video: "動画" videos: "動画" diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 2b850016c588..9a107c367484 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only :class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]" @click="toggleReaction()" > - + {{ count }} @@ -188,7 +188,7 @@ if (!mock) { } } -.icon { +.limitWidth { max-width: 150px; } diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index e3130e236ff5..341e2b5c7a54 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -57,6 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only + {{ i18n.ts.limitWidthOfReaction }} @@ -227,6 +228,7 @@ const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serve const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover')); const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter')); const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize')); +const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction')); const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes')); const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v)); const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal')); @@ -292,6 +294,7 @@ watch([ overridedDeviceKind, mediaListWithOneImageAppearance, reactionsDisplaySize, + limitWidthOfReaction, highlightSensitiveMedia, keepScreenOn, disableStreamingTimeline, diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 369470be78f0..d0f0c5300fc4 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -338,6 +338,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: 'medium' as 'small' | 'medium' | 'large', }, + limitWidthOfReaction: { + where: 'device', + default: true, + }, forceShowAds: { where: 'device', default: false, From e57b07372885490f5f980c5cbfdcebaa5a89fb1a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 27 Nov 2023 15:45:19 +0900 Subject: [PATCH 02/10] =?UTF-8?q?docs(changelog):=20=E3=83=AA=E3=82=A2?= =?UTF-8?q?=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AE=E6=A8=AA=E5=B9=85?= =?UTF-8?q?=E3=82=92150px=E3=81=AB=E5=88=B6=E9=99=90=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=8B=E3=81=A9=E3=81=86=E3=81=8B=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=81=8C=E9=81=B8=E3=81=B9=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=20(#12416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26c23f5f58dc..2664ce4e08ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - こんにりらみすきー部がハイライトから除外されるようになりました ### Client +- リアクションの横幅を150pxに制限するかどうかユーザーが選べるように - Fix: ハードミュートにノート引っかかるとエラーが発生しTLが更新されないことがある問題 ### Server From b1657c8bd2a94edf54f61a6364471ffa86c39f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A1=E3=82=83=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Fri, 24 Nov 2023 06:37:06 +0900 Subject: [PATCH 03/10] =?UTF-8?q?enhance(frontend):=20=E7=B5=B5=E6=96=87?= =?UTF-8?q?=E5=AD=97=E3=81=AE=E3=82=AA=E3=83=BC=E3=83=88=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=83=97=E3=83=AA=E3=83=BC=E3=83=88=E3=81=AE=E3=82=A2=E3=83=AB?= =?UTF-8?q?=E3=82=B4=E3=83=AA=E3=82=BA=E3=83=A0=E3=81=AE=E6=94=B9=E5=96=84?= =?UTF-8?q?=20(MisskeyIO#261)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 実際は同じ絵文字なら重複してサジェストに出ないように * エイリアスではない絵文字>前方一致>部分一致>あいまい検索順で表示されるようになるように --- .../src/components/MkAutocomplete.vue | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index a0f49611160b..c1fcbd7ac1ce 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -265,7 +265,7 @@ function emojiAutoComplete(query: string | null, emojiDb: EmojiDef[], max = 30): // 前方一致(エイリアスなし) emojiDb.some(x => { if (x.name.startsWith(query) && !x.aliasOf) { - matched.set(x.name, { emoji: x, score: query.length }); + matched.set(x.name, { emoji: x, score: query.length + 1 }); } return matched.size === max; }); @@ -273,8 +273,8 @@ function emojiAutoComplete(query: string | null, emojiDb: EmojiDef[], max = 30): // 前方一致(エイリアス込み) if (matched.size < max) { emojiDb.some(x => { - if (x.name.startsWith(query)) { - matched.set(x.name, { emoji: x, score: query.length }); + if (x.name.startsWith(query) && !matched.has(x.aliasOf ?? x.name)) { + matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length }); } return matched.size === max; }); @@ -283,36 +283,32 @@ function emojiAutoComplete(query: string | null, emojiDb: EmojiDef[], max = 30): // 部分一致(エイリアス込み) if (matched.size < max) { emojiDb.some(x => { - if (x.name.includes(query)) { - matched.set(x.name, { emoji: x, score: query.length }); + if (x.name.includes(query) && !matched.has(x.aliasOf ?? x.name)) { + matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length - 1 }); } return matched.size === max; }); } - // 簡易あいまい検索 - if (matched.size < max) { + // 簡易あいまい検索(3文字以上) + if (matched.size < max && query.length > 3) { const queryChars = [...query]; const hitEmojis = new Map(); for (const x of emojiDb) { - // クエリ文字列の1文字単位で絵文字名にヒットするかを見る - // ただし、過剰に検出されるのを防ぐためクエリ文字列に登場する順番で絵文字名を走査する - - let queryCharHitPos = 0; - let queryCharHitCount = 0; - for (let idx = 0; idx < queryChars.length; idx++) { - queryCharHitPos = x.name.indexOf(queryChars[idx], queryCharHitPos); - if (queryCharHitPos <= -1) { - break; - } - - queryCharHitCount++; + // 文字列の位置を進めながら、クエリの文字を順番に探す + + let pos = 0; + let hit = 0; + for (const c of queryChars) { + pos = x.name.indexOf(c, pos); + if (pos <= -1) break; + hit++; } - // ヒット数が少なすぎると検索結果が汚れるので調節する - if (queryCharHitCount > 2) { - hitEmojis.set(x.name, { emoji: x, score: queryCharHitCount }); + // 半分以上の文字が含まれていればヒットとする + if (hit > Math.ceil(queryChars.length / 2) && hit - 2 > (matched.get(x.aliasOf ?? x.name)?.score ?? 0)) { + hitEmojis.set(x.aliasOf ?? x.name, { emoji: x, score: hit - 2 }); } } From fbf91a65c4d50582c739ca8ad7cbf1fc14af7092 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 27 Nov 2023 15:54:04 +0900 Subject: [PATCH 04/10] =?UTF-8?q?docs(changelog):=20=E7=B5=B5=E6=96=87?= =?UTF-8?q?=E5=AD=97=E5=85=A5=E5=8A=9B=E6=99=82=E3=81=AE=E3=82=B5=E3=82=B8?= =?UTF-8?q?=E3=82=A7=E3=82=B9=E3=83=88=E3=81=8C=E3=81=8A=E3=81=8B=E3=81=97?= =?UTF-8?q?=E3=81=84=20(MisskeyIO#261/b1657c8b)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2664ce4e08ff..ce6375ee58a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Client - リアクションの横幅を150pxに制限するかどうかユーザーが選べるように - Fix: ハードミュートにノート引っかかるとエラーが発生しTLが更新されないことがある問題 +- Fix: 絵文字入力時のサジェストがおかしい ### Server From 78b135d79496668a5b1ddef07ea9087a723023f5 Mon Sep 17 00:00:00 2001 From: Nafu Satsuki Date: Sat, 18 Nov 2023 05:20:11 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat(moderation):=20=E3=83=A2=E3=83=87?= =?UTF-8?q?=E3=83=AC=E3=83=BC=E3=82=BF=E3=83=BC=E3=81=8C=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=82=B6=E3=83=BC=E3=81=AE=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=82=82=E3=81=97=E3=81=8F=E3=81=AF=E3=83=90=E3=83=8A=E3=83=BC?= =?UTF-8?q?=E7=94=BB=E5=83=8F=E3=82=92=E6=9C=AA=E8=A8=AD=E5=AE=9A=E7=8A=B6?= =?UTF-8?q?=E6=85=8B=E3=81=AB=E3=81=A7=E3=81=8D=E3=82=8B=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0=20(MisskeyIO#222)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com> --- locales/en-US.yml | 4 ++ locales/index.d.ts | 4 ++ locales/ja-JP.yml | 4 ++ .../backend/src/server/api/EndpointsModule.ts | 8 ++++ packages/backend/src/server/api/endpoints.ts | 4 ++ .../api/endpoints/admin/delete-user-avatar.ts | 48 +++++++++++++++++++ .../api/endpoints/admin/delete-user-banner.ts | 48 +++++++++++++++++++ packages/frontend/src/pages/admin-user.vue | 42 ++++++++++++++++ packages/misskey-js/etc/misskey-js.api.md | 12 +++++ packages/misskey-js/src/api.types.ts | 2 + 10 files changed, 176 insertions(+) create mode 100644 packages/backend/src/server/api/endpoints/admin/delete-user-avatar.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/delete-user-banner.ts diff --git a/locales/en-US.yml b/locales/en-US.yml index cfd72935efe9..cc12d0551073 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -565,6 +565,10 @@ output: "Output" script: "Script" disablePagesScript: "Disable AiScript on Pages" updateRemoteUser: "Update remote user information" +deleteUserAvatar: "Delete user icon" +deleteUserAvatarConfirm: "Are you sure that you want to delete this user's icon?" +deleteUserBanner: "Delete user banner" +deleteUserBannerConfirm: "Are you sure that you want to delete this user's banner?" deleteAllFiles: "Delete all files" deleteAllFilesConfirm: "Are you sure that you want to delete all files?" removeAllFollowing: "Unfollow all followed users" diff --git a/locales/index.d.ts b/locales/index.d.ts index 51896daba22a..63bd91391a0c 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -568,6 +568,10 @@ export interface Locale { "script": string; "disablePagesScript": string; "updateRemoteUser": string; + "deleteUserAvatar": string; + "deleteUserAvatarConfirm": string; + "deleteUserBanner": string; + "deleteUserBannerConfirm": string; "deleteAllFiles": string; "deleteAllFilesConfirm": string; "removeAllFollowing": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 40e2d5453fcf..f7227eb86a05 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -565,6 +565,10 @@ output: "出力" script: "スクリプト" disablePagesScript: "Pagesのスクリプトを無効にする" updateRemoteUser: "リモートユーザー情報の更新" +deleteUserAvatar: "アイコンを削除" +deleteUserAvatarConfirm: "アイコンを削除しますか?" +deleteUserBanner: "バナーを削除" +deleteUserBannerConfirm: "バナーを削除しますか?" deleteAllFiles: "すべてのファイルを削除" deleteAllFilesConfirm: "すべてのファイルを削除しますか?" removeAllFollowing: "フォローを全解除" diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 6d813ae04e41..3797b46d04fd 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -24,6 +24,8 @@ import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-d import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js'; import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js'; import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js'; +import * as ep___admin_deleteUserAvatar from './endpoints/admin/delete-user-avatar.js'; +import * as ep___admin_deleteUserBanner from './endpoints/admin/delete-user-banner.js'; import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js'; import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js'; import * as ep___admin_drive_files from './endpoints/admin/drive/files.js'; @@ -383,6 +385,8 @@ const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-de const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default }; const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default }; const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default }; +const $admin_deleteUserAvatar: Provider = { provide: 'ep:admin/delete-user-avatar', useClass: ep___admin_deleteUserAvatar.default }; +const $admin_deleteUserBanner: Provider = { provide: 'ep:admin/delete-user-banner', useClass: ep___admin_deleteUserBanner.default }; const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default }; const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default }; const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass: ep___admin_drive_files.default }; @@ -746,6 +750,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_avatarDecorations_list, $admin_avatarDecorations_update, $admin_deleteAllFilesOfAUser, + $admin_deleteUserAvatar, + $admin_deleteUserBanner, $admin_drive_cleanRemoteFiles, $admin_drive_cleanup, $admin_drive_files, @@ -1103,6 +1109,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_avatarDecorations_list, $admin_avatarDecorations_update, $admin_deleteAllFilesOfAUser, + $admin_deleteUserAvatar, + $admin_deleteUserBanner, $admin_drive_cleanRemoteFiles, $admin_drive_cleanup, $admin_drive_files, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index ef4bd3e2b5dc..4162ace337b4 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -24,6 +24,8 @@ import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-d import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js'; import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js'; import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js'; +import * as ep___admin_deleteUserAvatar from './endpoints/admin/delete-user-avatar.js'; +import * as ep___admin_deleteUserBanner from './endpoints/admin/delete-user-banner.js'; import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js'; import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js'; import * as ep___admin_drive_files from './endpoints/admin/drive/files.js'; @@ -381,6 +383,8 @@ const eps = [ ['admin/avatar-decorations/list', ep___admin_avatarDecorations_list], ['admin/avatar-decorations/update', ep___admin_avatarDecorations_update], ['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser], + ['admin/delete-user-avatar', ep___admin_deleteUserAvatar], + ['admin/delete-user-banner', ep___admin_deleteUserBanner], ['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles], ['admin/drive/cleanup', ep___admin_drive_cleanup], ['admin/drive/files', ep___admin_drive_files], diff --git a/packages/backend/src/server/api/endpoints/admin/delete-user-avatar.ts b/packages/backend/src/server/api/endpoints/admin/delete-user-avatar.ts new file mode 100644 index 000000000000..d3c78d7fb6ee --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/delete-user-avatar.ts @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import type { UsersRepository } from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + await this.usersRepository.update(user.id, { + avatar: null, + avatarId: null, + avatarUrl: null, + avatarBlurhash: null, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/delete-user-banner.ts b/packages/backend/src/server/api/endpoints/admin/delete-user-banner.ts new file mode 100644 index 000000000000..e076cdcfc152 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/delete-user-banner.ts @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import type { UsersRepository } from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + await this.usersRepository.update(user.id, { + banner: null, + bannerId: null, + bannerUrl: null, + bannerBlurhash: null, + }); + }); + } +} diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 5d671acf31ee..9f4975e88872 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -122,6 +122,10 @@ SPDX-License-Identifier: AGPL-3.0-only +
+ {{ i18n.ts.deleteUserAvatar }} + {{ i18n.ts.deleteUserBanner }} +
{{ i18n.ts.deleteAccount }} @@ -320,6 +324,44 @@ async function toggleSuspend(v) { } } +async function deleteUserAvatar() { + const confirm = await os.confirm({ + type: 'warning', + text: i18n.ts.deleteUserAvatarConfirm, + }); + if (confirm.canceled) return; + const process = async () => { + await os.api('admin/delete-user-avatar', { userId: user.id }); + os.success(); + }; + await process().catch(err => { + os.alert({ + type: 'error', + text: err.toString(), + }); + }); + refreshUser(); +} + +async function deleteUserBanner() { + const confirm = await os.confirm({ + type: 'warning', + text: i18n.ts.deleteUserBannerConfirm, + }); + if (confirm.canceled) return; + const process = async () => { + await os.api('admin/delete-user-banner', { userId: user.id }); + os.success(); + }; + await process().catch(err => { + os.alert({ + type: 'error', + text: err.toString(), + }); + }); + refreshUser(); +} + async function deleteAllFiles() { const confirm = await os.confirm({ type: 'warning', diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index a55d634b55ae..7ea47eafebdf 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -345,6 +345,18 @@ export type Endpoints = { }; res: null; }; + 'admin/delete-user-avatar': { + req: { + userId: User['id']; + }; + res: null; + }; + 'admin/delete-user-banner': { + req: { + userId: User['id']; + }; + res: null; + }; 'admin/delete-logs': { req: NoParams; res: null; diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts index e1e2eed6cdb5..5fa1dbb63cdd 100644 --- a/packages/misskey-js/src/api.types.ts +++ b/packages/misskey-js/src/api.types.ts @@ -15,6 +15,8 @@ export type Endpoints = { // admin 'admin/abuse-user-reports': { req: TODO; res: TODO; }; 'admin/delete-all-files-of-a-user': { req: { userId: User['id']; }; res: null; }; + 'admin/delete-user-avatar': { req: { userId: User['id']; }; res: null; }; + 'admin/delete-user-banner': { req: { userId: User['id']; }; res: null; }; 'admin/delete-logs': { req: NoParams; res: null; }; 'admin/get-index-stats': { req: TODO; res: TODO; }; 'admin/get-table-stats': { req: TODO; res: TODO; }; From 61fa3360250b651f142880d1414df30657bd0e80 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 27 Nov 2023 16:04:04 +0900 Subject: [PATCH 06/10] =?UTF-8?q?docs(changelog):=20=E3=83=A2=E3=83=87?= =?UTF-8?q?=E3=83=AC=E3=83=BC=E3=82=BF=E3=83=BC=E3=81=8C=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=82=B6=E3=83=BC=E3=81=AE=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=82=82=E3=81=97=E3=81=8F=E3=81=AF=E3=83=90=E3=83=8A=E3=83=BC?= =?UTF-8?q?=E7=94=BB=E5=83=8F=E3=82=92=E6=9C=AA=E8=A8=AD=E5=AE=9A=E7=8A=B6?= =?UTF-8?q?=E6=85=8B=E3=81=AB=E3=81=A7=E3=81=8D=E3=82=8B=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0=20(MisskeyIO#222/78b135d79)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce6375ee58a5..57a5ce8f7622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### General - こんにりらみすきー部がハイライトから除外されるようになりました +- モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 ### Client - リアクションの横幅を150pxに制限するかどうかユーザーが選べるように From 9e3a63fc5210724ac122b31c4fb4c757977eb4be Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 27 Nov 2023 16:08:12 +0900 Subject: [PATCH 07/10] docs(api.md): update 78b135d79 --- locales/index.d.ts | 2 +- packages/misskey-js/etc/misskey-js.api.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 63bd91391a0c..ddbc3949acf5 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1042,7 +1042,7 @@ export interface Locale { "enableChartsForFederatedInstances": string; "showClipButtonInNoteFooter": string; "reactionsDisplaySize": string; - "limitWidthOfReaction": string; + "limitWidthOfReaction": string; "noteIdOrUrl": string; "video": string; "videos": string; diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 7ea47eafebdf..d9e34b74925e 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -3048,8 +3048,8 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u // Warnings were encountered during analysis: // // src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts -// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts -// src/api.types.ts:633:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts +// src/api.types.ts:20:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts +// src/api.types.ts:635:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts // src/entities.ts:117:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts // src/entities.ts:628:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts From 9a2e2b3c2d9b760094d52495b137f5ffed10db7c 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, 26 Nov 2023 13:20:46 +0900 Subject: [PATCH 08/10] =?UTF-8?q?fix(frontend):=20=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E9=9F=B3=E3=81=8C=E3=81=BB=E3=81=BC=E5=90=8C=E6=99=82=E3=81=AB?= =?UTF-8?q?=E9=B3=B4=E3=81=A3=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=AF=E5=86=8D?= =?UTF-8?q?=E7=94=9F=E3=82=92=E3=83=96=E3=83=AD=E3=83=83=E3=82=AF=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=EF=BC=88=E9=9F=B3=E5=89=B2?= =?UTF-8?q?=E3=82=8C=E9=98=B2=E6=AD=A2=EF=BC=89=20(#12433)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * (fix) 通知音がダブって音割れしないように * Update Changelog --- CHANGELOG.md | 1 + packages/frontend/src/scripts/sound.ts | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57a5ce8f7622..f32b7d7e9227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ ### Client - Enhance: 絵文字のオートコンプリート機能強化 #12364 - fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正 +- Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正 ### Server - Fix: 何もノートしていないユーザーのフィードにアクセスするとエラーになる問題を修正 diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts index 4b0cd0bb3922..a423e35724bc 100644 --- a/packages/frontend/src/scripts/sound.ts +++ b/packages/frontend/src/scripts/sound.ts @@ -7,6 +7,7 @@ import { defaultStore } from '@/store.js'; const ctx = new AudioContext(); const cache = new Map(); +let canPlay = true; export const soundsTypes = [ null, @@ -86,8 +87,15 @@ export function setVolume(audio: HTMLAudioElement, volume: number): HTMLAudioEle export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notification') { const sound = defaultStore.state[`sound_${type}`]; if (_DEV_) console.log('play', type, sound); - if (sound.type == null) return; - playFile(sound.type, sound.volume); + if (sound.type == null || !canPlay) return; + + canPlay = false; + playFile(sound.type, sound.volume).then(() => { + // ごく短時間に音が重複しないように + setTimeout(() => { + canPlay = true; + }, 25); + }); } export async function playFile(file: string, volume: number) { From 1dd79161fb8ef268af4314c38358eb173f70f697 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 27 Nov 2023 17:07:57 +0900 Subject: [PATCH 09/10] =?UTF-8?q?chore:=20=E9=9F=B3=E5=A3=B0=E3=81=8C?= =?UTF-8?q?=E4=B8=80=E5=88=87=E9=B3=B4=E3=82=89=E3=81=AA=E3=81=8F=E3=81=AA?= =?UTF-8?q?=E3=82=8B=E5=8F=AF=E8=83=BD=E6=80=A7=E3=82=92=E8=BB=BD=E6=B8=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/misskey-dev/misskey/pull/12433#discussion_r1405774767 --- packages/frontend/src/scripts/sound.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts index a423e35724bc..b2739290db2c 100644 --- a/packages/frontend/src/scripts/sound.ts +++ b/packages/frontend/src/scripts/sound.ts @@ -89,13 +89,17 @@ export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notifica if (_DEV_) console.log('play', type, sound); if (sound.type == null || !canPlay) return; - canPlay = false; - playFile(sound.type, sound.volume).then(() => { - // ごく短時間に音が重複しないように - setTimeout(() => { - canPlay = true; - }, 25); - }); + (async () => { + canPlay = false; + try { + await playFile(sound.type, sound.volume); + } finally { + // ごく短時間に音が重複しないように + setTimeout(() => { + canPlay = true; + }, 25); + } + })(); } export async function playFile(file: string, volume: number) { From 03fc1ecb3543df9d2fe1aa95156038fec7c88dbd 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, 21 Nov 2023 20:05:04 +0900 Subject: [PATCH 10/10] =?UTF-8?q?=E3=82=B5=E3=82=A6=E3=83=B3=E3=83=89?= =?UTF-8?q?=E5=86=8D=E7=94=9F=E6=96=B9=E6=B3=95=E3=81=AE=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=E3=81=AB=E8=BF=BD=E5=BE=93=E3=81=A7=E3=81=8D=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F=E6=89=80=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20(#12368)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com> --- CHANGELOG.md | 1 + packages/frontend/src/scripts/sound.ts | 20 +++++++++---------- .../frontend/src/widgets/WidgetJobQueue.vue | 14 ++++++++++--- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f32b7d7e9227..edc8bfb7c736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ - Enhance: 絵文字のオートコンプリート機能強化 #12364 - fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正 - Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正 +- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367 ### Server - Fix: 何もノートしていないユーザーのフィードにアクセスするとエラーになる問題を修正 diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts index b2739290db2c..a15f8ea0894a 100644 --- a/packages/frontend/src/scripts/sound.ts +++ b/packages/frontend/src/scripts/sound.ts @@ -62,7 +62,7 @@ export const soundsTypes = [ 'noizenecio/kick_gaba7', ] as const; -export async function getAudio(file: string, useCache = true) { +export async function loadAudio(file: string, useCache = true) { if (useCache && cache.has(file)) { return cache.get(file)!; } @@ -78,12 +78,6 @@ export async function getAudio(file: string, useCache = true) { return audioBuffer; } -export function setVolume(audio: HTMLAudioElement, volume: number): HTMLAudioElement { - const masterVolume = defaultStore.state.sound_masterVolume; - audio.volume = masterVolume - ((1 - volume) * masterVolume); - return audio; -} - export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notification') { const sound = defaultStore.state[`sound_${type}`]; if (_DEV_) console.log('play', type, sound); @@ -103,16 +97,22 @@ export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notifica } export async function playFile(file: string, volume: number) { + const buffer = await loadAudio(file); + createSourceNode(buffer, volume)?.start(); +} + +export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBufferSourceNode | null { const masterVolume = defaultStore.state.sound_masterVolume; if (masterVolume === 0 || volume === 0) { - return; + return null; } const gainNode = ctx.createGain(); gainNode.gain.value = masterVolume * volume; const soundSource = ctx.createBufferSource(); - soundSource.buffer = await getAudio(file); + soundSource.buffer = buffer; soundSource.connect(gainNode).connect(ctx.destination); - soundSource.start(); + + return soundSource; } diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue index 89770b2216d5..fa829975700f 100644 --- a/packages/frontend/src/widgets/WidgetJobQueue.vue +++ b/packages/frontend/src/widgets/WidgetJobQueue.vue @@ -99,7 +99,10 @@ const current = reactive({ }, }); const prev = reactive({} as typeof current); -const jammedSound = sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1); +let jammedAudioBuffer: AudioBuffer | null = $ref(null); +let jammedSoundNodePlaying: boolean = $ref(false); + +sound.loadAudio('syuilo/queue-jammed').then(buf => jammedAudioBuffer = buf); for (const domain of ['inbox', 'deliver']) { prev[domain] = deepClone(current[domain]); @@ -113,8 +116,13 @@ const onStats = (stats) => { current[domain].waiting = stats[domain].waiting; current[domain].delayed = stats[domain].delayed; - if (current[domain].waiting > 0 && widgetProps.sound && jammedSound.paused) { - jammedSound.play(); + if (current[domain].waiting > 0 && widgetProps.sound && jammedAudioBuffer && !jammedSoundNodePlaying) { + const soundNode = sound.createSourceNode(jammedAudioBuffer, 1); + if (soundNode) { + jammedSoundNodePlaying = true; + soundNode.onended = () => jammedSoundNodePlaying = false; + soundNode.start(); + } } } };