Date: Tue, 15 Apr 2025 16:14:52 +0900
Subject: [PATCH 097/190] feat: render quote note with `quote-inline` class for
ap compatibility (#15818)
---
packages/backend/src/core/MfmService.ts | 10 +++++++--
.../src/core/activitypub/ApMfmService.ts | 10 ++++-----
.../src/core/activitypub/ApRendererService.ts | 22 +++++++++++++++----
3 files changed, 31 insertions(+), 11 deletions(-)
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 00208927e2..28d980f718 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -6,7 +6,7 @@
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import * as parse5 from 'parse5';
-import { Window, XMLSerializer } from 'happy-dom';
+import { type Document, type HTMLParagraphElement, Window, XMLSerializer } from 'happy-dom';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { intersperse } from '@/misc/prelude/array.js';
@@ -23,6 +23,8 @@ type ChildNode = DefaultTreeAdapterMap['childNode'];
const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
+export type Appender = (document: Document, body: HTMLParagraphElement) => void;
+
@Injectable()
export class MfmService {
constructor(
@@ -267,7 +269,7 @@ export class MfmService {
}
@bindThis
- public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) {
+ public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], additionalAppenders: Appender[] = []) {
if (nodes == null) {
return null;
}
@@ -492,6 +494,10 @@ export class MfmService {
appendChildren(nodes, body);
+ for (const additionalAppender of additionalAppenders) {
+ additionalAppender(doc, body);
+ }
+
// Remove the unnecessary namespace
const serialized = new XMLSerializer().serializeToString(body).replace(/^\s*/, '
');
diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts
index 4036d2794a..f4c07e472c 100644
--- a/packages/backend/src/core/activitypub/ApMfmService.ts
+++ b/packages/backend/src/core/activitypub/ApMfmService.ts
@@ -5,7 +5,7 @@
import { Injectable } from '@nestjs/common';
import * as mfm from 'mfm-js';
-import { MfmService } from '@/core/MfmService.js';
+import { MfmService, Appender } from '@/core/MfmService.js';
import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
import { extractApHashtagObjects } from './models/tag.js';
@@ -25,17 +25,17 @@ export class ApMfmService {
}
@bindThis
- public getNoteHtml(note: Pick, apAppend?: string) {
+ public getNoteHtml(note: Pick, additionalAppender: Appender[] = []) {
let noMisskeyContent = false;
- const srcMfm = (note.text ?? '') + (apAppend ?? '');
+ const srcMfm = (note.text ?? '');
const parsed = mfm.parse(srcMfm);
- if (!apAppend && parsed?.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
+ if (!additionalAppender.length && parsed.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
noMisskeyContent = true;
}
- const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers));
+ const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers), additionalAppender);
return {
content,
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index f01874952f..55521d6e3a 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -19,7 +19,7 @@ import type { MiEmoji } from '@/models/Emoji.js';
import type { MiPoll } from '@/models/Poll.js';
import type { MiPollVote } from '@/models/PollVote.js';
import { UserKeypairService } from '@/core/UserKeypairService.js';
-import { MfmService } from '@/core/MfmService.js';
+import { MfmService, type Appender } from '@/core/MfmService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import type { MiUserKeypair } from '@/models/UserKeypair.js';
@@ -430,10 +430,24 @@ export class ApRendererService {
poll = await this.pollsRepository.findOneBy({ noteId: note.id });
}
- let apAppend = '';
+ const apAppend: Appender[] = [];
if (quote) {
- apAppend += `\n\nRE: ${quote}`;
+ // Append quote link as `
RE: ...`
+ // the claas name `quote-inline` is used in non-misskey clients for styling quote notes.
+ // For compatibility, the span part should be kept as possible.
+ apAppend.push((doc, body) => {
+ body.appendChild(doc.createElement('br'));
+ body.appendChild(doc.createElement('br'));
+ const span = doc.createElement('span');
+ span.className = 'quote-inline';
+ span.appendChild(doc.createTextNode('RE: '));
+ const link = doc.createElement('a');
+ link.setAttribute('href', quote);
+ link.textContent = quote;
+ span.appendChild(link);
+ body.appendChild(span);
+ });
}
const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
@@ -509,7 +523,7 @@ export class ApRendererService {
const urlPart = match[0];
const urlPartParsed = new URL(urlPart);
const restPart = maybeUrl.slice(match[0].length);
-
+
return `${urlPart}${restPart}`;
} catch (e) {
return maybeUrl;
From b2e3e658965b52873dd6771cf9771b3032a0ed15 Mon Sep 17 00:00:00 2001
From: anatawa12
Date: Tue, 15 Apr 2025 16:15:27 +0900
Subject: [PATCH 098/190] fix: use ftt for outbox (#15819)
* fix: use ftt for outbox
* chore: check for enableFanoutTimeline
* lint: fix lint
---
.../src/core/FanoutTimelineEndpointService.ts | 2 +-
.../src/server/ActivityPubServerService.ts | 48 +++++++++++++++----
2 files changed, 39 insertions(+), 11 deletions(-)
diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts
index b05af99c5e..ce8cc83dfd 100644
--- a/packages/backend/src/core/FanoutTimelineEndpointService.ts
+++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts
@@ -54,7 +54,7 @@ export class FanoutTimelineEndpointService {
}
@bindThis
- private async getMiNotes(ps: TimelineOptions): Promise {
+ async getMiNotes(ps: TimelineOptions): Promise {
// 呼び出し元と以下の処理をシンプルにするためにdbFallbackを置き換える
if (!ps.useDbFallback) ps.dbFallback = () => Promise.resolve([]);
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index 72d57a9b1b..f7b22c44c4 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -32,6 +32,7 @@ import { isQuote, isRenote } from '@/misc/is-renote.js';
import * as Acct from '@/misc/acct.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
import type { FindOptionsWhere } from 'typeorm';
+import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
const ACTIVITY_JSON = 'application/activity+json; charset=utf-8';
const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
@@ -75,6 +76,7 @@ export class ActivityPubServerService {
private queueService: QueueService,
private userKeypairService: UserKeypairService,
private queryService: QueryService,
+ private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
) {
//this.createServer = this.createServer.bind(this);
}
@@ -461,16 +463,28 @@ export class ActivityPubServerService {
const partOf = `${this.config.url}/users/${userId}/outbox`;
if (page) {
- const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), sinceId, untilId)
- .andWhere('note.userId = :userId', { userId: user.id })
- .andWhere(new Brackets(qb => {
- qb
- .where('note.visibility = \'public\'')
- .orWhere('note.visibility = \'home\'');
- }))
- .andWhere('note.localOnly = FALSE');
-
- const notes = await query.limit(limit).getMany();
+ const notes = this.meta.enableFanoutTimeline ? await this.fanoutTimelineEndpointService.getMiNotes({
+ sinceId: sinceId ?? null,
+ untilId: untilId ?? null,
+ limit: limit,
+ allowPartial: false, // Possibly true? IDK it's OK for ordered collection.
+ me: null,
+ redisTimelines: [
+ `userTimeline:${user.id}`,
+ `userTimelineWithReplies:${user.id}`,
+ ],
+ useDbFallback: true,
+ ignoreAuthorFromMute: true,
+ excludePureRenotes: false,
+ noteFilter: (note) => {
+ if (note.visibility !== 'home' && note.visibility !== 'public') return false;
+ if (note.localOnly) return false;
+ return true;
+ },
+ dbFallback: async (untilId, sinceId, limit) => {
+ return await this.getUserNotesFromDb(sinceId, untilId, limit, user.id);
+ },
+ }) : await this.getUserNotesFromDb(sinceId ?? null, untilId ?? null, limit, user.id);
if (sinceId) notes.reverse();
@@ -508,6 +522,20 @@ export class ActivityPubServerService {
}
}
+ @bindThis
+ private async getUserNotesFromDb(untilId: string | null, sinceId: string | null, limit: number, userId: MiUser['id']) {
+ return await this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), sinceId, untilId)
+ .andWhere('note.userId = :userId', { userId })
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('note.visibility = \'public\'')
+ .orWhere('note.visibility = \'home\'');
+ }))
+ .andWhere('note.localOnly = FALSE')
+ .limit(limit)
+ .getMany();
+ }
+
@bindThis
private async userInfo(request: FastifyRequest, reply: FastifyReply, user: MiUser | null) {
if (this.meta.federation === 'none') {
From fff2783c1b8b7d0ce30841e83d917091f40b521b Mon Sep 17 00:00:00 2001
From: Kisaragi <48310258+KisaragiEffective@users.noreply.github.com>
Date: Tue, 15 Apr 2025 16:15:56 +0900
Subject: [PATCH 099/190] =?UTF-8?q?docs:=20=E3=83=90=E3=83=83=E3=82=AF?=
=?UTF-8?q?=E3=82=A2=E3=83=83=E3=83=97=E3=81=A8=E3=83=AD=E3=82=B0=E3=82=A2?=
=?UTF-8?q?=E3=82=A6=E3=83=88=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=82=AF=E3=83=AA?=
=?UTF-8?q?=E3=82=A2=E3=81=AB=E3=81=A4=E3=81=84=E3=81=A6=E6=9B=B8=E3=81=8D?=
=?UTF-8?q?=E6=96=B9=E3=82=92=E5=A4=89=E3=81=88=E3=82=8B=20(#15808)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. 「新しいクライアントからログインしたとき」がすでにログインしたことのあるクライアントだとバックアップが必要ないと読めるので「新しい」を外す
2. ログアウトしてから設定データをブラウザから削除したあとで復元できるのはバックアップをとっているときのみであることを強調
cc 1312fe34c1e499bf537f0ee49e6d72683e0101ef
---
CHANGELOG.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3214ef936f..f74f92c606 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -49,7 +49,7 @@
- プラグイン、テーマ、クライアントに追加されたすべてのアカウント情報も含まれるようになりました
- 自動で設定データをサーバーにバックアップできるように
- 設定→設定のプロファイル→自動バックアップ で有効にできます
- - 新しいデバイスからログインしたり、ブラウザから設定データが消えてしまったときに自動で復元されます(復元をスキップすることも可能)
+ - ログインしたとき、ブラウザから設定データが消えてしまったときに自動で復元されます(復元をスキップすることも可能)
- 任意の設定項目をデバイス間で同期できるように
- 設定項目の「...」メニュー→「デバイス間で同期」
- 同期をオンにした際にサーバーに保存された値とローカルの値が競合する場合はどちらを優先するか選択できます
@@ -58,7 +58,7 @@
- アカウントごとに設定値が分離される設定とそうでないクライアント設定が混在していた(かつ分離するかどうかを設定不可だった)のを、基本的に一律でクライアント全体に適用されるようにし、個別でアカウントごとに異なる設定を行えるように
- 設定項目の「...」メニュー→「アカウントで上書き」をオンにすることで、設定値をそのアカウントでだけ適用するようにできます
- ログアウトすると設定データもブラウザから消去されるようになりプライバシーが向上しました
- - 再度ログインすればサーバーのバックアップから設定データを復元可能です
+ - バックアップを有効にしている場合、ログインした後にバックアップから設定データを復元可能です
- エクスポートした設定データを他のサーバーでインポートして適用すること(設定の持ち運び)が可能になりました
- 設定情報の移行は自動で行われますが、何らかの理由で失敗した場合、設定→その他→旧設定情報を移行 で再試行可能です
- 過去に作成されたバックアップデータとは現在互換性がありませんのでご注意ください
From fc6037af461412d914cd35a00fa004ad26d19c0f Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 15 Apr 2025 18:27:45 +0900
Subject: [PATCH 100/190] enhance(backend): push notification for chat message
Resolve #15831
---
CHANGELOG.md | 2 +-
packages/backend/src/core/ChatService.ts | 4 ++--
.../src/core/PushNotificationService.ts | 1 +
packages/sw/src/scripts/create-notification.ts | 18 ++++++++++++++++++
packages/sw/src/scripts/operations.ts | 10 +++++++++-
packages/sw/src/sw.ts | 4 ++++
packages/sw/src/types.ts | 1 +
7 files changed, 36 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f74f92c606..81b8f88536 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,7 @@
## Unreleased
### General
--
+- Enhance: チャットの新規メッセージをプッシュ通知するように
### Client
- Feat: チャットウィジェットを追加
diff --git a/packages/backend/src/core/ChatService.ts b/packages/backend/src/core/ChatService.ts
index b0e8cfb61c..9d294a80cb 100644
--- a/packages/backend/src/core/ChatService.ts
+++ b/packages/backend/src/core/ChatService.ts
@@ -232,7 +232,7 @@ export class ChatService {
const packedMessageForTo = await this.chatEntityService.packMessageDetailed(inserted, toUser);
this.globalEventService.publishMainStream(toUser.id, 'newChatMessage', packedMessageForTo);
- //this.pushNotificationService.pushNotification(toUser.id, 'newChatMessage', packedMessageForTo);
+ this.pushNotificationService.pushNotification(toUser.id, 'newChatMessage', packedMessageForTo);
}, 3000);
}
@@ -302,7 +302,7 @@ export class ChatService {
if (marker == null) continue;
this.globalEventService.publishMainStream(membershipsOtherThanMe[i].userId, 'newChatMessage', packedMessageForTo);
- //this.pushNotificationService.pushNotification(membershipsOtherThanMe[i].userId, 'newChatMessage', packedMessageForTo);
+ this.pushNotificationService.pushNotification(membershipsOtherThanMe[i].userId, 'newChatMessage', packedMessageForTo);
}
}, 3000);
diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts
index 1479bb00d9..9333c1ebc5 100644
--- a/packages/backend/src/core/PushNotificationService.ts
+++ b/packages/backend/src/core/PushNotificationService.ts
@@ -22,6 +22,7 @@ type PushNotificationsTypes = {
note: Packed<'Note'>;
};
'readAllNotifications': undefined;
+ newChatMessage: Packed<'ChatMessage'>;
};
// Reduce length because push message servers have character limits
diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts
index 364328d4b0..77b3f88791 100644
--- a/packages/sw/src/scripts/create-notification.ts
+++ b/packages/sw/src/scripts/create-notification.ts
@@ -268,6 +268,24 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
data,
renotify: true,
}];
+ case 'newChatMessage':
+ if (data.body.toRoom != null) {
+ return [`${data.body.toRoom.name}: ${getUserName(data.body.fromUser)}: ${data.body.text}`, {
+ icon: data.body.fromUser.avatarUrl,
+ badge: iconUrl('messages'),
+ tag: `chat:room:${data.body.toRoomId}`,
+ data,
+ renotify: true,
+ }];
+ } else {
+ return [`${getUserName(data.body.fromUser)}: ${data.body.text}`, {
+ icon: data.body.fromUser.avatarUrl,
+ badge: iconUrl('messages'),
+ tag: `chat:user:${data.body.fromUserId}`,
+ data,
+ renotify: true,
+ }];
+ }
default:
return null;
}
diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts
index 8862c6faa5..3e72b7e7c2 100644
--- a/packages/sw/src/scripts/operations.ts
+++ b/packages/sw/src/scripts/operations.ts
@@ -16,7 +16,7 @@ export const cli = new Misskey.api.APIClient({ origin, fetch: (...args): Promise
export async function api<
E extends keyof Misskey.Endpoints,
- P extends Misskey.Endpoints[E]['req']
+ P extends Misskey.Endpoints[E]['req'],
>(endpoint: E, userId?: string, params?: P): Promise | undefined> {
let account: Pick | undefined;
@@ -60,6 +60,14 @@ export function openAntenna(antennaId: string, loginId: string): ReturnType {
+ if (body.toRoomId != null) {
+ return openClient('push', `/chat/room/${body.toRoomId}`, loginId, { body });
+ } else {
+ return openClient('push', `/chat/user/${body.toUserId}`, loginId, { body });
+ }
+}
+
// post-formのオプションから投稿フォームを開く
export async function openPost(options: { initialText?: string; reply?: Misskey.entities.Note; renote?: Misskey.entities.Note }, loginId?: string): ReturnType {
// クエリを作成しておく
diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts
index bf980b83a4..298af4b4b6 100644
--- a/packages/sw/src/sw.ts
+++ b/packages/sw/src/sw.ts
@@ -76,6 +76,7 @@ globalThis.addEventListener('push', ev => {
// case 'driveFileCreated':
case 'notification':
case 'unreadAntennaNote':
+ case 'newChatMessage':
// 1日以上経過している場合は無視
if (Date.now() - data.dateTime > 1000 * 60 * 60 * 24) break;
@@ -155,6 +156,9 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
case 'unreadAntennaNote':
client = await swos.openAntenna(data.body.antenna.id, loginId);
break;
+ case 'newChatMessage':
+ client = await swos.openChat(data.body, loginId);
+ break;
default:
switch (action) {
case 'markAllAsRead':
diff --git a/packages/sw/src/types.ts b/packages/sw/src/types.ts
index 4f82779808..549220396c 100644
--- a/packages/sw/src/types.ts
+++ b/packages/sw/src/types.ts
@@ -23,6 +23,7 @@ type PushNotificationDataSourceMap = {
note: Misskey.entities.Note;
};
readAllNotifications: undefined;
+ newChatMessage: Misskey.entities.ChatMessage;
};
export type PushNotificationData = {
From 45d65ffbba1145870686c0f8c7799ce900e98a9a Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 15 Apr 2025 18:29:22 +0900
Subject: [PATCH 101/190] New Crowdin updates (#15786)
* New translations ja-jp.yml (Chinese Traditional)
* New translations ja-jp.yml (Korean)
* New translations ja-jp.yml (Korean)
* New translations ja-jp.yml (Chinese Traditional)
* New translations ja-jp.yml (Korean)
* New translations ja-jp.yml (Korean)
* New translations ja-jp.yml (Italian)
* New translations ja-jp.yml (Italian)
* New translations ja-jp.yml (Catalan)
* New translations ja-jp.yml (Catalan)
* New translations ja-jp.yml (Catalan)
* New translations ja-jp.yml (Catalan)
* New translations ja-jp.yml (Spanish)
* New translations ja-jp.yml (Chinese Simplified)
* New translations ja-jp.yml (Chinese Traditional)
* New translations ja-jp.yml (Catalan)
* New translations ja-jp.yml (Italian)
* New translations ja-jp.yml (Catalan)
* New translations ja-jp.yml (German)
* New translations ja-jp.yml (Italian)
* New translations ja-jp.yml (Korean)
* New translations ja-jp.yml (Chinese Simplified)
* New translations ja-jp.yml (Chinese Traditional)
* New translations ja-jp.yml (English)
---
locales/ca-ES.yml | 19 +++++++++++--------
locales/de-DE.yml | 2 ++
locales/en-US.yml | 2 ++
locales/es-ES.yml | 39 +++++++++++++++++++++++++++++++++++++++
locales/it-IT.yml | 19 ++++++++++++++-----
locales/ko-KR.yml | 24 +++++++++++++-----------
locales/zh-CN.yml | 3 +++
locales/zh-TW.yml | 8 ++++++--
8 files changed, 90 insertions(+), 26 deletions(-)
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 8f83a33356..129c3d24a5 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -356,7 +356,7 @@ banner: "Bàner"
displayOfSensitiveMedia: "Visualització de contingut sensible"
whenServerDisconnected: "Quan es perdi la connexió al servidor"
disconnectedFromServer: "Desconnectat pel servidor"
-reload: "Actualitza"
+reload: "Actualitzar"
doNothing: "Ignora"
reloadConfirm: "Vols recarregar?"
watch: "Veure"
@@ -708,7 +708,7 @@ notificationSetting: "Paràmetres de notificacions"
notificationSettingDesc: "Selecciona els tipus de notificacions que es mostraran"
useGlobalSetting: "Fer servir la configuració global"
useGlobalSettingDesc: "Si s'activa, es farà servir la configuració de notificacions del teu comte. Si no s'activa es poden fer configuracions individuals."
-other: "Altre"
+other: "Altres"
regenerateLoginToken: "Regenerar clau de seguretat d'inici de sessió"
regenerateLoginTokenDescription: "Regenera la clau de seguretat que es fa servir internament durant l'inici de sessió. Normalment aquesta acció no és necessària. Si es regenera es tancarà la sessió a tots els dispositius amb una sessió activa."
theKeywordWhenSearchingForCustomEmoji: "Cercar un emoji personalitzat "
@@ -979,6 +979,7 @@ document: "Documentació"
numberOfPageCache: "Nombre de pàgines a la memòria cau"
numberOfPageCacheDescription: "Incrementant aquest nombre farà que millori l'experiència de l'usuari, però es farà servir més memòria al dispositiu de l'usuari."
logoutConfirm: "Vols sortir?"
+logoutWillClearClientData: "En tancar la sessió, la informació del client al navegador s'esborrarà. Per garantir que la informació de configuració es pugui restaurar en tornar a iniciar sessió activa la còpia de seguretat automàtica de la configuració."
lastActiveDate: "Fet servir per última vegada"
statusbar: "Barra d'estat"
pleaseSelect: "Selecciona una opció"
@@ -1334,14 +1335,14 @@ postForm: "Formulari de publicació"
textCount: "Nombre de caràcters "
information: "Informació"
chat: "Xat"
-migrateOldSettings: "Migració de la configuració antiga "
+migrateOldSettings: "Migrar la configuració anterior"
migrateOldSettings_description: "Normalment això es fa automàticament, però si la transició no es fa, el procés es pot iniciar manualment. S'esborrarà la configuració actual."
compress: "Comprimir "
right: "Dreta"
bottom: "A baix "
top: "A dalt "
embed: "Incrustar"
-settingsMigrating: "Estem fent la migració de la teva configuració. Si us plau espera un moment... (També pots fer la migració més tard i manualment anant a Preferències → Altres configuracions → Migrar configuració antiga)"
+settingsMigrating: "Estem migrant la teva configuració. Si us plau espera un moment... (També pots fer la migració més tard, manualment, anant a Preferències → Altres → Migrar configuració antiga)"
readonly: "Només lectura"
goToDeck: "Tornar al tauler"
_chat:
@@ -1359,7 +1360,7 @@ _chat:
noInvitations: "No tens cap invitació "
history: "Historial de converses "
noHistory: "No hi ha un registre previ"
- noRooms: "No hi ha habitacions"
+ noRooms: "No hi ha cap sala"
inviteUser: "Invitar usuaris"
sentInvitations: "Enviar invitacions"
join: "Afegir-se "
@@ -1417,8 +1418,8 @@ _settings:
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 "
+ ifOn: "Quan s'activa"
+ ifOff: "Quan es desactiva"
enableSyncThemesBetweenDevices: "Sincronitzar els temes instal·lats entre dispositius"
_chat:
showSenderName: "Mostrar el nom del remitent"
@@ -1604,7 +1605,7 @@ _accountMigration:
moveTo: "Migrar aquest compte a un altre"
moveToLabel: "Compte al qual es vol migrar:"
moveCannotBeUndone: "Les migracions dels comptes no es poden desfer."
- moveAccountDescription: "Això migrarà la teva compte a un altre diferent.\n ・Els seguidors d'aquest compte és passaran al compte nou de forma automàtica\n ・Es deixaran de seguir a tots els usuaris que es segueixen actualment en aquest compte\n ・No es poden crear notes noves, etc. en aquest compte\n\nSi bé la migració de seguidors es automàtica, has de preparar alguns pasos manualment per migrar la llista d'usuaris que segueixes. Per fer això has d'exportar els seguidors que després importaraes al compte nou mitjançant el menú de configuració. El mateix procediment s'ha de seguir per less teves llistes i els teus usuaris silenciats i bloquejats.\n\n(Aquesta explicació s'aplica a Misskey v13.12.0 i posteriors. Altres aplicacions, com Mastodon, poden funcionar diferent.)"
+ moveAccountDescription: "Això migrarà el teu compte a un altre diferent.\n ・Els seguidors d'aquest compte és passaran al compte nou de forma automàtica\n ・Es deixaran de seguir a tots els usuaris que es segueixen actualment en aquest compte\n ・No es poden crear notes noves, etc. en aquest compte\n\nSi bé la migració de seguidors es automàtica, has de preparar alguns pasos manualment per migrar la llista d'usuaris que segueixes. Per fer això has d'exportar els seguidors que després importaraes al compte nou mitjançant el menú de configuració. El mateix procediment s'ha de seguir per less teves llistes i els teus usuaris silenciats i bloquejats.\n\n(Aquesta explicació s'aplica a Misskey v13.12.0 i posteriors. Altres aplicacions, com Mastodon, poden funcionar diferent.)"
moveAccountHowTo: "Per fer la migració, primer has de crear un àlies per aquest compte al compte al qual vols migrar.\nDesprés de crear l'àlies, introdueix el compte al qual vols migrar amb el format següent: @nomusuari@servidor.exemple.com"
startMigration: "Migrar"
migrationConfirm: "Vols migrar aquest compte a {account}? Una vegada comenci la migració no es podrà parar O fer marxa enrere i no podràs tornar a fer servir aquest compte mai més."
@@ -2368,6 +2369,7 @@ _widgets:
chooseList: "Tria una llista"
clicker: "Clicker"
birthdayFollowings: "Usuaris que fan l'aniversari avui"
+ chat: "Xat"
_cw:
hide: "Amagar"
show: "Carregar més"
@@ -2634,6 +2636,7 @@ _deck:
mentions: "Mencions"
direct: "Publicacions directes"
roleTimeline: "Línia de temps dels rols"
+ chat: "Xat"
_dialog:
charactersExceeded: "Has arribat al màxim de caràcters! Actualment és {current} de {max}"
charactersBelow: "Ets per sota del mínim de caràcters! Actualment és {current} de {min}"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index b953a5717e..237603299c 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -2368,6 +2368,7 @@ _widgets:
chooseList: "Liste auswählen"
clicker: "Klickzähler"
birthdayFollowings: "Nutzer, die heute Geburtstag haben"
+ chat: "Chat"
_cw:
hide: "Inhalt verbergen"
show: "Inhalt anzeigen"
@@ -2634,6 +2635,7 @@ _deck:
mentions: "Erwähnungen"
direct: "Direktnachrichten"
roleTimeline: "Rollenchronik"
+ chat: "Chat"
_dialog:
charactersExceeded: "Maximallänge überschritten! Momentan {current} von {max}"
charactersBelow: "Minimallänge unterschritten! Momentan {current} von {min}"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 02cc24a8b3..533682fd4f 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -2368,6 +2368,7 @@ _widgets:
chooseList: "Select a list"
clicker: "Clicker"
birthdayFollowings: "Today's Birthdays"
+ chat: "Chat"
_cw:
hide: "Hide"
show: "Show content"
@@ -2634,6 +2635,7 @@ _deck:
mentions: "Mentions"
direct: "Direct notes"
roleTimeline: "Role Timeline"
+ chat: "Chat"
_dialog:
charactersExceeded: "You've exceeded the maximum character limit! Currently at {current} of {max}."
charactersBelow: "You're below the minimum character limit! Currently at {current} of {min}."
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index f60e9beabe..713478b67e 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -301,6 +301,7 @@ uploadFromUrlMayTakeTime: "Subir el fichero puede tardar un tiempo."
explore: "Explorar"
messageRead: "Ya leído"
noMoreHistory: "El historial se ha acabado"
+startChat: "Nuevo Chat"
nUsersRead: "Leído por {n} personas"
agreeTo: "De acuerdo con {0}"
agree: "De acuerdo."
@@ -694,6 +695,7 @@ userSaysSomethingAbout: "{name} dijo algo sobre {word}"
makeActive: "Activar"
display: "Apariencia"
copy: "Copiar"
+copiedToClipboard: "Texto copiado al portapapeles"
metrics: "Métricas"
overview: "Resumen"
logs: "Registros"
@@ -1293,18 +1295,55 @@ passkeyVerificationFailed: "La verificación de la clave de acceso ha fallado."
passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verificación de la clave de acceso ha sido satisfactoria pero se ha deshabilitado el inicio de sesión sin contraseña."
messageToFollower: "Mensaje a seguidores"
target: "Para"
+prohibitedWordsForNameOfUser: "Palabras prohibidas para nombres de usuario"
+prohibitedWordsForNameOfUserDescription: "Si alguna de las cadenas de esta lista está incluida en el nombre del usuario, el nombre será denegado. Los usuarios con privilegios de moderador no se ven afectados por esta restricción."
+yourNameContainsProhibitedWords: "Tu nombre contiene palabras prohibidas"
+yourNameContainsProhibitedWordsDescription: "Si deseas usar este nombre, por favor contacta con tu administrador/a de tu servidor"
+lockdown: "Bloqueo"
+pleaseSelectAccount: "Seleccione una cuenta, por favor."
+availableRoles: "Roles disponibles "
+acknowledgeNotesAndEnable: "Activar después de comprender las precauciones"
federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador."
federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores"
preferences: "Preferencias"
postForm: "Formulario"
information: "Información"
+right: "Derecha"
+bottom: "Abajo"
+top: "Arriba"
+embed: "Insertar"
+settingsMigrating: "La configuración está siendo migrada, por favor espera un momento... (También puedes migrar manualmente más tarde yendo a Ajustes otros migrar configuración antigua"
+readonly: "Solo Lectura"
_chat:
+ noMessagesYet: "Aún no hay mensajes"
+ newMessage: "Mensajes nuevos"
+ individualChat: "Chat individual"
+ individualChat_description: "Mantén una conversación privada con otra persona."
invitations: "Invitar"
noHistory: "No hay datos en el historial"
members: "Miembros"
home: "Inicio"
send: "Enviar"
+ chatNotAvailableInOtherAccount: "La función de chat está desactivada para el otro usuario."
+ cannotChatWithTheUser: "No se puede iniciar un chat con este usuario"
+ cannotChatWithTheUser_description: "El chat no está disponible o la otra parte no ha habilitado el chat."
+ chatWithThisUser: "Chatear"
+ thisUserAllowsChatOnlyFromFollowers: "Este usuario sólo acepta chats de seguidores."
+ thisUserAllowsChatOnlyFromFollowing: "Este usuario sólo acepta chats de los usuarios a los que sigue."
+ thisUserAllowsChatOnlyFromMutualFollowing: "Este usuario sólo acepta chats de usuarios que son seguidores mutuos."
+ thisUserNotAllowedChatAnyone: "Este usuario no acepta chats de nadie."
+ chatAllowedUsers: "A quién permitir chatear."
+ chatAllowedUsers_note: "Puedes chatear con cualquier persona a la que hayas enviado un mensaje de chat, independientemente de esta configuración."
+ _chatAllowedUsers:
+ everyone: "Todos"
+ followers: "Sólo sus propios seguidores."
+ following: "Solo usuarios que sigues"
+ mutual: "Solo seguidores mutuos"
+ none: "Nadie"
+_emojiPalette:
+ palettes: "Paleta\n"
_settings:
+ api: "API"
webhook: "Webhook"
_accountSettings:
requireSigninToViewContents: "Se requiere iniciar sesión para ver el contenido"
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 44b0858fe8..75691d817f 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -424,6 +424,7 @@ antennaExcludeBots: "Escludere i Bot"
antennaKeywordsDescription: "Sparando con uno spazio indichi la condizione E (and). Separando con un a capo, indichi la condizione O (or)."
notifyAntenna: "Invia notifiche delle nuove note"
withFileAntenna: "Solo note con file in allegato"
+excludeNotesInSensitiveChannel: "Escludere le Note dai canali espliciti"
enableServiceworker: "Abilita ServiceWorker"
antennaUsersDescription: "Elenca un nome utente per riga"
caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole"
@@ -727,7 +728,7 @@ reporterOrigin: "Segnalazione da"
send: "Inviare"
openInNewTab: "Apri in una nuova scheda"
openInSideView: "Apri in vista laterale"
-defaultNavigationBehaviour: "Navigazione preimpostata"
+defaultNavigationBehaviour: "Tipo di navigazione predefinita"
editTheseSettingsMayBreakAccount: "Modificare queste impostazioni può danneggiare il profilo"
instanceTicker: "Informazioni sull'istanza da cui vengono le note"
waitingFor: "Aspettando {x}"
@@ -866,7 +867,7 @@ noBotProtectionWarning: "Non è stata impostata alcuna protezione dai Bot"
configure: "Imposta"
postToGallery: "Pubblicare nella galleria"
postToHashtag: "Pubblica a questo hashtag"
-gallery: "Galleria"
+gallery: "Gallerie"
recentPosts: "Pubblicazioni recenti"
popularPosts: "Le più visualizzate"
shareWithNote: "Condividere in nota"
@@ -978,6 +979,7 @@ document: "Documentazione"
numberOfPageCache: "Numero di pagine cache"
numberOfPageCacheDescription: "Aumenta l'usabilità, ma aumenta anche il carico e l'utilizzo della memoria."
logoutConfirm: "Vuoi davvero uscire da Misskey? "
+logoutWillClearClientData: "All'uscita, la configurazione del client viene rimossa dal browser. Per ripristinarla quando si effettua nuovamente l'accesso, abilitare il backup automatico."
lastActiveDate: "Data dell'ultimo utilizzo"
statusbar: "Barra di stato"
pleaseSelect: "Scegli un'opzione"
@@ -1340,6 +1342,9 @@ right: "Destra"
bottom: "Sotto"
top: "Sopra"
embed: "Incorporare"
+settingsMigrating: "Migrazione delle impostazioni. Attendere prego ... (Puoi anche migrare manualmente in un secondo momento, nel menu: Impostazioni → Altro → Migrazione delle impostazioni)"
+readonly: "Sola lettura"
+goToDeck: "Torna al Deck"
_chat:
noMessagesYet: "Ancora nessun messaggio"
newMessage: "Nuovo messaggio"
@@ -1369,6 +1374,7 @@ _chat:
muteThisRoom: "Silenzia stanza"
deleteRoom: "Elimina stanza"
chatNotAvailableForThisAccountOrServer: "Questo server, o questo profilo ha disabilitato la chat."
+ chatIsReadOnlyForThisAccountOrServer: "Le chat, su questo server o su questo profilo, sono di sola lettura. Impossibile scrivere in chat o creare e partecipare a stanze."
chatNotAvailableInOtherAccount: "La chat non è disponibile nel profilo dell'altra persona."
cannotChatWithTheUser: "Impossibile chattare con questa persona"
cannotChatWithTheUser_description: "La chat potrebbe non essere disponibile, oppure l'altra persona potrebbe non esserlo."
@@ -1929,6 +1935,7 @@ _role:
canImportFollowing: "Può importare Following"
canImportMuting: "Può importare Silenziati"
canImportUserLists: "Può importare liste di Profili"
+ chatAvailability: "Chat consentita"
_condition:
roleAssignedTo: "Assegnato a ruoli manualmente"
isLocal: "Profilo locale"
@@ -2362,6 +2369,7 @@ _widgets:
chooseList: "Seleziona una lista"
clicker: "Cliccheria"
birthdayFollowings: "Compleanni del giorno"
+ chat: "Chat"
_cw:
hide: "Nascondere"
show: "Continua la lettura..."
@@ -2594,8 +2602,8 @@ _notification:
renote: "Rinota"
_deck:
alwaysShowMainColumn: "Mostra sempre la colonna principale"
- columnAlign: "Allineare colonne"
- columnGap: "Margine tra le colonne"
+ columnAlign: "Allineamento delle colonne"
+ columnGap: "Spessore del margine tra colonne"
deckMenuPosition: "Posizione del menu Deck"
navbarPosition: "Posizione barra di navigazione"
addColumn: "Aggiungi colonna"
@@ -2624,10 +2632,11 @@ _deck:
tl: "Timeline"
antenna: "Antenne"
list: "Liste"
- channel: "Canale"
+ channel: "Canali"
mentions: "Menzioni"
direct: "Note Dirette"
roleTimeline: "Timeline Ruolo"
+ chat: "Chat"
_dialog:
charactersExceeded: "Hai superato il limite di {max} caratteri! ({current})"
charactersBelow: "Sei al di sotto del minimo di {min} caratteri! ({current})"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index fc578758a2..fb7db80186 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -489,7 +489,7 @@ next: "다음"
retype: "다시 입력"
noteOf: "{user}의 노트"
quoteAttached: "인용함"
-quoteQuestion: "인용해서 작성하시겠습니까?"
+quoteQuestion: "인용해서 첨부하시겠습니까?"
attachAsFileQuestion: "붙여넣으려는 글이 너무 깁니다. 텍스트 파일로 첨부하시겠습니까?"
onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
signinRequired: "진행하기 전에 로그인을 해 주세요"
@@ -571,8 +571,8 @@ objectStorageSetPublicRead: "업로드할 때 'public-read'를 설정하기"
s3ForcePathStyleDesc: "s3ForcePathStyle을 활성화하면, 버킷 이름을 URL의 호스트명이 아닌 경로의 일부로써 취급합니다. 셀프 호스트 Minio와 같은 서비스를 사용할 경우 활성화해야 할 수 있습니다."
serverLogs: "서버 로그"
deleteAll: "모두 삭제"
-showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
-showFixedPostFormInChannel: "채널 타임라인 상단에 글 작성란을 표시"
+showFixedPostForm: "타임라인 상단에 글 입력란을 표시"
+showFixedPostFormInChannel: "채널 타임라인 상단에 글 입력란을 표시"
withRepliesByDefaultForNewlyFollowed: "팔로우 할 때 기본적으로 답글을 타임라인에 나오게 하기"
newNoteRecived: "새 노트가 있습니다"
sounds: "소리"
@@ -720,7 +720,7 @@ abuseReports: "신고"
reportAbuse: "신고"
reportAbuseRenote: "리노트 신고하기"
reportAbuseOf: "{name} 신고하기"
-fillAbuseReportDescription: "신고하려는 이유를 자세히 알려주세요. 특정 게시물을 신고할 때에는 게시물의 URL도 포함해 주세요."
+fillAbuseReportDescription: "신고 사유를 자세히 기재해 주세요. 대상 노트나 페이지 등이 있는 경우에는 해당 URL도 기재해 주세요."
abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다."
reporter: "신고자"
reporteeOrigin: "피신고자"
@@ -825,7 +825,7 @@ editCode: "코드 수정"
apply: "적용"
receiveAnnouncementFromInstance: "이 서버의 알림을 이메일로 수신할게요"
emailNotification: "메일 알림"
-publish: "게시"
+publish: "공개"
inChannelSearch: "채널에서 검색"
useReactionPickerForContextMenu: "우클릭하여 리액션 선택기 열기"
typingUsers: "{users}님이 입력 중"
@@ -1081,7 +1081,7 @@ sensitiveWords: "민감한 단어"
sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
prohibitedWords: "금지 단어"
-prohibitedWordsDescription: "설정된 단어가 포함되는 노트를 작성하려고 하면, 오류가 발생하도록 합니다. 줄바꿈으로 구분지어 복수 설정할 수 있습니다."
+prohibitedWordsDescription: "설정된 단어가 포함되는 노트를 게시하려고 하면, 오류가 발생하도록 합니다. 줄바꿈으로 구분지어 복수 설정할 수 있습니다."
prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
hiddenTags: "숨긴 해시태그"
hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다."
@@ -1373,7 +1373,7 @@ _chat:
muteThisRoom: "이 룸을 뮤트"
deleteRoom: "룸을 삭제"
chatNotAvailableForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅이 활성화되어 있지 않습니다."
- chatIsReadOnlyForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅은 읽기 전용입니다. 새로 쓰거나 채팅방을 만들거나 참가할 수 없습니다."
+ chatIsReadOnlyForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅은 읽기 전용입니다. 새로 쓰거나 채팅 룸을 만들거나 참가할 수 없습니다."
chatNotAvailableInOtherAccount: "상대방 계정에서 채팅 기능을 사용할 수 없는 상태입니다."
cannotChatWithTheUser: "이 유저와 채팅을 시작할 수 없습니다"
cannotChatWithTheUser_description: "채팅을 사용할 수 없는 상태이거나 상대방이 채팅을 열지 않은 상태입니다."
@@ -1542,7 +1542,7 @@ _initialTutorial:
description3: "이 외에도, '리스트 타임라인'이나 '채널 타임라인' 등이 있습니다. 자세한 사항은 {link}에서 확인하실 수 있습니다."
_postNote:
title: "노트 게시 설정"
- description1: "Misskey에 노트를 쓸 때에는 다양한 옵션을 설정할 수 있습니다. 노트를 작성하는 화면은 이렇게 생겼습니다."
+ description1: "Misskey에 노트를 게시할 때에는 다양한 옵션 설정이 가능합니다. 노트를 게시할 때 쓰이는 '글 입력란'은 이렇게 생겼습니다."
_visibility:
description: "노트를 볼 수 있는 사람을 제한할 수 있습니다."
public: "모든 유저에게 공개합니다."
@@ -1562,7 +1562,7 @@ _initialTutorial:
_howToMakeAttachmentsSensitive:
title: "첨부 파일을 열람주의로 설정하려면?"
description: "서버의 가이드라인에 따라 필요한 이미지, 또는 그대로 노출되기에 부적절한 미디어는 '열람 주의'를 설정해 주세요."
- tryThisFile: "이 작성 창에 첨부된 이미지를 열람 주의로 설정해 보세요!"
+ tryThisFile: "이 입력란에 첨부된 이미지를 열람 주의로 설정해 보세요!"
_exampleNote:
note: "낫또 뚜껑 뜯다가 실수했다…"
method: "첨부 파일을 열람 주의로 설정하려면, 해당 파일을 클릭하여 메뉴를 열고, '열람주의로 설정'을 클릭합니다."
@@ -1816,7 +1816,7 @@ _achievements:
description: "드라이브 폴더에 스스로를 넣게 했다"
_reactWithoutRead:
title: "읽고 답하긴 하시는 건가요?"
- description: "100자가 넘는 노트를 작성한 지 3초 안에 리액션했다"
+ description: "100자가 넘는 노트를 게시한 지 3초 안에 리액션했다"
_clickedClickHere:
title: "여길 눌러보세요"
description: "여기를 눌렀다"
@@ -1845,7 +1845,7 @@ _achievements:
_cookieClicked:
title: "쿠키를 클릭하는 게임"
description: "쿠키를 클릭했다"
- flavor: "소프트웨어 착각하지 않았어?"
+ flavor: "소프트웨어 착각하지 않으셨나요?"
_brainDiver:
title: "Brain Diver"
description: "Brain Diver로의 링크를 첨부했다"
@@ -2368,6 +2368,7 @@ _widgets:
chooseList: "리스트 선택"
clicker: "클리커"
birthdayFollowings: "오늘이 생일인 유저"
+ chat: "채팅"
_cw:
hide: "숨기기"
show: "더 보기"
@@ -2634,6 +2635,7 @@ _deck:
mentions: "받은 멘션"
direct: "다이렉트"
roleTimeline: "역할 타임라인"
+ chat: "채팅"
_dialog:
charactersExceeded: "최대 글자수를 초과하였습니다! 현재 {current} / 최대 {max}"
charactersBelow: "최소 글자수 미만입니다! 현재 {current} / 최소 {min}"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index b313c4ce1f..4b78b0a362 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -979,6 +979,7 @@ document: "文档"
numberOfPageCache: "缓存页数"
numberOfPageCacheDescription: "设置较高的值会更方便用户,但设备的负载和内存使用量会增加。"
logoutConfirm: "是否确认登出?"
+logoutWillClearClientData: "登出时将会从浏览器中删除客户端的设置信息。如果想要在再次登入时恢复设置信息,请在设置里打开自动备份。"
lastActiveDate: "最后活跃时间"
statusbar: "状态栏"
pleaseSelect: "请选择"
@@ -2367,6 +2368,7 @@ _widgets:
chooseList: "选择列表"
clicker: "点击器"
birthdayFollowings: "今天是他们的生日"
+ chat: "聊天"
_cw:
hide: "隐藏"
show: "查看更多"
@@ -2633,6 +2635,7 @@ _deck:
mentions: "提及"
direct: "指定用户"
roleTimeline: "角色时间线"
+ chat: "聊天"
_dialog:
charactersExceeded: "已经超过了最大字符数! 当前字符数 {current} / 限制字符数 {max}"
charactersBelow: "低于最小字符数!当前字符数 {current} / 限制字符数 {min}"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 57b7e84b8a..cfe3b729f0 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -424,6 +424,7 @@ antennaExcludeBots: "排除機器人帳戶"
antennaKeywordsDescription: "空格代表「以及」(AND),換行代表「或者」(OR)"
notifyAntenna: "通知有新貼文"
withFileAntenna: "僅帶有附件的貼文"
+excludeNotesInSensitiveChannel: "排除敏感頻道的貼文"
enableServiceworker: "啟用瀏覽器的推播通知"
antennaUsersDescription: "填寫使用者名稱,以換行分隔"
caseSensitive: "區分大小寫"
@@ -978,6 +979,7 @@ document: "文件"
numberOfPageCache: "快取頁面數"
numberOfPageCacheDescription: "增加數量會提高便利性,但也會增加負荷與記憶體使用量。"
logoutConfirm: "確定要登出嗎?"
+logoutWillClearClientData: "當您登出時,客戶端的設定資訊將從瀏覽器中清除。為了能夠在重新登入時恢復您的設定資訊,請啟用設定內的自動備份選項。"
lastActiveDate: "上次使用日期及時間"
statusbar: "狀態列"
pleaseSelect: "請選擇"
@@ -1307,7 +1309,7 @@ availableRoles: "可用角色"
acknowledgeNotesAndEnable: "了解注意事項後再開啟。"
federationSpecified: "此伺服器以白名單聯邦的方式運作。除了管理員指定的伺服器外,它無法與其他伺服器互動。"
federationDisabled: "此伺服器未開啟站台聯邦。無法與其他伺服器上的使用者互動。"
-confirmOnReact: "反應時確認"
+confirmOnReact: "在做出反應前先確認"
reactAreYouSure: "用「 {emoji} 」反應嗎?"
markAsSensitiveConfirm: "要將這個媒體設定為敏感嗎?"
unmarkAsSensitiveConfirm: "要解除這個媒體的敏感設定嗎?"
@@ -1444,7 +1446,7 @@ _accountSettings:
makeNotesHiddenBefore: "隱藏過去的貼文"
makeNotesHiddenBeforeDescription: "啟用此功能後,超過設定的日期和時間或超過設定時間的貼文將僅對自己顯示(私密化)。 如果您再次停用它,貼文的公開狀態也會恢復原狀。"
mayNotEffectForFederatedNotes: "聯邦發送至遠端伺服器的貼文可能會不受影響。"
- mayNotEffectSomeSituations: "這些限制已經簡化。它們可能不適用於某些情況,例如在遠端伺服器上檢視或管理時。"
+ mayNotEffectSomeSituations: "這些限制僅是簡化版本。在某些情況下,例如在遠端伺服器上瀏覽或進行審核時,可能不會套用這些限制。"
notesHavePassedSpecifiedPeriod: "早於指定時間的貼文"
notesOlderThanSpecifiedDateAndTime: "指定時間和日期之前的貼文"
_abuseUserReport:
@@ -2366,6 +2368,7 @@ _widgets:
chooseList: "選擇清單"
clicker: "點擊器"
birthdayFollowings: "今天生日的使用者"
+ chat: "聊天"
_cw:
hide: "隱藏"
show: "顯示內容"
@@ -2632,6 +2635,7 @@ _deck:
mentions: "提及"
direct: "指定使用者"
roleTimeline: "角色時間軸"
+ chat: "聊天"
_dialog:
charactersExceeded: "您的貼文太長了!現時字數 {current}/限制字數 {max}"
charactersBelow: "您的貼文太短了!現時字數 {current}/限制字數 {min}"
From f5a89c253347eb78977e2d7adb3ba8998f5ebe7c Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Tue, 15 Apr 2025 09:32:12 +0000
Subject: [PATCH 102/190] Bump version to 2025.4.1-alpha.0
---
CHANGELOG.md | 2 +-
package.json | 2 +-
packages/misskey-js/package.json | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 81b8f88536..97c754a86b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-## Unreleased
+## 2025.4.1
### General
- Enhance: チャットの新規メッセージをプッシュ通知するように
diff --git a/package.json b/package.json
index 3e59baf982..88dfe766a9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "misskey",
- "version": "2025.4.0",
+ "version": "2025.4.1-alpha.0",
"codename": "nasubi",
"repository": {
"type": "git",
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 64ac05e9a1..189826177a 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
- "version": "2025.4.0",
+ "version": "2025.4.1-alpha.0",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
From ee29f31324ca3b0513325cbb14b25a59e6485d23 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 15 Apr 2025 20:33:04 +0900
Subject: [PATCH 103/190] fix(frontend): make keep scroll pos of timeline
---
CHANGELOG.md | 1 +
.../src/use/use-scroll-position-keeper.ts | 58 +++++++++++++++++++
2 files changed, 59 insertions(+)
create mode 100644 packages/frontend/src/use/use-scroll-position-keeper.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 97c754a86b..693b31c44a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
- Fix: ログアウトした際に処理が終了しない問題を修正
- Fix: 自動バックアップが設定されている環境でログアウト直前に設定をバックアップするように
- Fix: フォルダを開いた状態でメニューからアップロードしてもルートフォルダにアップロードされる問題を修正 #15836
+- Fix: タイムラインのスクロール位置を記憶するように修正
### Server
- Enhance: フォローしているユーザーならフォロワー限定投稿のノートでもアンテナで検知できるように
diff --git a/packages/frontend/src/use/use-scroll-position-keeper.ts b/packages/frontend/src/use/use-scroll-position-keeper.ts
new file mode 100644
index 0000000000..00cc51a459
--- /dev/null
+++ b/packages/frontend/src/use/use-scroll-position-keeper.ts
@@ -0,0 +1,58 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { throttle } from 'throttle-debounce';
+import { nextTick, onActivated, onUnmounted, watch } from 'vue';
+import type { Ref } from 'vue';
+
+// note render skippingがオンだとズレるため、遷移直前にスクロール範囲に表示されているdata-scroll-anchor要素を特定して、復元時に当該要素までスクロールするようにする
+
+export function useScrollPositionKeeper(scrollContainerRef: Ref): void {
+ let anchorId: string | null = null;
+
+ watch(scrollContainerRef, (el) => {
+ if (!el) return;
+
+ const onScroll = () => {
+ if (!el) return;
+ const scrollContainerRect = el.getBoundingClientRect();
+ const viewPosition = scrollContainerRect.height / 2;
+
+ const anchorEls = el.querySelectorAll('[data-scroll-anchor]');
+ for (let i = anchorEls.length - 1; i > -1; i--) { // 下から見た方が速い
+ const anchorEl = anchorEls[i] as HTMLElement;
+ const anchorRect = anchorEl.getBoundingClientRect();
+ const anchorTop = anchorRect.top;
+ const anchorBottom = anchorRect.bottom;
+ if (anchorTop <= viewPosition && anchorBottom >= viewPosition) {
+ anchorId = anchorEl.getAttribute('data-scroll-anchor');
+ break;
+ }
+ }
+ };
+
+ // ほんとはscrollイベントじゃなくてonBeforeDeactivatedでやりたい
+ // https://github.com/vuejs/vue/issues/9454
+ // https://github.com/vuejs/rfcs/pull/284
+ el.addEventListener('scroll', throttle(1000, onScroll), { passive: true });
+ }, {
+ immediate: true,
+ });
+
+ onActivated(() => {
+ nextTick(() => {
+ if (!anchorId) return;
+ const scrollContainer = scrollContainerRef.value;
+ if (!scrollContainer) return;
+ const scrollAnchorEl = scrollContainer.querySelector(`[data-scroll-anchor="${anchorId}"]`);
+ if (!scrollAnchorEl) return;
+ scrollAnchorEl.scrollIntoView({
+ behavior: 'instant',
+ block: 'center',
+ inline: 'center',
+ });
+ });
+ });
+}
From 165830d6c8331f14da455cedcb4cd695aaa6c9b3 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 15 Apr 2025 20:34:00 +0900
Subject: [PATCH 104/190] =?UTF-8?q?=E3=82=B3=E3=83=9F=E3=83=83=E3=83=88?=
=?UTF-8?q?=E5=BF=98=E3=82=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/frontend/src/components/MkNotes.vue | 4 ++--
packages/frontend/src/pages/timeline.vue | 3 +++
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue
index ad6210816d..214d52ec7f 100644
--- a/packages/frontend/src/components/MkNotes.vue
+++ b/packages/frontend/src/components/MkNotes.vue
@@ -23,13 +23,13 @@ SPDX-License-Identifier: AGPL-3.0-only
tag="div"
>
-
+
-
+
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 644b2d3d13..d2bf162ce5 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -52,12 +52,15 @@ 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';
+import { useScrollPositionKeeper } from '@/use/use-scroll-position-keeper.js';
provide('shouldOmitHeaderTitle', true);
const tlComponent = useTemplateRef('tlComponent');
const rootEl = useTemplateRef('rootEl');
+useScrollPositionKeeper(rootEl);
+
const router = useRouter();
router.useListener('same', () => {
top();
From de19d9a4d429b4ce9d4713e647659fe6460bdd11 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 15 Apr 2025 20:48:25 +0900
Subject: [PATCH 105/190] refactor(frontend): MkHorizontalSwipe -> MkSwiper
---
.../src/components/{MkHorizontalSwipe.vue => MkSwiper.vue} | 0
packages/frontend/src/pages/about.vue | 6 +++---
packages/frontend/src/pages/announcements.vue | 6 +++---
packages/frontend/src/pages/channel.vue | 6 +++---
packages/frontend/src/pages/channels.vue | 6 +++---
packages/frontend/src/pages/chat/home.vue | 6 +++---
packages/frontend/src/pages/drive.file.vue | 6 +++---
packages/frontend/src/pages/explore.vue | 6 +++---
packages/frontend/src/pages/flash/flash-index.vue | 6 +++---
packages/frontend/src/pages/follow-requests.vue | 6 +++---
packages/frontend/src/pages/gallery/index.vue | 6 +++---
packages/frontend/src/pages/instance-info.vue | 6 +++---
packages/frontend/src/pages/my-clips/index.vue | 6 +++---
packages/frontend/src/pages/notifications.vue | 2 +-
packages/frontend/src/pages/pages.vue | 6 +++---
packages/frontend/src/pages/search.vue | 6 +++---
packages/frontend/src/pages/user/index.vue | 6 +++---
packages/frontend/src/utility/touch.ts | 2 +-
18 files changed, 47 insertions(+), 47 deletions(-)
rename packages/frontend/src/components/{MkHorizontalSwipe.vue => MkSwiper.vue} (100%)
diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkSwiper.vue
similarity index 100%
rename from packages/frontend/src/components/MkHorizontalSwipe.vue
rename to packages/frontend/src/components/MkSwiper.vue
diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue
index b4315a5cfa..ec286f109f 100644
--- a/packages/frontend/src/pages/about.vue
+++ b/packages/frontend/src/pages/about.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -28,7 +28,7 @@ import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import { claimAchievement } from '@/utility/achievements.js';
import { definePage } from '@/page.js';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
const XOverview = defineAsyncComponent(() => import('@/pages/about.overview.vue'));
const XEmojis = defineAsyncComponent(() => import('@/pages/about.emojis.vue'));
diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue
index 1902267a6a..7a04914dd7 100644
--- a/packages/frontend/src/pages/announcements.vue
+++ b/packages/frontend/src/pages/announcements.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
{{ i18n.ts.youHaveUnreadAnnouncements }}
@@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -50,7 +50,7 @@ import { ref, computed } from 'vue';
import MkPagination from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index a62e035198..e71d372722 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.notesSearchNotAvailable }}
-
+
@@ -93,7 +93,7 @@ import { prefer } from '@/preferences.js';
import MkNote from '@/components/MkNote.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
import { isSupportShare } from '@/utility/navigator.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { notesSearchAvailable } from '@/utility/check-permissions.js';
diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue
index 76800aaf70..27a6a6168d 100644
--- a/packages/frontend/src/pages/channels.vue
+++ b/packages/frontend/src/pages/channels.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -67,7 +67,7 @@ import MkInput from '@/components/MkInput.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import { useRouter } from '@/router.js';
diff --git a/packages/frontend/src/pages/chat/home.vue b/packages/frontend/src/pages/chat/home.vue
index e29ab28f2d..1edd18ddf0 100644
--- a/packages/frontend/src/pages/chat/home.vue
+++ b/packages/frontend/src/pages/chat/home.vue
@@ -7,12 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
-
+
@@ -25,7 +25,7 @@ import XJoiningRooms from './home.joiningRooms.vue';
import XOwnedRooms from './home.ownedRooms.vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
import MkPolkadots from '@/components/MkPolkadots.vue';
const tab = ref('home');
diff --git a/packages/frontend/src/pages/drive.file.vue b/packages/frontend/src/pages/drive.file.vue
index 3063d5a4d6..170d48064f 100644
--- a/packages/frontend/src/pages/drive.file.vue
+++ b/packages/frontend/src/pages/drive.file.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, ref, defineAsyncComponent } from 'vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
const props = defineProps<{
fileId: string;
diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue
index 85b9fe4932..bcece47e35 100644
--- a/packages/frontend/src/pages/explore.vue
+++ b/packages/frontend/src/pages/explore.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -25,7 +25,7 @@ import XFeatured from './explore.featured.vue';
import XUsers from './explore.users.vue';
import XRoles from './explore.roles.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue
index 98ab587b55..4ef33cbe0f 100644
--- a/packages/frontend/src/pages/flash/flash-index.vue
+++ b/packages/frontend/src/pages/flash/flash-index.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -43,7 +43,7 @@ import { computed, ref } from 'vue';
import MkFlashPreview from '@/components/MkFlashPreview.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { useRouter } from '@/router.js';
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index 36643b1acb..d467d875fd 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -52,7 +52,7 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { infoImageUrl } from '@/instance.js';
import { $i } from '@/i.js';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
const paginationComponent = useTemplateRef('paginationComponent');
diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue
index 4cf3fca83b..c6ce773ab0 100644
--- a/packages/frontend/src/pages/gallery/index.vue
+++ b/packages/frontend/src/pages/gallery/index.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
{{ i18n.ts.recentPosts }}
@@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -50,7 +50,7 @@ import { watch, ref, computed } from 'vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import { useRouter } from '@/router.js';
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index 66ddf627e4..fde462944c 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
![]()
@@ -126,7 +126,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -153,7 +153,7 @@ import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkPagination from '@/components/MkPagination.vue';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
import { dateString } from '@/filters/date.js';
import MkTextarea from '@/components/MkTextarea.vue';
diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue
index 1525bbef9b..5b9b3af90b 100644
--- a/packages/frontend/src/pages/my-clips/index.vue
+++ b/packages/frontend/src/pages/my-clips/index.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
{{ i18n.ts.add }}
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -33,7 +33,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { clipsCache } from '@/cache.js';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
const pagination = {
endpoint: 'clips/list' as const,
diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue
index 0a2bc02de5..61a1b2725c 100644
--- a/packages/frontend/src/pages/notifications.vue
+++ b/packages/frontend/src/pages/notifications.vue
@@ -24,7 +24,7 @@ import { computed, ref } from 'vue';
import { notificationTypes } from '@@/js/const.js';
import XNotifications from '@/components/MkNotifications.vue';
import MkNotes from '@/components/MkNotes.vue';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue
index c99d7f1a0f..d412bad616 100644
--- a/packages/frontend/src/pages/pages.vue
+++ b/packages/frontend/src/pages/pages.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -41,7 +41,7 @@ import { computed, ref } from 'vue';
import MkPagePreview from '@/components/MkPagePreview.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { useRouter } from '@/router.js';
diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue
index e0cb2dcbab..814ddf3cb9 100644
--- a/packages/frontend/src/pages/search.vue
+++ b/packages/frontend/src/pages/search.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -28,7 +28,7 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { notesSearchAvailable } from '@/utility/check-permissions.js';
import MkInfo from '@/components/MkInfo.vue';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
const props = withDefaults(defineProps<{
query?: string,
diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue
index 58f44d7591..83c7bf45bb 100644
--- a/packages/frontend/src/pages/user/index.vue
+++ b/packages/frontend/src/pages/user/index.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
{ tab = 'files'; }"/>
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -36,7 +36,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/i.js';
-import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkSwiper from '@/components/MkSwiper.vue';
import { serverContext, assertServerContext } from '@/server-context.js';
const XHome = defineAsyncComponent(() => import('./home.vue'));
diff --git a/packages/frontend/src/utility/touch.ts b/packages/frontend/src/utility/touch.ts
index adc2e4c093..361246b328 100644
--- a/packages/frontend/src/utility/touch.ts
+++ b/packages/frontend/src/utility/touch.ts
@@ -18,5 +18,5 @@ if (isTouchSupported && !isTouchUsing) {
}, { passive: true });
}
-/** (MkHorizontalSwipe) 横スワイプ中か? */
+/** (MkSwiper) 横スワイプ中か? */
export const isHorizontalSwipeSwiping = ref(false);
From 86774ad75304620b59dc3b5af01d4388d8a43369 Mon Sep 17 00:00:00 2001
From: anatawa12
Date: Wed, 16 Apr 2025 07:51:21 +0900
Subject: [PATCH 106/190] fix: improve flaky federation test (#15845)
---
packages/backend/test-federation/test/user.test.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/backend/test-federation/test/user.test.ts b/packages/backend/test-federation/test/user.test.ts
index 83dcb8df44..ee69e857bc 100644
--- a/packages/backend/test-federation/test/user.test.ts
+++ b/packages/backend/test-federation/test/user.test.ts
@@ -381,7 +381,8 @@ describe('User', () => {
await alice.client.request('i/delete-account', { password: alice.password });
// NOTE: user deletion query is slow
- await sleep(4000);
+ // FIXME: ensure user is removed successfully
+ await sleep(10000);
const following = await bob.client.request('users/following', { userId: bob.id });
strictEqual(following.length, 0); // no following relation
@@ -480,7 +481,8 @@ describe('User', () => {
await aAdmin.client.request('admin/suspend-user', { userId: alice.id });
// NOTE: user deletion query is slow
- await sleep(4000);
+ // FIXME: ensure user is removed successfully
+ await sleep(10000);
const following = await bob.client.request('users/following', { userId: bob.id });
strictEqual(following.length, 0); // no following relation
From c5f1ce60fa4800d51d3f09b911b85ceff0a69e83 Mon Sep 17 00:00:00 2001
From: anatawa12
Date: Wed, 16 Apr 2025 07:51:41 +0900
Subject: [PATCH 107/190] fix(sw): type error in sw (#15844)
---
packages/sw/src/scripts/create-notification.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts
index 77b3f88791..783c78f7dc 100644
--- a/packages/sw/src/scripts/create-notification.ts
+++ b/packages/sw/src/scripts/create-notification.ts
@@ -271,7 +271,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
case 'newChatMessage':
if (data.body.toRoom != null) {
return [`${data.body.toRoom.name}: ${getUserName(data.body.fromUser)}: ${data.body.text}`, {
- icon: data.body.fromUser.avatarUrl,
+ icon: data.body.fromUser.avatarUrl ?? undefined,
badge: iconUrl('messages'),
tag: `chat:room:${data.body.toRoomId}`,
data,
@@ -279,7 +279,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
}];
} else {
return [`${getUserName(data.body.fromUser)}: ${data.body.text}`, {
- icon: data.body.fromUser.avatarUrl,
+ icon: data.body.fromUser.avatarUrl ?? undefined,
badge: iconUrl('messages'),
tag: `chat:user:${data.body.fromUserId}`,
data,
From 75267f87d5248c4fc573e7dabdb9d3fae364a0ca Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Wed, 16 Apr 2025 09:25:13 +0900
Subject: [PATCH 108/190] =?UTF-8?q?refactor(frontend):=20=E3=82=A2?=
=?UTF-8?q?=E3=82=AB=E3=82=A6=E3=83=B3=E3=83=88=E6=83=85=E5=A0=B1=E3=81=AF?=
=?UTF-8?q?store=E7=AE=A1=E7=90=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/frontend/src/accounts.ts | 88 +++++++++++--------
.../frontend/src/components/MkAuthConfirm.vue | 4 +-
.../frontend/src/components/MkPostForm.vue | 2 +-
packages/frontend/src/preferences/def.ts | 7 +-
packages/frontend/src/store.ts | 4 +
5 files changed, 61 insertions(+), 44 deletions(-)
diff --git a/packages/frontend/src/accounts.ts b/packages/frontend/src/accounts.ts
index a25f3c51d1..3693ac3308 100644
--- a/packages/frontend/src/accounts.ts
+++ b/packages/frontend/src/accounts.ts
@@ -21,14 +21,19 @@ type AccountWithToken = Misskey.entities.MeDetailed & { token: string };
export async function getAccounts(): Promise<{
host: string;
- user: Misskey.entities.User;
+ id: Misskey.entities.User['id'];
+ username: Misskey.entities.User['username'];
+ user?: Misskey.entities.User | null;
token: string | null;
}[]> {
const tokens = store.s.accountTokens;
+ const accountInfos = store.s.accountInfos;
const accounts = prefer.s.accounts;
return accounts.map(([host, user]) => ({
host,
- user,
+ id: user.id,
+ username: user.username,
+ user: accountInfos[host + '/' + user.id],
token: tokens[host + '/' + user.id] ?? null,
}));
}
@@ -36,7 +41,8 @@ export async function getAccounts(): Promise<{
async function addAccount(host: string, user: Misskey.entities.User, token: AccountWithToken['token']) {
if (!prefer.s.accounts.some(x => x[0] === host && x[1].id === user.id)) {
store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + user.id]: token });
- prefer.commit('accounts', [...prefer.s.accounts, [host, user]]);
+ store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + user.id]: user });
+ prefer.commit('accounts', [...prefer.s.accounts, [host, { id: user.id, username: user.username }]]);
}
}
@@ -44,6 +50,10 @@ export async function removeAccount(host: string, id: AccountWithToken['id']) {
const tokens = JSON.parse(JSON.stringify(store.s.accountTokens));
delete tokens[host + '/' + id];
store.set('accountTokens', tokens);
+ const accountInfos = JSON.parse(JSON.stringify(store.s.accountInfos));
+ delete accountInfos[host + '/' + id];
+ store.set('accountInfos', accountInfos);
+
prefer.commit('accounts', prefer.s.accounts.filter(x => x[0] !== host || x[1].id !== id));
}
@@ -121,14 +131,7 @@ export function updateCurrentAccount(accountData: Misskey.entities.MeDetailed) {
for (const [key, value] of Object.entries(accountData)) {
$i[key] = value;
}
- prefer.commit('accounts', prefer.s.accounts.map(([host, user]) => {
- // TODO: $iのホストも比較したいけど通常null
- if (user.id === $i.id) {
- return [host, $i];
- } else {
- return [host, user];
- }
- }));
+ store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + $i.id]: $i });
$i.token = token;
miLocalStorage.setItem('account', JSON.stringify($i));
}
@@ -138,17 +141,9 @@ export function updateCurrentAccountPartial(accountData: Partial {
- // TODO: $iのホストも比較したいけど通常null
- if (user.id === $i.id) {
- const newUser = JSON.parse(JSON.stringify($i));
- for (const [key, value] of Object.entries(accountData)) {
- newUser[key] = value;
- }
- return [host, newUser];
- }
- return [host, user];
- }));
+
+ store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + $i.id]: $i });
+
miLocalStorage.setItem('account', JSON.stringify($i));
}
@@ -223,25 +218,42 @@ export async function openAccountMenu(opts: {
}, ev: MouseEvent) {
if (!$i) return;
- function createItem(host: string, account: Misskey.entities.User): MenuItem {
- return {
- type: 'user' as const,
- user: account,
- active: opts.active != null ? opts.active === account.id : false,
- action: async () => {
- if (opts.onChoose) {
- opts.onChoose(account);
- } else {
- switchAccount(host, account.id);
- }
- },
- };
+ function createItem(host: string, id: Misskey.entities.User['id'], username: Misskey.entities.User['username'], account: Misskey.entities.User | null | undefined, token: string): MenuItem {
+ if (account) {
+ return {
+ type: 'user' as const,
+ user: account,
+ active: opts.active != null ? opts.active === id : false,
+ action: async () => {
+ if (opts.onChoose) {
+ opts.onChoose(account);
+ } else {
+ switchAccount(host, id);
+ }
+ },
+ };
+ } else {
+ return {
+ type: 'button' as const,
+ text: username,
+ active: opts.active != null ? opts.active === id : false,
+ action: async () => {
+ if (opts.onChoose) {
+ fetchAccount(token, id).then(account => {
+ opts.onChoose(account);
+ });
+ } else {
+ switchAccount(host, id);
+ }
+ },
+ };
+ }
}
const menuItems: MenuItem[] = [];
// TODO: $iのホストも比較したいけど通常null
- const accountItems = (await getAccounts().then(accounts => accounts.filter(x => x.user.id !== $i.id))).map(a => createItem(a.host, a.user));
+ const accountItems = (await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id))).map(a => createItem(a.host, a.id, a.username, a.user, a.token));
if (opts.withExtraOperation) {
menuItems.push({
@@ -254,7 +266,7 @@ export async function openAccountMenu(opts: {
});
if (opts.includeCurrentAccount) {
- menuItems.push(createItem(host, $i));
+ menuItems.push(createItem(host, $i.id, $i.username, $i, $i.token));
}
menuItems.push(...accountItems);
@@ -290,7 +302,7 @@ export async function openAccountMenu(opts: {
});
} else {
if (opts.includeCurrentAccount) {
- menuItems.push(createItem(host, $i));
+ menuItems.push(createItem(host, $i.id, $i.username, $i, $i.token));
}
menuItems.push(...accountItems);
diff --git a/packages/frontend/src/components/MkAuthConfirm.vue b/packages/frontend/src/components/MkAuthConfirm.vue
index 00bf8e68d9..b3331d742b 100644
--- a/packages/frontend/src/components/MkAuthConfirm.vue
+++ b/packages/frontend/src/components/MkAuthConfirm.vue
@@ -157,7 +157,7 @@ async function init() {
const accounts = await getAccounts();
- const accountIdsToFetch = accounts.map(a => a.user.id).filter(id => !users.value.has(id));
+ const accountIdsToFetch = accounts.map(a => a.id).filter(id => !users.value.has(id));
if (accountIdsToFetch.length > 0) {
const usersRes = await misskeyApi('users/show', {
@@ -169,7 +169,7 @@ async function init() {
users.value.set(user.id, {
...user,
- token: accounts.find(a => a.user.id === user.id)!.token,
+ token: accounts.find(a => a.id === user.id)!.token,
});
}
}
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index e43ff65e1d..c4857b7f65 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -879,7 +879,7 @@ async function post(ev?: MouseEvent) {
if (postAccount.value) {
const storedAccounts = await getAccounts();
- token = storedAccounts.find(x => x.user.id === postAccount.value?.id)?.token;
+ token = storedAccounts.find(x => x.id === postAccount.value?.id)?.token;
}
posting.value = true;
diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts
index bd96431fad..ac8996058f 100644
--- a/packages/frontend/src/preferences/def.ts
+++ b/packages/frontend/src/preferences/def.ts
@@ -32,10 +32,11 @@ export type SoundStore = {
// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
export const PREF_DEF = {
- // TODO: 持つのはホストやユーザーID、ユーザー名など最低限にしといて、その他のプロフィール情報はpreferences外で管理した方が綺麗そう
- // 現状だと、updateCurrentAccount/updateCurrentAccountPartialが呼ばれるたびに「設定」へのcommitが行われて不自然(明らかに設定の更新とは捉えにくい)だし
accounts: {
- default: [] as [host: string, user: Misskey.entities.User][],
+ default: [] as [host: string, user: {
+ id: string;
+ username: string;
+ }][],
},
pinnedUserLists: {
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index fc1d463674..456006e7ff 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -108,6 +108,10 @@ export const store = markRaw(new Pizzax('base', {
where: 'device',
default: {} as Record, // host/userId, token
},
+ accountInfos: {
+ where: 'device',
+ default: {} as Record, // host/userId, user
+ },
enablePreferencesAutoCloudBackup: {
where: 'device',
From 4bd23c4c8c9a250276551738e18918e41f375014 Mon Sep 17 00:00:00 2001
From: anatawa12
Date: Wed, 16 Apr 2025 09:49:27 +0900
Subject: [PATCH 109/190] feat: migrate antenna on account move (#15843)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: migrate antenna on account move
* docs(changelog): アカウントの移行時にアンテナのフィルターのユーザが更新されない問題を修正
* refactor: move to AntennaService
---
CHANGELOG.md | 1 +
.../backend/src/core/AccountMoveService.ts | 3 ++
packages/backend/src/core/AntennaService.ts | 36 +++++++++++++++++++
3 files changed, 40 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 693b31c44a..32d57311a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@
- Fix: 自動バックアップが設定されている環境でログアウト直前に設定をバックアップするように
- Fix: フォルダを開いた状態でメニューからアップロードしてもルートフォルダにアップロードされる問題を修正 #15836
- Fix: タイムラインのスクロール位置を記憶するように修正
+- Fix: アカウントの移行時にアンテナのフィルターのユーザが更新されない問題を修正 #15843
### Server
- Enhance: フォローしているユーザーならフォロワー限定投稿のノートでもアンテナで検知できるように
diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts
index 406563bee8..f8e3eaf01f 100644
--- a/packages/backend/src/core/AccountMoveService.ts
+++ b/packages/backend/src/core/AccountMoveService.ts
@@ -25,6 +25,7 @@ import InstanceChart from '@/core/chart/charts/instance.js';
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
import { SystemAccountService } from '@/core/SystemAccountService.js';
import { RoleService } from '@/core/RoleService.js';
+import { AntennaService } from '@/core/AntennaService.js';
@Injectable()
export class AccountMoveService {
@@ -63,6 +64,7 @@ export class AccountMoveService {
private queueService: QueueService,
private systemAccountService: SystemAccountService,
private roleService: RoleService,
+ private antennaService: AntennaService,
) {
}
@@ -123,6 +125,7 @@ export class AccountMoveService {
this.copyMutings(src, dst),
this.copyRoles(src, dst),
this.updateLists(src, dst),
+ this.antennaService.onMoveAccount(src, dst),
]);
} catch {
/* skip if any error happens */
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 7db3e69155..ec79675b06 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -5,6 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
+import { In } from 'typeorm';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
@@ -219,6 +220,41 @@ export class AntennaService implements OnApplicationShutdown {
return this.antennas;
}
+ @bindThis
+ public async onMoveAccount(src: MiUser, dst: MiUser): Promise {
+ // There is a possibility for users to add the srcUser to their antennas, but it's low, so we don't check it.
+
+ // Get MiAntenna[] from cache and filter to select antennas with the src user is in the users list
+ const srcUserAcct = this.utilityService.getFullApAccount(src.username, src.host).toLowerCase();
+ const antennasToMigrate = (await this.getAntennas()).filter(antenna => {
+ return antenna.users.some(user => {
+ const { username, host } = Acct.parse(user);
+ return this.utilityService.getFullApAccount(username, host).toLowerCase() === srcUserAcct;
+ });
+ });
+
+ if (antennasToMigrate.length === 0) return;
+
+ const antennaIds = antennasToMigrate.map(x => x.id);
+
+ // Update the antennas by appending dst users acct to the users list
+ const dstUserAcct = '@' + Acct.toString({ username: dst.username, host: dst.host });
+
+ await this.antennasRepository.createQueryBuilder('antenna')
+ .update()
+ .set({
+ users: () => 'array_append(antenna.users, :dstUserAcct)',
+ })
+ .where('antenna.id IN (:...antennaIds)', { antennaIds })
+ .setParameters({ dstUserAcct })
+ .execute();
+
+ // announce update to event
+ for (const newAntenna of await this.antennasRepository.findBy({ id: In(antennaIds) })) {
+ this.globalEventService.publishInternalEvent('antennaUpdated', newAntenna);
+ }
+ }
+
@bindThis
public dispose(): void {
this.redisForSub.off('message', this.onRedisMessage);
From ef477ce1b5aca91c7cc0f54f86d644a019a3eb22 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Wed, 16 Apr 2025 10:15:18 +0900
Subject: [PATCH 110/190] =?UTF-8?q?=F0=9F=8E=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../frontend/src/ui/_common_/mobile-footer-menu.vue | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/packages/frontend/src/ui/_common_/mobile-footer-menu.vue b/packages/frontend/src/ui/_common_/mobile-footer-menu.vue
index 37b70847ca..cd8d4bdb1f 100644
--- a/packages/frontend/src/ui/_common_/mobile-footer-menu.vue
+++ b/packages/frontend/src/ui/_common_/mobile-footer-menu.vue
@@ -77,14 +77,17 @@ watch(rootEl, () => {
From 17e7340933887fb4044bdee4798ed87c873f1dad Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Wed, 16 Apr 2025 10:59:05 +0900
Subject: [PATCH 113/190] =?UTF-8?q?enhance(frontend):=20=E3=83=86=E3=83=BC?=
=?UTF-8?q?=E3=83=9E=E3=81=A7=E3=83=9A=E3=83=BC=E3=82=B8=E3=83=98=E3=83=83?=
=?UTF-8?q?=E3=83=80=E3=83=BC=E3=81=AE=E8=89=B2=E3=82=92=E5=A4=89=E6=9B=B4?=
=?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/frontend-shared/themes/_dark.json5 | 2 ++
packages/frontend-shared/themes/_light.json5 | 2 ++
packages/frontend/src/components/MkThemePreview.vue | 5 ++++-
.../frontend/src/components/global/MkPageHeader.vue | 11 +++++++++--
4 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/packages/frontend-shared/themes/_dark.json5 b/packages/frontend-shared/themes/_dark.json5
index 924be27455..8ebaf20b64 100644
--- a/packages/frontend-shared/themes/_dark.json5
+++ b/packages/frontend-shared/themes/_dark.json5
@@ -33,6 +33,8 @@
navFg: '@fg',
navActive: '@accent',
navIndicator: '@indicator',
+ pageHeaderBg: '@bg',
+ pageHeaderFg: '@fg',
link: '#44a4c1',
hashtag: '#ff9156',
mention: '@accent',
diff --git a/packages/frontend-shared/themes/_light.json5 b/packages/frontend-shared/themes/_light.json5
index e3c62ff543..63ad95ff84 100644
--- a/packages/frontend-shared/themes/_light.json5
+++ b/packages/frontend-shared/themes/_light.json5
@@ -33,6 +33,8 @@
navFg: '@fg',
navActive: '@accent',
navIndicator: '@indicator',
+ pageHeaderBg: '@bg',
+ pageHeaderFg: '@fg',
link: '#44a4c1',
hashtag: '#ff9156',
mention: '@accent',
diff --git a/packages/frontend/src/components/MkThemePreview.vue b/packages/frontend/src/components/MkThemePreview.vue
index 013ab9d6a4..cc4254a2f6 100644
--- a/packages/frontend/src/components/MkThemePreview.vue
+++ b/packages/frontend/src/components/MkThemePreview.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -62,6 +62,7 @@ const themeVariables = ref<{
accent: string;
accentedBg: string;
navBg: string;
+ pageHeaderBg: string;
success: string;
warn: string;
error: string;
@@ -76,6 +77,7 @@ const themeVariables = ref<{
accent: 'var(--MI_THEME-accent)',
accentedBg: 'var(--MI_THEME-accentedBg)',
navBg: 'var(--MI_THEME-navBg)',
+ pageHeaderBg: 'var(--MI_THEME-pageHeaderBg)',
success: 'var(--MI_THEME-success)',
warn: 'var(--MI_THEME-warn)',
error: 'var(--MI_THEME-error)',
@@ -104,6 +106,7 @@ watch(() => props.theme, (theme) => {
accent: compiled.accent ?? 'var(--MI_THEME-accent)',
accentedBg: compiled.accentedBg ?? 'var(--MI_THEME-accentedBg)',
navBg: compiled.navBg ?? 'var(--MI_THEME-navBg)',
+ pageHeaderBg: compiled.pageHeaderBg ?? 'var(--MI_THEME-pageHeaderBg)',
success: compiled.success ?? 'var(--MI_THEME-success)',
warn: compiled.warn ?? 'var(--MI_THEME-warn)',
error: compiled.error ?? 'var(--MI_THEME-error)',
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index 93f46a866a..dc0b52b141 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -124,11 +124,18 @@ onUnmounted(() => {
diff --git a/packages/frontend/src/components/MkTl.vue b/packages/frontend/src/components/MkTl.vue
new file mode 100644
index 0000000000..95cc4d2a2a
--- /dev/null
+++ b/packages/frontend/src/components/MkTl.vue
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
{{ item.prevText }}
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/pages/admin/queue.chart.chart.vue b/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue
similarity index 100%
rename from packages/frontend/src/pages/admin/queue.chart.chart.vue
rename to packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue
diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/federation-job-queue.chart.vue
similarity index 97%
rename from packages/frontend/src/pages/admin/queue.chart.vue
rename to packages/frontend/src/pages/admin/federation-job-queue.chart.vue
index 1ba02d6e0e..4b10d682a5 100644
--- a/packages/frontend/src/pages/admin/queue.chart.vue
+++ b/packages/frontend/src/pages/admin/federation-job-queue.chart.vue
@@ -50,8 +50,8 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 8d03838a8f..d2246b7512 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
@@ -138,11 +138,16 @@ const menuDef = computed(() => [{
text: i18n.ts.federation,
to: '/admin/federation',
active: currentPage.value?.route.name === 'federation',
+ }, {
+ icon: 'ti ti-clock-play',
+ text: i18n.ts.federationJobs,
+ to: '/admin/federation-job-queue',
+ active: currentPage.value?.route.name === 'federationJobQueue',
}, {
icon: 'ti ti-clock-play',
text: i18n.ts.jobQueue,
- to: '/admin/queue',
- active: currentPage.value?.route.name === 'queue',
+ to: '/admin/job-queue',
+ active: currentPage.value?.route.name === 'jobQueue',
}, {
icon: 'ti ti-cloud',
text: i18n.ts.files,
@@ -329,6 +334,8 @@ defineExpose({
diff --git a/packages/frontend/src/pages/admin/job-queue.vue b/packages/frontend/src/pages/admin/job-queue.vue
new file mode 100644
index 0000000000..528c473c4f
--- /dev/null
+++ b/packages/frontend/src/pages/admin/job-queue.vue
@@ -0,0 +1,370 @@
+
+
+
+
+
+
+
+
+
{{ q.name }}
+
+
+ Active
+ {{ kmg(q.counts.active, 2) }}
+
+
+ Delayed
+ {{ kmg(q.counts.delayed, 2) }}
+
+
+ Waiting
+ {{ kmg(q.counts.waiting, 2) }}
+
+
+
+
+
+
+
+
+ Overview: {{ tab }}
+
+ #{{ queueInfo.db.processId }}:{{ queueInfo.db.port }} / {{ queueInfo.db.runId }}
+ {{ queueInfo.qualifiedName }}
+
+
+ Promote all jobs
+ Add job
+ Resume queue
+ Pause queue
+ Empty queue
+
+
+
+
+
+
+
+ Active
+ {{ kmg(queueInfo.counts.active, 2) }}
+
+
+ Delayed
+ {{ kmg(queueInfo.counts.delayed, 2) }}
+
+
+ Waiting
+ {{ kmg(queueInfo.counts.waiting, 2) }}
+
+
+
+
+
+ Clients: Connected
+ {{ queueInfo.db.clients.connected }}
+
+
+ Clients: Blocked
+ {{ queueInfo.db.clients.blocked }}
+
+
+ Memory: Peak
+ {{ bytes(queueInfo.db.memory.peak, 1) }}
+
+
+ Memory: Total
+ {{ bytes(queueInfo.db.memory.total, 1) }}
+
+
+ Memory: Used
+ {{ bytes(queueInfo.db.memory.used, 1) }}
+
+
+ Uptime
+ {{ queueInfo.db.uptime }}
+
+
+
+
+
+
+ Jobs: {{ tab }}
+
+ <A:{{ kmg(queueInfo.counts.active, 2) }}> <D:{{ kmg(queueInfo.counts.delayed, 2) }}> <W:{{ kmg(queueInfo.counts.waiting, 2) }}>
+
+
+
+
+
+ Refresh view
+ Remove jobs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/router.definition.ts b/packages/frontend/src/router.definition.ts
index d59b160b8b..a0a22b4338 100644
--- a/packages/frontend/src/router.definition.ts
+++ b/packages/frontend/src/router.definition.ts
@@ -392,9 +392,13 @@ export const ROUTE_DEF = [{
name: 'avatarDecorations',
component: page(() => import('@/pages/avatar-decorations.vue')),
}, {
- path: '/queue',
- name: 'queue',
- component: page(() => import('@/pages/admin/queue.vue')),
+ path: '/federation-job-queue',
+ name: 'federationJobQueue',
+ component: page(() => import('@/pages/admin/federation-job-queue.vue')),
+ }, {
+ path: '/job-queue',
+ name: 'jobQueue',
+ component: page(() => import('@/pages/admin/job-queue.vue')),
}, {
path: '/files',
name: 'files',
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index 5237d5bbf8..b43906109f 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -267,7 +267,22 @@ type AdminQueueDeliverDelayedResponse = operations['admin___queue___deliver-dela
type AdminQueueInboxDelayedResponse = operations['admin___queue___inbox-delayed']['responses']['200']['content']['application/json'];
// @public (undocumented)
-type AdminQueuePromoteRequest = operations['admin___queue___promote']['requestBody']['content']['application/json'];
+type AdminQueueJobsRequest = operations['admin___queue___jobs']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type AdminQueuePromoteJobsRequest = operations['admin___queue___promote-jobs']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type AdminQueueQueueStatsRequest = operations['admin___queue___queue-stats']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type AdminQueueRemoveJobRequest = operations['admin___queue___remove-job']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type AdminQueueRetryJobRequest = operations['admin___queue___retry-job']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type AdminQueueShowJobRequest = operations['admin___queue___show-job']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminQueueStatsResponse = operations['admin___queue___stats']['responses']['200']['content']['application/json'];
@@ -1531,7 +1546,12 @@ declare namespace entities {
AdminQueueClearRequest,
AdminQueueDeliverDelayedResponse,
AdminQueueInboxDelayedResponse,
- AdminQueuePromoteRequest,
+ AdminQueueJobsRequest,
+ AdminQueuePromoteJobsRequest,
+ AdminQueueQueueStatsRequest,
+ AdminQueueRemoveJobRequest,
+ AdminQueueRetryJobRequest,
+ AdminQueueShowJobRequest,
AdminQueueStatsResponse,
AdminRelaysAddRequest,
AdminRelaysAddResponse,
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index ae97084116..b607c93e1e 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -636,12 +636,78 @@ declare module '../api.js' {
credential?: string | null,
): Promise>;
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:admin:queue*
*/
- request(
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:queue*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:queue*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
+ */
+ request(
endpoint: E,
params: P,
credential?: string | null,
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index d22395e866..6390314429 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -78,7 +78,12 @@ import type {
AdminQueueClearRequest,
AdminQueueDeliverDelayedResponse,
AdminQueueInboxDelayedResponse,
- AdminQueuePromoteRequest,
+ AdminQueueJobsRequest,
+ AdminQueuePromoteJobsRequest,
+ AdminQueueQueueStatsRequest,
+ AdminQueueRemoveJobRequest,
+ AdminQueueRetryJobRequest,
+ AdminQueueShowJobRequest,
AdminQueueStatsResponse,
AdminRelaysAddRequest,
AdminRelaysAddResponse,
@@ -694,7 +699,13 @@ export type Endpoints = {
'admin/queue/clear': { req: AdminQueueClearRequest; res: EmptyResponse };
'admin/queue/deliver-delayed': { req: EmptyRequest; res: AdminQueueDeliverDelayedResponse };
'admin/queue/inbox-delayed': { req: EmptyRequest; res: AdminQueueInboxDelayedResponse };
- 'admin/queue/promote': { req: AdminQueuePromoteRequest; res: EmptyResponse };
+ 'admin/queue/jobs': { req: AdminQueueJobsRequest; res: EmptyResponse };
+ 'admin/queue/promote-jobs': { req: AdminQueuePromoteJobsRequest; res: EmptyResponse };
+ 'admin/queue/queue-stats': { req: AdminQueueQueueStatsRequest; res: EmptyResponse };
+ 'admin/queue/queues': { req: EmptyRequest; res: EmptyResponse };
+ 'admin/queue/remove-job': { req: AdminQueueRemoveJobRequest; res: EmptyResponse };
+ 'admin/queue/retry-job': { req: AdminQueueRetryJobRequest; res: EmptyResponse };
+ 'admin/queue/show-job': { req: AdminQueueShowJobRequest; res: EmptyResponse };
'admin/queue/stats': { req: EmptyRequest; res: AdminQueueStatsResponse };
'admin/relays/add': { req: AdminRelaysAddRequest; res: AdminRelaysAddResponse };
'admin/relays/list': { req: EmptyRequest; res: AdminRelaysListResponse };
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index a772c1559d..f814d7b3da 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -81,7 +81,12 @@ export type AdminPromoCreateRequest = operations['admin___promo___create']['requ
export type AdminQueueClearRequest = operations['admin___queue___clear']['requestBody']['content']['application/json'];
export type AdminQueueDeliverDelayedResponse = operations['admin___queue___deliver-delayed']['responses']['200']['content']['application/json'];
export type AdminQueueInboxDelayedResponse = operations['admin___queue___inbox-delayed']['responses']['200']['content']['application/json'];
-export type AdminQueuePromoteRequest = operations['admin___queue___promote']['requestBody']['content']['application/json'];
+export type AdminQueueJobsRequest = operations['admin___queue___jobs']['requestBody']['content']['application/json'];
+export type AdminQueuePromoteJobsRequest = operations['admin___queue___promote-jobs']['requestBody']['content']['application/json'];
+export type AdminQueueQueueStatsRequest = operations['admin___queue___queue-stats']['requestBody']['content']['application/json'];
+export type AdminQueueRemoveJobRequest = operations['admin___queue___remove-job']['requestBody']['content']['application/json'];
+export type AdminQueueRetryJobRequest = operations['admin___queue___retry-job']['requestBody']['content']['application/json'];
+export type AdminQueueShowJobRequest = operations['admin___queue___show-job']['requestBody']['content']['application/json'];
export type AdminQueueStatsResponse = operations['admin___queue___stats']['responses']['200']['content']['application/json'];
export type AdminRelaysAddRequest = operations['admin___relays___add']['requestBody']['content']['application/json'];
export type AdminRelaysAddResponse = operations['admin___relays___add']['responses']['200']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 3c51bedb5c..bb7ba30f1b 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -531,14 +531,68 @@ export type paths = {
*/
post: operations['admin___queue___inbox-delayed'];
};
- '/admin/queue/promote': {
+ '/admin/queue/jobs': {
/**
- * admin/queue/promote
+ * admin/queue/jobs
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
+ */
+ post: operations['admin___queue___jobs'];
+ };
+ '/admin/queue/promote-jobs': {
+ /**
+ * admin/queue/promote-jobs
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:admin:queue*
*/
- post: operations['admin___queue___promote'];
+ post: operations['admin___queue___promote-jobs'];
+ };
+ '/admin/queue/queue-stats': {
+ /**
+ * admin/queue/queue-stats
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
+ */
+ post: operations['admin___queue___queue-stats'];
+ };
+ '/admin/queue/queues': {
+ /**
+ * admin/queue/queues
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
+ */
+ post: operations['admin___queue___queues'];
+ };
+ '/admin/queue/remove-job': {
+ /**
+ * admin/queue/remove-job
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:queue*
+ */
+ post: operations['admin___queue___remove-job'];
+ };
+ '/admin/queue/retry-job': {
+ /**
+ * admin/queue/retry-job
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:queue*
+ */
+ post: operations['admin___queue___retry-job'];
+ };
+ '/admin/queue/show-job': {
+ /**
+ * admin/queue/show-job
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
+ */
+ post: operations['admin___queue___show-job'];
};
'/admin/queue/stats': {
/**
@@ -8809,9 +8863,9 @@ export type operations = {
content: {
'application/json': {
/** @enum {string} */
- type: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
/** @enum {string} */
- state: '*' | 'wait' | 'delayed';
+ state: '*' | 'completed' | 'wait' | 'active' | 'paused' | 'prioritized' | 'delayed' | 'failed';
};
};
};
@@ -8945,17 +8999,326 @@ export type operations = {
};
};
/**
- * admin/queue/promote
+ * admin/queue/jobs
* @description No description provided.
*
- * **Credential required**: *Yes* / **Permission**: *write:admin:queue*
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
*/
- admin___queue___promote: {
+ admin___queue___jobs: {
requestBody: {
content: {
'application/json': {
/** @enum {string} */
- type: 'deliver' | 'inbox';
+ queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ state: ('active' | 'wait' | 'delayed' | 'completed' | 'failed')[];
+ search?: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
+ };
+ /** @description Client error */
+ 400: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Authentication error */
+ 401: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Forbidden error */
+ 403: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description I'm Ai */
+ 418: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Internal server error */
+ 500: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ };
+ };
+ /**
+ * admin/queue/promote-jobs
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:queue*
+ */
+ 'admin___queue___promote-jobs': {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** @enum {string} */
+ queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ };
+ };
+ };
+ responses: {
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
+ };
+ /** @description Client error */
+ 400: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Authentication error */
+ 401: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Forbidden error */
+ 403: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description I'm Ai */
+ 418: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Internal server error */
+ 500: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ };
+ };
+ /**
+ * admin/queue/queue-stats
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
+ */
+ 'admin___queue___queue-stats': {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** @enum {string} */
+ queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ };
+ };
+ };
+ responses: {
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
+ };
+ /** @description Client error */
+ 400: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Authentication error */
+ 401: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Forbidden error */
+ 403: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description I'm Ai */
+ 418: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Internal server error */
+ 500: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ };
+ };
+ /**
+ * admin/queue/queues
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
+ */
+ admin___queue___queues: {
+ responses: {
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
+ };
+ /** @description Client error */
+ 400: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Authentication error */
+ 401: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Forbidden error */
+ 403: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description I'm Ai */
+ 418: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Internal server error */
+ 500: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ };
+ };
+ /**
+ * admin/queue/remove-job
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:queue*
+ */
+ 'admin___queue___remove-job': {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** @enum {string} */
+ queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ jobId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
+ };
+ /** @description Client error */
+ 400: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Authentication error */
+ 401: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Forbidden error */
+ 403: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description I'm Ai */
+ 418: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Internal server error */
+ 500: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ };
+ };
+ /**
+ * admin/queue/retry-job
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:queue*
+ */
+ 'admin___queue___retry-job': {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** @enum {string} */
+ queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ jobId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
+ };
+ /** @description Client error */
+ 400: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Authentication error */
+ 401: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Forbidden error */
+ 403: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description I'm Ai */
+ 418: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Internal server error */
+ 500: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ };
+ };
+ /**
+ * admin/queue/show-job
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:queue*
+ */
+ 'admin___queue___show-job': {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** @enum {string} */
+ queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ jobId: string;
};
};
};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d142cde7b7..858060b731 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -170,6 +170,9 @@ importers:
'@twemoji/parser':
specifier: 15.1.1
version: 15.1.1
+ '@types/redis-info':
+ specifier: 3.0.3
+ version: 3.0.3
accepts:
specifier: 1.3.8
version: 1.3.8
@@ -368,6 +371,9 @@ importers:
re2:
specifier: 1.21.4
version: 1.21.4(patch_hash=018babd22b7ce951bcd10d6246f1e541a7ac7ba212f7fa8985e774ece67d08e1)
+ redis-info:
+ specifier: 3.1.0
+ version: 3.1.0
redis-lock:
specifier: 0.1.4
version: 0.1.4
@@ -4462,6 +4468,9 @@ packages:
'@types/readdir-glob@1.1.1':
resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==}
+ '@types/redis-info@3.0.3':
+ resolution: {integrity: sha512-VIkNy6JbYI/RLdbPHdm9JQvv6RVld2uE2/6Hdid38Qdq+zvDli2FTpImI8pC5zwp8xS8qVqfzlfyAub8xZEd5g==}
+
'@types/rename@1.0.7':
resolution: {integrity: sha512-E9qapfghUGfBMi3jNhsmCKPIp3f2zvNKpaX1BDGLGJNjzpgsZ/RTx7NaNksFjGoJ+r9NvWF1NSM5vVecnNjVmw==}
@@ -9429,6 +9438,9 @@ packages:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'}
+ redis-info@3.1.0:
+ resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==}
+
redis-lock@0.1.4:
resolution: {integrity: sha512-7/+zu86XVQfJVx1nHTzux5reglDiyUCDwmW7TSlvVezfhH2YLc/Rc8NE0ejQG+8/0lwKzm29/u/4+ogKeLosiA==}
engines: {node: '>=0.6'}
@@ -14937,6 +14949,8 @@ snapshots:
dependencies:
'@types/node': 22.14.0
+ '@types/redis-info@3.0.3': {}
+
'@types/rename@1.0.7': {}
'@types/resolve@1.20.3': {}
@@ -21162,6 +21176,10 @@ snapshots:
redis-errors@1.2.0: {}
+ redis-info@3.1.0:
+ dependencies:
+ lodash: 4.17.21
+
redis-lock@0.1.4: {}
redis-parser@3.0.0:
From a0b9f456e7ac698d187e5d078933b7d3ad855296 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Sat, 19 Apr 2025 05:10:26 +0000
Subject: [PATCH 126/190] Bump version to 2025.4.1-alpha.2
---
package.json | 2 +-
packages/misskey-js/package.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index a30367e3e6..175ee9d2d8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "misskey",
- "version": "2025.4.1-alpha.1",
+ "version": "2025.4.1-alpha.2",
"codename": "nasubi",
"repository": {
"type": "git",
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 93d6d18aff..230ead4efe 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
- "version": "2025.4.1-alpha.1",
+ "version": "2025.4.1-alpha.2",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
From ff85eff2096b5b680125264c1f158251c63df0b1 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Sat, 19 Apr 2025 14:32:19 +0900
Subject: [PATCH 127/190] =?UTF-8?q?=F0=9F=8E=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/pages/admin/modlog.ModLog.vue | 48 ++++++++++++--
packages/frontend/src/pages/admin/modlog.vue | 62 ++++++++++++++-----
2 files changed, 87 insertions(+), 23 deletions(-)
diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue
index 7ab9417267..40c7b0b1b4 100644
--- a/packages/frontend/src/pages/admin/modlog.ModLog.vue
+++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue
@@ -84,7 +84,48 @@ SPDX-License-Identifier: AGPL-3.0-only
: @{{ log.info.room.name }}
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -199,11 +240,6 @@ const props = defineProps<{
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
index ee87fae606..a569ab7c33 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
@@ -4,11 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
-
-
-
-
-
+
@@ -41,14 +37,13 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
+
+
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue
similarity index 83%
rename from packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue
rename to packages/frontend/src/pages/admin/custom-emojis-manager.register.vue
index eff7efd0fa..566acdea31 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue
@@ -4,69 +4,71 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
-
-
-
- {{ i18n.ts._customEmojisManager._local._register.uploadSettingTitle }}
- {{ i18n.ts._customEmojisManager._local._register.uploadSettingDescription }}
+
+
+
+
+ {{ i18n.ts._customEmojisManager._local._register.uploadSettingTitle }}
+ {{ i18n.ts._customEmojisManager._local._register.uploadSettingDescription }}
-
-
- {{ i18n.ts.uploadFolder }}
-
-
+
+
+ {{ i18n.ts.uploadFolder }}
+
+
-
- {{ i18n.ts._customEmojisManager._local._register.directoryToCategoryLabel }}
- {{ i18n.ts._customEmojisManager._local._register.directoryToCategoryCaption }}
-
+
+ {{ i18n.ts._customEmojisManager._local._register.directoryToCategoryLabel }}
+ {{ i18n.ts._customEmojisManager._local._register.directoryToCategoryCaption }}
+
+
+
+
+
+
+ {{ i18n.ts._customEmojisManager._gridCommon.registrationLogs }}
+
+ {{ i18n.ts._customEmojisManager._gridCommon.registrationLogsCaption }}
+
+
+
+
+
+
+ {{ i18n.ts._customEmojisManager._local._register.emojiInputAreaCaption }}
+
+
-
-
-
- {{ i18n.ts._customEmojisManager._gridCommon.registrationLogs }}
-
- {{ i18n.ts._customEmojisManager._gridCommon.registrationLogsCaption }}
-
-
-
-
-
-
- {{ i18n.ts._customEmojisManager._local._register.emojiInputAreaCaption }}
+
+
-
-
-
-
+
+
+ {{ i18n.ts.registration }}
+
+
+ {{ i18n.ts.clear }}
+
+
-
-
-
- {{ i18n.ts.registration }}
-
-
- {{ i18n.ts.clear }}
-
-
-
+
@@ -52,7 +61,7 @@ defineExpose({
}
-.body {
+.body, .swiper {
min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px)));
}
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index d2bf162ce5..af28d3decd 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -4,36 +4,32 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
-
-
-
-
-
- {{ i18n.ts._timelineDescription[src] }}
-
-
-
-
-
-
-
+
+
+
+ {{ i18n.ts._timelineDescription[src] }}
+
+
+
+
+
+
-
-
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index 34cf598b84..ec6ea7c569 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -22,7 +22,6 @@ import MkLoading from './global/MkLoading.vue';
import MkError from './global/MkError.vue';
import MkAd from './global/MkAd.vue';
import MkPageHeader from './global/MkPageHeader.vue';
-import MkSpacer from './global/MkSpacer.vue';
import MkStickyContainer from './global/MkStickyContainer.vue';
import MkLazy from './global/MkLazy.vue';
import PageWithHeader from './global/PageWithHeader.vue';
@@ -60,7 +59,6 @@ export const components = {
MkError: MkError,
MkAd: MkAd,
MkPageHeader: MkPageHeader,
- MkSpacer: MkSpacer,
MkStickyContainer: MkStickyContainer,
MkLazy: MkLazy,
PageWithHeader: PageWithHeader,
@@ -92,7 +90,6 @@ declare module '@vue/runtime-core' {
MkError: typeof MkError;
MkAd: typeof MkAd;
MkPageHeader: typeof MkPageHeader;
- MkSpacer: typeof MkSpacer;
MkStickyContainer: typeof MkStickyContainer;
MkLazy: typeof MkLazy;
PageWithHeader: typeof PageWithHeader;
diff --git a/packages/frontend/src/di.ts b/packages/frontend/src/di.ts
index 58a2cce207..e2590da60b 100644
--- a/packages/frontend/src/di.ts
+++ b/packages/frontend/src/di.ts
@@ -17,5 +17,4 @@ export const DI = {
mfmEmojiReactCallback: Symbol() as InjectionKey<(emoji: string) => void>,
inModal: Symbol() as InjectionKey
,
inAppSearchMarkerId: Symbol() as InjectionKey[>,
- forceSpacerMin: Symbol() as InjectionKey,
};
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index 481088fc30..7605ceba30 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
]
-
+
@@ -127,7 +127,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._aboutMisskey.morePatrons }}
-
+
diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue
index b4ccd55452..0edf2db1eb 100644
--- a/packages/frontend/src/pages/about.vue
+++ b/packages/frontend/src/pages/about.vue
@@ -5,18 +5,18 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
diff --git a/packages/frontend/src/pages/achievements.vue b/packages/frontend/src/pages/achievements.vue
index 423e709da4..1560403b70 100644
--- a/packages/frontend/src/pages/achievements.vue
+++ b/packages/frontend/src/pages/achievements.vue
@@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
-
+
diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue
index 1e3e106842..8495642a8c 100644
--- a/packages/frontend/src/pages/admin-file.vue
+++ b/packages/frontend/src/pages/admin-file.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue
index 9d92ccda60..15cd219834 100644
--- a/packages/frontend/src/pages/admin-user.vue
+++ b/packages/frontend/src/pages/admin-user.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -206,7 +206,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
index 10925fa4ab..e81de8c9e7 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ mode === 'create' ? i18n.ts._abuseReport._notificationRecipient.createRecipient : i18n.ts._abuseReport._notificationRecipient.modifyRecipient }}
-
+
{{ i18n.ts.title }}
@@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.enable }}
-
+
diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue
index ea7f0cc73d..b2d7b4889a 100644
--- a/packages/frontend/src/pages/admin/announcements.vue
+++ b/packages/frontend/src/pages/admin/announcements.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
{{ i18n.ts._announcement.shouldNotBeUsedToPresentPermanentInfo }}
{{ i18n.ts._announcement.tooManyActiveAnnouncementDescription }}
@@ -79,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index 2674879f90..19258216f6 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -89,12 +89,12 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
-
+
{{ i18n.ts.save }}
-
+
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.logs.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.logs.vue
index 4b145db0ed..c544561b13 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.logs.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.logs.vue
@@ -14,9 +14,9 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._customEmojisManager._gridCommon.registrationLogs }}
-
+
-
+
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.search.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.search.vue
index ae43507d66..9938d5cc4a 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.search.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.search.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.search }}
-
+
{{ i18n.ts.search }}
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue
index 566acdea31..e8e944df32 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
-
+
@@ -68,7 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
From 7041a3de2a4880b4aa8fbab84de5e1a63b86eaca Mon Sep 17 00:00:00 2001
From: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
Date: Mon, 28 Apr 2025 12:57:47 +0900
Subject: [PATCH 176/190] chore: fix Chromatic CI diff strategy (#15902)
---
.github/workflows/storybook.yml | 18 +++++-------------
1 file changed, 5 insertions(+), 13 deletions(-)
diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml
index 037e6dd7f1..57d1518d9d 100644
--- a/.github/workflows/storybook.yml
+++ b/.github/workflows/storybook.yml
@@ -35,10 +35,7 @@ jobs:
ref: "refs/pull/${{ github.event.number }}/merge"
- name: Checkout actual HEAD
if: github.event_name == 'pull_request_target'
- id: rev
- run: |
- echo "base=$(git rev-list --parents -n1 HEAD | cut -d" " -f2)" >> $GITHUB_OUTPUT
- git checkout $(git rev-list --parents -n1 HEAD | cut -d" " -f3)
+ run: git checkout "$(git rev-list --parents -n1 HEAD | cut -d" " -f3)"
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js 20.x
@@ -81,21 +78,16 @@ jobs:
if: github.event_name == 'pull_request_target'
id: chromatic_pull_request
run: |
- DIFF="${{ steps.rev.outputs.base }} HEAD"
- if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then
- DIFF="HEAD"
- fi
- CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r $(echo "$DIFF") | xargs))"
+ CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r origin/${GITHUB_BASE_REF}...origin/${GITHUB_HEAD_REF} | xargs))"
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
fi
- BRANCH="${{ github.event.pull_request.head.user.login }}:$HEAD_REF"
- if [ "$BRANCH" = "misskey-dev:$HEAD_REF" ]; then
- BRANCH="$HEAD_REF"
+ BRANCH="${{ github.event.pull_request.head.user.login }}:$GITHUB_HEAD_REF"
+ if [ "$BRANCH" = "misskey-dev:$GITHUB_HEAD_REF" ]; then
+ BRANCH="$GITHUB_HEAD_REF"
fi
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name "$BRANCH" $(echo "$CHROMATIC_PARAMETER")
env:
- HEAD_REF: ${{ github.event.pull_request.head.ref }}
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Notify that Chromatic detects changes
uses: actions/github-script@v7.0.1
From b09bf25e14494030c375e6c1cbc2463ab5f8cbae Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 28 Apr 2025 13:03:36 +0900
Subject: [PATCH 177/190] chore(deps): update actions/setup-node action to
v4.4.0 (#15901)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
.github/workflows/api-misskey-js.yml | 2 +-
.github/workflows/changelog-check.yml | 2 +-
.github/workflows/check-misskey-js-autogen.yml | 2 +-
.github/workflows/get-api-diff.yml | 2 +-
.github/workflows/lint.yml | 6 +++---
.github/workflows/locale.yml | 2 +-
.github/workflows/on-release-created.yml | 2 +-
.github/workflows/storybook.yml | 2 +-
.github/workflows/test-backend.yml | 4 ++--
.github/workflows/test-federation.yml | 2 +-
.github/workflows/test-frontend.yml | 4 ++--
.github/workflows/test-misskey-js.yml | 2 +-
.github/workflows/test-production.yml | 2 +-
.github/workflows/validate-api-json.yml | 2 +-
14 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml
index 7d085821b7..6117e69c03 100644
--- a/.github/workflows/api-misskey-js.yml
+++ b/.github/workflows/api-misskey-js.yml
@@ -22,7 +22,7 @@ jobs:
uses: pnpm/action-setup@v4.1.0
- name: Setup Node.js
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml
index 2e94f433b7..5ca27749bb 100644
--- a/.github/workflows/changelog-check.yml
+++ b/.github/workflows/changelog-check.yml
@@ -14,7 +14,7 @@ jobs:
- name: Checkout head
uses: actions/checkout@v4.2.2
- name: Setup Node.js
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version-file: '.node-version'
diff --git a/.github/workflows/check-misskey-js-autogen.yml b/.github/workflows/check-misskey-js-autogen.yml
index 090dc70bd5..22d500c306 100644
--- a/.github/workflows/check-misskey-js-autogen.yml
+++ b/.github/workflows/check-misskey-js-autogen.yml
@@ -29,7 +29,7 @@ jobs:
- name: setup node
id: setup-node
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version-file: '.node-version'
cache: pnpm
diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml
index c5a4f77336..2de73aff09 100644
--- a/.github/workflows/get-api-diff.yml
+++ b/.github/workflows/get-api-diff.yml
@@ -33,7 +33,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 4c8b97e785..f27cce5b97 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -38,7 +38,7 @@ jobs:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- - uses: actions/setup-node@v4.3.0
+ - uses: actions/setup-node@v4.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
@@ -69,7 +69,7 @@ jobs:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- - uses: actions/setup-node@v4.3.0
+ - uses: actions/setup-node@v4.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
@@ -99,7 +99,7 @@ jobs:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- - uses: actions/setup-node@v4.3.0
+ - uses: actions/setup-node@v4.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
diff --git a/.github/workflows/locale.yml b/.github/workflows/locale.yml
index cee4c27ceb..68e45fdf61 100644
--- a/.github/workflows/locale.yml
+++ b/.github/workflows/locale.yml
@@ -20,7 +20,7 @@ jobs:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- - uses: actions/setup-node@v4.3.0
+ - uses: actions/setup-node@v4.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml
index 9d15e0fcf1..13390f3aae 100644
--- a/.github/workflows/on-release-created.yml
+++ b/.github/workflows/on-release-created.yml
@@ -26,7 +26,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml
index 57d1518d9d..ba745b6a2a 100644
--- a/.github/workflows/storybook.yml
+++ b/.github/workflows/storybook.yml
@@ -39,7 +39,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js 20.x
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml
index 9c54b3665b..ba4eb27a58 100644
--- a/.github/workflows/test-backend.yml
+++ b/.github/workflows/test-backend.yml
@@ -62,7 +62,7 @@ jobs:
fi
done
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
@@ -109,7 +109,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
diff --git a/.github/workflows/test-federation.yml b/.github/workflows/test-federation.yml
index dc29de4d4f..fe30deb835 100644
--- a/.github/workflows/test-federation.yml
+++ b/.github/workflows/test-federation.yml
@@ -44,7 +44,7 @@ jobs:
fi
done
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml
index bec5169ed9..25d263f102 100644
--- a/.github/workflows/test-frontend.yml
+++ b/.github/workflows/test-frontend.yml
@@ -38,7 +38,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
@@ -93,7 +93,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml
index 2d1bd20183..5b3aed9712 100644
--- a/.github/workflows/test-misskey-js.yml
+++ b/.github/workflows/test-misskey-js.yml
@@ -33,7 +33,7 @@ jobs:
uses: pnpm/action-setup@v4.1.0
- name: Setup Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml
index b77550a01f..70685e908e 100644
--- a/.github/workflows/test-production.yml
+++ b/.github/workflows/test-production.yml
@@ -26,7 +26,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml
index 4023815cb1..77feb2b373 100644
--- a/.github/workflows/validate-api-json.yml
+++ b/.github/workflows/validate-api-json.yml
@@ -27,7 +27,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4.3.0
+ uses: actions/setup-node@v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
From 01230f6990f8a71246491a7189c51cb67b852c8e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Mon, 28 Apr 2025 04:08:47 +0000
Subject: [PATCH 178/190] Bump version to 2025.4.1-beta.7
---
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 84b445a334..2cbb3b41a0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "misskey",
- "version": "2025.4.1-beta.6",
+ "version": "2025.4.1-beta.7",
"codename": "nasubi",
"repository": {
"type": "git",
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index bc72e74550..16a7170069 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
- "version": "2025.4.1-beta.6",
+ "version": "2025.4.1-beta.7",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
From c3b9a5346fff59817b4bc165ae13bbb9715795fb Mon Sep 17 00:00:00 2001
From: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
Date: Mon, 28 Apr 2025 18:31:13 +0900
Subject: [PATCH 179/190] chore(ci): change the Test step to terminate with
error in the federation test (#15903)
* chore(ci): change the Test step to terminate with error in the federation test
* chore(ci): always stop servers in the federation test
---
.github/workflows/test-federation.yml | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/test-federation.yml b/.github/workflows/test-federation.yml
index fe30deb835..c739688dc9 100644
--- a/.github/workflows/test-federation.yml
+++ b/.github/workflows/test-federation.yml
@@ -71,18 +71,16 @@ jobs:
docker compose logs | tail -n 300
exit 1
- name: Test
- id: test
- continue-on-error: true
run: |
cd packages/backend/test-federation
docker compose run --no-deps tester
- name: Log
- if: ${{ steps.test.outcome == 'failure' }}
+ if: always()
run: |
cd packages/backend/test-federation
docker compose logs
- exit 1
- name: Stop servers
+ if: always()
run: |
cd packages/backend/test-federation
docker compose down
From aaa31c9d64498b9627c0ae3d0623f90bb9e589a5 Mon Sep 17 00:00:00 2001
From: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
Date: Mon, 28 Apr 2025 18:58:08 +0900
Subject: [PATCH 180/190] fix(backend): correct response schema of chat
endpoints (#15904)
---
.../api/endpoints/chat/messages/delete.ts | 3 -
.../api/endpoints/chat/messages/react.ts | 3 -
.../api/endpoints/chat/messages/unreact.ts | 3 -
.../server/api/endpoints/chat/rooms/delete.ts | 3 -
.../chat/rooms/invitations/ignore.ts | 3 -
.../server/api/endpoints/chat/rooms/join.ts | 3 -
.../server/api/endpoints/chat/rooms/leave.ts | 3 -
.../server/api/endpoints/chat/rooms/mute.ts | 3 -
packages/misskey-js/etc/misskey-js.api.md | 32 ----------
packages/misskey-js/src/autogen/endpoint.ts | 24 +++----
packages/misskey-js/src/autogen/entities.ts | 8 ---
packages/misskey-js/src/autogen/types.ts | 64 +++++++------------
12 files changed, 32 insertions(+), 120 deletions(-)
diff --git a/packages/backend/src/server/api/endpoints/chat/messages/delete.ts b/packages/backend/src/server/api/endpoints/chat/messages/delete.ts
index 63b75fb6a7..52a054303b 100644
--- a/packages/backend/src/server/api/endpoints/chat/messages/delete.ts
+++ b/packages/backend/src/server/api/endpoints/chat/messages/delete.ts
@@ -16,9 +16,6 @@ export const meta = {
kind: 'write:chat',
- res: {
- },
-
errors: {
noSuchMessage: {
message: 'No such message.',
diff --git a/packages/backend/src/server/api/endpoints/chat/messages/react.ts b/packages/backend/src/server/api/endpoints/chat/messages/react.ts
index 5f61e7e992..2197e7bf80 100644
--- a/packages/backend/src/server/api/endpoints/chat/messages/react.ts
+++ b/packages/backend/src/server/api/endpoints/chat/messages/react.ts
@@ -16,9 +16,6 @@ export const meta = {
kind: 'write:chat',
- res: {
- },
-
errors: {
noSuchMessage: {
message: 'No such message.',
diff --git a/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts b/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts
index 6784bb6ecf..adfcd232f9 100644
--- a/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts
+++ b/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts
@@ -16,9 +16,6 @@ export const meta = {
kind: 'write:chat',
- res: {
- },
-
errors: {
noSuchMessage: {
message: 'No such message.',
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts b/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts
index 82a8e1f30d..1ea81448c1 100644
--- a/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts
@@ -16,9 +16,6 @@ export const meta = {
kind: 'write:chat',
- res: {
- },
-
errors: {
noSuchRoom: {
message: 'No such room.',
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts
index b8a228089b..88ea234527 100644
--- a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts
@@ -16,9 +16,6 @@ export const meta = {
kind: 'write:chat',
- res: {
- },
-
errors: {
noSuchRoom: {
message: 'No such room.',
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/join.ts b/packages/backend/src/server/api/endpoints/chat/rooms/join.ts
index d561f9e03f..550b4da1a6 100644
--- a/packages/backend/src/server/api/endpoints/chat/rooms/join.ts
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/join.ts
@@ -16,9 +16,6 @@ export const meta = {
kind: 'write:chat',
- res: {
- },
-
errors: {
noSuchRoom: {
message: 'No such room.',
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts b/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts
index a3ad0c2d6f..f99b408d67 100644
--- a/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts
@@ -16,9 +16,6 @@ export const meta = {
kind: 'write:chat',
- res: {
- },
-
errors: {
noSuchRoom: {
message: 'No such room.',
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts b/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts
index 11cbe7b8b9..ee60f92505 100644
--- a/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts
@@ -16,9 +16,6 @@ export const meta = {
kind: 'write:chat',
- res: {
- },
-
errors: {
noSuchRoom: {
message: 'No such room.',
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index b43906109f..7069d32317 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -1046,15 +1046,9 @@ type ChatMessagesCreateToUserResponse = operations['chat___messages___create-to-
// @public (undocumented)
type ChatMessagesDeleteRequest = operations['chat___messages___delete']['requestBody']['content']['application/json'];
-// @public (undocumented)
-type ChatMessagesDeleteResponse = operations['chat___messages___delete']['responses']['200']['content']['application/json'];
-
// @public (undocumented)
type ChatMessagesReactRequest = operations['chat___messages___react']['requestBody']['content']['application/json'];
-// @public (undocumented)
-type ChatMessagesReactResponse = operations['chat___messages___react']['responses']['200']['content']['application/json'];
-
// @public (undocumented)
type ChatMessagesRoomTimelineRequest = operations['chat___messages___room-timeline']['requestBody']['content']['application/json'];
@@ -1076,9 +1070,6 @@ type ChatMessagesShowResponse = operations['chat___messages___show']['responses'
// @public (undocumented)
type ChatMessagesUnreactRequest = operations['chat___messages___unreact']['requestBody']['content']['application/json'];
-// @public (undocumented)
-type ChatMessagesUnreactResponse = operations['chat___messages___unreact']['responses']['200']['content']['application/json'];
-
// @public (undocumented)
type ChatMessagesUserTimelineRequest = operations['chat___messages___user-timeline']['requestBody']['content']['application/json'];
@@ -1103,9 +1094,6 @@ type ChatRoomsCreateResponse = operations['chat___rooms___create']['responses'][
// @public (undocumented)
type ChatRoomsDeleteRequest = operations['chat___rooms___delete']['requestBody']['content']['application/json'];
-// @public (undocumented)
-type ChatRoomsDeleteResponse = operations['chat___rooms___delete']['responses']['200']['content']['application/json'];
-
// @public (undocumented)
type ChatRoomsInvitationsCreateRequest = operations['chat___rooms___invitations___create']['requestBody']['content']['application/json'];
@@ -1115,9 +1103,6 @@ type ChatRoomsInvitationsCreateResponse = operations['chat___rooms___invitations
// @public (undocumented)
type ChatRoomsInvitationsIgnoreRequest = operations['chat___rooms___invitations___ignore']['requestBody']['content']['application/json'];
-// @public (undocumented)
-type ChatRoomsInvitationsIgnoreResponse = operations['chat___rooms___invitations___ignore']['responses']['200']['content']['application/json'];
-
// @public (undocumented)
type ChatRoomsInvitationsInboxRequest = operations['chat___rooms___invitations___inbox']['requestBody']['content']['application/json'];
@@ -1139,15 +1124,9 @@ type ChatRoomsJoiningResponse = operations['chat___rooms___joining']['responses'
// @public (undocumented)
type ChatRoomsJoinRequest = operations['chat___rooms___join']['requestBody']['content']['application/json'];
-// @public (undocumented)
-type ChatRoomsJoinResponse = operations['chat___rooms___join']['responses']['200']['content']['application/json'];
-
// @public (undocumented)
type ChatRoomsLeaveRequest = operations['chat___rooms___leave']['requestBody']['content']['application/json'];
-// @public (undocumented)
-type ChatRoomsLeaveResponse = operations['chat___rooms___leave']['responses']['200']['content']['application/json'];
-
// @public (undocumented)
type ChatRoomsMembersRequest = operations['chat___rooms___members']['requestBody']['content']['application/json'];
@@ -1157,9 +1136,6 @@ type ChatRoomsMembersResponse = operations['chat___rooms___members']['responses'
// @public (undocumented)
type ChatRoomsMuteRequest = operations['chat___rooms___mute']['requestBody']['content']['application/json'];
-// @public (undocumented)
-type ChatRoomsMuteResponse = operations['chat___rooms___mute']['responses']['200']['content']['application/json'];
-
// @public (undocumented)
type ChatRoomsOwnedRequest = operations['chat___rooms___owned']['requestBody']['content']['application/json'];
@@ -1688,9 +1664,7 @@ declare namespace entities {
ChatMessagesCreateToUserRequest,
ChatMessagesCreateToUserResponse,
ChatMessagesDeleteRequest,
- ChatMessagesDeleteResponse,
ChatMessagesReactRequest,
- ChatMessagesReactResponse,
ChatMessagesRoomTimelineRequest,
ChatMessagesRoomTimelineResponse,
ChatMessagesSearchRequest,
@@ -1698,31 +1672,25 @@ declare namespace entities {
ChatMessagesShowRequest,
ChatMessagesShowResponse,
ChatMessagesUnreactRequest,
- ChatMessagesUnreactResponse,
ChatMessagesUserTimelineRequest,
ChatMessagesUserTimelineResponse,
ChatRoomsCreateRequest,
ChatRoomsCreateResponse,
ChatRoomsDeleteRequest,
- ChatRoomsDeleteResponse,
ChatRoomsInvitationsCreateRequest,
ChatRoomsInvitationsCreateResponse,
ChatRoomsInvitationsIgnoreRequest,
- ChatRoomsInvitationsIgnoreResponse,
ChatRoomsInvitationsInboxRequest,
ChatRoomsInvitationsInboxResponse,
ChatRoomsInvitationsOutboxRequest,
ChatRoomsInvitationsOutboxResponse,
ChatRoomsJoinRequest,
- ChatRoomsJoinResponse,
ChatRoomsJoiningRequest,
ChatRoomsJoiningResponse,
ChatRoomsLeaveRequest,
- ChatRoomsLeaveResponse,
ChatRoomsMembersRequest,
ChatRoomsMembersResponse,
ChatRoomsMuteRequest,
- ChatRoomsMuteResponse,
ChatRoomsOwnedRequest,
ChatRoomsOwnedResponse,
ChatRoomsShowRequest,
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index 6390314429..56224cdbaf 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -220,9 +220,7 @@ import type {
ChatMessagesCreateToUserRequest,
ChatMessagesCreateToUserResponse,
ChatMessagesDeleteRequest,
- ChatMessagesDeleteResponse,
ChatMessagesReactRequest,
- ChatMessagesReactResponse,
ChatMessagesRoomTimelineRequest,
ChatMessagesRoomTimelineResponse,
ChatMessagesSearchRequest,
@@ -230,31 +228,25 @@ import type {
ChatMessagesShowRequest,
ChatMessagesShowResponse,
ChatMessagesUnreactRequest,
- ChatMessagesUnreactResponse,
ChatMessagesUserTimelineRequest,
ChatMessagesUserTimelineResponse,
ChatRoomsCreateRequest,
ChatRoomsCreateResponse,
ChatRoomsDeleteRequest,
- ChatRoomsDeleteResponse,
ChatRoomsInvitationsCreateRequest,
ChatRoomsInvitationsCreateResponse,
ChatRoomsInvitationsIgnoreRequest,
- ChatRoomsInvitationsIgnoreResponse,
ChatRoomsInvitationsInboxRequest,
ChatRoomsInvitationsInboxResponse,
ChatRoomsInvitationsOutboxRequest,
ChatRoomsInvitationsOutboxResponse,
ChatRoomsJoinRequest,
- ChatRoomsJoinResponse,
ChatRoomsJoiningRequest,
ChatRoomsJoiningResponse,
ChatRoomsLeaveRequest,
- ChatRoomsLeaveResponse,
ChatRoomsMembersRequest,
ChatRoomsMembersResponse,
ChatRoomsMuteRequest,
- ChatRoomsMuteResponse,
ChatRoomsOwnedRequest,
ChatRoomsOwnedResponse,
ChatRoomsShowRequest,
@@ -789,24 +781,24 @@ export type Endpoints = {
'chat/history': { req: ChatHistoryRequest; res: ChatHistoryResponse };
'chat/messages/create-to-room': { req: ChatMessagesCreateToRoomRequest; res: ChatMessagesCreateToRoomResponse };
'chat/messages/create-to-user': { req: ChatMessagesCreateToUserRequest; res: ChatMessagesCreateToUserResponse };
- 'chat/messages/delete': { req: ChatMessagesDeleteRequest; res: ChatMessagesDeleteResponse };
- 'chat/messages/react': { req: ChatMessagesReactRequest; res: ChatMessagesReactResponse };
+ 'chat/messages/delete': { req: ChatMessagesDeleteRequest; res: EmptyResponse };
+ 'chat/messages/react': { req: ChatMessagesReactRequest; res: EmptyResponse };
'chat/messages/room-timeline': { req: ChatMessagesRoomTimelineRequest; res: ChatMessagesRoomTimelineResponse };
'chat/messages/search': { req: ChatMessagesSearchRequest; res: ChatMessagesSearchResponse };
'chat/messages/show': { req: ChatMessagesShowRequest; res: ChatMessagesShowResponse };
- 'chat/messages/unreact': { req: ChatMessagesUnreactRequest; res: ChatMessagesUnreactResponse };
+ 'chat/messages/unreact': { req: ChatMessagesUnreactRequest; res: EmptyResponse };
'chat/messages/user-timeline': { req: ChatMessagesUserTimelineRequest; res: ChatMessagesUserTimelineResponse };
'chat/rooms/create': { req: ChatRoomsCreateRequest; res: ChatRoomsCreateResponse };
- 'chat/rooms/delete': { req: ChatRoomsDeleteRequest; res: ChatRoomsDeleteResponse };
+ 'chat/rooms/delete': { req: ChatRoomsDeleteRequest; res: EmptyResponse };
'chat/rooms/invitations/create': { req: ChatRoomsInvitationsCreateRequest; res: ChatRoomsInvitationsCreateResponse };
- 'chat/rooms/invitations/ignore': { req: ChatRoomsInvitationsIgnoreRequest; res: ChatRoomsInvitationsIgnoreResponse };
+ 'chat/rooms/invitations/ignore': { req: ChatRoomsInvitationsIgnoreRequest; res: EmptyResponse };
'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/join': { req: ChatRoomsJoinRequest; res: EmptyResponse };
'chat/rooms/joining': { req: ChatRoomsJoiningRequest; res: ChatRoomsJoiningResponse };
- 'chat/rooms/leave': { req: ChatRoomsLeaveRequest; res: ChatRoomsLeaveResponse };
+ 'chat/rooms/leave': { req: ChatRoomsLeaveRequest; res: EmptyResponse };
'chat/rooms/members': { req: ChatRoomsMembersRequest; res: ChatRoomsMembersResponse };
- 'chat/rooms/mute': { req: ChatRoomsMuteRequest; res: ChatRoomsMuteResponse };
+ 'chat/rooms/mute': { req: ChatRoomsMuteRequest; res: EmptyResponse };
'chat/rooms/owned': { req: ChatRoomsOwnedRequest; res: ChatRoomsOwnedResponse };
'chat/rooms/show': { req: ChatRoomsShowRequest; res: ChatRoomsShowResponse };
'chat/rooms/update': { req: ChatRoomsUpdateRequest; res: ChatRoomsUpdateResponse };
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index f814d7b3da..b5370e99fa 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -223,9 +223,7 @@ export type ChatMessagesCreateToRoomResponse = operations['chat___messages___cre
export type ChatMessagesCreateToUserRequest = operations['chat___messages___create-to-user']['requestBody']['content']['application/json'];
export type ChatMessagesCreateToUserResponse = operations['chat___messages___create-to-user']['responses']['200']['content']['application/json'];
export type ChatMessagesDeleteRequest = operations['chat___messages___delete']['requestBody']['content']['application/json'];
-export type ChatMessagesDeleteResponse = operations['chat___messages___delete']['responses']['200']['content']['application/json'];
export type ChatMessagesReactRequest = operations['chat___messages___react']['requestBody']['content']['application/json'];
-export type ChatMessagesReactResponse = operations['chat___messages___react']['responses']['200']['content']['application/json'];
export type ChatMessagesRoomTimelineRequest = operations['chat___messages___room-timeline']['requestBody']['content']['application/json'];
export type ChatMessagesRoomTimelineResponse = operations['chat___messages___room-timeline']['responses']['200']['content']['application/json'];
export type ChatMessagesSearchRequest = operations['chat___messages___search']['requestBody']['content']['application/json'];
@@ -233,31 +231,25 @@ export type ChatMessagesSearchResponse = operations['chat___messages___search'][
export type ChatMessagesShowRequest = operations['chat___messages___show']['requestBody']['content']['application/json'];
export type ChatMessagesShowResponse = operations['chat___messages___show']['responses']['200']['content']['application/json'];
export type ChatMessagesUnreactRequest = operations['chat___messages___unreact']['requestBody']['content']['application/json'];
-export type ChatMessagesUnreactResponse = operations['chat___messages___unreact']['responses']['200']['content']['application/json'];
export type ChatMessagesUserTimelineRequest = operations['chat___messages___user-timeline']['requestBody']['content']['application/json'];
export type ChatMessagesUserTimelineResponse = operations['chat___messages___user-timeline']['responses']['200']['content']['application/json'];
export type ChatRoomsCreateRequest = operations['chat___rooms___create']['requestBody']['content']['application/json'];
export type ChatRoomsCreateResponse = operations['chat___rooms___create']['responses']['200']['content']['application/json'];
export type ChatRoomsDeleteRequest = operations['chat___rooms___delete']['requestBody']['content']['application/json'];
-export type ChatRoomsDeleteResponse = operations['chat___rooms___delete']['responses']['200']['content']['application/json'];
export type ChatRoomsInvitationsCreateRequest = operations['chat___rooms___invitations___create']['requestBody']['content']['application/json'];
export type ChatRoomsInvitationsCreateResponse = operations['chat___rooms___invitations___create']['responses']['200']['content']['application/json'];
export type ChatRoomsInvitationsIgnoreRequest = operations['chat___rooms___invitations___ignore']['requestBody']['content']['application/json'];
-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'];
export type ChatRoomsJoiningResponse = operations['chat___rooms___joining']['responses']['200']['content']['application/json'];
export type ChatRoomsLeaveRequest = operations['chat___rooms___leave']['requestBody']['content']['application/json'];
-export type ChatRoomsLeaveResponse = operations['chat___rooms___leave']['responses']['200']['content']['application/json'];
export type ChatRoomsMembersRequest = operations['chat___rooms___members']['requestBody']['content']['application/json'];
export type ChatRoomsMembersResponse = operations['chat___rooms___members']['responses']['200']['content']['application/json'];
export type ChatRoomsMuteRequest = operations['chat___rooms___mute']['requestBody']['content']['application/json'];
-export type ChatRoomsMuteResponse = operations['chat___rooms___mute']['responses']['200']['content']['application/json'];
export type ChatRoomsOwnedRequest = operations['chat___rooms___owned']['requestBody']['content']['application/json'];
export type ChatRoomsOwnedResponse = operations['chat___rooms___owned']['responses']['200']['content']['application/json'];
export type ChatRoomsShowRequest = operations['chat___rooms___show']['requestBody']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 9da5540bc1..b9d48f02ae 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -14594,11 +14594,9 @@ export type operations = {
};
};
responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': unknown;
- };
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
};
/** @description Client error */
400: {
@@ -14649,11 +14647,9 @@ export type operations = {
};
};
responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': unknown;
- };
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
};
/** @description Client error */
400: {
@@ -14877,11 +14873,9 @@ export type operations = {
};
};
responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': unknown;
- };
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
};
/** @description Client error */
400: {
@@ -15051,11 +15045,9 @@ export type operations = {
};
};
responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': unknown;
- };
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
};
/** @description Client error */
400: {
@@ -15167,11 +15159,9 @@ export type operations = {
};
};
responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': unknown;
- };
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
};
/** @description Client error */
400: {
@@ -15339,11 +15329,9 @@ export type operations = {
};
};
responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': unknown;
- };
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
};
/** @description Client error */
400: {
@@ -15451,11 +15439,9 @@ export type operations = {
};
};
responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': unknown;
- };
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
};
/** @description Client error */
400: {
@@ -15566,11 +15552,9 @@ export type operations = {
};
};
responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': unknown;
- };
+ /** @description OK (without any results) */
+ 204: {
+ content: never;
};
/** @description Client error */
400: {
From e5fcb5b53f3719c0bafa1d6e39805b40c999cd2f Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Mon, 28 Apr 2025 21:25:47 +0900
Subject: [PATCH 181/190] enhance(frontend): disable router view transition
Fix #15723
---
.../src/components/global/RouterView.vue | 46 +------------------
1 file changed, 2 insertions(+), 44 deletions(-)
diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue
index 78ac6900a3..27f7b18559 100644
--- a/packages/frontend/src/components/global/RouterView.vue
+++ b/packages/frontend/src/components/global/RouterView.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
-
+
@@ -42,37 +42,6 @@ provide(DI.viewId, viewId);
const currentDepth = inject(DI.routerCurrentDepth, 0);
provide(DI.routerCurrentDepth, currentDepth + 1);
-const rootEl = useTemplateRef('rootEl');
-onMounted(() => {
- if (prefer.s.animation) {
- rootEl.value.style.viewTransitionName = viewId; // view-transition-nameにcss varが使えないっぽいため直接代入
- }
-});
-
-// view-transition-newなどのにはcss varが使えず、v-bindできないため直接スタイルを生成
-const viewTransitionStylesTag = window.document.createElement('style');
-viewTransitionStylesTag.textContent = `
-@keyframes ${viewId}-old {
- to { transform: scale(0.95); opacity: 0; }
-}
-
-@keyframes ${viewId}-new {
- from { transform: scale(0.95); opacity: 0; }
-}
-
-::view-transition-old(${viewId}) {
- animation-duration: 0.2s;
- animation-name: ${viewId}-old;
-}
-
-::view-transition-new(${viewId}) {
- animation-duration: 0.2s;
- animation-name: ${viewId}-new;
-}
-`;
-
-window.document.head.appendChild(viewTransitionStylesTag);
-
const current = router.current!;
const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
const currentPageProps = ref(current.props);
@@ -90,18 +59,7 @@ router.useListener('change', ({ resolved }) => {
currentRoutePath = resolved.route.path;
}
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
- if (prefer.s.animation && window.document.startViewTransition) {
- window.document.startViewTransition(() => new Promise((res) => {
- _();
- nextTick(() => {
- res();
- //setTimeout(res, 100);
- });
- }));
- } else {
- _();
- }
+ _();
});
From 7e8cc4d7c0a86ad0bf71a727fb16132e8bc180a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?=
<46447427+samunohito@users.noreply.github.com>
Date: Tue, 29 Apr 2025 08:15:09 +0900
Subject: [PATCH 182/190] =?UTF-8?q?fix:=20=E6=B7=BB=E4=BB=98=E3=83=95?=
=?UTF-8?q?=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AE=E3=81=82=E3=82=8B=E3=83=AA?=
=?UTF-8?q?=E3=82=AF=E3=82=A8=E3=82=B9=E3=83=88=E3=82=92=E5=8F=97=E3=81=91?=
=?UTF-8?q?=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AE=E5=88=9D=E5=8B=95=E3=82=92?=
=?UTF-8?q?=E6=94=B9=E5=96=84=20(#15896)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* wip
* ロールポリシーの値も参照するように
* エンドポイントのテストを追加
* fix review
* add spdx
* fix CHANGELOG.md
* fix test
* regenerate
* add log
* Revert "add log"
This reverts commit 4b2bf59a609b85ca0bfcc9b71438db782f11983d.
* add log
* fix
* Revert "add log"
This reverts commit c5a73d57da0f30ec5215e08a8b4d78785cce48d1.
---
CHANGELOG.md | 1 +
packages/backend/package.json | 4 +-
packages/backend/src/server/ServerService.ts | 7 +-
.../backend/src/server/api/ApiCallService.ts | 166 ++++++++++++------
.../backend/src/server/api/endpoint-base.ts | 10 +-
packages/backend/test/e2e/api.ts | 4 +-
.../unit/server/api/drive/files/create.ts | 108 ++++++++++++
pnpm-lock.yaml | 158 +++++++++++++----
8 files changed, 363 insertions(+), 95 deletions(-)
create mode 100644 packages/backend/test/unit/server/api/drive/files/create.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8151ed3184..f52bf43171 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,7 @@
- Fix: システムアカウントの名前がサーバー名と同期されない問題を修正
- Fix: 大文字を含むユーザの URL で紹介された場合に 404 エラーを返す問題 #15813
- Fix: リードレプリカ設定時にレコードの追加・更新・削除を伴うクエリを発行した際はmasterノードで実行されるように調整( #10897 )
+- Fix: ファイルアップロード時の挙動を一部調整(#15895)
## 2025.4.0
diff --git a/packages/backend/package.json b/packages/backend/package.json
index faf331ed85..c4de44df18 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -222,6 +222,7 @@
"@types/semver": "7.7.0",
"@types/simple-oauth2": "5.0.7",
"@types/sinonjs__fake-timers": "8.1.5",
+ "@types/supertest": "6.0.3",
"@types/tinycolor2": "1.4.6",
"@types/tmp": "0.2.6",
"@types/vary": "1.1.3",
@@ -238,6 +239,7 @@
"jest-mock": "29.7.0",
"nodemon": "3.1.10",
"pid-port": "1.0.2",
- "simple-oauth2": "5.1.0"
+ "simple-oauth2": "5.1.0",
+ "supertest": "7.1.0"
}
}
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 355d7ca08e..7decdd2c10 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -73,7 +73,7 @@ export class ServerService implements OnApplicationShutdown {
}
@bindThis
- public async launch(): Promise {
+ public async launch() {
const fastify = Fastify({
trustProxy: true,
logger: false,
@@ -133,8 +133,8 @@ export class ServerService implements OnApplicationShutdown {
reply.header('content-type', 'text/plain; charset=utf-8');
reply.header('link', `<${encodeURI(location)}>; rel="canonical"`);
done(null, [
- "Refusing to relay remote ActivityPub object lookup.",
- "",
+ 'Refusing to relay remote ActivityPub object lookup.',
+ '',
`Please remove 'application/activity+json' and 'application/ld+json' from the Accept header or fetch using the authoritative URL at ${location}.`,
].join('\n'));
});
@@ -301,6 +301,7 @@ export class ServerService implements OnApplicationShutdown {
}
await fastify.ready();
+ return fastify;
}
@bindThis
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index a42fdaf730..960c7b5476 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -6,8 +6,11 @@
import { randomUUID } from 'node:crypto';
import * as fs from 'node:fs';
import * as stream from 'node:stream/promises';
+import { Transform } from 'node:stream';
+import { type MultipartFile } from '@fastify/multipart';
import { Inject, Injectable } from '@nestjs/common';
import * as Sentry from '@sentry/node';
+import { AttachmentFile } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { getIpHash } from '@/misc/get-ip-hash.js';
import type { MiLocalUser, MiUser } from '@/models/User.js';
@@ -16,7 +19,7 @@ import type Logger from '@/logger.js';
import type { MiMeta, UserIpsRepository } from '@/models/_.js';
import { createTemp } from '@/misc/create-temp.js';
import { bindThis } from '@/decorators.js';
-import { RoleService } from '@/core/RoleService.js';
+import { type RolePolicies, RoleService } from '@/core/RoleService.js';
import type { Config } from '@/config.js';
import { ApiError } from './error.js';
import { RateLimiterService } from './RateLimiterService.js';
@@ -200,18 +203,6 @@ export class ApiCallService implements OnApplicationShutdown {
return;
}
- const [path, cleanup] = await createTemp();
- await stream.pipeline(multipartData.file, fs.createWriteStream(path));
-
- // ファイルサイズが制限を超えていた場合
- // なお truncated はストリームを読み切ってからでないと機能しないため、stream.pipeline より後にある必要がある
- if (multipartData.file.truncated) {
- cleanup();
- reply.code(413);
- reply.send();
- return;
- }
-
const fields = {} as Record;
for (const [k, v] of Object.entries(multipartData.fields)) {
fields[k] = typeof v === 'object' && 'value' in v ? v.value : undefined;
@@ -226,10 +217,7 @@ export class ApiCallService implements OnApplicationShutdown {
return;
}
this.authenticateService.authenticate(token).then(([user, app]) => {
- this.call(endpoint, user, app, fields, {
- name: multipartData.filename,
- path: path,
- }, request).then((res) => {
+ this.call(endpoint, user, app, fields, multipartData, request).then((res) => {
this.send(reply, res);
}).catch((err: ApiError) => {
this.#sendApiError(reply, err);
@@ -294,10 +282,7 @@ export class ApiCallService implements OnApplicationShutdown {
user: MiLocalUser | null | undefined,
token: MiAccessToken | null | undefined,
data: any,
- file: {
- name: string;
- path: string;
- } | null,
+ multipartFile: MultipartFile | null,
request: FastifyRequest<{ Body: Record | undefined, Querystring: Record }>,
) {
const isSecure = user != null && token == null;
@@ -371,6 +356,37 @@ export class ApiCallService implements OnApplicationShutdown {
}
}
+ // Cast non JSON input
+ if ((ep.meta.requireFile || request.method === 'GET') && ep.params.properties) {
+ for (const k of Object.keys(ep.params.properties)) {
+ const param = ep.params.properties![k];
+ if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') {
+ try {
+ data[k] = JSON.parse(data[k]);
+ } catch (e) {
+ throw new ApiError({
+ message: 'Invalid param.',
+ code: 'INVALID_PARAM',
+ id: '0b5f1631-7c1a-41a6-b399-cce335f34d85',
+ }, {
+ param: k,
+ reason: `cannot cast to ${param.type}`,
+ });
+ }
+ }
+ }
+ }
+
+ if (token && ((ep.meta.kind && !token.permission.some(p => p === ep.meta.kind))
+ || (!ep.meta.kind && (ep.meta.requireCredential || ep.meta.requireModerator || ep.meta.requireAdmin)))) {
+ throw new ApiError({
+ message: 'Your app does not have the necessary permissions to use this endpoint.',
+ code: 'PERMISSION_DENIED',
+ kind: 'permission',
+ id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
+ });
+ }
+
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && (this.meta.rootUserId !== user!.id)) {
const myRoles = await this.roleService.getUserRoles(user!.id);
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
@@ -404,49 +420,91 @@ export class ApiCallService implements OnApplicationShutdown {
}
}
- if (token && ((ep.meta.kind && !token.permission.some(p => p === ep.meta.kind))
- || (!ep.meta.kind && (ep.meta.requireCredential || ep.meta.requireModerator || ep.meta.requireAdmin)))) {
- throw new ApiError({
- message: 'Your app does not have the necessary permissions to use this endpoint.',
- code: 'PERMISSION_DENIED',
- kind: 'permission',
- id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
- });
- }
-
- // Cast non JSON input
- if ((ep.meta.requireFile || request.method === 'GET') && ep.params.properties) {
- for (const k of Object.keys(ep.params.properties)) {
- const param = ep.params.properties![k];
- if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') {
- try {
- data[k] = JSON.parse(data[k]);
- } catch (e) {
- throw new ApiError({
- message: 'Invalid param.',
- code: 'INVALID_PARAM',
- id: '0b5f1631-7c1a-41a6-b399-cce335f34d85',
- }, {
- param: k,
- reason: `cannot cast to ${param.type}`,
- });
- }
- }
- }
+ let attachmentFile: AttachmentFile | null = null;
+ let cleanup = () => {};
+ if (ep.meta.requireFile && request.method === 'POST' && multipartFile) {
+ const policies = await this.roleService.getUserPolicies(user!.id);
+ const result = await this.handleAttachmentFile(
+ Math.min((policies.maxFileSizeMb * 1024 * 1024), this.config.maxFileSize),
+ multipartFile,
+ );
+ attachmentFile = result.attachmentFile;
+ cleanup = result.cleanup;
}
// API invoking
if (this.config.sentryForBackend) {
return await Sentry.startSpan({
name: 'API: ' + ep.name,
- }, () => ep.exec(data, user, token, file, request.ip, request.headers)
- .catch((err: Error) => this.#onExecError(ep, data, err, user?.id)));
+ }, () => {
+ return ep.exec(data, user, token, attachmentFile, request.ip, request.headers)
+ .catch((err: Error) => this.#onExecError(ep, data, err, user?.id))
+ .finally(() => cleanup());
+ });
} else {
- return await ep.exec(data, user, token, file, request.ip, request.headers)
- .catch((err: Error) => this.#onExecError(ep, data, err, user?.id));
+ return await ep.exec(data, user, token, attachmentFile, request.ip, request.headers)
+ .catch((err: Error) => this.#onExecError(ep, data, err, user?.id))
+ .finally(() => cleanup());
}
}
+ @bindThis
+ private async handleAttachmentFile(
+ fileSizeLimit: number,
+ multipartFile: MultipartFile,
+ ) {
+ function createTooLongError() {
+ return new ApiError({
+ httpStatusCode: 413,
+ kind: 'client',
+ message: 'File size is too large.',
+ code: 'FILE_SIZE_TOO_LARGE',
+ id: 'ff827ce8-9b4b-4808-8511-422222a3362f',
+ });
+ }
+
+ function createLimitStream(limit: number) {
+ let total = 0;
+
+ return new Transform({
+ transform(chunk, _, callback) {
+ total += chunk.length;
+ if (total > limit) {
+ callback(createTooLongError());
+ } else {
+ callback(null, chunk);
+ }
+ },
+ });
+ }
+
+ const [path, cleanup] = await createTemp();
+ try {
+ await stream.pipeline(
+ multipartFile.file,
+ createLimitStream(fileSizeLimit),
+ fs.createWriteStream(path),
+ );
+
+ // ファイルサイズが制限を超えていた場合
+ // なお truncated はストリームを読み切ってからでないと機能しないため、stream.pipeline より後にある必要がある
+ if (multipartFile.file.truncated) {
+ throw createTooLongError();
+ }
+ } catch (err) {
+ cleanup();
+ throw err;
+ }
+
+ return {
+ attachmentFile: {
+ name: multipartFile.filename,
+ path,
+ },
+ cleanup,
+ };
+ }
+
@bindThis
public dispose(): void {
clearInterval(this.userIpHistoriesClearIntervalId);
diff --git a/packages/backend/src/server/api/endpoint-base.ts b/packages/backend/src/server/api/endpoint-base.ts
index e061aa3a8e..b063487305 100644
--- a/packages/backend/src/server/api/endpoint-base.ts
+++ b/packages/backend/src/server/api/endpoint-base.ts
@@ -21,23 +21,23 @@ ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
export type Response = Record | void;
-type File = {
+export type AttachmentFile = {
name: string | null;
path: string;
};
// TODO: paramsの型をT['params']のスキーマ定義から推論する
type Executor =
- (params: SchemaType, user: T['requireCredential'] extends true ? MiLocalUser : MiLocalUser | null, token: MiAccessToken | null, file?: File, cleanup?: () => any, ip?: string | null, headers?: Record | null) =>
- Promise>>;
+ (params: SchemaType, user: T['requireCredential'] extends true ? MiLocalUser : MiLocalUser | null, token: MiAccessToken | null, file?: AttachmentFile, cleanup?: () => any, ip?: string | null, headers?: Record | null) =>
+ Promise>>;
export abstract class Endpoint {
- public exec: (params: any, user: T['requireCredential'] extends true ? MiLocalUser : MiLocalUser | null, token: MiAccessToken | null, file?: File, ip?: string | null, headers?: Record | null) => Promise;
+ public exec: (params: any, user: T['requireCredential'] extends true ? MiLocalUser : MiLocalUser | null, token: MiAccessToken | null, file?: AttachmentFile, ip?: string | null, headers?: Record | null) => Promise;
constructor(meta: T, paramDef: Ps, cb: Executor) {
const validate = ajv.compile(paramDef);
- this.exec = (params: any, user: T['requireCredential'] extends true ? MiLocalUser : MiLocalUser | null, token: MiAccessToken | null, file?: File, ip?: string | null, headers?: Record | null) => {
+ this.exec = (params: any, user: T['requireCredential'] extends true ? MiLocalUser : MiLocalUser | null, token: MiAccessToken | null, file?: AttachmentFile, ip?: string | null, headers?: Record | null) => {
let cleanup: undefined | (() => void) = undefined;
if (meta.requireFile) {
diff --git a/packages/backend/test/e2e/api.ts b/packages/backend/test/e2e/api.ts
index 49c6a0636b..f9e65aaa84 100644
--- a/packages/backend/test/e2e/api.ts
+++ b/packages/backend/test/e2e/api.ts
@@ -159,8 +159,8 @@ describe('API', () => {
user: { token: application3 },
}, {
status: 403,
- code: 'ROLE_PERMISSION_DENIED',
- id: 'c3d38592-54c0-429d-be96-5636b0431a61',
+ code: 'PERMISSION_DENIED',
+ id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
});
await failedApiCall({
diff --git a/packages/backend/test/unit/server/api/drive/files/create.ts b/packages/backend/test/unit/server/api/drive/files/create.ts
new file mode 100644
index 0000000000..b98892fa03
--- /dev/null
+++ b/packages/backend/test/unit/server/api/drive/files/create.ts
@@ -0,0 +1,108 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { S3Client } from '@aws-sdk/client-s3';
+import { Test, TestingModule } from '@nestjs/testing';
+import { mockClient } from 'aws-sdk-client-mock';
+import { FastifyInstance } from 'fastify';
+import request from 'supertest';
+import { CoreModule } from '@/core/CoreModule.js';
+import { RoleService } from '@/core/RoleService.js';
+import { DI } from '@/di-symbols.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { MiRole, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import { MiUser } from '@/models/User.js';
+import { ServerModule } from '@/server/ServerModule.js';
+import { ServerService } from '@/server/ServerService.js';
+
+describe('/drive/files/create', () => {
+ let module: TestingModule;
+ let server: FastifyInstance;
+ const s3Mock = mockClient(S3Client);
+ let roleService: RoleService;
+
+ let root: MiUser;
+ let role_tinyAttachment: MiRole;
+
+ beforeAll(async () => {
+ module = await Test.createTestingModule({
+ imports: [GlobalModule, CoreModule, ServerModule],
+ }).compile();
+ module.enableShutdownHooks();
+
+ const serverService = module.get(ServerService);
+ server = await serverService.launch();
+
+ const usersRepository = module.get(DI.usersRepository);
+ root = await usersRepository.insert({
+ id: 'root',
+ username: 'root',
+ usernameLower: 'root',
+ token: '1234567890123456',
+ }).then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
+
+ const userProfilesRepository = module.get(DI.userProfilesRepository);
+ await userProfilesRepository.insert({
+ userId: root.id,
+ });
+
+ roleService = module.get(RoleService);
+ role_tinyAttachment = await roleService.create({
+ name: 'test-role001',
+ description: 'Test role001 description',
+ target: 'manual',
+ policies: {
+ maxFileSizeMb: {
+ useDefault: false,
+ priority: 1,
+ // 10byte
+ value: 10 / 1024 / 1024,
+ },
+ },
+ });
+ });
+
+ beforeEach(async () => {
+ s3Mock.reset();
+ await roleService.unassign(root.id, role_tinyAttachment.id).catch(() => {});
+ });
+
+ afterAll(async () => {
+ await server.close();
+ await module.close();
+ });
+
+ test('200 ok', async () => {
+ const result = await request(server.server)
+ .post('/api/drive/files/create')
+ .set('Content-Type', 'multipart/form-data')
+ .set('Authorization', `Bearer ${root.token}`)
+ .attach('file', Buffer.from('a'.repeat(1024 * 1024)));
+ expect(result.statusCode).toBe(200);
+ });
+
+ test('200 ok(with role)', async () => {
+ await roleService.assign(root.id, role_tinyAttachment.id);
+
+ const result = await request(server.server)
+ .post('/api/drive/files/create')
+ .set('Content-Type', 'multipart/form-data')
+ .set('Authorization', `Bearer ${root.token}`)
+ .attach('file', Buffer.from('a'.repeat(10)));
+ expect(result.statusCode).toBe(200);
+ });
+
+ test('413 too large', async () => {
+ await roleService.assign(root.id, role_tinyAttachment.id);
+
+ const result = await request(server.server)
+ .post('/api/drive/files/create')
+ .set('Content-Type', 'multipart/form-data')
+ .set('Authorization', `Bearer ${root.token}`)
+ .attach('file', Buffer.from('a'.repeat(11)));
+ expect(result.statusCode).toBe(413);
+ expect(result.body.error.code).toBe('FILE_SIZE_TOO_LARGE');
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 717421d68e..5b3c95d703 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -547,6 +547,9 @@ importers:
'@types/sinonjs__fake-timers':
specifier: 8.1.5
version: 8.1.5
+ '@types/supertest':
+ specifier: 6.0.3
+ version: 6.0.3
'@types/tinycolor2':
specifier: 1.4.6
version: 1.4.6
@@ -598,6 +601,9 @@ importers:
simple-oauth2:
specifier: 5.1.0
version: 5.1.0
+ supertest:
+ specifier: 7.1.0
+ version: 7.1.0
optionalDependencies:
'@swc/core-android-arm64':
specifier: 1.3.11
@@ -3139,6 +3145,9 @@ packages:
peerDependencies:
'@opentelemetry/api': ^1.1.0
+ '@paralleldrive/cuid2@2.2.2':
+ resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
+
'@parcel/watcher-android-arm64@2.5.0':
resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==}
engines: {node: '>= 10.0.0'}
@@ -4282,6 +4291,9 @@ packages:
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
+ '@types/cookiejar@2.1.5':
+ resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
+
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@@ -4369,6 +4381,9 @@ packages:
'@types/mdx@2.0.3':
resolution: {integrity: sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ==}
+ '@types/methods@1.1.4':
+ resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
+
'@types/micromatch@4.0.9':
resolution: {integrity: sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==}
@@ -4507,6 +4522,12 @@ packages:
'@types/statuses@2.0.4':
resolution: {integrity: sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==}
+ '@types/superagent@8.1.9':
+ resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==}
+
+ '@types/supertest@6.0.3':
+ resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==}
+
'@types/tedious@4.0.14':
resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==}
@@ -5572,6 +5593,9 @@ packages:
compare-versions@6.1.1:
resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
+ component-emitter@1.3.1:
+ resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
+
compress-commons@6.0.2:
resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==}
engines: {node: '>= 14'}
@@ -5629,6 +5653,9 @@ packages:
resolution: {integrity: sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==}
engines: {node: '>=18'}
+ cookiejar@2.1.4:
+ resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==}
+
core-js@3.29.1:
resolution: {integrity: sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==}
@@ -5938,6 +5965,9 @@ packages:
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+ dezalgo@1.0.4:
+ resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==}
+
diff-match-patch@1.0.5:
resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==}
@@ -6599,6 +6629,10 @@ packages:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
+ formidable@3.5.4:
+ resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==}
+ engines: {node: '>=14.0.0'}
+
forwarded-parse@2.1.2:
resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==}
@@ -10017,6 +10051,14 @@ packages:
peerDependencies:
postcss: ^8.4.31
+ superagent@9.0.2:
+ resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==}
+ engines: {node: '>=14.18.0'}
+
+ supertest@7.1.0:
+ resolution: {integrity: sha512-5QeSO8hSrKghtcWEoPiO036fxH0Ii2wVQfFZSP0oqQhmjk8bOLhDFXr4JrvaFmPuEWUoq4znY3uSi8UzLKxGqw==}
+ engines: {node: '>=14.18.0'}
+
supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
@@ -11530,7 +11572,7 @@ snapshots:
'@babel/traverse': 7.24.7
'@babel/types': 7.25.6
convert-source-map: 2.0.0
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -11550,7 +11592,7 @@ snapshots:
'@babel/traverse': 7.24.7
'@babel/types': 7.25.6
convert-source-map: 2.0.0
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -11772,7 +11814,7 @@ snapshots:
'@babel/helper-split-export-declaration': 7.24.7
'@babel/parser': 7.25.6
'@babel/types': 7.25.6
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@@ -12108,7 +12150,7 @@ snapshots:
'@eslint/config-array@0.20.0':
dependencies:
'@eslint/object-schema': 2.1.6
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -12122,7 +12164,7 @@ snapshots:
'@eslint/eslintrc@3.3.1':
dependencies:
ajv: 6.12.6
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
espree: 10.3.0
globals: 14.0.0
ignore: 5.3.1
@@ -13161,6 +13203,10 @@ snapshots:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@paralleldrive/cuid2@2.2.2':
+ dependencies:
+ '@noble/hashes': 1.7.1
+
'@parcel/watcher-android-arm64@2.5.0':
optional: true
@@ -14508,7 +14554,7 @@ snapshots:
'@tokenizer/inflate@0.2.7':
dependencies:
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
fflate: 0.8.2
token-types: 6.0.0
transitivePeerDependencies:
@@ -14588,6 +14634,8 @@ snapshots:
'@types/cookie@0.6.0': {}
+ '@types/cookiejar@2.1.5': {}
+
'@types/debug@4.1.12':
dependencies:
'@types/ms': 0.7.34
@@ -14681,6 +14729,8 @@ snapshots:
'@types/mdx@2.0.3': {}
+ '@types/methods@1.1.4': {}
+
'@types/micromatch@4.0.9':
dependencies:
'@types/braces': 3.0.1
@@ -14816,6 +14866,18 @@ snapshots:
'@types/statuses@2.0.4': {}
+ '@types/superagent@8.1.9':
+ dependencies:
+ '@types/cookiejar': 2.1.5
+ '@types/methods': 1.1.4
+ '@types/node': 22.15.2
+ form-data: 4.0.2
+
+ '@types/supertest@6.0.3':
+ dependencies:
+ '@types/methods': 1.1.4
+ '@types/superagent': 8.1.9
+
'@types/tedious@4.0.14':
dependencies:
'@types/node': 22.15.2
@@ -14880,7 +14942,7 @@ snapshots:
'@typescript-eslint/types': 8.31.0
'@typescript-eslint/typescript-estree': 8.31.0(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.31.0
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
eslint: 9.25.1
typescript: 5.8.3
transitivePeerDependencies:
@@ -14895,7 +14957,7 @@ snapshots:
dependencies:
'@typescript-eslint/typescript-estree': 8.31.0(typescript@5.8.3)
'@typescript-eslint/utils': 8.31.0(eslint@9.25.1)(typescript@5.8.3)
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
eslint: 9.25.1
ts-api-utils: 2.1.0(typescript@5.8.3)
typescript: 5.8.3
@@ -14908,7 +14970,7 @@ snapshots:
dependencies:
'@typescript-eslint/types': 8.31.0
'@typescript-eslint/visitor-keys': 8.31.0
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.5
@@ -14945,7 +15007,7 @@ snapshots:
dependencies:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 1.0.2
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-lib-source-maps: 5.0.6
@@ -15256,14 +15318,14 @@ snapshots:
agent-base@6.0.2:
dependencies:
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
optional: true
agent-base@7.1.0:
dependencies:
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -16100,6 +16162,8 @@ snapshots:
compare-versions@6.1.1: {}
+ component-emitter@1.3.1: {}
+
compress-commons@6.0.2:
dependencies:
crc-32: 1.2.2
@@ -16152,6 +16216,8 @@ snapshots:
cookie@1.0.1: {}
+ cookiejar@2.1.4: {}
+
core-js@3.29.1: {}
core-util-is@1.0.2: {}
@@ -16534,6 +16600,11 @@ snapshots:
dependencies:
dequal: 2.0.3
+ dezalgo@1.0.4:
+ dependencies:
+ asap: 2.0.6
+ wrappy: 1.0.2
+
diff-match-patch@1.0.5: {}
diff-sequences@29.6.3: {}
@@ -16836,7 +16907,7 @@ snapshots:
esbuild-register@3.5.0(esbuild@0.25.3):
dependencies:
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
esbuild: 0.25.3
transitivePeerDependencies:
- supports-color
@@ -17013,7 +17084,7 @@ snapshots:
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
escape-string-regexp: 4.0.0
eslint-scope: 8.3.0
eslint-visitor-keys: 4.2.0
@@ -17464,7 +17535,7 @@ snapshots:
follow-redirects@1.15.9(debug@4.4.0):
optionalDependencies:
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
for-each@0.3.3:
dependencies:
@@ -17492,6 +17563,12 @@ snapshots:
dependencies:
fetch-blob: 3.2.0
+ formidable@3.5.4:
+ dependencies:
+ '@paralleldrive/cuid2': 2.2.2
+ dezalgo: 1.0.4
+ once: 1.4.0
+
forwarded-parse@2.1.2: {}
forwarded@0.2.0: {}
@@ -17906,7 +17983,7 @@ snapshots:
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.3
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -17934,7 +18011,7 @@ snapshots:
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
optional: true
@@ -17942,14 +18019,14 @@ snapshots:
https-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.0
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.3
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -18047,7 +18124,7 @@ snapshots:
dependencies:
'@ioredis/commands': 1.2.0
cluster-key-slot: 1.1.2
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
denque: 2.1.0
lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0
@@ -18281,7 +18358,7 @@ snapshots:
istanbul-lib-source-maps@4.0.1:
dependencies:
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
istanbul-lib-coverage: 3.2.2
source-map: 0.6.1
transitivePeerDependencies:
@@ -18290,7 +18367,7 @@ snapshots:
istanbul-lib-source-maps@5.0.6:
dependencies:
'@jridgewell/trace-mapping': 0.3.25
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
istanbul-lib-coverage: 3.2.2
transitivePeerDependencies:
- supports-color
@@ -19314,7 +19391,7 @@ snapshots:
micromark@4.0.0:
dependencies:
'@types/debug': 4.1.12
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
decode-named-character-reference: 1.0.2
devlop: 1.1.0
micromark-core-commonmark: 2.0.0
@@ -20719,7 +20796,7 @@ snapshots:
require-in-the-middle@7.3.0:
dependencies:
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
module-details-from-path: 1.0.3
resolve: 1.22.8
transitivePeerDependencies:
@@ -21171,7 +21248,7 @@ snapshots:
socks-proxy-agent@8.0.2:
dependencies:
agent-base: 7.1.3
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
socks: 2.7.1
transitivePeerDependencies:
- supports-color
@@ -21280,7 +21357,7 @@ snapshots:
arg: 5.0.2
bluebird: 3.7.2
check-more-types: 2.24.0
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
execa: 5.1.1
lazy-ass: 1.6.0
ps-tree: 1.2.0
@@ -21472,6 +21549,27 @@ snapshots:
postcss: 8.5.3
postcss-selector-parser: 6.1.2
+ superagent@9.0.2:
+ dependencies:
+ component-emitter: 1.3.1
+ cookiejar: 2.1.4
+ debug: 4.4.0(supports-color@8.1.1)
+ fast-safe-stringify: 2.1.1
+ form-data: 4.0.2
+ formidable: 3.5.4
+ methods: 1.1.2
+ mime: 2.6.0
+ qs: 6.14.0
+ transitivePeerDependencies:
+ - supports-color
+
+ supertest@7.1.0:
+ dependencies:
+ methods: 1.1.2
+ superagent: 9.0.2
+ transitivePeerDependencies:
+ - supports-color
+
supports-color@5.5.0:
dependencies:
has-flag: 3.0.0
@@ -21829,7 +21927,7 @@ snapshots:
app-root-path: 3.1.0
buffer: 6.0.3
dayjs: 1.11.13
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
dotenv: 16.4.7
glob: 10.4.5
reflect-metadata: 0.2.2
@@ -22023,7 +22121,7 @@ snapshots:
vite-node@3.1.2(@types/node@22.15.2)(sass@1.87.0)(terser@5.39.0)(tsx@4.19.3):
dependencies:
cac: 6.7.14
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
es-module-lexer: 1.6.0
pathe: 2.0.3
vite: 6.3.3(@types/node@22.15.2)(sass@1.87.0)(terser@5.39.0)(tsx@4.19.3)
@@ -22072,7 +22170,7 @@ snapshots:
'@vitest/spy': 3.1.2
'@vitest/utils': 3.1.2
chai: 5.2.0
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
expect-type: 1.2.1
magic-string: 0.30.17
pathe: 2.0.3
@@ -22159,7 +22257,7 @@ snapshots:
vue-eslint-parser@10.1.3(eslint@9.25.1):
dependencies:
- debug: 4.4.0(supports-color@5.5.0)
+ debug: 4.4.0(supports-color@8.1.1)
eslint: 9.25.1
eslint-scope: 8.3.0
eslint-visitor-keys: 4.2.0
From 7407d51df84eb5c88465279e52ffe206a7e77a32 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 29 Apr 2025 08:15:29 +0900
Subject: [PATCH 183/190] New Crowdin updates (#15897)
* New translations ja-jp.yml (Thai)
* New translations ja-jp.yml (Spanish)
* New translations ja-jp.yml (Chinese Traditional)
* New translations ja-jp.yml (English)
* New translations ja-jp.yml (Catalan)
* New translations ja-jp.yml (Vietnamese)
* New translations ja-jp.yml (German)
* New translations ja-jp.yml (Vietnamese)
* New translations ja-jp.yml (Chinese Simplified)
---
locales/ca-ES.yml | 1 +
locales/de-DE.yml | 1 +
locales/en-US.yml | 1 +
locales/es-ES.yml | 27 ++++++++++++++++++++++
locales/th-TH.yml | 11 +++++++++
locales/vi-VN.yml | 58 +++++++++++++++++++++++++++++++++++++++++++++++
locales/zh-CN.yml | 1 +
locales/zh-TW.yml | 1 +
8 files changed, 101 insertions(+)
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 61840724a2..23d20b8e13 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -1346,6 +1346,7 @@ settingsMigrating: "Estem migrant la teva configuració. Si us plau espera un mo
readonly: "Només lectura"
goToDeck: "Tornar al tauler"
federationJobs: "Treballs sindicats "
+driveAboutTip: "Al Disc veure's una llista de tots els arxius que has anat pujant.
\nPots tornar-los a fer servir adjuntant-los a notes noves o pots adelantar-te i pujar arxius per publicar-los més tard!
\nTingués en compte que si esborres un arxiu també desapareixerà de tots els llocs on l'has fet servir (notes, pàgines, avatars, imatges de capçalera, etc.)
\nTambé pots crear carpetes per organitzar les."
_chat:
noMessagesYet: "Encara no tens missatges "
newMessage: "Missatge nou"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index fa68309d58..41c3f495a2 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -1346,6 +1346,7 @@ settingsMigrating: "Ihre Einstellungen werden gerade migriert, Bitte warten Sie
readonly: "Nur Lesezugriff"
goToDeck: "Zurück zum Deck"
federationJobs: "Föderation Jobs"
+driveAboutTip: "In Drive sehen Sie eine Liste der Dateien, die Sie in der Vergangenheit hochgeladen haben.
\nSie können diese Dateien wiederverwenden um sie zu beispiel an Notizen anzuhängen, oder sie können Dateien vorab hochzuladen, um sie später zu versenden!
\nWenn Sie eine Datei löschen, verschwindet sie auch von allen Stellen, an denen Sie sie verwendet haben (Notizen, Seiten, Avatare, Banner usw.).
\nSie können auch Ordner erstellen, um sie zu organisieren."
_chat:
noMessagesYet: "Noch keine Nachrichten"
newMessage: "Neue Nachricht"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index f20501d698..8e4063ae8a 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1346,6 +1346,7 @@ settingsMigrating: "Settings are being migrated, please wait a moment... (You ca
readonly: "Read only"
goToDeck: "Return to Deck"
federationJobs: "Federation Jobs"
+driveAboutTip: "In Drive, a list of files you've uploaded in the past will be displayed.
\nYou can reuse these files when attaching them to notes, or you can upload files in advance to post later.
\nBe careful when deleting a file, as it will not be available in all places where it was used (such as notes, pages, avatars, banners, etc.).
\nYou can also create folders to organize your files."
_chat:
noMessagesYet: "No messages yet"
newMessage: "New message"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index 60f8c81043..e14f37d1d6 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -1326,6 +1326,9 @@ restore: "Restaurar"
syncBetweenDevices: "Sincronizar entre dispositivos"
preferenceSyncConflictTitle: "Los valores configurados existen en el servidor."
preferenceSyncConflictText: "Los ajustes de sincronización activados guardarán sus valores en el servidor. Sin embargo, hay valores existentes en el servidor. ¿Qué conjunto de valores desea sobrescribir?"
+preferenceSyncConflictChoiceServer: "Valores de configuración del servidor"
+preferenceSyncConflictChoiceDevice: "Valor configurado en el dispositivo"
+paste: "Pegar"
emojiPalette: "Paleta emoji"
postForm: "Formulario"
information: "Información"
@@ -1337,16 +1340,38 @@ top: "Arriba"
embed: "Insertar"
settingsMigrating: "La configuración está siendo migrada, por favor espera un momento... (También puedes migrar manualmente más tarde yendo a Ajustes otros migrar configuración antigua"
readonly: "Solo Lectura"
+goToDeck: "Volver al Deck"
+federationJobs: "Trabajos de Federación"
_chat:
noMessagesYet: "Aún no hay mensajes"
newMessage: "Mensajes nuevos"
individualChat: "Chat individual"
individualChat_description: "Mantén una conversación privada con otra persona."
+ roomChat: "Sala de Chat"
+ roomChat_description: "Una sala de chat que puede tener varias personas.\nTambién puedes invitar a personas que no permiten chats privados si aceptan la invitación."
+ createRoom: "Crear sala"
+ inviteUserToChat: "Invitar usuarios para empezar a chatear"
+ yourRooms: "Salas creadas"
+ joiningRooms: "Salas que te has unido"
invitations: "Invitar"
+ noInvitations: "No hay invitación."
+ history: "Historial"
noHistory: "No hay datos en el historial"
+ noRooms: "Sala no encontrada"
+ inviteUser: "Invitar usuarios"
+ sentInvitations: "Invitaciones enviadas"
+ join: "Unirse"
+ ignore: "Ignorar"
+ leave: "Dejar sala"
members: "Miembros"
+ searchMessages: "Buscar mensajes"
home: "Inicio"
send: "Enviar"
+ newline: "Nueva línea"
+ muteThisRoom: "Silenciar esta sala"
+ deleteRoom: "Borrar sala"
+ chatNotAvailableForThisAccountOrServer: "El chat no está habilitado en este servidor ni para esta cuenta."
+ chatIsReadOnlyForThisAccountOrServer: "El chat es de sólo lectura en esta instancia o esta cuenta. No puedes escribir nuevos mensajes ni crear/unirte a salas de chat."
chatNotAvailableInOtherAccount: "La función de chat está desactivada para el otro usuario."
cannotChatWithTheUser: "No se puede iniciar un chat con este usuario"
cannotChatWithTheUser_description: "El chat no está disponible o la otra parte no ha habilitado el chat."
@@ -1365,9 +1390,11 @@ _chat:
none: "Nadie"
_emojiPalette:
palettes: "Paleta\n"
+ enableSyncBetweenDevicesForPalettes: "Activar la sincronización de paletas entre dispositivos"
_settings:
api: "API"
webhook: "Webhook"
+ timelineAndNote: "Líneas del tiempo y notas"
makeEveryTextElementsSelectable_description: "Activar esta opción puede reducir la usabilidad en algunas situaciones."
useStickyIcons: "Hacer que los iconos te sigan cuando desplaces"
showNavbarSubButtons: "Mostrar los sub-botones en la barra de navegación."
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index e872fa5833..6398268274 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -49,6 +49,7 @@ pin: "ปักหมุด"
unpin: "เลิกปักหมุด"
copyContent: "คัดลอกเนื้อหา"
copyLink: "คัดลอกลิงก์"
+copyRemoteLink: "คัดลอกลิงค์ระยะไกล"
copyLinkRenote: "คัดลอกลิงก์รีโน้ต"
delete: "ลบ"
deleteAndEdit: "ลบและแก้ไข"
@@ -681,10 +682,12 @@ smtpSecureInfo: "ปิดสิ่งนี้เมื่อใช้ STARTTLS
testEmail: "ทดสอบการส่งอีเมล"
wordMute: "ปิดเสียงคำ"
hardWordMute: "ปิดเสียงคำแบบแข็งโป๊ก"
+hardWordMuteDescription: "ซ่อนหมายเหตุที่มีวลีที่ระบุ ต่างจากการปิดเสียงคำ โน้ตต่างๆ จะถูกซ่อนไว้อย่างสมบูรณ์"
regexpError: "เกิดข้อผิดพลาดใน regular expression"
regexpErrorDescription: "เกิดข้อผิดพลาดใน regular expression บรรทัดที่ {line} ของการปิดเสียงคำ {tab} :"
instanceMute: "ปิดเสียงเซิร์ฟเวอร์"
userSaysSomething: "{name} พูดอะไรบางอย่าง"
+userSaysSomethingAbout: "{name} พูดอะไรบางอย่างเกี่ยวกับ \"{word}\""
makeActive: "เปิดใช้งาน"
display: "แสดงผล"
copy: "คัดลอก"
@@ -1290,6 +1293,9 @@ prohibitedWordsForNameOfUserDescription: "หากมีสตริงใด
yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม"
yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ"
federationDisabled: "เซิร์ฟเวอร์นี้ปิดการใช้งานการรวมกลุ่ม คุณไม่สามารถโต้ตอบกับผู้ใช้บนเซิร์ฟเวอร์อื่นได้"
+reactAreYouSure: "คุณต้องการที่จะตอบสนองต่อ \" {emoji}\" หรือไม่?"
+markAsSensitiveConfirm: "คุณต้องการทำเครื่องหมายสื่อนี้ว่าละเอียดอ่อนหรือไม่?"
+unmarkAsSensitiveConfirm: "คุณต้องการลบการกำหนดความไวของสื่อนี้หรือไม่?"
postForm: "แบบฟอร์มการโพสต์"
information: "เกี่ยวกับ"
right: "ขวา"
@@ -1302,6 +1308,11 @@ _chat:
send: "ส่ง"
_settings:
webhook: "Webhook"
+_accountSettings:
+ requireSigninToViewContents: "ต้องเข้าสู่ระบบเพื่อดูเนื้อหา"
+ requireSigninToViewContentsDescription1: "ต้องเข้าสู่ระบบเพื่อดูบันทึกและเนื้อหาอื่น ๆ ทั้งหมดที่คุณสร้าง คาดว่าจะมีประสิทธิผลในการป้องกันไม่ให้ข้อมูลถูกเก็บรวบรวมโดยโปรแกรมรวบรวมข้อมูล"
+ requireSigninToViewContentsDescription2: "นอกจากนี้ จะไม่สามารถดูจากเซิร์ฟเวอร์ที่ไม่รองรับการดูตัวอย่าง URL (OGP), การฝังในหน้าเว็บ หรือการอ้างอิงหมายเหตุได้"
+ requireSigninToViewContentsDescription3: "เนื้อหาที่ถูกรวมเข้ากับเซิร์ฟเวอร์ระยะไกลอาจไม่อยู่ภายใต้ข้อจำกัดเหล่านี้"
_abuseUserReport:
forward: "ส่งต่อ"
forwardDescription: "ส่งรายงานไปยังเซิร์ฟเวอร์ระยะไกลโดยใช้บัญชีระบบที่ไม่ระบุตัวตน"
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index e2a41d952d..525c3c233f 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -120,6 +120,8 @@ cantReRenote: "Không thể đăng lại một tút đăng lại."
quote: "Trích dẫn"
inChannelRenote: "Chia sẻ trong kênh này"
inChannelQuote: "Trích dẫn trong kênh này"
+renoteToChannel: "Đăng lại tới kênh"
+renoteToOtherChannel: "Đăng lại tới kênh khác"
pinnedNote: "Bài viết đã ghim"
pinned: "Ghim"
you: "Bạn"
@@ -129,7 +131,11 @@ add: "Thêm"
reaction: "Biểu cảm"
reactions: "Biểu cảm"
emojiPicker: "Bộ chọn biểu tượng cảm xúc"
+pinnedEmojisForReactionSettingDescription: "Ghim các biểu tượng cảm xúc sẽ hiển thị khi phản hồi"
+pinnedEmojisSettingDescription: "Ghim các biểu tượng cảm xúc sẽ hiển thị trong bảng chọn emoji"
emojiPickerDisplay: "Hiển thị bộ chọn"
+overwriteFromPinnedEmojisForReaction: "Ghi đè thiết lập phản hồi"
+overwriteFromPinnedEmojis: "Ghi đè thiết lập chung"
reactionSettingDescription2: "Kéo để sắp xếp, nhấn để xóa, nhấn \"+\" để thêm."
rememberNoteVisibility: "Lưu kiểu tút mặc định"
attachCancel: "Gỡ tập tin đính kèm"
@@ -154,6 +160,7 @@ editList: "Chỉnh sửa danh sách"
selectChannel: "Lựa chọn kênh"
selectAntenna: "Chọn một antenna"
editAntenna: "Chỉnh sửa Ăngten"
+createAntenna: "Tạo Ăngten "
selectWidget: "Chọn tiện ích"
editWidgets: "Sửa tiện ích"
editWidgetsExit: "Xong"
@@ -182,6 +189,8 @@ loginFailed: "Đăng nhập không thành công"
showOnRemote: "Truy cập trang của người này"
continueOnRemote: "Tiếp tục trên phiên bản từ xa"
chooseServerOnMisskeyHub: "Chọn một máy chủ từ Misskey Hub"
+specifyServerHost: "Thiết lập một máy chủ"
+inputHostName: "Nhập địa chỉ máy chủ"
general: "Tổng quan"
wallpaper: "Ảnh bìa"
setWallpaper: "Đặt ảnh bìa"
@@ -208,6 +217,7 @@ perDay: "Mỗi Ngày"
stopActivityDelivery: "Ngưng gửi hoạt động"
blockThisInstance: "Chặn máy chủ này"
silenceThisInstance: "Máy chủ im lặng"
+mediaSilenceThisInstance: "Tắt nội dung đa phương tiện từ máy chủ này"
operations: "Vận hành"
software: "Phần mềm"
version: "Phiên bản"
@@ -229,6 +239,10 @@ blockedInstances: "Máy chủ đã chặn"
blockedInstancesDescription: "Danh sách những máy chủ bạn muốn chặn. Chúng sẽ không thể giao tiếp với máy chủy này nữa."
silencedInstances: "Máy chủ im lặng"
silencedInstancesDescription: "Đặt máy chủ mà bạn muốn tắt tiếng, phân tách bằng dấu xuống dòng. Tất cả tài khoản trên máy chủ bị tắt tiếng sẽ được coi là \"bị tắt tiếng\" và mọi hành động theo dõi sẽ được coi là yêu cầu. Không có tác dụng với những trường hợp bị chặn."
+mediaSilencedInstances: "Các máy chủ đã tắt nội dung đa phương tiện "
+mediaSilencedInstancesDescription: "Đặt máy chủ mà bạn muốn tắt nội dung đa phương tiện, phân tách bằng dấu xuống dòng. Tất cả tài khoản trên máy chủ bị tắt tiếng sẽ được coi là \"nhạy cảm\" và biểu tượng cảm xúc tùy chỉnh sẽ không thể được sử dụng. Không có tác dụng với những trường hợp bị chặn."
+federationAllowedHosts: "Các máy chủ được phép liên kết"
+federationAllowedHostsDescription: "Điền tên các máy chủ mà bạn muốn cho phép liên kết, cách nhau bởi dấu xuống dòng"
muteAndBlock: "Ẩn và Chặn"
mutedUsers: "Người đã ẩn"
blockedUsers: "Người đã chặn"
@@ -287,6 +301,7 @@ uploadFromUrlMayTakeTime: "Sẽ mất một khoảng thời gian để tải lê
explore: "Khám phá"
messageRead: "Đã đọc"
noMoreHistory: "Không còn gì để đọc"
+startChat: "Bắt đầu trò chuyện"
nUsersRead: "đọc bởi {n}"
agreeTo: "Tôi đồng ý {0}"
agree: "Đồng ý"
@@ -317,6 +332,7 @@ selectFile: "Chọn tập tin"
selectFiles: "Chọn nhiều tập tin"
selectFolder: "Chọn thư mục"
selectFolders: "Chọn nhiều thư mục"
+fileNotSelected: "Chưa chọn tệp nào"
renameFile: "Đổi tên tập tin"
folderName: "Tên thư mục"
createFolder: "Tạo thư mục"
@@ -324,6 +340,7 @@ renameFolder: "Đổi tên thư mục"
deleteFolder: "Xóa thư mục"
folder: "Thư mục"
addFile: "Thêm tập tin"
+showFile: "Hiển thị tập tin"
emptyDrive: "Ổ đĩa của bạn trống trơn"
emptyFolder: "Thư mục trống"
unableToDelete: "Không thể xóa"
@@ -407,6 +424,7 @@ antennaExcludeBots: "Loại trừ các tài khoản bot"
antennaKeywordsDescription: "Phân cách bằng dấu cách cho điều kiện AND hoặc bằng xuống dòng cho điều kiện OR."
notifyAntenna: "Thông báo có tút mới"
withFileAntenna: "Chỉ những tút có media"
+excludeNotesInSensitiveChannel: "Không hiển thị trong kênh nhạy cảm"
enableServiceworker: "Bật ServiceWorker"
antennaUsersDescription: "Liệt kê mỗi hàng một tên người dùng"
caseSensitive: "Trường hợp nhạy cảm"
@@ -475,6 +493,7 @@ quoteQuestion: "Trích dẫn lại?"
attachAsFileQuestion: "Văn bản ở trong bộ nhớ tạm rất dài. Bạn có muốn đăng nó dưới dạng một tệp văn bản không?"
onlyOneFileCanBeAttached: "Bạn chỉ có thể đính kèm một tập tin"
signinRequired: "Vui lòng đăng nhập"
+signinOrContinueOnRemote: "Để tiếp tục, bạn cần chuyển máy chủ hoặc đăng nhập/đăng ký ở máy chủ này."
invitations: "Mời"
invitationCode: "Mã mời"
checking: "Đang kiểm tra..."
@@ -496,7 +515,12 @@ uiLanguage: "Ngôn ngữ giao diện"
aboutX: "Giới thiệu {x}"
emojiStyle: "Kiểu cách Emoji"
native: "Bản xứ"
+menuStyle: "Kiểu Menu"
+style: "Phong cách"
+drawer: "Ngăn ứng dụng"
+popup: "Cửa sổ bật lên"
showNoteActionsOnlyHover: "Chỉ hiển thị các hành động ghi chú khi di chuột"
+showReactionsCount: "Hiển thị số reaction trong bài đăng"
noHistory: "Không có dữ liệu"
signinHistory: "Lịch sử đăng nhập"
enableAdvancedMfm: "Xem bài MFM chất lượng cao."
@@ -509,6 +533,7 @@ createAccount: "Tạo tài khoản"
existingAccount: "Tài khoản hiện có"
regenerate: "Tạo lại"
fontSize: "Cỡ chữ"
+mediaListWithOneImageAppearance: "Chiều cao của danh sách nội dung đã phương tiện mà chỉ có một hình ảnh"
limitTo: "Giới hạn tỷ lệ {x}"
noFollowRequests: "Bạn không có yêu cầu theo dõi nào"
openImageInNewTab: "Mở ảnh trong tab mới"
@@ -543,10 +568,12 @@ objectStorageUseSSLDesc: "Tắt nếu bạn không dùng HTTPS để kết nối
objectStorageUseProxy: "Kết nối thông qua Proxy"
objectStorageUseProxyDesc: "Tắt nếu bạn không dùng Proxy để kết nối API"
objectStorageSetPublicRead: "Đặt \"public-read\" khi tải lên"
+s3ForcePathStyleDesc: "Nếu s3ForcePathStyle được bật, tên bucket phải được thêm vào địa chỉ URL thay vì chỉ có tên miền. Bạn có thể phải sử dụng thiết lập này nếu bạn sử dụng các dịch vụ như Minio mà bạn tự cung cấp."
serverLogs: "Nhật ký máy chủ"
deleteAll: "Xóa tất cả"
showFixedPostForm: "Hiện khung soạn tút ở phía trên bảng tin"
showFixedPostFormInChannel: "Hiển thị mẫu bài đăng ở phía trên bản tin"
+withRepliesByDefaultForNewlyFollowed: "Mặc định hiển thị trả lời từ những người dùng mới theo dõi trong dòng thời gian"
newNoteRecived: "Đã nhận tút mới"
sounds: "Âm thanh"
sound: "Âm thanh"
@@ -557,6 +584,7 @@ popout: "Pop-out"
volume: "Âm lượng"
masterVolume: "Âm thanh chung"
notUseSound: "Tắt tiếng"
+useSoundOnlyWhenActive: "Chỉ phát âm thanh khi Misskey đang được hiển thị"
details: "Chi tiết"
renoteDetails: "Tìm hiểu thêm về đăng lại "
chooseEmoji: "Chọn emoji"
@@ -574,6 +602,7 @@ ascendingOrder: "Tăng dần"
descendingOrder: "Giảm dần"
scratchpad: "Scratchpad"
scratchpadDescription: "Scratchpad cung cấp môi trường cho các thử nghiệm AiScript. Bạn có thể viết, thực thi và kiểm tra kết quả tương tác với Misskey trong đó."
+uiInspector: "Trình kiểm tra UI"
output: "Nguồn ra"
script: "Kịch bản"
disablePagesScript: "Tắt AiScript trên Trang"
@@ -632,6 +661,7 @@ medium: "Vừa"
small: "Nhỏ"
generateAccessToken: "Tạo mã truy cập"
permission: "Cho phép "
+adminPermission: "Quyền quản trị viên"
enableAll: "Bật toàn bộ"
disableAll: "Tắt toàn bộ"
tokenRequested: "Cấp quyền truy cập vào tài khoản"
@@ -653,6 +683,10 @@ smtpSecure: "Dùng SSL/TLS ngầm định cho các kết nối SMTP"
smtpSecureInfo: "Tắt cái này nếu dùng STARTTLS"
testEmail: "Kiểm tra vận chuyển email"
wordMute: "Ẩn chữ"
+wordMuteDescription: "Thu nhỏ các bài đăng chứa các từ hoặc cụm từ nhất định. Các bài đăng này có thể được hiển thị khi click vào."
+hardWordMute: "Ẩn cụm từ hoàn toàn"
+showMutedWord: "Hiển thị từ đã ẩn"
+hardWordMuteDescription: "Ẩn hoàn toàn các bài đăng chứa từ hoặc cụm từ. Khác với mute, bài đăng sẽ bị ẩn hoàn toàn."
regexpError: "Lỗi biểu thức"
regexpErrorDescription: "Xảy ra lỗi biểu thức ở dòng {line} của {tab} chữ ẩn:"
instanceMute: "Những máy chủ ẩn"
@@ -676,12 +710,14 @@ useGlobalSettingDesc: "Nếu được bật, cài đặt thông báo của bạn
other: "Khác"
regenerateLoginToken: "Tạo lại mã đăng nhập"
regenerateLoginTokenDescription: "Tạo lại mã nội bộ có thể dùng để đăng nhập. Thông thường hành động này là không cần thiết. Nếu được tạo lại, tất cả các thiết bị sẽ bị đăng xuất."
+theKeywordWhenSearchingForCustomEmoji: "Đây là từ khoá được sử dụng để tìm kiếm emoji"
setMultipleBySeparatingWithSpace: "Tách nhiều mục nhập bằng dấu cách."
fileIdOrUrl: "ID tập tin hoặc URL"
behavior: "Thao tác"
sample: "Ví dụ"
abuseReports: "Lượt báo cáo"
reportAbuse: "Báo cáo"
+reportAbuseRenote: "Báo cáo bài đăng lại"
reportAbuseOf: "Báo cáo {name}"
fillAbuseReportDescription: "Vui lòng điền thông tin chi tiết về báo cáo này. Nếu đó là về một tút cụ thể, hãy kèm theo URL của tút."
abuseReported: "Báo cáo đã được gửi. Cảm ơn bạn nhiều."
@@ -731,6 +767,7 @@ lockedAccountInfo: "Ghi chú của bạn sẽ hiển thị với bất kỳ ai,
alwaysMarkSensitive: "Luôn đánh dấu NSFW"
loadRawImages: "Tải ảnh gốc thay vì ảnh thu nhỏ"
disableShowingAnimatedImages: "Không phát ảnh động"
+highlightSensitiveMedia: "Đánh dấu nội dung nhạy cảm"
verificationEmailSent: "Một email xác minh đã được gửi. Vui lòng nhấn vào liên kết đính kèm để hoàn tất xác minh."
notSet: "Chưa đặt"
emailVerified: "Email đã được xác minh"
@@ -824,6 +861,7 @@ administration: "Quản lý"
accounts: "Tài khoản của bạn"
switch: "Chuyển đổi"
noMaintainerInformationWarning: "Chưa thiết lập thông tin vận hành."
+noInquiryUrlWarning: "Địa chỉ hỏi đáp chưa được đặt"
noBotProtectionWarning: "Bảo vệ Bot chưa thiết lập."
configure: "Thiết lập"
postToGallery: "Tạo tút có ảnh"
@@ -888,6 +926,7 @@ followersVisibility: "Hiển thị người theo dõi"
continueThread: "Tiếp tục xem chuỗi tút"
deleteAccountConfirm: "Điều này sẽ khiến tài khoản bị xóa vĩnh viễn. Vẫn tiếp tục?"
incorrectPassword: "Sai mật khẩu."
+incorrectTotp: "Mã OTP không đúng hoặc đã quá hạn"
voteConfirm: "Xác nhận bình chọn \"{choice}\"?"
hide: "Ẩn"
useDrawerReactionPickerForMobile: "Hiện bộ chọn biểu cảm dạng xổ ra trên điện thoại"
@@ -912,6 +951,9 @@ oneHour: "1 giờ"
oneDay: "1 ngày"
oneWeek: "1 tuần"
oneMonth: "1 tháng"
+threeMonths: "3 tháng"
+oneYear: "1 năm"
+threeDays: "3 ngày "
reflectMayTakeTime: "Có thể mất một thời gian để điều này được áp dụng."
failedToFetchAccountInformation: "Không thể lấy thông tin tài khoản"
rateLimitExceeded: "Giới hạn quá mức"
@@ -936,6 +978,7 @@ document: "Tài liệu"
numberOfPageCache: "Số lượng trang bộ nhớ đệm"
numberOfPageCacheDescription: "Việc tăng con số này sẽ cải thiện sự thuận tiện cho người dùng nhưng gây ra nhiều áp lực hơn cho máy chủ cũng như sử dụng nhiều bộ nhớ hơn."
logoutConfirm: "Bạn có chắc muốn đăng xuất?"
+logoutWillClearClientData: "Đăng xuất sẽ xoá các thiết lập của bạn khỏi trình duyệt. Để có thể khôi phục thiết lập khi đăng nhập lại, bạn phải bật tự động sao lưu cài đặt."
lastActiveDate: "Lần cuối vào"
statusbar: "Thanh trạng thái"
pleaseSelect: "Chọn một lựa chọn"
@@ -985,6 +1028,7 @@ neverShow: "Không hiển thị nữa"
remindMeLater: "Để sau"
didYouLikeMisskey: "Bạn có ưa thích Mískey không?"
pleaseDonate: "Misskey là phần mềm miễn phí mà {host} đang sử dụng. Xin mong bạn quyên góp cho chúng tôi để chúng tôi có thể tiếp tục phát triển dịch vụ này. Xin cảm ơn!!"
+correspondingSourceIsAvailable: "Mã nguồn có thể được xem tại {anchor}"
roles: "Vai trò"
role: "Vai trò"
noRole: "Bạn chưa được cấp quyền."
@@ -1012,6 +1056,7 @@ thisPostMayBeAnnoyingHome: "Đăng trên trang chính"
thisPostMayBeAnnoyingCancel: "Từ chối"
thisPostMayBeAnnoyingIgnore: "Đăng bài để nguyên"
collapseRenotes: "Không hiển thị bài viết đã từng xem"
+collapseRenotesDescription: "Các bài đăng bị thu gọn mà bạn đã phản hồi hoặc đăng lại trước đây."
internalServerError: "Lỗi trong chủ máy"
internalServerErrorDescription: "Trong chủ máy lỗi bất ngờ xảy ra"
copyErrorInfo: "Sao chép thông tin lỗi"
@@ -1027,12 +1072,25 @@ postToTheChannel: "Đăng lên kênh"
cannotBeChangedLater: "Không thể thay đổi sau này."
reactionAcceptance: "Phản ứng chấp nhận"
likeOnly: "Chỉ lượt thích"
+likeOnlyForRemote: "Tất cả (chỉ bao gồm lượt thích trên các máy chủ khác)"
+nonSensitiveOnly: "Chỉ nội dung không nhạy cảm"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "Chỉ nội dung không nhạy cảm (chỉ bao gồm lượt thích từ máy chủ khác)"
rolesAssignedToMe: "Vai trò được giao cho tôi"
resetPasswordConfirm: "Bạn thực sự muốn đặt lại mật khẩu?"
sensitiveWords: "Các từ nhạy cảm"
+sensitiveWordsDescription: "Phạm vi của tất cả bài đăng chứa các từ được cấu hình sẽ tự động được đặt về \"Home\". Ban có thể thêm nhiều từ trên mỗi dòng."
+sensitiveWordsDescription2: "Sử dụng dấu cách sẽ tạo cấu trúc AND và thêm dấu gạch xuôi để sử dụng như một regex."
prohibitedWords: "Các từ bị cấm"
+prohibitedWordsDescription: "Hiển thị lỗi khi đăng một bài đăng chứa các từ sau. Nhiều từ có thể được thêm bằng cách viết một từ trên mỗi dòng."
+prohibitedWordsDescription2: "Sử dụng dấu cách sẽ tạo cấu trúc AND và thêm dấu gạch xuôi để sử dụng như một regex."
+hiddenTags: "Hashtag ẩn"
+hiddenTagsDescription: "Các hashtag này sẽ không được hiển thị trên danh sách Trending. Nhiều tag có thể được thêm bằng cách viết một tag trên mỗi dòng."
+notesSearchNotAvailable: "Tìm kiếm bài đăng hiện không khả dụng."
license: "Giấy phép"
unfavoriteConfirm: "Bạn thực sự muốn xoá khỏi mục yêu thích?"
+myClips: "Các clip của tôi"
+drivecleaner: "Trình dọn đĩa"
+retryAllQueuesNow: "Thử lại cho tất cả hàng chờ"
retryAllQueuesConfirmTitle: "Bạn có muốn thử lại?"
retryAllQueuesConfirmText: "Điều này sẽ tạm thời làm tăng mức độ tải của máy chủ."
enableChartsForRemoteUser: "Tạo biểu đồ người dùng từ xa"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index 6c0f42ae15..1508fca431 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -1346,6 +1346,7 @@ settingsMigrating: "正在迁移设置,请稍候。(之后也可以在设置
readonly: "只读"
goToDeck: "返回至 Deck"
federationJobs: "联合作业"
+driveAboutTip: "网盘可以显示以前上传的文件。
\n也可以在发布帖子时重复使用文件,或在发布帖子前预先上传文件。
\n删除文件时,其将从至今为止所有用到该文件的地方(如帖子、页面、头像、横幅)消失。
\n也可以新建文件夹来整理文件。"
_chat:
noMessagesYet: "还没有消息"
newMessage: "新消息"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 4dde465351..152f34feeb 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -1346,6 +1346,7 @@ settingsMigrating: "正在移轉設定。請稍候……(之後也可以到「
readonly: "唯讀"
goToDeck: "回去甲板"
federationJobs: "聯邦通訊作業"
+driveAboutTip: "在「雲端硬碟」中,會顯示過去上傳的檔案列表。
\n可以在附加到貼文時重新利用,或者事先上傳之後再用於發布。
\n請注意,刪除檔案後,之前使用過該檔案的所有地方(貼文、頁面、大頭貼、橫幅等)也會一併無法顯示。
\n也可以建立資料夾來整理檔案。"
_chat:
noMessagesYet: "尚無訊息"
newMessage: "新訊息"
From d10fdfe9738b17a9d81037c031b40a2cc4cb8038 Mon Sep 17 00:00:00 2001
From: Julia
Date: Mon, 28 Apr 2025 19:15:54 -0400
Subject: [PATCH 184/190] Merge commit from fork
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* SP-2025-03.1 always wrap icon&thumbnail URLs
if they're not HTTP URLs, the frontend won't be able to display them
anyway (`
` or '` aren't going to work!), so let's always run them through the
media proxy, which will fail harder (fetching a `javascript:` URL
won't do anything in the backend, might do something in the frontend)
and will always protect the client's address in cases like `gemini:`
where the browser could try to fetch
* SP-2025-03.2 use object binding for more styles
interpolating a random (remote-controlled!) string into a `style`
attribute is a bad idea; using VueJS object binding, we should get
proper quoting and therefore safe parse failures instead of CSS
injections / XSS
* SP-2025-03.3 slightly more robust "self" URL handling
parse URLs instead of treating them as strings; this is still not
perfect, but the `URL` class only handles full URLs, not relative
ones, so there's so way to ask it "give me a URL object that
represents this resource relative to this base URL"
notice that passing very weird URLs to `MkUrl` and `MkUrlPreview` will
break the frontend (in dev mode) because there's an untrapped `new
URL(…)` that may explode; production builds seem to safely ignore the
error, though
---------
Co-authored-by: dakkar
---
.../backend/src/server/web/UrlPreviewService.ts | 10 ++++------
packages/frontend-shared/js/url.ts | 17 +++++++++++++++++
packages/frontend/src/components/MkLink.vue | 6 ++++--
.../frontend/src/components/MkUrlPreview.vue | 8 +++++---
packages/frontend/src/components/MkUserInfo.vue | 2 +-
.../frontend/src/components/MkUserPopup.vue | 2 +-
.../src/components/MkUserSetupDialog.User.vue | 2 +-
.../frontend/src/components/global/MkUrl.vue | 6 ++++--
packages/frontend/src/widgets/WidgetPhotos.vue | 2 +-
.../src/widgets/server-metric/cpu-mem.vue | 4 ++--
10 files changed, 40 insertions(+), 19 deletions(-)
diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts
index 9b5f0acd2c..531d085315 100644
--- a/packages/backend/src/server/web/UrlPreviewService.ts
+++ b/packages/backend/src/server/web/UrlPreviewService.ts
@@ -37,12 +37,10 @@ export class UrlPreviewService {
@bindThis
private wrap(url?: string | null): string | null {
return url != null
- ? url.match(/^https?:\/\//)
- ? `${this.config.mediaProxy}/preview.webp?${query({
- url,
- preview: '1',
- })}`
- : url
+ ? `${this.config.mediaProxy}/preview.webp?${query({
+ url,
+ preview: '1',
+ })}`
: null;
}
diff --git a/packages/frontend-shared/js/url.ts b/packages/frontend-shared/js/url.ts
index eb830b1eea..e4f9ca513d 100644
--- a/packages/frontend-shared/js/url.ts
+++ b/packages/frontend-shared/js/url.ts
@@ -26,3 +26,20 @@ export function extractDomain(url: string) {
const match = url.match(/^(?:https?:)?(?:\/\/)?(?:[^@\n]+@)?([^:\/\n]+)/im);
return match ? match[1] : null;
}
+
+export function maybeMakeRelative(urlStr: string, baseStr: string): string {
+ try {
+ const baseObj = new URL(baseStr);
+ const urlObj = new URL(urlStr);
+ /* in all places where maybeMakeRelative is used, baseStr is the
+ * instance's public URL, which can't have path components, so the
+ * relative URL will always have the whole path from the urlStr
+ */
+ if (urlObj.origin === baseObj.origin) {
+ return urlObj.pathname + urlObj.search + urlObj.hash;
+ }
+ return urlStr;
+ } catch (e) {
+ return '';
+ }
+}
diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue
index 4bac2bcea4..4cbf289448 100644
--- a/packages/frontend/src/components/MkLink.vue
+++ b/packages/frontend/src/components/MkLink.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
@@ -21,6 +21,7 @@ import { useTooltip } from '@/use/use-tooltip.js';
import * as os from '@/os.js';
import { isEnabledUrlPreview } from '@/instance.js';
import type { MkABehavior } from '@/components/global/MkA.vue';
+import { maybeMakeRelative } from '@@/js/url.js';
const props = withDefaults(defineProps<{
url: string;
@@ -29,7 +30,8 @@ const props = withDefaults(defineProps<{
}>(), {
});
-const self = props.url.startsWith(local);
+const maybeRelativeUrl = maybeMakeRelative(props.url, local);
+const self = maybeRelativeUrl !== props.url;
const attr = self ? 'to' : 'href';
const target = self ? null : '_blank';
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index 20dab6f028..71c8a6a6e8 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -44,8 +44,8 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
+
+
@@ -94,6 +94,7 @@ import MkButton from '@/components/MkButton.vue';
import { transformPlayerUrl } from '@/utility/player-url-transform.js';
import { store } from '@/store.js';
import { prefer } from '@/preferences.js';
+import { maybeMakeRelative } from '@@/js/url.js';
type SummalyResult = Awaited>;
@@ -111,7 +112,8 @@ const props = withDefaults(defineProps<{
const MOBILE_THRESHOLD = 500;
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
-const self = props.url.startsWith(local);
+const maybeRelativeUrl = maybeMakeRelative(props.url, local);
+const self = maybeRelativeUrl !== props.url;
const attr = self ? 'to' : 'href';
const target = self ? null : '_blank';
const fetching = ref(true);
diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue
index cff531b2ca..69144d3824 100644
--- a/packages/frontend/src/components/MkUserInfo.vue
+++ b/packages/frontend/src/components/MkUserInfo.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue
index 17a882a3a6..3bd2a2ffae 100644
--- a/packages/frontend/src/components/MkUserPopup.vue
+++ b/packages/frontend/src/components/MkUserPopup.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
{ emit('mouseover'); }" @mouseleave="() => { emit('mouseleave'); }">
-
+
{{ i18n.ts.followsYou }}
@@ -67,7 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
MEM {{ memP }}%
From 4408d070df1e8d39d6e5727ac174c3dc84f172c6 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 29 Apr 2025 08:20:48 +0900
Subject: [PATCH 185/190] Update CHANGELOG.md
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f52bf43171..f6d56e50a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
- Enhance: サーバーブロックの対象になっているサーバーについて、当該サーバーのユーザーや既知投稿を見えないように
- Enhance: 依存関係の更新
- Enhance: 翻訳の更新
+- Fix: セキュリティに関する修正
### Client
- Feat: チャットウィジェットを追加
From 2e91cd6d452bd729932c906e2d7ccacab5e44a2e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Mon, 28 Apr 2025 23:26:26 +0000
Subject: [PATCH 186/190] Bump version to 2025.4.1-beta.8
---
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 2cbb3b41a0..e6f4522525 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "misskey",
- "version": "2025.4.1-beta.7",
+ "version": "2025.4.1-beta.8",
"codename": "nasubi",
"repository": {
"type": "git",
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 16a7170069..3cf6827f9c 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
- "version": "2025.4.1-beta.7",
+ "version": "2025.4.1-beta.8",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
From d6ae4c980ba0e7893af4a2c327cad367cda24e5c Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 29 Apr 2025 09:43:15 +0900
Subject: [PATCH 187/190] =?UTF-8?q?feat(frontend):=20=E3=82=BF=E3=82=A4?=
=?UTF-8?q?=E3=83=88=E3=83=AB=E3=83=90=E3=83=BC=E3=82=92=E8=A1=A8=E7=A4=BA?=
=?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGELOG.md | 1 +
packages/backend/src/server/web/style.css | 1 +
.../backend/src/server/web/style.embed.css | 1 +
.../src/pages/settings/preferences.vue | 9 ++
packages/frontend/src/preferences/def.ts | 3 +
packages/frontend/src/ui/_common_/common.vue | 2 +-
.../src/ui/_common_/navbar-for-mobile.vue | 1 +
packages/frontend/src/ui/_common_/navbar.vue | 22 +--
.../frontend/src/ui/_common_/titlebar.vue | 87 +++++++++++
packages/frontend/src/ui/deck.vue | 142 +++++++++---------
packages/frontend/src/ui/universal.vue | 44 ++++--
11 files changed, 213 insertions(+), 100 deletions(-)
create mode 100644 packages/frontend/src/ui/_common_/titlebar.vue
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f6d56e50a4..05704b509c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@
### Client
- Feat: チャットウィジェットを追加
- Feat: デッキにチャットカラムを追加
+- Feat: タイトルバーを表示できるように
- Enhance: Unicode絵文字をslugから入力する際に`:ok:`のように最後の`:`を入力したあとにUnicode絵文字に変換できるように
- Enhance: コントロールパネルでジョブキューをクリアできるように
- Enhance: テーマでページヘッダーの色を変更できるように
diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css
index 5d81f2bed0..8e63a2ea66 100644
--- a/packages/backend/src/server/web/style.css
+++ b/packages/backend/src/server/web/style.css
@@ -31,6 +31,7 @@ html {
margin: auto;
width: 64px;
height: 64px;
+ border-radius: 10px;
pointer-events: none;
}
diff --git a/packages/backend/src/server/web/style.embed.css b/packages/backend/src/server/web/style.embed.css
index 5e8786cc4e..0911d562bf 100644
--- a/packages/backend/src/server/web/style.embed.css
+++ b/packages/backend/src/server/web/style.embed.css
@@ -53,6 +53,7 @@ html.embed.noborder #splash {
margin: auto;
width: 64px;
height: 64px;
+ border-radius: 10px;
pointer-events: none;
}
diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue
index f96accf68a..57b140f97b 100644
--- a/packages/frontend/src/pages/settings/preferences.vue
+++ b/packages/frontend/src/pages/settings/preferences.vue
@@ -42,6 +42,14 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
+ {{ i18n.ts.showTitlebar }}
+
+
+
+
@@ -742,6 +750,7 @@ const lang = ref(miLocalStorage.getItem('lang'));
const dataSaver = ref(prefer.s.dataSaver);
const overridedDeviceKind = prefer.model('overridedDeviceKind');
+const showTitlebar = prefer.model('showTitlebar');
const keepCw = prefer.model('keepCw');
const serverDisconnectedBehavior = prefer.model('serverDisconnectedBehavior');
const hemisphere = prefer.model('hemisphere');
diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts
index ac8996058f..73c6ff96c9 100644
--- a/packages/frontend/src/preferences/def.ts
+++ b/packages/frontend/src/preferences/def.ts
@@ -333,6 +333,9 @@ export const PREF_DEF = {
showNavbarSubButtons: {
default: true,
},
+ showTitlebar: {
+ default: false,
+ },
plugins: {
default: [] as Plugin[],
},
diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue
index d7d89d3f5c..5fe99e0d14 100644
--- a/packages/frontend/src/ui/_common_/common.vue
+++ b/packages/frontend/src/ui/_common_/common.vue
@@ -413,7 +413,7 @@ if ($i) {
#devTicker {
position: fixed;
- top: 0;
+ bottom: 0;
left: 0;
z-index: 2147483647;
color: #ff0;
diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
index e0cd58439e..826e03751a 100644
--- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
+++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
@@ -121,6 +121,7 @@ function more() {
display: inline-block;
width: 38px;
aspect-ratio: 1;
+ border-radius: 8px;
}
.bottom {
diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue
index 9c6cdecf5c..ce8efa3324 100644
--- a/packages/frontend/src/ui/_common_/navbar.vue
+++ b/packages/frontend/src/ui/_common_/navbar.vue
@@ -7,7 +7,6 @@ SPDX-License-Identifier: AGPL-3.0-only
-
@@ -183,12 +182,9 @@ function menuEdit() {
}
.body {
- position: fixed;
- top: 0;
- left: 0;
- z-index: 1001;
+ position: relative;
width: var(--nav-icon-only-width);
- height: 100dvh;
+ height: 100%;
box-sizing: border-box;
overflow: auto;
overflow-x: clip;
@@ -303,18 +299,6 @@ function menuEdit() {
backdrop-filter: var(--MI-blur, blur(8px));
}
- .banner {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-size: cover;
- background-position: center center;
- -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
- mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
- }
-
.instance {
position: relative;
display: block;
@@ -335,6 +319,7 @@ function menuEdit() {
display: inline-block;
width: 38px;
aspect-ratio: 1;
+ border-radius: 8px;
}
.bottom {
@@ -559,6 +544,7 @@ function menuEdit() {
display: inline-block;
width: 30px;
aspect-ratio: 1;
+ border-radius: 8px;
}
.bottom {
diff --git a/packages/frontend/src/ui/_common_/titlebar.vue b/packages/frontend/src/ui/_common_/titlebar.vue
new file mode 100644
index 0000000000..c62b13b73a
--- /dev/null
+++ b/packages/frontend/src/ui/_common_/titlebar.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+
![]()
+
{{ instance.name ?? host }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 7556f513c2..f702e8ef44 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -4,71 +4,75 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
{{ i18n.ts._deck.introduction }}
-
{{ i18n.ts._deck.introduction2 }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ i18n.ts._deck.introduction }}
+
{{ i18n.ts._deck.introduction2 }}
+
+
+
+
-
-
-
-
+
@@ -82,6 +86,7 @@ import XCommon from './_common_/common.vue';
import XSidebar from '@/ui/_common_/navbar.vue';
import XNavbarH from '@/ui/_common_/navbar-h.vue';
import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue';
+import XTitlebar from '@/ui/_common_/titlebar.vue';
import * as os from '@/os.js';
import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
@@ -209,30 +214,25 @@ async function deleteProfile() {
window.document.documentElement.style.overflowY = 'hidden';
window.document.documentElement.style.scrollBehavior = 'auto';
-
-if (prefer.s['deck.wallpaper'] != null) {
- window.document.documentElement.style.backgroundImage = `url(${prefer.s['deck.wallpaper']})`;
-}