From acac759d8720f72ef21c14e521a864a74414cb08 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 20 Mar 2025 08:35:45 +0900 Subject: [PATCH 001/262] =?UTF-8?q?fix(frontend):=20=E3=83=A2=E3=83=90?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=83=9B=E3=83=BC=E3=83=A0=E3=82=92=E6=8A=BC?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=82=82=E6=9C=80=E4=B8=8A=E9=83=A8=E3=81=B8?= =?UTF-8?q?=E3=82=B9=E3=82=AF=E3=83=AD=E3=83=BC=E3=83=AB=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #15679 --- packages/frontend/src/pages/timeline.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 38497e7c1b..05afc3471f 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -56,12 +56,18 @@ import { deepMerge } from '@/utility/merge.js'; import { miLocalStorage } from '@/local-storage.js'; import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js'; import { prefer } from '@/preferences.js'; +import { useRouter } from '@/router.js'; provide('shouldOmitHeaderTitle', true); const tlComponent = useTemplateRef('tlComponent'); const rootEl = useTemplateRef('rootEl'); +const router = useRouter(); +router.useListener('same', () => { + top(); +}); + type TimelinePageSrc = BasicTimelineType | `list:${string}`; const queue = ref(0); @@ -128,7 +134,7 @@ function queueUpdated(q: number): void { } function top(): void { - if (rootEl.value) scroll(rootEl.value, { top: 0 }); + if (rootEl.value) scroll(rootEl.value, { top: 0, behavior: 'smooth' }); } async function chooseList(ev: MouseEvent): Promise { From b067d4dcd682177a73f614df3e52924b65749bac Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 20 Mar 2025 08:59:54 +0900 Subject: [PATCH 002/262] follow up of 7b323031b774745ee2146c89ead2a9ebe628d613 --- packages/frontend/src/components/MkMenu.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index 5438dac3a8..e380d0bc37 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -179,7 +179,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -112,13 +150,13 @@ const emits = defineEmits<{ .extInstallerRoot { border-radius: var(--MI-radius); background: var(--MI_THEME-panel); - padding: 1.5rem; + padding: 20px; } .extInstallerIconWrapper { width: 48px; height: 48px; - font-size: 24px; + font-size: 20px; line-height: 48px; text-align: center; border-radius: 50%; @@ -135,10 +173,6 @@ const emits = defineEmits<{ margin: 0; } -.extInstallerNormDesc { - text-align: center; -} - .extInstallerKVList { margin-top: 0; margin-bottom: 0; diff --git a/packages/frontend/src/pages/install-extensions.vue b/packages/frontend/src/pages/install-extensions.vue index 95f8c878c9..3f4a846586 100644 --- a/packages/frontend/src/pages/install-extensions.vue +++ b/packages/frontend/src/pages/install-extensions.vue @@ -6,12 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -47,12 +59,20 @@ const isOwner = computed(() => { }); const memberships = ref([]); +const invitations = ref([]); onMounted(async () => { memberships.value = await misskeyApi('chat/rooms/members', { roomId: props.room.id, limit: 50, }); + + if (isOwner.value) { + invitations.value = await misskeyApi('chat/rooms/invitations/outbox', { + roomId: props.room.id, + limit: 50, + }); + } }); @@ -65,9 +85,15 @@ onMounted(async () => { flex: 1; min-width: 0; margin-right: 8px; +} - &:hover { - text-decoration: none; - } +.invitation { + display: flex; +} + +.invitationBody { + flex: 1; + min-width: 0; + margin-right: 8px; } diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index e79cd794a6..8610a21304 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1046,6 +1046,12 @@ type ChatRoomsInvitationsInboxRequest = operations['chat___rooms___invitations__ // @public (undocumented) type ChatRoomsInvitationsInboxResponse = operations['chat___rooms___invitations___inbox']['responses']['200']['content']['application/json']; +// @public (undocumented) +type ChatRoomsInvitationsOutboxRequest = operations['chat___rooms___invitations___outbox']['requestBody']['content']['application/json']; + +// @public (undocumented) +type ChatRoomsInvitationsOutboxResponse = operations['chat___rooms___invitations___outbox']['responses']['200']['content']['application/json']; + // @public (undocumented) type ChatRoomsJoiningRequest = operations['chat___rooms___joining']['requestBody']['content']['application/json']; @@ -1619,6 +1625,8 @@ declare namespace entities { ChatRoomsInvitationsIgnoreResponse, ChatRoomsInvitationsInboxRequest, ChatRoomsInvitationsInboxResponse, + ChatRoomsInvitationsOutboxRequest, + ChatRoomsInvitationsOutboxResponse, ChatRoomsJoinRequest, ChatRoomsJoinResponse, ChatRoomsJoiningRequest, diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index e12ba0d6b2..e5d0b94a10 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -1688,6 +1688,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:chat* + */ + 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 558f4ab514..1dfdb53320 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -235,6 +235,8 @@ import type { ChatRoomsInvitationsIgnoreResponse, ChatRoomsInvitationsInboxRequest, ChatRoomsInvitationsInboxResponse, + ChatRoomsInvitationsOutboxRequest, + ChatRoomsInvitationsOutboxResponse, ChatRoomsJoinRequest, ChatRoomsJoinResponse, ChatRoomsJoiningRequest, @@ -784,6 +786,7 @@ export type Endpoints = { 'chat/rooms/invitations/create': { req: ChatRoomsInvitationsCreateRequest; res: ChatRoomsInvitationsCreateResponse }; 'chat/rooms/invitations/ignore': { req: ChatRoomsInvitationsIgnoreRequest; res: ChatRoomsInvitationsIgnoreResponse }; 'chat/rooms/invitations/inbox': { req: ChatRoomsInvitationsInboxRequest; res: ChatRoomsInvitationsInboxResponse }; + 'chat/rooms/invitations/outbox': { req: ChatRoomsInvitationsOutboxRequest; res: ChatRoomsInvitationsOutboxResponse }; 'chat/rooms/join': { req: ChatRoomsJoinRequest; res: ChatRoomsJoinResponse }; 'chat/rooms/joining': { req: ChatRoomsJoiningRequest; res: ChatRoomsJoiningResponse }; 'chat/rooms/leave': { req: ChatRoomsLeaveRequest; res: ChatRoomsLeaveResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index bb21272e01..0e3ca7202e 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -238,6 +238,8 @@ export type ChatRoomsInvitationsIgnoreRequest = operations['chat___rooms___invit export type ChatRoomsInvitationsIgnoreResponse = operations['chat___rooms___invitations___ignore']['responses']['200']['content']['application/json']; export type ChatRoomsInvitationsInboxRequest = operations['chat___rooms___invitations___inbox']['requestBody']['content']['application/json']; export type ChatRoomsInvitationsInboxResponse = operations['chat___rooms___invitations___inbox']['responses']['200']['content']['application/json']; +export type ChatRoomsInvitationsOutboxRequest = operations['chat___rooms___invitations___outbox']['requestBody']['content']['application/json']; +export type ChatRoomsInvitationsOutboxResponse = operations['chat___rooms___invitations___outbox']['responses']['200']['content']['application/json']; export type ChatRoomsJoinRequest = operations['chat___rooms___join']['requestBody']['content']['application/json']; export type ChatRoomsJoinResponse = operations['chat___rooms___join']['responses']['200']['content']['application/json']; export type ChatRoomsJoiningRequest = operations['chat___rooms___joining']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 31bc34e473..3acef22101 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -1484,6 +1484,15 @@ export type paths = { */ post: operations['chat___rooms___invitations___inbox']; }; + '/chat/rooms/invitations/outbox': { + /** + * chat/rooms/invitations/outbox + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:chat* + */ + post: operations['chat___rooms___invitations___outbox']; + }; '/chat/rooms/join': { /** * chat/rooms/join @@ -14737,6 +14746,66 @@ export type operations = { }; }; }; + /** + * chat/rooms/invitations/outbox + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:chat* + */ + chat___rooms___invitations___outbox: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + roomId: string; + /** @default 30 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['ChatRoomInvitation'][]; + }; + }; + /** @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']; + }; + }; + }; + }; /** * chat/rooms/join * @description No description provided. From 2fc3baa9882dd257a1ed91277ced85b862eb2836 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 25 Mar 2025 04:45:07 +0000 Subject: [PATCH 067/262] Bump version to 2025.3.2-beta.12 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8acf676bff..fc2de6e887 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.3.2-beta.11", + "version": "2025.3.2-beta.12", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index ec98e1542b..dc4d49183b 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2025.3.2-beta.11", + "version": "2025.3.2-beta.12", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", From 304d0eb83b6e10336ec319600f060f0a719d07b9 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 25 Mar 2025 15:25:43 +0900 Subject: [PATCH 068/262] =?UTF-8?q?enhance:=20=E3=83=81=E3=83=A3=E3=83=83?= =?UTF-8?q?=E3=83=88=E3=83=AB=E3=83=BC=E3=83=A0=E3=81=AB=E6=8B=9B=E5=BE=85?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AE=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 8 ++++ locales/ja-JP.yml | 2 + packages/backend/src/core/ChatService.ts | 6 +++ .../entities/NotificationEntityService.ts | 23 +++++++--- packages/backend/src/models/Notification.ts | 6 +++ .../src/models/json-schema/notification.ts | 15 +++++++ .../backend/src/models/json-schema/user.ts | 1 + .../server/api/endpoints/admin/show-user.ts | 1 + .../src/server/api/endpoints/i/update.ts | 1 + packages/backend/src/types.ts | 2 + packages/frontend-shared/js/const.ts | 1 + .../src/components/MkNotification.vue | 5 +++ packages/misskey-js/etc/misskey-js.api.md | 2 +- packages/misskey-js/src/autogen/types.ts | 43 +++++++++++++++++-- packages/misskey-js/src/consts.ts | 2 +- 15 files changed, 105 insertions(+), 13 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index eee9de8f0d..f7d875d2c8 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -9883,6 +9883,10 @@ export interface Locale extends ILocale { * ロールが付与されました */ "roleAssigned": string; + /** + * チャットルームへ招待されました + */ + "chatRoomInvitationReceived": string; /** * プッシュ通知の更新をしました */ @@ -9992,6 +9996,10 @@ export interface Locale extends ILocale { * ロールが付与された */ "roleAssigned": string; + /** + * チャットルームへ招待された + */ + "chatRoomInvitationReceived": string; /** * 実績の獲得 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e4c17276b1..efaa279454 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2612,6 +2612,7 @@ _notification: newNote: "新しい投稿" unreadAntennaNote: "アンテナ {name}" roleAssigned: "ロールが付与されました" + chatRoomInvitationReceived: "チャットルームへ招待されました" emptyPushNotificationMessage: "プッシュ通知の更新をしました" achievementEarned: "実績を獲得" testNotification: "通知テスト" @@ -2641,6 +2642,7 @@ _notification: receiveFollowRequest: "フォロー申請を受け取った" followRequestAccepted: "フォローが受理された" roleAssigned: "ロールが付与された" + chatRoomInvitationReceived: "チャットルームへ招待された" achievementEarned: "実績の獲得" exportCompleted: "エクスポートが完了した" login: "ログイン" diff --git a/packages/backend/src/core/ChatService.ts b/packages/backend/src/core/ChatService.ts index 062ca96f17..4c010b2ef7 100644 --- a/packages/backend/src/core/ChatService.ts +++ b/packages/backend/src/core/ChatService.ts @@ -26,6 +26,7 @@ import { Packed } from '@/misc/json-schema.js'; import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { emojiRegex } from '@/misc/emoji-regex.js'; +import { NotificationService } from '@/core/NotificationService.js'; const MAX_ROOM_MEMBERS = 30; const MAX_REACTIONS_PER_MESSAGE = 100; @@ -68,6 +69,7 @@ export class ChatService { private apRendererService: ApRendererService, private queueService: QueueService, private pushNotificationService: PushNotificationService, + private notificationService: NotificationService, private userBlockingService: UserBlockingService, private queryService: QueryService, private roleService: RoleService, @@ -544,6 +546,10 @@ export class ChatService { const created = await this.chatRoomInvitationsRepository.insertOne(invitation); + this.notificationService.createNotification(inviteeId, 'chatRoomInvitationReceived', { + invitationId: invitation.id, + }, inviterId); + return created; } diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index dff6968f9c..d8b947839f 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -16,6 +16,7 @@ import { bindThis } from '@/decorators.js'; import { FilterUnionByProperty, groupedNotificationTypes } from '@/types.js'; import { CacheService } from '@/core/CacheService.js'; import { RoleEntityService } from './RoleEntityService.js'; +import { ChatEntityService } from './ChatEntityService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { UserEntityService } from './UserEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js'; @@ -27,6 +28,7 @@ export class NotificationEntityService implements OnModuleInit { private userEntityService: UserEntityService; private noteEntityService: NoteEntityService; private roleEntityService: RoleEntityService; + private chatEntityService: ChatEntityService; constructor( private moduleRef: ModuleRef, @@ -41,9 +43,6 @@ export class NotificationEntityService implements OnModuleInit { private followRequestsRepository: FollowRequestsRepository, private cacheService: CacheService, - - //private userEntityService: UserEntityService, - //private noteEntityService: NoteEntityService, ) { } @@ -51,6 +50,7 @@ export class NotificationEntityService implements OnModuleInit { this.userEntityService = this.moduleRef.get('UserEntityService'); this.noteEntityService = this.moduleRef.get('NoteEntityService'); this.roleEntityService = this.moduleRef.get('RoleEntityService'); + this.chatEntityService = this.moduleRef.get('ChatEntityService'); } /** @@ -59,7 +59,6 @@ export class NotificationEntityService implements OnModuleInit { async #packInternal ( src: T, meId: MiUser['id'], - options: { checkValidNotifier?: boolean; }, @@ -92,7 +91,7 @@ export class NotificationEntityService implements OnModuleInit { // if the user has been deleted, don't show this notification if (needsUser && !userIfNeed) return null; - // #region Grouped notifications + //#region Grouped notifications if (notification.type === 'reaction:grouped') { const reactions = (await Promise.all(notification.reactions.map(async reaction => { const user = hint?.packedUsers != null @@ -137,7 +136,7 @@ export class NotificationEntityService implements OnModuleInit { users, }); } - // #endregion + //#endregion const needsRole = notification.type === 'roleAssigned'; const role = needsRole ? await this.roleEntityService.pack(notification.roleId) : undefined; @@ -146,6 +145,13 @@ export class NotificationEntityService implements OnModuleInit { return null; } + const needsChatRoomInvitation = notification.type === 'chatRoomInvitationReceived'; + const chatRoomInvitation = needsChatRoomInvitation ? await this.chatEntityService.packRoomInvitation(notification.invitationId, { id: meId }) : undefined; + // if the invitation has been deleted, don't show this notification + if (needsChatRoomInvitation && !chatRoomInvitation) { + return null; + } + return await awaitAll({ id: notification.id, createdAt: new Date(notification.createdAt).toISOString(), @@ -159,6 +165,9 @@ export class NotificationEntityService implements OnModuleInit { ...(notification.type === 'roleAssigned' ? { role: role, } : {}), + ...(notification.type === 'chatRoomInvitationReceived' ? { + invitation: chatRoomInvitation, + } : {}), ...(notification.type === 'followRequestAccepted' ? { message: notification.message, } : {}), @@ -236,7 +245,7 @@ export class NotificationEntityService implements OnModuleInit { public async pack( src: MiNotification | MiGroupedNotification, meId: MiUser['id'], - + options: { checkValidNotifier?: boolean; }, diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts index 5772ace338..5764a307b0 100644 --- a/packages/backend/src/models/Notification.ts +++ b/packages/backend/src/models/Notification.ts @@ -75,6 +75,12 @@ export type MiNotification = { id: string; createdAt: string; roleId: MiRole['id']; +} | { + type: 'chatRoomInvitationReceived'; + id: string; + createdAt: string; + notifierId: MiUser['id']; + invitationId: string; } | { type: 'achievementEarned'; id: string; diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts index 1638b2b3c7..7f23d2d6a1 100644 --- a/packages/backend/src/models/json-schema/notification.ts +++ b/packages/backend/src/models/json-schema/notification.ts @@ -287,6 +287,21 @@ export const packedNotificationSchema = { optional: false, nullable: false, }, }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['chatRoomInvitationReceived'], + }, + invitation: { + type: 'object', + ref: 'ChatRoomInvitation', + optional: false, nullable: false, + }, + }, }, { type: 'object', properties: { diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index b10430782b..0094f25e34 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -608,6 +608,7 @@ export const packedMeDetailedOnlySchema = { receiveFollowRequest: { optional: true, ...notificationRecieveConfig }, followRequestAccepted: { optional: true, ...notificationRecieveConfig }, roleAssigned: { optional: true, ...notificationRecieveConfig }, + chatRoomInvitationReceived: { optional: true, ...notificationRecieveConfig }, achievementEarned: { optional: true, ...notificationRecieveConfig }, app: { optional: true, ...notificationRecieveConfig }, test: { optional: true, ...notificationRecieveConfig }, diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 655bd32bce..1ba6853dbe 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -106,6 +106,7 @@ export const meta = { receiveFollowRequest: { optional: true, ...notificationRecieveConfig }, followRequestAccepted: { optional: true, ...notificationRecieveConfig }, roleAssigned: { optional: true, ...notificationRecieveConfig }, + chatRoomInvitationReceived: { optional: true, ...notificationRecieveConfig }, achievementEarned: { optional: true, ...notificationRecieveConfig }, app: { optional: true, ...notificationRecieveConfig }, test: { optional: true, ...notificationRecieveConfig }, diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index baf397bf06..082d97f5d4 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -212,6 +212,7 @@ export const paramDef = { receiveFollowRequest: notificationRecieveConfig, followRequestAccepted: notificationRecieveConfig, roleAssigned: notificationRecieveConfig, + chatRoomInvitationReceived: notificationRecieveConfig, achievementEarned: notificationRecieveConfig, app: notificationRecieveConfig, test: notificationRecieveConfig, diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index c6b1035554..4e215c93c6 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -15,6 +15,7 @@ * receiveFollowRequest - フォローリクエストされた * followRequestAccepted - 自分の送ったフォローリクエストが承認された * roleAssigned - ロールが付与された + * chatRoomInvitationReceived - チャットルームに招待された * achievementEarned - 実績を獲得 * exportCompleted - エクスポートが完了 * login - ログイン @@ -34,6 +35,7 @@ export const notificationTypes = [ 'receiveFollowRequest', 'followRequestAccepted', 'roleAssigned', + 'chatRoomInvitationReceived', 'achievementEarned', 'exportCompleted', 'login', diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index 0241446784..fa60476b69 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -66,6 +66,7 @@ export const notificationTypes = [ 'receiveFollowRequest', 'followRequestAccepted', 'roleAssigned', + 'chatRoomInvitationReceived', 'achievementEarned', 'exportCompleted', 'login', diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 2824701579..c2e8b8e2fe 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -43,6 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only + From ed86b1706de715925be2d3a22ae8f68cdc12195b Mon Sep 17 00:00:00 2001 From: Acid Chicken Date: Wed, 26 Mar 2025 12:17:56 +0900 Subject: [PATCH 085/262] ci(storybook): prevent running for bots --- .github/workflows/storybook.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index 6608761c13..07f196b7b8 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -5,13 +5,15 @@ on: branches: - master - develop - - dev/storybook8 # for testing pull_request_target: branches-ignore: # Since pull requests targets master mostly is the "develop" branch. # Storybook CI is checked on the "push" event of "develop" branch so it would cause a duplicate build. # This is a waste of chromatic build quota, so we don't run storybook CI on pull requests targets master. - master + # Neither Dependabot nor Renovate will change the actual behavior for components. + - dependabot/** + - renovate/** jobs: build: From c29a5764d3836bc05906036be20fab2eeea8b85b Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:51:08 +0900 Subject: [PATCH 086/262] refactor(backend): better method name --- packages/backend/src/core/QueryService.ts | 4 ++-- packages/backend/src/core/SearchService.ts | 4 ++-- packages/backend/src/server/api/endpoints/antennas/notes.ts | 4 ++-- .../backend/src/server/api/endpoints/channels/timeline.ts | 4 ++-- packages/backend/src/server/api/endpoints/clips/notes.ts | 4 ++-- packages/backend/src/server/api/endpoints/notes/children.ts | 4 ++-- .../backend/src/server/api/endpoints/notes/global-timeline.ts | 4 ++-- .../backend/src/server/api/endpoints/notes/hybrid-timeline.ts | 4 ++-- .../backend/src/server/api/endpoints/notes/local-timeline.ts | 4 ++-- packages/backend/src/server/api/endpoints/notes/mentions.ts | 4 ++-- packages/backend/src/server/api/endpoints/notes/renotes.ts | 4 ++-- packages/backend/src/server/api/endpoints/notes/replies.ts | 4 ++-- .../backend/src/server/api/endpoints/notes/search-by-tag.ts | 4 ++-- packages/backend/src/server/api/endpoints/notes/timeline.ts | 4 ++-- .../src/server/api/endpoints/notes/user-list-timeline.ts | 4 ++-- packages/backend/src/server/api/endpoints/roles/notes.ts | 4 ++-- packages/backend/src/server/api/endpoints/users/notes.ts | 4 ++-- .../backend/src/server/api/endpoints/users/recommendation.ts | 2 +- 18 files changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index c4feeaf971..412ab33b3f 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -69,7 +69,7 @@ export class QueryService { // ここでいうBlockedは被Blockedの意 @bindThis - public generateBlockedUserQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] }): void { + public generateBlockedUserQueryForNotes(q: SelectQueryBuilder, me: { id: MiUser['id'] }): void { const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking') .select('blocking.blockerId') .where('blocking.blockeeId = :blockeeId', { blockeeId: me.id }); @@ -127,7 +127,7 @@ export class QueryService { } @bindThis - public generateMutedUserQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] }, exclude?: { id: MiUser['id'] }): void { + public generateMutedUserQueryForNotes(q: SelectQueryBuilder, me: { id: MiUser['id'] }, exclude?: { id: MiUser['id'] }): void { const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') .select('muting.muteeId') .where('muting.muterId = :muterId', { muterId: me.id }); diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index bc62559e46..aa787c93de 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -234,8 +234,8 @@ export class SearchService { } this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQuery(query, me); - if (me) this.queryService.generateBlockedUserQuery(query, me); + if (me) this.queryService.generateMutedUserQueryForNotes(query, me); + if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); return query.limit(pagination.limit).getMany(); } diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 727697ea14..4b8543c2d1 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -109,8 +109,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQuery(query, me); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me); const notes = await query.getMany(); if (sinceId != null && untilId == null) { diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index d4fd75e049..cec5f8fd9c 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -122,8 +122,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('note.channel', 'channel'); if (me) { - this.queryService.generateMutedUserQuery(query, me); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me); } //#endregion diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 943c31c894..7638aae442 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -87,8 +87,8 @@ export default class extends Endpoint { // eslint- if (me) { this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQuery(query, me); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me); } const notes = await query diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index 0c6533d336..e73c98282c 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -71,8 +71,8 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); if (me) { - this.queryService.generateMutedUserQuery(query, me); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me); } const notes = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 258a0bfb8f..8d38bb1c65 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -79,8 +79,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); if (me) { - this.queryService.generateMutedUserQuery(query, me); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me); this.queryService.generateMutedUserRenotesQueryForNotes(query, me); } diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index aed9065bf9..99d1c9f19c 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -243,8 +243,8 @@ export default class extends Endpoint { // eslint- } this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQuery(query, me); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me); this.queryService.generateMutedUserRenotesQueryForNotes(query, me); if (ps.includeMyRenotes === false) { diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 0b48f2c78b..97acf2ad39 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -156,8 +156,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQuery(query, me); - if (me) this.queryService.generateBlockedUserQuery(query, me); + if (me) this.queryService.generateMutedUserQueryForNotes(query, me); + if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me); if (ps.withFiles) { diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 18a3915ab5..bbb63646e9 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -72,9 +72,9 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateMutedNoteThreadQuery(query, me); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me); if (ps.visibility) { query.andWhere('note.visibility = :visibility', { visibility: ps.visibility }); diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index ffe1ee6eb8..b34d9261a1 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -72,8 +72,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQuery(query, me); - if (me) this.queryService.generateBlockedUserQuery(query, me); + if (me) this.queryService.generateMutedUserQueryForNotes(query, me); + if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); const renotes = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index 5f32332a6a..f36af1a328 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -56,8 +56,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQuery(query, me); - if (me) this.queryService.generateBlockedUserQuery(query, me); + if (me) this.queryService.generateMutedUserQueryForNotes(query, me); + if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); const timeline = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 626ff080c7..c45851548a 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -81,8 +81,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQuery(query, me); - if (me) this.queryService.generateBlockedUserQuery(query, me); + if (me) this.queryService.generateMutedUserQueryForNotes(query, me); + if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); try { if (ps.tag) { diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 7cb11cc1eb..a88b28892e 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -199,8 +199,8 @@ export default class extends Endpoint { // eslint- })); this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQuery(query, me); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me); this.queryService.generateMutedUserRenotesQueryForNotes(query, me); if (ps.includeMyRenotes === false) { diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 87f9b322a6..80f1c69b25 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -184,8 +184,8 @@ export default class extends Endpoint { // eslint- })); this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQuery(query, me); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me); this.queryService.generateMutedUserRenotesQueryForNotes(query, me); if (ps.includeMyRenotes === false) { diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index 71f2782a5d..6cd9f80929 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -102,8 +102,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); - this.queryService.generateMutedUserQuery(query, me); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me); const notes = await query.getMany(); notes.sort((a, b) => a.id > b.id ? -1 : 1); diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index e9c334057e..f5b7a07b01 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -185,8 +185,8 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); if (me) { - this.queryService.generateMutedUserQuery(query, me, { id: ps.userId }); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserQueryForNotes(query, me, { id: ps.userId }); + this.queryService.generateBlockedUserQueryForNotes(query, me); } if (ps.withFiles) { diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 5b3b4527f7..5b1c6b514b 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -63,7 +63,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateMutedUserQueryForUsers(query, me); this.queryService.generateBlockQueryForUsers(query, me); - this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateBlockedUserQueryForNotes(query, me); const followingQuery = this.followingsRepository.createQueryBuilder('following') .select('following.followeeId') From b95da9c9a46d55c673787ed78526afbc9107785c Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:12:23 +0900 Subject: [PATCH 087/262] =?UTF-8?q?enhance(backend):=20=E3=83=9F=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B=E3=83=A6?= =?UTF-8?q?=E3=83=BC=E3=82=B6=E3=83=BC=E3=82=92=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E6=A4=9C=E7=B4=A2=E3=81=AE=E7=B5=90=E6=9E=9C=E3=81=8B?= =?UTF-8?q?=E3=82=89=E9=99=A4=E5=A4=96=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + .../backend/src/core/UserSearchService.ts | 100 +++++++++++++++++- .../users/search-by-username-and-host.ts | 2 +- .../src/server/api/endpoints/users/search.ts | 81 ++------------ 4 files changed, 107 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff95b6b5dc..6d94dbe213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - メッセージにはリアクションも可能です - Enhance: セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。 - Misskeyネイティブでダッシュボードを実装予定です +- Enhance: ミュートしているユーザーをユーザー検索の結果から除外するように ### Client - Feat: 設定の管理が強化されました diff --git a/packages/backend/src/core/UserSearchService.ts b/packages/backend/src/core/UserSearchService.ts index 0d03cf6ee0..4be7bd9bdb 100644 --- a/packages/backend/src/core/UserSearchService.ts +++ b/packages/backend/src/core/UserSearchService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets, SelectQueryBuilder } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { type FollowingsRepository, MiUser, type UsersRepository } from '@/models/_.js'; +import { type FollowingsRepository, MiUser, type MutingsRepository, type UserProfilesRepository, type UsersRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; import type { Config } from '@/config.js'; @@ -22,10 +22,19 @@ export class UserSearchService { constructor( @Inject(DI.config) private config: Config, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, + + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + private userEntityService: UserEntityService, ) { } @@ -58,7 +67,7 @@ export class UserSearchService { * @see {@link UserSearchService#buildSearchUserNoLoginQueries} */ @bindThis - public async search( + public async searchByUsernameAndHost( params: { username?: string | null, host?: string | null, @@ -202,4 +211,91 @@ export class UserSearchService { return userQuery; } + + @bindThis + public async search(query: string, meId: MiUser['id'] | null, options: Partial<{ + limit: number; + offset: number; + origin: 'local' | 'remote' | 'combined'; + }> = {}) { + const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 + + const isUsername = query.startsWith('@') && !query.includes(' ') && query.indexOf('@', 1) === -1; + + let users: MiUser[] = []; + + const mutingQuery = meId == null ? null : this.mutingsRepository.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: meId }); + + const nameQuery = this.usersRepository.createQueryBuilder('user') + .where(new Brackets(qb => { + qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(query) + '%' }); + + if (isUsername) { + qb.orWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(query.replace('@', '').toLowerCase()) + '%' }); + } else if (this.userEntityService.validateLocalUsername(query)) { // Also search username if it qualifies as username + qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(query.toLowerCase()) + '%' }); + } + })) + .andWhere(new Brackets(qb => { + qb + .where('user.updatedAt IS NULL') + .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); + })) + .andWhere('user.isSuspended = FALSE'); + + if (mutingQuery) { + nameQuery.andWhere(`user.id NOT IN (${mutingQuery.getQuery()})`); + nameQuery.setParameters(mutingQuery.getParameters()); + } + + if (options.origin === 'local') { + nameQuery.andWhere('user.host IS NULL'); + } else if (options.origin === 'remote') { + nameQuery.andWhere('user.host IS NOT NULL'); + } + + users = await nameQuery + .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') + .limit(options.limit) + .offset(options.offset) + .getMany(); + + if (users.length < (options.limit ?? 30)) { + const profQuery = this.userProfilesRepository.createQueryBuilder('prof') + .select('prof.userId') + .where('prof.description ILIKE :query', { query: '%' + sqlLikeEscape(query) + '%' }); + + if (mutingQuery) { + profQuery.andWhere(`prof.userId NOT IN (${mutingQuery.getQuery()})`); + profQuery.setParameters(mutingQuery.getParameters()); + } + + if (options.origin === 'local') { + profQuery.andWhere('prof.userHost IS NULL'); + } else if (options.origin === 'remote') { + profQuery.andWhere('prof.userHost IS NOT NULL'); + } + + const userQuery = this.usersRepository.createQueryBuilder('user') + .where(`user.id IN (${ profQuery.getQuery() })`) + .andWhere(new Brackets(qb => { + qb + .where('user.updatedAt IS NULL') + .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); + })) + .andWhere('user.isSuspended = FALSE') + .setParameters(profQuery.getParameters()); + + users = users.concat(await userQuery + .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') + .limit(options.limit) + .offset(options.offset) + .getMany(), + ); + } + + return users; + } } diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 8ff952dcb5..134f1a8e87 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -46,7 +46,7 @@ export default class extends Endpoint { // eslint- private userSearchService: UserSearchService, ) { super(meta, paramDef, (ps, me) => { - return this.userSearchService.search({ + return this.userSearchService.searchByUsernameAndHost({ username: ps.username, host: ps.host, }, { diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 0b0136066d..5d36847e03 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -3,14 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; -import type { MiUser } from '@/models/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; -import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; +import { UserSearchService } from '@/core/UserSearchService.js'; export const meta = { tags: ['users'], @@ -45,79 +42,15 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.userProfilesRepository) - private userProfilesRepository: UserProfilesRepository, - private userEntityService: UserEntityService, + private userSearchService: UserSearchService, ) { super(meta, paramDef, async (ps, me) => { - const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 - - ps.query = ps.query.trim(); - const isUsername = ps.query.startsWith('@') && !ps.query.includes(' ') && ps.query.indexOf('@', 1) === -1; - - let users: MiUser[] = []; - - const nameQuery = this.usersRepository.createQueryBuilder('user') - .where(new Brackets(qb => { - qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }); - - if (isUsername) { - qb.orWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.query.replace('@', '').toLowerCase()) + '%' }); - } else if (this.userEntityService.validateLocalUsername(ps.query)) { // Also search username if it qualifies as username - qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(ps.query.toLowerCase()) + '%' }); - } - })) - .andWhere(new Brackets(qb => { - qb - .where('user.updatedAt IS NULL') - .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); - })) - .andWhere('user.isSuspended = FALSE'); - - if (ps.origin === 'local') { - nameQuery.andWhere('user.host IS NULL'); - } else if (ps.origin === 'remote') { - nameQuery.andWhere('user.host IS NOT NULL'); - } - - users = await nameQuery - .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') - .limit(ps.limit) - .offset(ps.offset) - .getMany(); - - if (users.length < ps.limit) { - const profQuery = this.userProfilesRepository.createQueryBuilder('prof') - .select('prof.userId') - .where('prof.description ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }); - - if (ps.origin === 'local') { - profQuery.andWhere('prof.userHost IS NULL'); - } else if (ps.origin === 'remote') { - profQuery.andWhere('prof.userHost IS NOT NULL'); - } - - const query = this.usersRepository.createQueryBuilder('user') - .where(`user.id IN (${ profQuery.getQuery() })`) - .andWhere(new Brackets(qb => { - qb - .where('user.updatedAt IS NULL') - .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); - })) - .andWhere('user.isSuspended = FALSE') - .setParameters(profQuery.getParameters()); - - users = users.concat(await query - .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') - .limit(ps.limit) - .offset(ps.offset) - .getMany(), - ); - } + const users = await this.userSearchService.search(ps.query.trim(), me?.id ?? null, { + offset: ps.offset, + limit: ps.limit, + origin: ps.origin, + }); return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' }); }); From f7e901deb2a3d7457231c9a240d13d332e24461a Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:30:04 +0900 Subject: [PATCH 088/262] test fixes --- .../backend/test/unit/UserSearchService.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/backend/test/unit/UserSearchService.ts b/packages/backend/test/unit/UserSearchService.ts index 66a7f39ff1..697425beb8 100644 --- a/packages/backend/test/unit/UserSearchService.ts +++ b/packages/backend/test/unit/UserSearchService.ts @@ -134,13 +134,13 @@ describe('UserSearchService', () => { await app.close(); }); - describe('search', () => { + describe('searchByUsernameAndHost', () => { test('フォロー中のアクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => { await createFollowings(root, [alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]); await setActive([alice, alyce, alyssa, bob, bobbi, bobbie, bobby]); await setInactive([alycia, alysha, alyson]); - const result = await service.search( + const result = await service.searchByUsernameAndHost( { username: 'al' }, { limit: 100 }, root, @@ -154,7 +154,7 @@ describe('UserSearchService', () => { await createFollowings(root, [alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]); await setInactive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]); - const result = await service.search( + const result = await service.searchByUsernameAndHost( { username: 'al' }, { limit: 100 }, root, @@ -168,7 +168,7 @@ describe('UserSearchService', () => { await setActive([alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]); await setInactive([alice, alyce, alycia]); - const result = await service.search( + const result = await service.searchByUsernameAndHost( { username: 'al' }, { limit: 100 }, root, @@ -181,7 +181,7 @@ describe('UserSearchService', () => { test('フォローしていない非アクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => { await setInactive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]); - const result = await service.search( + const result = await service.searchByUsernameAndHost( { username: 'al' }, { limit: 100 }, root, @@ -195,7 +195,7 @@ describe('UserSearchService', () => { await setActive([root, alyssa, bob, bobbi, alyce, alycia]); await setInactive([alyson, alice, alysha, bobbie, bobby]); - const result = await service.search( + const result = await service.searchByUsernameAndHost( { }, { limit: 100 }, root, @@ -216,7 +216,7 @@ describe('UserSearchService', () => { await setActive([alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]); await setInactive([alice, alyce, alycia]); - const result = await service.search( + const result = await service.searchByUsernameAndHost( { username: 'al' }, { limit: 100 }, ); @@ -228,7 +228,7 @@ describe('UserSearchService', () => { test('[非ログイン] 非アクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => { await setInactive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]); - const result = await service.search( + const result = await service.searchByUsernameAndHost( { username: 'al' }, { limit: 100 }, ); @@ -240,7 +240,7 @@ describe('UserSearchService', () => { await createFollowings(root, [alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]); await setActive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]); - const result = await service.search( + const result = await service.searchByUsernameAndHost( { username: 'al', host: 'exam' }, { limit: 100 }, root, @@ -253,7 +253,7 @@ describe('UserSearchService', () => { await setActive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]); await setSuspended([alice, alyce, alycia]); - const result = await service.search( + const result = await service.searchByUsernameAndHost( { username: 'al' }, { limit: 100 }, root, From a78db27a3ca4e2125b217bdef4a0920d33029423 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:30:06 +0900 Subject: [PATCH 089/262] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d94dbe213..ff77553206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ - 投稿フォームをリセットできるように - 文字数カウントを復活 - Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように +- Enhance: 全体的なブラッシュアップ - Fix: テーマ切り替え時に一部の色が変わらない問題を修正 ### Server From e40846c46bdcbb8be0c15ac7e874e8ec2291f519 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 27 Mar 2025 19:50:03 +0900 Subject: [PATCH 090/262] fix e2e test --- packages/backend/test/e2e/users.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index f82d4c6a30..e04d258c0d 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -732,7 +732,7 @@ describe('ユーザー', () => { }); test.each([ { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: () => userNotExplorable }, - { label: 'ミュートユーザーが含まれる', user: () => userMutedByAlice }, + { label: 'ミュートユーザーが含まれない', user: () => userMutedByAlice, excluded: true }, { label: 'ブロックされているユーザーが含まれる', user: () => userBlockedByAlice }, { label: 'ブロックしてきているユーザーが含まれる', user: () => userBlockingAlice }, { label: '承認制ユーザーが含まれる', user: () => userLocking }, From 61e09d483e35b6d464a04a2d04633fd6b878b475 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Fri, 28 Mar 2025 09:22:37 +0900 Subject: [PATCH 091/262] refactor --- .../src/components/global/MkPageHeader.vue | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index d5680b8413..6a926f7718 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index a1e1460da1..4ed4cdc773 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -122,11 +122,6 @@ const menuDef = computed(() => [{ text: i18n.ts.sounds, to: '/settings/sounds', active: currentPage.value?.route.name === 'sounds', - }, { - icon: 'ti ti-accessible', - text: i18n.ts.accessibility, - to: '/settings/accessibility', - active: currentPage.value?.route.name === 'accessibility', }, { icon: 'ti ti-plug', text: i18n.ts.plugins, diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 7e7ed21dd5..a4e9616325 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -418,6 +418,116 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + +
+ + {{ i18n.ts._settings.accessibilityBanner }} + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ @@ -671,6 +781,18 @@ const defaultFollowWithReplies = prefer.model('defaultFollowWithReplies'); const chatShowSenderName = prefer.model('chat.showSenderName'); const chatSendOnEnter = prefer.model('chat.sendOnEnter'); const useStickyIcons = prefer.model('useStickyIcons'); +const reduceAnimation = prefer.model('animation', v => !v, v => !v); +const animatedMfm = prefer.model('animatedMfm'); +const disableShowingAnimatedImages = prefer.model('disableShowingAnimatedImages'); +const keepScreenOn = prefer.model('keepScreenOn'); +const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe'); +const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer'); +const contextMenu = prefer.model('contextMenu'); +const menuStyle = prefer.model('menuStyle'); +const makeEveryTextElementsSelectable = prefer.model('makeEveryTextElementsSelectable'); + +const fontSize = ref(miLocalStorage.getItem('fontSize')); +const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); watch(lang, () => { miLocalStorage.setItem('lang', lang.value as string); @@ -678,6 +800,22 @@ watch(lang, () => { miLocalStorage.removeItem('localeVersion'); }); +watch(fontSize, () => { + if (fontSize.value == null) { + miLocalStorage.removeItem('fontSize'); + } else { + miLocalStorage.setItem('fontSize', fontSize.value); + } +}); + +watch(useSystemFont, () => { + if (useSystemFont.value) { + miLocalStorage.setItem('useSystemFont', 't'); + } else { + miLocalStorage.removeItem('useSystemFont'); + } +}); + watch([ hemisphere, lang, @@ -700,6 +838,11 @@ watch([ enableSeasonalScreenEffect, chatShowSenderName, useStickyIcons, + keepScreenOn, + contextMenu, + fontSize, + useSystemFont, + makeEveryTextElementsSelectable, ], async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); diff --git a/packages/frontend/src/router.definition.ts b/packages/frontend/src/router.definition.ts index 0585a31fd1..23118160e2 100644 --- a/packages/frontend/src/router.definition.ts +++ b/packages/frontend/src/router.definition.ts @@ -128,10 +128,6 @@ export const ROUTE_DEF = [{ path: '/sounds', name: 'sounds', component: page(() => import('@/pages/settings/sounds.vue')), - }, { - path: '/accessibility', - name: 'accessibility', - component: page(() => import('@/pages/settings/accessibility.vue')), }, { path: '/plugin/install', name: 'plugin', diff --git a/packages/frontend/src/utility/autogen/settings-search-index.ts b/packages/frontend/src/utility/autogen/settings-search-index.ts index 9e1f41e3c7..b77957fe84 100644 --- a/packages/frontend/src/utility/autogen/settings-search-index.ts +++ b/packages/frontend/src/utility/autogen/settings-search-index.ts @@ -489,17 +489,79 @@ export const searchIndexes: SearchIndexItem[] = [ id: '96LnS1sxB', children: [ { - id: '5h8vhCX1S', + id: 'vPQPvmntL', + label: i18n.ts.reduceUiAnimation, + keywords: ['animation', 'motion', 'reduce'], + }, + { + id: 'wfJ91vwzq', + label: i18n.ts.disableShowingAnimatedImages, + keywords: ['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif'], + }, + { + id: '42b1L4xdq', + label: i18n.ts.enableAnimatedMfm, + keywords: ['mfm', 'enable', 'show', 'animated'], + }, + { + id: 'dLkRNHn3k', + label: i18n.ts.enableHorizontalSwipe, + keywords: ['swipe', 'horizontal', 'tab'], + }, + { + id: 'BvooTWFW5', + label: i18n.ts.keepScreenOn, + keywords: ['keep', 'screen', 'display', 'on'], + }, + { + id: 'yzbghkAq0', + label: i18n.ts.useNativeUIForVideoAudioPlayer, + keywords: ['native', 'system', 'video', 'audio', 'player', 'media'], + }, + { + id: 'aSbKFHbOy', + label: i18n.ts._settings.makeEveryTextElementsSelectable, + keywords: ['text', 'selectable'], + }, + { + id: 'bTcAsPvNz', + label: i18n.ts.menuStyle, + keywords: ['menu', 'style', 'popup', 'drawer'], + }, + { + id: 'lSVBaLnyW', + label: i18n.ts._contextMenu.title, + keywords: ['contextmenu', 'system', 'native'], + }, + { + id: 'pec0uMPq5', + label: i18n.ts.fontSize, + keywords: ['font', 'size'], + }, + { + id: 'Eh7vTluDO', + label: i18n.ts.useSystemFont, + keywords: ['font', 'system', 'native'], + }, + ], + label: i18n.ts.accessibility, + keywords: ['accessibility', i18n.ts._settings.accessibilityBanner], + }, + { + id: 'vTRSKf1JA', + children: [ + { + id: '2VjlA02wB', label: i18n.ts.turnOffToImprovePerformance, keywords: ['blur'], }, { - id: 'Cbjosj3TG', + id: 'f6J0lmg1g', label: i18n.ts.turnOffToImprovePerformance, keywords: ['blur', 'modal'], }, { - id: 'BKndoHcCj', + id: 'hQqXhfNg8', label: i18n.ts.turnOffToImprovePerformance, keywords: ['sticky'], }, @@ -508,55 +570,55 @@ export const searchIndexes: SearchIndexItem[] = [ keywords: ['performance'], }, { - id: '4yCgcFElF', + id: 'utM8dEobb', label: i18n.ts.dataSaver, keywords: ['datasaver'], }, { - id: 'DILm2LlCn', + id: 'gOUvwkE9t', children: [ { - id: 'Fd0rFTSry', + id: 'iUMUvFURf', label: i18n.ts.squareAvatars, keywords: ['avatar', 'icon', 'square'], }, { - id: 'xNsLokqeA', + id: 'ceyPO9Ywi', label: i18n.ts.seasonalScreenEffect, keywords: ['effect', 'show'], }, { - id: 'sZcalFBE8', + id: 'ztwIlsXhP', label: i18n.ts.openImageInNewTab, keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'], }, { - id: 'Eh7vTluDO', + id: 'vLSsQbZEo', label: i18n.ts.withRepliesByDefaultForNewlyFollowed, keywords: ['follow', 'replies'], }, { - id: 'vTRSKf1JA', + id: 'hQt85bBIX', label: i18n.ts.whenServerDisconnected, keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'], }, { - id: 'zlO5cBZFH', + id: 'C9SyK2m0', label: i18n.ts.numberOfPageCache, keywords: ['cache', 'page'], }, { - id: 'huQ8nc4iD', + id: '2U0iVUtfW', label: i18n.ts.forceShowAds, keywords: ['ad', 'show'], }, { - id: 'nJWfqwQ4R', + id: '1rA7ADEXY', label: i18n.ts.hemisphere, keywords: [], }, { - id: 'kwEEgTlwR', + id: 'vRayx89Rt', label: i18n.ts.additionalEmojiDictionary, keywords: ['emoji', 'dictionary', 'additional', 'extra'], }, @@ -906,70 +968,6 @@ export const searchIndexes: SearchIndexItem[] = [ path: '/settings/account-data', icon: 'ti ti-package', }, - { - id: 'f08Mi1Uwn', - children: [ - { - id: 'C5dRH2Ypy', - label: i18n.ts.reduceUiAnimation, - keywords: ['animation', 'motion', 'reduce'], - }, - { - id: '5mZxz2cru', - label: i18n.ts.disableShowingAnimatedImages, - keywords: ['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif'], - }, - { - id: 'c0Iy5hL5o', - label: i18n.ts.enableAnimatedMfm, - keywords: ['mfm', 'enable', 'show', 'animated'], - }, - { - id: '4HYFjs2Nv', - label: i18n.ts.enableHorizontalSwipe, - keywords: ['swipe', 'horizontal', 'tab'], - }, - { - id: 'kYVJ3SVNq', - label: i18n.ts.keepScreenOn, - keywords: ['keep', 'screen', 'display', 'on'], - }, - { - id: 'w4Bv0meAt', - label: i18n.ts.useNativeUIForVideoAudioPlayer, - keywords: ['native', 'system', 'video', 'audio', 'player', 'media'], - }, - { - id: 'b1GYEEJeh', - label: i18n.ts._settings.makeEveryTextElementsSelectable, - keywords: ['text', 'selectable'], - }, - { - id: 'vVLxwINTJ', - label: i18n.ts.menuStyle, - keywords: ['menu', 'style', 'popup', 'drawer'], - }, - { - id: '14cMhMLHL', - label: i18n.ts._contextMenu.title, - keywords: ['contextmenu', 'system', 'native'], - }, - { - id: 'oSo4LXMX9', - label: i18n.ts.fontSize, - keywords: ['font', 'size'], - }, - { - id: '7LQSAThST', - label: i18n.ts.useSystemFont, - keywords: ['font', 'system', 'native'], - }, - ], - label: i18n.ts.accessibility, - keywords: ['accessibility', i18n.ts._settings.accessibilityBanner], - path: '/settings/accessibility', - icon: 'ti ti-accessible', - }, ] as const; export type SearchIndex = typeof searchIndexes; From c06d0b9b42f426078b549354476606c3f4d33b93 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sun, 30 Mar 2025 11:27:35 +0900 Subject: [PATCH 144/262] enhance(frontend): organize settings page --- .../src/pages/settings/preferences.vue | 18 +++++++-------- .../utility/autogen/settings-search-index.ts | 22 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index a4e9616325..86ae8af213 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -104,15 +104,6 @@ SPDX-License-Identifier: AGPL-3.0-only - - - - - - {{ i18n.ts.add }} - {{ i18n.ts.remove }} - - @@ -164,6 +155,15 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + {{ i18n.ts.add }} + {{ i18n.ts.remove }} + +
diff --git a/packages/frontend/src/utility/autogen/settings-search-index.ts b/packages/frontend/src/utility/autogen/settings-search-index.ts index b77957fe84..df621beb7d 100644 --- a/packages/frontend/src/utility/autogen/settings-search-index.ts +++ b/packages/frontend/src/utility/autogen/settings-search-index.ts @@ -323,43 +323,43 @@ export const searchIndexes: SearchIndexItem[] = [ label: i18n.ts.emojiStyle, keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'], }, - { - id: 'wo0s0CaI1', - label: i18n.ts.pinnedList, - keywords: ['pinned', 'list'], - }, ], label: i18n.ts.general, keywords: ['general'], }, { - id: 'l78F2W9Ok', + id: '5G6O6qdis', children: [ { - id: 'iOJ3Crlky', + id: 'khT3n6byY', label: i18n.ts.showFixedPostForm, keywords: ['post', 'form', 'timeline'], }, { - id: 'CQldliCSi', + id: 'q5ElfNSou', label: i18n.ts.showFixedPostFormInChannel, keywords: ['post', 'form', 'timeline', 'channel'], }, { - id: 'd2H4E5ys6', + id: '3GcWIaZf8', label: i18n.ts.collapseRenotes, keywords: ['renote', i18n.ts.collapseRenotesDescription], }, { - id: 'yb11lSY1G', + id: 'd2H4E5ys6', label: i18n.ts.showGapBetweenNotesInTimeline, keywords: ['note', 'timeline', 'gap'], }, { - id: 'fL49Zxe9i', + id: '1LHOhDKGW', label: i18n.ts.disableStreamingTimeline, keywords: ['disable', 'streaming', 'timeline'], }, + { + id: 'DSzwvTp7i', + label: i18n.ts.pinnedList, + keywords: ['pinned', 'list'], + }, { id: 'ykifk3NHS', label: i18n.ts.showNoteActionsOnlyHover, From 88c743aa3386049f2b2de1afe7fb5c125e0581f1 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sun, 30 Mar 2025 11:45:41 +0900 Subject: [PATCH 145/262] chore(frontend): remove unused style --- packages/frontend/src/components/MkNote.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 8840a6a8bf..66a70c9be3 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -658,7 +658,6 @@ function emitUpdReaction(emoji: string, delta: number) { diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue deleted file mode 100644 index 43d24da28b..0000000000 --- a/packages/frontend/src/ui/classic.sidebar.vue +++ /dev/null @@ -1,245 +0,0 @@ - - - - - - - diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue deleted file mode 100644 index c984cde342..0000000000 --- a/packages/frontend/src/ui/classic.vue +++ /dev/null @@ -1,326 +0,0 @@ - - - - - - - From 17f3113b92a4ff79b4f7588663c7f08ae955325d Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sun, 30 Mar 2025 14:30:04 +0900 Subject: [PATCH 149/262] =?UTF-8?q?=F0=9F=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/MkPostForm.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 78c69306b0..e43ff65e1d 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -1314,7 +1314,7 @@ html[data-color-scheme=light] .preview { padding: 0 24px; margin: 0; width: 100%; - font-size: 16px; + font-size: 110%; border: none; border-radius: 0; background: transparent; From 0655c8a29bb6122cdda7235720a1ef220bb0a293 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sun, 30 Mar 2025 14:33:52 +0900 Subject: [PATCH 150/262] clean up --- packages/frontend/src/pages/settings/navbar.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue index f944490a66..91968c5300 100644 --- a/packages/frontend/src/pages/settings/navbar.vue +++ b/packages/frontend/src/pages/settings/navbar.vue @@ -40,8 +40,6 @@ SPDX-License-Identifier: AGPL-3.0-only - - From 303b62aff3c01cd0945ccef2e0fea17dbd27da40 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sun, 30 Mar 2025 14:34:32 +0900 Subject: [PATCH 151/262] New Crowdin updates (#15721) * New translations ja-jp.yml (German) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (German) * New translations ja-jp.yml (German) * New translations ja-jp.yml (German) * New translations ja-jp.yml (German) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (English) --- locales/ca-ES.yml | 1 + locales/de-DE.yml | 183 +++++++++++++++++++++++++++++++++++++++++++++- locales/en-US.yml | 1 + locales/zh-CN.yml | 1 + locales/zh-TW.yml | 1 + 5 files changed, 186 insertions(+), 1 deletion(-) diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index b371a6b025..fde5b2aad9 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1406,6 +1406,7 @@ _settings: timelineAndNote: "Línia de temps i nota" makeEveryTextElementsSelectable: "Fes que tots els elements del text siguin seleccionables" makeEveryTextElementsSelectable_description: "L'activació pot reduir la usabilitat en determinades ocasions." + useStickyIcons: "Utilitza icones fixes" showNavbarSubButtons: "Mostrar sub botons a la barra de navegació " ifOn: "Quan s'encén " ifOff: "Quan s'apaga " diff --git a/locales/de-DE.yml b/locales/de-DE.yml index b32f4512be..ee0a97098c 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -301,6 +301,7 @@ uploadFromUrlMayTakeTime: "Es kann eine Weile dauern, bis das Hochladen abgeschl explore: "Erkunden" messageRead: "Gelesen" noMoreHistory: "Kein weiterer Verlauf vorhanden" +startChat: "Chat starten" nUsersRead: "Von {n} Benutzern gelesen" agreeTo: "Ich stimme {0} zu" agree: "Zustimmen" @@ -1256,7 +1257,7 @@ replaying: "Aufzeichnung" endReplay: "Aufzeichnung verlassen" copyReplayData: "Aufzeichnung kopieren" ranking: "Rangliste" -lastNDays: "Letzten {n} Tage" +lastNDays: "Letzte {n} Tage" backToTitle: "Zurück zum Startbildschirm" hemisphere: "Hemisphäre" withSensitive: "Zeige \"sensitive Inhalte\" an" @@ -1318,25 +1319,93 @@ noName: "Kein Name" skip: "Überspringen" restore: "Wiederherstellen" syncBetweenDevices: "Zwischen Geräten synchronisieren" +preferenceSyncConflictTitle: "Der konfigurierte Wert ist auf dem Server bereits vorhanden." +preferenceSyncConflictText: "Die Einstellungen mit aktivierter Synchronisierung werden ihre Werte auf dem Server speichern. Es gibt jedoch bereits Werte auf dem Server. Welche Einstellungswerte sollen überschrieben werden?" +preferenceSyncConflictChoiceServer: "Konfigurierte Werte auf dem Server" +preferenceSyncConflictChoiceDevice: "Konfigurierte Werte auf dem Gerät" +preferenceSyncConflictChoiceCancel: "Einrichten der Synchronisierung abbrechen" paste: "Einfügen" +emojiPalette: "Emoji-Palette" postForm: "Notizfenster" textCount: "Zeichenanzahl" information: "Über" +chat: "Chat" +migrateOldSettings: "Alte Client-Einstellungen migrieren" +migrateOldSettings_description: "Dies sollte normalerweise automatisch geschehen, aber wenn die Migration aus irgendeinem Grund nicht erfolgreich war, kannst du den Migrationsprozess selbst manuell auslösen. Die aktuellen Konfigurationsinformationen werden dabei überschrieben." +compress: "Komprimieren" _chat: + noMessagesYet: "Noch keine Nachrichten" + newMessage: "Neue Nachricht" + individualChat: "Privater Chat" + individualChat_description: "Führe einen privaten Chat mit einer anderen Person." + roomChat_description: "Ein Chat-Raum, an dem mehrere Personen teilnehmen können.\nDu kannst auch Personen einladen, die keine privaten Chats zulassen, wenn sie die Einladung annehmen." + createRoom: "Raum erstellen" + inviteUserToChat: "Lade Benutzer ein, um mit dem Chatten zu beginnen" + yourRooms: "Erstellte Räume" invitations: "Einladen" + noInvitations: "Keine Einladungen" + history: "Verlauf" noHistory: "Kein Verlauf gefunden" + noRooms: "Keine Räume gefunden" + inviteUser: "Benutzer einladen" + sentInvitations: "Verschickte Einladungen" + join: "Beitreten" + ignore: "Ignorieren" + leave: "Raum verlassen" members: "Mitglieder" + searchMessages: "Nachrichten suchen" home: "Startseite" send: "Senden" + newline: "Neue Zeile" + muteThisRoom: "Raum stummschalten" + deleteRoom: "Raum löschen" + chatNotAvailableForThisAccountOrServer: "Der Chat ist auf diesem Server oder für dieses Konto nicht aktiviert." + chatNotAvailableInOtherAccount: "Die Chatfunktion wurde vom anderen Benutzer deaktiviert." + cannotChatWithTheUser: "Starten eines Chats mit diesem Benutzer nicht möglich" + cannotChatWithTheUser_description: "Der Chat ist entweder nicht verfügbar oder die andere Seite hat den Chat nicht aktiviert." + chatWithThisUser: "Mit dem Benutzer chatten" + thisUserAllowsChatOnlyFromFollowers: "Dieser Benutzer nimmt nur Chats von Followern an." + thisUserAllowsChatOnlyFromFollowing: "Dieser Benutzer nimmt nur Chats von Benutzern an, denen er folgt." + thisUserAllowsChatOnlyFromMutualFollowing: "Dieser Benutzer akzeptiert nur Chats von Benutzern, die sich gegenseitig folgen." + thisUserNotAllowedChatAnyone: "Dieser Benutzer nimmt keine Chats von anderen Benutzern an." + chatAllowedUsers: "Wem das Chatten erlaubt werden soll" + chatAllowedUsers_note: "Du kannst unabhängig von dieser Einstellung mit allen Personen chatten, denen du eine Chat-Nachricht gesendet hast." + _chatAllowedUsers: + everyone: "Jeder" + followers: "Nur deine Follower" + following: "Nur Benutzer, denen du folgst" + mutual: "Nur Benutzer, die sich gegenseitig folgen" + none: "Niemand" _emojiPalette: palettes: "Palette" enableSyncBetweenDevicesForPalettes: "Synchronisierung der Paletten zwischen Geräten aktivieren" paletteForMain: "Hauptpalette" + paletteForReaction: "Reaktions-Palette" _settings: + driveBanner: "Du kannst den Drive verwalten und konfigurieren, die Auslastung überprüfen und Einstellungen für das Hochladen von Dateien vornehmen." + pluginBanner: "Du kannst die Funktionen des Clients mit Plugins erweitern. Plugins können installiert, individuell konfiguriert und verwaltet werden." api: "API" webhook: "Webhook" + serviceConnectionBanner: "Du kannst Zugriffstoken und Webhooks für die Integration mit externen Anwendungen und Diensten verwalten und konfigurieren." accountData: "Kontodaten" + accountDataBanner: "Export/Import und Verwaltung von Kontodatenarchiven." + muteAndBlockBanner: "Du kannst Einstellungen konfigurieren und verwalten, um Inhalte auszublenden und Aktionen für bestimmte Benutzer zu beschränken." + accessibilityBanner: "Die Clients können personalisiert und für eine optimale Nutzung im Hinblick auf ihre Darstellung und ihr Verhalten eingerichtet werden." + privacyBanner: "Du kannst Einstellungen für die Privatsphäre deines Kontos vornehmen, z. B. inwieweit Inhalte veröffentlicht werden, wie leicht sie zu finden sind und ob Follower genehmigt werden müssen." + securityBanner: "Du kannst Einstellungen für die Kontosicherheit konfigurieren, z. B. Passwörter, Anmeldemethoden, Authentifizierungs-Apps und Passkeys." + appearanceBanner: "Du kannst das Erscheinungsbild und die Anzeigeeinstellungen für den Client nach deinen Wünschen konfigurieren." + soundsBanner: "Du kannst die Einstellungen für die Wiedergabe von Klängen im Client konfigurieren." + timelineAndNote: "Chroniken und Notizen" + makeEveryTextElementsSelectable: "Alle Textelemente auswählbar machen" + makeEveryTextElementsSelectable_description: "Die Aktivierung kann in manchen Situationen die Benutzerfreundlichkeit beeinträchtigen." + ifOn: "Wenn eingeschaltet" + ifOff: "Wenn ausgeschaltet" + _chat: + showSenderName: "Name des Absenders anzeigen" + sendOnEnter: "Eingabetaste sendet Nachricht" _preferencesProfile: + profileName: "Profilname" + profileNameDescription: "Lege einen Namen fest, der dieses Gerät identifiziert." profileNameDescription2: "Beispiel: \"Haupt-PC\", \"Smartphone\"" _preferencesBackup: autoBackup: "Automatische Sicherung" @@ -1353,9 +1422,11 @@ _accountSettings: requireSigninToViewContentsDescription2: "Der Inhalt wird nicht in URL-Vorschauen (OGP), eingebettet in Webseiten oder auf Servern, die keine Zitate unterstützen, angezeigt." requireSigninToViewContentsDescription3: "Diese Einschränkungen gelten möglicherweise nicht für föderierte Inhalte von anderen Servern." makeNotesFollowersOnlyBefore: "Macht frühere Notizen nur für Follower sichtbar" + makeNotesFollowersOnlyBeforeDescription: "Solange diese Funktion aktiviert ist, sind Notizen, die nach dem eingestellten Datum und der eingestellten Zeit liegen oder die eingestellte Zeit abgelaufen ist, nur für Follower sichtbar. Bei Deaktivierung wird auch der öffentliche Status der Notiz wiederhergestellt." makeNotesHiddenBefore: "Frühere Notizen privat machen" makeNotesHiddenBeforeDescription: "" mayNotEffectForFederatedNotes: "Dies hat möglicherweise keine Auswirkungen auf Notizen, die an andere Server föderiert werden." + mayNotEffectSomeSituations: "Diese Einschränkungen sind vereinfacht. Sie gelten möglicherweise nicht in allen Situationen, z. B. bei der Anzeige auf einem fremden Server oder während der Moderation." notesOlderThanSpecifiedDateAndTime: "Notizen vor einem bestimmtem Datum und Uhrzeit" _abuseUserReport: forward: "Weiterleiten" @@ -1363,11 +1434,15 @@ _abuseUserReport: resolve: "lösen" accept: "Akzeptieren" reject: "Ablehnen" + resolveTutorial: "Wenn der Inhalt der Meldung rechtmäßig ist, wähle „Akzeptieren“, um sie als gelöst zu markieren.\nWenn der Inhalt der Meldung unzulässig ist, wähle „Ablehnen“, um sie zu ignorieren." _delivery: + status: "Auslieferungsstatus" stop: "Gesperrt" _type: none: "Wird veröffentlicht" manuallySuspended: "Manuell gesperrt" + goneSuspended: "Gesperrt wegen Löschung des Servers" + autoSuspendedForNotResponding: "Gesperrt, weil der Server nicht antwortet" _bubbleGame: howToPlay: "Wie man spielt" hold: "Halten" @@ -1377,6 +1452,8 @@ _bubbleGame: highScore: "Höchstpunktzahl" maxChain: "Maximale Anzahl an Verkettungen" yen: "{yen} Yen" + estimatedQty: "{qty} Stück" + scoreSweets: "{onigiriQtyWithUnit} Onigiri" _howToPlay: section1: "Passe die Position an und lasse das Objekt in das Spielfeld fallen." section2: "Wenn sich zwei Objekte der gleichen Art berühren, verwandeln sie sich in ein anderes Objekt und du bekommst Punkte." @@ -1434,15 +1511,20 @@ _initialTutorial: reactDone: "Du kannst eine Reaktion zurücknehmen, indem du auf den '-' Button drückst." _timeline: title: "So funktionieren die Chroniken" + description1: "Misskey stellt mehrere Chroniken bereit (einige können je nach den Richtlinien des Servers nicht verfügbar sein)." home: "Du kannst Beiträge von den Konten sehen, denen du folgst." local: "Du kannst Beiträge aller Benutzer auf diesem Server sehen." social: "Notizen von der Startseite und der lokalen Chronik werden angezeigt." global: "Du kannst Notizen von allen föderierten Servern sehen." description2: "Du kannst jederzeit am oberen Rand des Bildschirms zwischen den jeweiligen Chroniken wechseln." + description3: "Darüber hinaus gibt es Listen-Chroniken und Kanal-Chroniken. Weitere Einzelheiten findest du unter {link}." _postNote: + description1: "Wenn du eine Notiz auf Misskey veröffentlichst, stehen dir verschiedene Optionen zur Verfügung. Die Oberfläche sieht folgendermaßen aus." _visibility: description: "Du kannst einschränken, wer deine Notiz sehen kann." public: "Deine Notiz wird für alle Nutzer sichtbar sein." + home: "Nur auf der Startseite sichtbar. Kann von Followern, Profilbesuchern und durch Renotes gesehen werden." + followers: "Nur für Follower sichtbar. Nur Follower können es sehen und niemand sonst, und es kann nicht von anderen gerenoted werden." direct: "Die Notiz wird nur für den angegebenen Benutzer veröffentlicht und der Empfänger wird benachrichtigt. Kann anstelle von Direktnachrichten verwendet werden." doNotSendConfidencialOnDirect1: "Sei vorsichtig, wenn du sensible Informationen verschickst!" doNotSendConfidencialOnDirect2: "Die Administratoren des Servers können den Inhalt der Notiz sehen. Sei vorsichtig mit sensiblen Informationen, wenn du Direktnachrichten an Benutzer auf nicht vertrauenswürdigen Servern sendest." @@ -1453,8 +1535,10 @@ _initialTutorial: _exampleNote: cw: "Das wird dich bestimmt hungrig machen!" note: "Ich hatte gerade einen Donut mit Schokoladenüberzug 🍩😋" + useCases: "Dient zur Kennzeichnung von Notizen, wie sie in den Serverrichtlinien vorgeschrieben sind, oder zur eigenen Festlegung von Spoiler-Beiträgen oder sensiblem Text." _howToMakeAttachmentsSensitive: title: "Wie markiert man Anhänge als sensibel?" + description: "Markiere Anhänge als sensibel, die aufgrund von den Serverregeln nicht sichtbar sein sollen." tryThisFile: "Versuche, das angehängte Bild als sensibel zu markieren!" _exampleNote: note: "Ups, ich habe es vergeigt, den Natto-Deckel zu öffnen..." @@ -1465,7 +1549,9 @@ _initialTutorial: title: "Du hast das Tutorial abgeschlossen! 🎉" description: "Die hier beschriebenen Funktionen sind nur ein kleiner Teil dessen, was Misskey zu bieten hat; um mehr darüber zu erfahren, wie du Misskey benutzen kannst, besuche bitte {link}." _timelineDescription: + home: "In der Startseiten-Chronik kannst du Notizen von Konten sehen, denen du folgst." local: "In der lokalen Chronik siehst du Notizen von allen Benutzern auf diesem Server." + social: "Die soziale Chronik zeigt Notizen von der Startseite und der lokalen Chronik." global: "In der globalen Chronik siehst du Notizen von allen föderierten Servern." _serverRules: description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen." @@ -1483,6 +1569,8 @@ _serverSettings: fanoutTimelineDbFallbackDescription: "Ist diese Option aktiviert, wird die Chronik auf zusätzliche Abfragen in der Datenbank zurückgreifen, wenn sich die Chronik nicht im Cache befindet. Eine Deaktivierung führt zu geringerer Serverlast, aber schränkt den Zeitraum der abrufbaren Chronik ein. " reactionsBufferingDescription: "Wenn diese Option aktiviert ist, kann sie die Leistung beim Erstellen von Reaktionen erheblich verbessern und die Belastung der Datenbank verringern. Allerdings steigt die Speichernutzung von Redis." inquiryUrl: "Kontakt-URL" + inquiryUrlDescription: "Gib eine URL für das Kontaktformular der Serverbetreiber oder eine Webseite an, die Kontaktinformationen enthält." + openRegistration: "Registrierung von Konten aktivieren" openRegistrationWarning: "Das Aktivieren von Registrierungen ist riskant. Es wird empfohlen, sie nur dann zu aktivieren, wenn der Server ständig überwacht wird und im Falle eines Problems sofort reagiert werden kann." thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Wenn über einen bestimmten Zeitraum keine Moderatorenaktivität festgestellt wird, wird diese Einstellung automatisch deaktiviert, um Spam zu verhindern." _accountMigration: @@ -1800,6 +1888,7 @@ _role: canManageAvatarDecorations: "Profilbilddekorationen verwalten" driveCapacity: "Drive-Kapazität" alwaysMarkNsfw: "Dateien immer als NSFW markieren" + canUpdateBioMedia: "Kann ein Profil- oder ein Bannerbild bearbeiten" pinMax: "Maximale Anzahl an angehefteten Notizen" antennaMax: "Maximale Anzahl an Antennen" wordMuteMax: "Maximale Zeichenlänge für Wortstummschaltungen" @@ -1815,12 +1904,20 @@ _role: canUseTranslator: "Verwendung des Übersetzers" avatarDecorationLimit: "Maximale Anzahl an Profilbilddekorationen, die angebracht werden können" canImportAntennas: "Importieren von Antennen erlauben" + canImportBlocking: "Importieren von Blockierungen zulassen" + canImportFollowing: "Importieren von Gefolgten zulassen" + canImportMuting: "Importieren von Stummgeschalteten zulassen" canImportUserLists: "Importieren von Listen erlauben" + canChat: "Chatten erlauben" _condition: + roleAssignedTo: "Manuellen Rollen zugewiesen" isLocal: "Lokaler Benutzer" isRemote: "Benutzer fremder Instanz" isCat: "Katzen-Benutzer" isBot: "Bot-Benutzer" + isSuspended: "Gesperrter Benutzer" + isLocked: "Private Konten" + isExplorable: "Benutzer, die ihr Konto im \"Erkunden\"-Bereich sichtbar machen" createdLessThan: "Kontoerstellung liegt weniger als X zurück" createdMoreThan: "Kontoerstellung liegt mehr als X zurück" followersLessThanOrEq: "Hat X oder weniger Follower" @@ -1975,6 +2072,7 @@ _theme: installed: "{name} wurde installiert" installedThemes: "Installierte Farbschemata" builtinThemes: "Eingebaute Farbschemata" + instanceTheme: "Server-Thema" alreadyInstalled: "Dieses Farbschema ist bereits installiert" invalid: "Der Code dieses Farbschemas ist ungültig" make: "Farbschema erstellen" @@ -2041,6 +2139,7 @@ _sfx: noteMy: "Meine Notizen" notification: "Benachrichtigungen" reaction: "Auswählen einer Reaktion" + chatMessage: "Chat-Nachrichten" _soundSettings: driveFile: "Audiodatei aus dem Drive verwenden" driveFileWarn: "Wähle eine Audiodatei aus dem Drive" @@ -2064,6 +2163,10 @@ _timeIn: seconds: "In {n}s" minutes: "In {n} Min." hours: "In {n} Std." + days: "In {n} Tagen" + weeks: "In {n} Wochen" + months: "In {n} Monaten" + years: "In {n} Jahren" _time: second: "Sekunde(n)" minute: "Minute(n)" @@ -2097,6 +2200,7 @@ _2fa: backupCodesDescription: "Verwende diese Codes, falls du nicht mehr auf deine App zur Zweifaktorauthentifizierung zugreifen kannst. Jeder Code kann nur einmal verwendet werden. Bewahre sie an einem sicheren Ort auf." backupCodeUsedWarning: "Ein Backup-Code wurde verwendet. Falls du den Zugriff zu deiner Zweifaktorauthentifizierungsapp verloren hast, konfiguriere diese bitte möglichst bald erneut." backupCodesExhaustedWarning: "Alle Backup-Codes wurden verwendet. Falls du den Zugang zu deiner Zweifaktorauthentifizierungsapp verlierst, wirst du dich nicht mehr in dieses Konto einloggen können. Bitte konfiguriere diese App erneut." + moreDetailedGuideHere: "Hier ist eine ausführliche Anleitung" _permissions: "read:account": "Deine Benutzerkontoinformationen lesen" "write:account": "Deine Benutzerkontoinformationen bearbeiten" @@ -2147,8 +2251,12 @@ _permissions: "read:admin:server-info": "Serverinformationen anzeigen" "read:admin:show-moderation-log": "Moderationsprotokoll einsehen" "read:admin:show-user": "Private Benutzerinformationen einsehen" + "write:admin:suspend-user": "Benutzer sperren" "write:admin:unset-user-avatar": "Benutzer-Profilbild entfernen" "write:admin:unset-user-banner": "Benutzer-Banner entfernen" + "write:admin:unsuspend-user": "Benutzer entsperren" + "write:admin:meta": "Metadaten der Instanz verwalten" + "write:admin:user-note": "Moderationsvermerke verwalten" "write:admin:roles": "Rollen verwalten" "read:admin:roles": "Rollen anzeigen" "write:admin:relays": "Relays verwalten" @@ -2172,7 +2280,10 @@ _permissions: "read:admin:ad": "Werbung ansehen" "write:invite-codes": "Einladungscodes erstellen" "read:invite-codes": "Einladungscodes anzeigen" + "read:federation": "Informationen zur Föderation einsehen" + "write:report-abuse": "Verstöße melden" "write:chat": "Chats bedienen" + "read:chat": "Chats durchsuchen" _auth: shareAccessTitle: "Verteilung von App-Berechtigungen" shareAccess: "Möchtest du „{name}“ authorisieren, auf dieses Benutzerkonto zugreifen zu können?" @@ -2295,6 +2406,7 @@ _profile: avatarDecorationMax: "Du kannst bis zu {max} Dekorationen hinzufügen." followedMessage: "Nachricht, wenn dir jemand folgt" followedMessageDescription: "Du kannst eine kurze Nachricht festlegen, die dem Empfänger angezeigt wird, wenn er dir folgt." + followedMessageDescriptionForLockedAccount: "Wenn Folgeanfragen deine Genehmigung brauchen, wird dies beim Genehmigen einer Anfrage angezeigt." _exportOrImport: allNotes: "Alle Notizen" favoritedNotes: "Als Favorit markierte Notizen" @@ -2352,6 +2464,7 @@ _play: title: "Titel" script: "Skript" summary: "Beschreibung" + visibilityDescription: "Wenn du die Sichtbarkeit auf Privat stellst, wird der Play nicht auf deinem Profil sichtbar sein, aber jeder, der die URL hat, kann ihn trotzdem aufrufen." _pages: newPage: "Seite erstellen" editPage: "Seite bearbeiten" @@ -2383,6 +2496,7 @@ _pages: eyeCatchingImageSet: "Vorschaubild festlegen" eyeCatchingImageRemove: "Vorschaubild entfernen" chooseBlock: "Block hinzufügen" + enterSectionTitle: "Titel des Abschnitts eingeben" selectType: "Typ auswählen" contentBlocks: "Inhalt" inputBlocks: "Eingabe" @@ -2393,6 +2507,8 @@ _pages: section: "Abschnitt" image: "Bild" button: "Knopf" + dynamic: "Dynamische Bausteine" + dynamicDescription: "Dieser Baustein wurde abgeschafft. Bitte verwende von nun an {play}." note: "Eingebettete Notiz" _note: id: "Notiz-ID" @@ -2415,6 +2531,7 @@ _notification: newNote: "Neue Notiz" unreadAntennaNote: "Antenne {name}" roleAssigned: "Rolle zugewiesen" + chatRoomInvitationReceived: "Du wurdest in einen Chatraum eingeladen" emptyPushNotificationMessage: "Push-Benachrichtigungen wurden aktualisiert" achievementEarned: "Errungenschaft freigeschaltet" testNotification: "Testbenachrichtigung" @@ -2442,9 +2559,11 @@ _notification: receiveFollowRequest: "Erhaltene Follow-Anfragen" followRequestAccepted: "Akzeptierte Follow-Anfragen" roleAssigned: "Rolle zugewiesen" + chatRoomInvitationReceived: "Einladungen zum Chatraum" achievementEarned: "Errungenschaft freigeschaltet" exportCompleted: "Der Export ist abgeschlossen" login: "Anmeldung" + createToken: "Erstellung von Zugriffstokens" test: "Test-Benachrichtigungen" app: "Benachrichtigungen von Apps" _actions: @@ -2560,6 +2679,7 @@ _moderationLogTypes: unmarkSensitiveDriveFile: "Datei als nicht sensitiv markiert" resolveAbuseReport: "Meldung bearbeitet" forwardAbuseReport: "Meldung weitergeleitet" + updateAbuseReportNote: "Moderationsnotiz einer Meldung aktualisiert" createInvitation: "Einladung erstellt" createAd: "Werbung erstellt" deleteAd: "Werbung gelöscht" @@ -2579,6 +2699,8 @@ _moderationLogTypes: deletePage: "Seite gelöscht" deleteFlash: "Play gelöscht" deleteGalleryPost: "Galeriebeitrag gelöscht" + deleteChatRoom: "Chatraum gelöscht" + updateProxyAccountDescription: "Beschreibung des Proxy-Benutzerkontos aktualisiert" _fileViewer: title: "Dateiinformationen" type: "Dateityp" @@ -2644,6 +2766,7 @@ _hemisphere: S: "Südliche Erdhalbkugel" caption: "Wird in einigen Client-Einstellungen zur Bestimmung der Jahreszeit verwendet." _reversi: + reversi: "Reversi" gameSettings: "Spieleinstellungen" chooseBoard: "Spielbrett auswählen" blackOrWhite: "Schwarz/Weiß" @@ -2661,6 +2784,7 @@ _reversi: pastTurnOf: "Zug von {name}" surrender: "Aufgeben" surrendered: "Aufgegeben" + timeout: "Zeit abgelaufen" drawn: "Unentschieden" won: "{name} hat gewonnen" black: "Schwarz" @@ -2675,6 +2799,7 @@ _reversi: freeMatch: "Freies Spiel" lookingForPlayer: "Gegner werden gesucht..." gameCanceled: "Das Spiel wurde abgesagt." + shareToTlTheGameWhenStart: "Spiel in der Chronik teilen, wenn es gestartet wurde" iStartedAGame: "Das Spiel hat begonnen! #MisskeyReversi" opponentHasSettingsChanged: "Der Gegner hat seine Einstellungen geändert." allowIrregularRules: "Irreguläre Regeln (völlig frei)" @@ -2695,7 +2820,9 @@ _urlPreviewSetting: requireContentLengthDescription: "Wenn der Server keine Content-Length zurückgibt, wird keine Vorschau erzeugt." userAgent: "User-Agent" userAgentDescription: "Legt den User-Agent fest, der beim Abrufen der Vorschau verwendet werden soll. Bleibt er leer, wird der Standard-User-Agent verwendet." + summaryProxy: "Proxy-Endpunkte, die Vorschaubilder erzeugen" summaryProxyDescription: "Generierung von Vorschaubildern mit Summaly Proxy anstelle von Misskey selbst." + summaryProxyDescription2: "Die folgenden Parameter werden als Abfrage-Strings mit dem Proxy verknüpft. Wenn der Proxy sie nicht unterstützt, werden die Werte ignoriert." _mediaControls: pip: "Bild-in-Bild" playbackRate: "Wiedergabegeschwindigkeit" @@ -2703,15 +2830,57 @@ _mediaControls: _contextMenu: title: "Kontextmenü" app: "Anwendung" + appWithShift: "Anwendung per Umschalttaste" + native: "Natives Browsermenü" _gridComponent: _error: requiredValue: "Dieser Wert ist ein Pflichtfeld" + columnTypeNotSupport: "Die Validierung regulärer Ausdrücke wird nur für Spalten vom Typ \"Text\" unterstützt." + patternNotMatch: "Dieser Wert stimmt nicht mit dem Schema in {pattern} überein" notUnique: "Dieser Wert muss eindeutig sein" +_roleSelectDialog: + notSelected: "Nicht ausgewählt" _customEmojisManager: + _gridCommon: + copySelectionRows: "Ausgewählte Zeilen kopieren" + copySelectionRanges: "Auswahl kopieren" + deleteSelectionRows: "Ausgewählte Zeilen löschen" + searchSettings: "Sucheinstellungen" + searchSettingCaption: "Detaillierte Suchkriterien festlegen." + searchLimit: "Anzahl der Ergebnisse" + sortOrder: "Sortierung" + registrationLogs: "Registrierungsprotokoll" + registrationLogsCaption: "Protokolle werden beim Aktualisieren oder Löschen von Emojis angezeigt. Sie verschwinden nach dem Aktualisieren oder Löschen, dem Wechsel zu einer neuen Seite oder dem Neuladen." + alertEmojisRegisterFailedDescription: "Emoji konnte nicht aktualisiert oder gelöscht werden. Bitte prüfe das Registrierungsprotokoll für Details." _logs: + failureLogNothing: "Es gibt kein Fehlerprotokoll." logNothing: "Keine Protokoll-Einträge." _remote: + selectionRowDetail: "Details der ausgewählten Zeile" + importSelectionRangesRows: "Zeilen in der Auswahl importieren" + importEmojisButton: "Ausgewählte Emojis importieren" confirmImportEmojisTitle: "Emojis importieren" + confirmImportEmojisDescription: "Importiere {count} Emoji(s), die von entfernten Server empfangen wurden. Bitte achte genau auf die Lizenz der Emojis. Bist du sicher, dass du fortfahren möchtest?" + _local: + tabTitleList: "Hinzugefügte Emojis" + tabTitleRegister: "Emojis hinzufügen" + _list: + emojisNothing: "Es wurden keine Emojis hinzugefügt." + alertUpdateEmojisNothingDescription: "Es wurden keine Emojis geändert." + alertDeleteEmojisNothingDescription: "Es gibt keine zu löschenden Emojis." + confirmUpdateEmojisDescription: "Aktualisiere {count} Emoji(s). Willst du fortfahren?" + confirmDeleteEmojisDescription: "Lösche {count} ausgewählte Emoji(s). Willst du fortfahren?" + confirmMovePageDesciption: "An den Emojis auf dieser Seite wurden Änderungen vorgenommen.\nWenn du die Seite verlässt, ohne zu speichern, werden alle auf dieser Seite vorgenommenen Änderungen verworfen." + _register: + uploadSettingDescription: "Hier kannst du das Verhalten beim Hochladen von Emojis konfigurieren." + directoryToCategoryLabel: "Gib den Namen des Verzeichnisses in das Feld „Kategorie“ ein" + directoryToCategoryCaption: "Wenn du ein Verzeichnis ziehst und ablegst, gib den Verzeichnisnamen in das Feld „Kategorie“ ein." + emojiInputAreaList1: "Ziehe Bilddateien oder Verzeichnisse per Drag-and-drop in diesen Rahmen" + emojiInputAreaList2: "Klicke auf diesen Link, um von deinem PC aus zu wählen" + emojiInputAreaList3: "Klicke auf diesen Link, um vom Drive aus zu wählen" + confirmRegisterEmojisDescription: "Füge die in der Liste aufgeführten Emojis als neue benutzerdefinierte Emojis hinzu. Bist du sicher? (Um eine Überlastung zu vermeiden, können nur {count} Emoji(s) in einem Vorgang hinzugefügt werden)" + confirmClearEmojisDescription: "Verwerfe die Bearbeitungen und lösche die Emojis aus der Liste. Bist du sicher, dass du fortfahren möchtest?" + confirmUploadEmojisDescription: "Lade die {count} abgelegte(n) Datei(en) in das Drive hoch. Bist du sicher, dass du fortfahren möchtest?" _embedCodeGen: title: "Einbettungscode anpassen" header: "Kopfzeile anzeigen" @@ -2719,6 +2888,9 @@ _embedCodeGen: maxHeight: "Maximale Höhe" maxHeightDescription: "Der Wert 0 deaktiviert die Einstellung der maximalen Höhe. Gib einen Wert an, um zu verhindern, dass das Widget weiterhin vertikal vergrößert wird." maxHeightWarn: "Die Begrenzung der maximalen Höhe ist deaktiviert (0). Wenn dies nicht beabsichtigt war, setze die maximale Höhe auf einen Wert fest." + previewIsNotActual: "Die Anzeige weicht von der tatsächlichen Einbettung ab, da sie den auf dem Vorschaufenster angezeigten Bereich überschreitet." + rounded: "Ecken abrunden" + border: "Dem äußeren Rand einen Rahmen hinzufügen" applyToPreview: "Auf die Vorschau anwenden" generateCode: "Einbettungscode generieren" codeGenerated: "Der Code wurde generiert" @@ -2749,8 +2921,17 @@ _remoteLookupErrors: title: "Nicht gefunden" description: "Die angeforderte Ressource konnte nicht gefunden werden, bitte überprüfe die URI erneut." _captcha: + verify: "Bitte beantworte das CAPTCHA" + testSiteKeyMessage: "Du kannst die Vorschau prüfen, indem du die Testwerte für den Site- und Secret-Key eingibst. Weitere Informationen findest du auf der folgenden Seite." _error: + _requestFailed: + title: "CAPTCHA-Anfrage fehlgeschlagen." + text: "Bitte probiere es später noch einmal oder überprüfe die Einstellungen erneut." + _verificationFailed: + title: "CAPTCHA-Prüfung fehlgeschlagen" + text: "Bitte überprüfe nochmals, ob die Einstellungen korrekt sind." _unknown: + title: "CAPTCHA-Fehler" text: "Es ist ein unerwarteter Fehler aufgetreten." _bootErrors: title: "Laden fehlgeschlagen" diff --git a/locales/en-US.yml b/locales/en-US.yml index 68d52c721d..089ed2383d 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1406,6 +1406,7 @@ _settings: timelineAndNote: "Timeline and note" makeEveryTextElementsSelectable: "Make all text elements selectable" makeEveryTextElementsSelectable_description: "Enabling this may reduce usability in some situations." + useStickyIcons: "Make icons follow while scrolling" showNavbarSubButtons: "Show sub-buttons on the navigation bar" ifOn: "When turned on" ifOff: "When turned off" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 3f2fa089da..a39ca4f8db 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1406,6 +1406,7 @@ _settings: timelineAndNote: "时间线和帖子" makeEveryTextElementsSelectable: "使所有的文字均可选择" makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。" + useStickyIcons: "使图标跟随滚动" showNavbarSubButtons: "在导航栏中显示副按钮" ifOn: "启用时" ifOff: "关闭时" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 5ff252eb49..71f3c45d6a 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1406,6 +1406,7 @@ _settings: timelineAndNote: "時間軸及貼文" makeEveryTextElementsSelectable: "允許選取所有文字" makeEveryTextElementsSelectable_description: "啟用此功能後,可能會在某些情境下降低可用性。" + useStickyIcons: "使大頭貼跟隨捲動" showNavbarSubButtons: "在導覽列顯示輔助按鈕" ifOn: "開啟時" ifOff: "關閉時" From 6e929ece6f1a1b5b369ebf1f6b534a69789a27b2 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sun, 30 Mar 2025 18:13:08 +0900 Subject: [PATCH 152/262] fix(frontend): suppress inject warn --- packages/frontend/src/components/global/MkPageHeader.vue | 2 +- packages/frontend/src/router.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 6b524785e4..2914012a0c 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -68,7 +68,7 @@ const emit = defineEmits<{ (ev: 'update:tab', key: string); }>(); -const viewId = inject(DI.viewId); +//const viewId = inject(DI.viewId); const injectedPageMetadata = inject(DI.pageMetadata); const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value); diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index d702da80fa..97ca63f50d 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -42,5 +42,5 @@ mainRouter.addListener('change', ctx => { mainRouter.init(); export function useRouter(): Router { - return inject(DI.router) ?? mainRouter; + return inject(DI.router, null) ?? mainRouter; } From e0d87028391500898adc2668d509e2d155d8b961 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sun, 30 Mar 2025 18:13:39 +0900 Subject: [PATCH 153/262] perf(frontend): tweak MkRange --- packages/frontend/src/components/MkRange.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue index 734b624541..c0e3e5db4b 100644 --- a/packages/frontend/src/components/MkRange.vue +++ b/packages/frontend/src/components/MkRange.vue @@ -159,12 +159,13 @@ function onMousedown(ev: MouseEvent | TouchEvent) { const onDrag = (ev: MouseEvent | TouchEvent) => { ev.preventDefault(); + let beforeValue = finalValue.value; const containerRect = containerEl.value!.getBoundingClientRect(); const pointerX = 'touches' in ev && ev.touches.length > 0 ? ev.touches[0].clientX : 'clientX' in ev ? ev.clientX : 0; const pointerPositionOnContainer = pointerX - (containerRect.left + (thumbWidth / 2)); rawValue.value = Math.min(1, Math.max(0, pointerPositionOnContainer / (containerEl.value!.offsetWidth - thumbWidth))); - if (props.continuousUpdate) { + if (props.continuousUpdate && beforeValue !== finalValue.value) { emit('update:modelValue', finalValue.value); } }; From 87a723897611fff9b5ddda8a8bec3cdb427a21dc Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sun, 30 Mar 2025 20:44:00 +0900 Subject: [PATCH 154/262] =?UTF-8?q?enhance(frontend):=20=E3=83=87=E3=83=83?= =?UTF-8?q?=E3=82=AD=E3=81=AE=E3=82=AA=E3=83=97=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 +- locales/index.d.ts | 24 ++ locales/ja-JP.yml | 6 + packages/frontend/src/pages/settings/deck.vue | 33 +++ packages/frontend/src/preferences/def.ts | 11 +- .../frontend/src/ui/_common_/navbar-h.vue | 214 ++++++++++++++++++ packages/frontend/src/ui/deck.vue | 166 +++++++++----- .../utility/autogen/settings-search-index.ts | 17 +- 8 files changed, 417 insertions(+), 60 deletions(-) create mode 100644 packages/frontend/src/ui/_common_/navbar-h.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 0268127cbe..0ecec21953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,9 @@ - Enhance: プラグインの管理が強化されました - インストール/アンインストール/設定の変更時にリロード不要になりました - Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように +- Enhance: デッキUIでカラム間のマージンを設定できるように +- Enhance: デッキUIでデッキメニューの位置を設定できるように +- Enhance: デッキUIでナビゲーションバーの位置を設定できるように - Enhance: アイコンのスクロール追従を無効化してパフォーマンス向上できるように - Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに - Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように @@ -52,8 +55,7 @@ - Fix: 読み込み直後にスクロールしようとすると途中で止まる場合があるのを修正 - Fix: テーマ切り替え時に一部の色が変わらない問題を修正 - NOTE: 構造上クラシックUIを新しいデザインシステムに移行することが困難なため、クラシックUIが削除されました - - デッキUIでカラムを中央寄せにし、メインカラムの左右にウィジェットカラムを配置することである程度クラシックUIを再現できます - - また、デッキでナビゲーションバーを上部に表示するオプションを実装予定です + - デッキUIでカラムを中央寄せにし、メインカラムの左右にウィジェットカラムを配置し、ナビゲーションバーを上部に表示することである程度クラシックUIを再現できます ### Server - Enhance 全体的なパフォーマンス向上 diff --git a/locales/index.d.ts b/locales/index.d.ts index 3dfd0e6029..3645639305 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5362,6 +5362,18 @@ export interface Locale extends ILocale { * 圧縮 */ "compress": string; + /** + * 右 + */ + "right": string; + /** + * 下 + */ + "bottom": string; + /** + * 上 + */ + "top": string; "_chat": { /** * まだメッセージはありません @@ -10065,6 +10077,18 @@ export interface Locale extends ILocale { * カラムの寄せ */ "columnAlign": string; + /** + * カラム間のマージン + */ + "columnGap": string; + /** + * デッキメニューの位置 + */ + "deckMenuPosition": string; + /** + * ナビゲーションバーの位置 + */ + "navbarPosition": string; /** * カラムを追加 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index eb1505270c..0e20001d6b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1336,6 +1336,9 @@ chat: "チャット" migrateOldSettings: "旧設定情報を移行" migrateOldSettings_description: "通常これは自動で行われていますが、何らかの理由により上手く移行されなかった場合は手動で移行処理をトリガーできます。現在の設定情報は上書きされます。" compress: "圧縮" +right: "右" +bottom: "下" +top: "上" _chat: noMessagesYet: "まだメッセージはありません" @@ -2662,6 +2665,9 @@ _notification: _deck: alwaysShowMainColumn: "常にメインカラムを表示" columnAlign: "カラムの寄せ" + columnGap: "カラム間のマージン" + deckMenuPosition: "デッキメニューの位置" + navbarPosition: "ナビゲーションバーの位置" addColumn: "カラムを追加" newNoteNotificationSettings: "新着ノート通知の設定" configureColumn: "カラムの設定" diff --git a/packages/frontend/src/pages/settings/deck.vue b/packages/frontend/src/pages/settings/deck.vue index 9b2b40374e..902e058e0d 100644 --- a/packages/frontend/src/pages/settings/deck.vue +++ b/packages/frontend/src/pages/settings/deck.vue @@ -45,6 +45,35 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -53,6 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref } from 'vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkRadios from '@/components/MkRadios.vue'; +import MkRange from '@/components/MkRange.vue'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { prefer } from '@/preferences.js'; @@ -62,6 +92,9 @@ const navWindow = prefer.model('deck.navWindow'); const useSimpleUiForNonRootPages = prefer.model('deck.useSimpleUiForNonRootPages'); const alwaysShowMainColumn = prefer.model('deck.alwaysShowMainColumn'); const columnAlign = prefer.model('deck.columnAlign'); +const columnGap = prefer.model('deck.columnGap'); +const menuPosition = prefer.model('deck.menuPosition'); +const navbarPosition = prefer.model('deck.navbarPosition'); const profilesSyncEnabled = ref(prefer.isSyncEnabled('deck.profiles')); diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index 37fa9471ee..5aa463f045 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -371,7 +371,16 @@ export const PREF_DEF = { default: true, }, 'deck.columnAlign': { - default: 'left' as 'left' | 'right' | 'center', + default: 'center' as 'left' | 'right' | 'center', + }, + 'deck.columnGap': { + default: 6, + }, + 'deck.menuPosition': { + default: 'bottom' as 'right' | 'bottom', + }, + 'deck.navbarPosition': { + default: 'left' as 'left' | 'top' | 'bottom', }, 'chat.showSenderName': { diff --git a/packages/frontend/src/ui/_common_/navbar-h.vue b/packages/frontend/src/ui/_common_/navbar-h.vue new file mode 100644 index 0000000000..c93935dd26 --- /dev/null +++ b/packages/frontend/src/ui/_common_/navbar-h.vue @@ -0,0 +1,214 @@ + + + + + + + diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index 3f3bc32fad..3de8137404 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -4,37 +4,43 @@ SPDX-License-Identifier: AGPL-3.0-only -->