Date: Tue, 15 Apr 2025 16:14:52 +0900
Subject: [PATCH 171/192] 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 172/192] 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 173/192] =?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 174/192] 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 175/192] 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 176/192] 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 177/192] 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 178/192] =?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 179/192] 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 180/192] 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 181/192] 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 182/192] =?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 183/192] 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 184/192] =?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 187/192] =?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(() => {