Compare commits
	
		
			No commits in common. "498098c5bba3add5d6c8d13383109ac85c861588" and "969b21a8e8181ffcef63ba401d98cd4beb3aa62d" have entirely different histories.
		
	
	
		
			498098c5bb
			...
			969b21a8e8
		
	
		|  | @ -165,11 +165,6 @@ id: 'aidx' | |||
| #    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' | ||||
| 
 | ||||
| #sentryForFrontend: | ||||
| #  vueIntegration: | ||||
| #    tracingOptions: | ||||
| #      trackComponents: true | ||||
| #  browserTracingIntegration: | ||||
| #  replayIntegration: | ||||
| #  options: | ||||
| #    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' | ||||
| 
 | ||||
|  |  | |||
|  | @ -177,11 +177,6 @@ id: 'aidx' | |||
| #    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' | ||||
| 
 | ||||
| #sentryForFrontend: | ||||
| #  vueIntegration: | ||||
| #    tracingOptions: | ||||
| #      trackComponents: true | ||||
| #  browserTracingIntegration: | ||||
| #  replayIntegration: | ||||
| #  options: | ||||
| #    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' | ||||
| 
 | ||||
|  |  | |||
|  | @ -259,11 +259,6 @@ id: 'aidx' | |||
| #    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' | ||||
| 
 | ||||
| #sentryForFrontend: | ||||
| #  vueIntegration: | ||||
| #    tracingOptions: | ||||
| #      trackComponents: true | ||||
| #  browserTracingIntegration: | ||||
| #  replayIntegration: | ||||
| #  options: | ||||
| #    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' | ||||
| 
 | ||||
|  |  | |||
|  | @ -152,11 +152,6 @@ id: 'aidx' | |||
| #    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' | ||||
| 
 | ||||
| #sentryForFrontend: | ||||
| #  vueIntegration: | ||||
| #    tracingOptions: | ||||
| #      trackComponents: true | ||||
| #  browserTracingIntegration: | ||||
| #  replayIntegration: | ||||
| #  options: | ||||
| #    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,12 +13,7 @@ | |||
|   - メッセージにはリアクションも可能です | ||||
| - Enhance: セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。 | ||||
|   - Misskeyネイティブでダッシュボードを実装予定です | ||||
| - Enhance: フロントエンドのエラートラッキングができるように | ||||
| 	- `.config/default.yml`中の項目`sentryForFrontend`を適宜設定してください。 | ||||
| 	- 外部サービスであるSentryへエラー情報が送信されます。ご利用の地域の法令に従い、適切なプライバシーポリシーを策定の上で運用してください。 | ||||
| - Enhance: ミュートしているユーザーをユーザー検索の結果から除外するように | ||||
| - Enhance: アンテナでセンシティブなチャンネルのノートを除外できるように `#14177` | ||||
| - Fix: 通知のページネーションで2つ以上読み込めなくなることがある問題を修正 | ||||
| 
 | ||||
| ### Client | ||||
| - Feat: 設定の管理が強化されました | ||||
|  | @ -61,7 +56,6 @@ | |||
| - Fix: テーマ切り替え時に一部の色が変わらない問題を修正 | ||||
| - NOTE: 構造上クラシックUIを新しいデザインシステムに移行することが困難なため、クラシックUIが削除されました | ||||
|   - デッキUIでカラムを中央寄せにし、メインカラムの左右にウィジェットカラムを配置し、ナビゲーションバーを上部に表示することである程度クラシックUIを再現できます | ||||
| - Fix: iPadOSでdeck uiをマウスカーソルによってスクロールできない問題を修正 | ||||
| 
 | ||||
| ### Server | ||||
| - Enhance 全体的なパフォーマンス向上 | ||||
|  | @ -69,7 +63,6 @@ | |||
| - Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正 | ||||
| - Fix: 連合無しモードでも外部から照会可能だった問題を修正 | ||||
| - Fix: テスト用WebHookのペイロードの`emojis`パラメータが実際のものと異なる問題を修正 | ||||
| - Fix: 非ログインでタイムラインのストリームに接続した際、表示にログイン必須のノートが流れる場合がある問題を修正 | ||||
| 
 | ||||
| ## 2025.3.1 | ||||
| 
 | ||||
|  |  | |||
|  | @ -173,11 +173,6 @@ id: "aidx" | |||
| #    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' | ||||
| 
 | ||||
| #sentryForFrontend: | ||||
| #  vueIntegration: | ||||
| #    tracingOptions: | ||||
| #      trackComponents: true | ||||
| #  browserTracingIntegration: | ||||
| #  replayIntegration: | ||||
| #  options: | ||||
| #    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' | ||||
| 
 | ||||
|  |  | |||
|  | @ -1714,10 +1714,6 @@ export interface Locale extends ILocale { | |||
|      * ファイルが添付されたノートのみ | ||||
|      */ | ||||
|     "withFileAntenna": string; | ||||
|     /** | ||||
|      * センシティブなチャンネルのノートを非表示 | ||||
|      */ | ||||
|     "hideNotesInSensitiveChannel": string; | ||||
|     /** | ||||
|      * ブラウザへのプッシュ通知を有効にする | ||||
|      */ | ||||
|  | @ -5378,10 +5374,6 @@ export interface Locale extends ILocale { | |||
|      * 上 | ||||
|      */ | ||||
|     "top": string; | ||||
|     /** | ||||
|      * 埋め込み | ||||
|      */ | ||||
|     "embed": string; | ||||
|     "_chat": { | ||||
|         /** | ||||
|          * まだメッセージはありません | ||||
|  | @ -5670,10 +5662,6 @@ export interface Locale extends ILocale { | |||
|          * オフのとき | ||||
|          */ | ||||
|         "ifOff": string; | ||||
|         /** | ||||
|          * デバイス間でインストールしたテーマを同期 | ||||
|          */ | ||||
|         "enableSyncThemesBetweenDevices": string; | ||||
|         "_chat": { | ||||
|             /** | ||||
|              * 送信者の名前を表示 | ||||
|  | @ -8247,19 +8235,23 @@ export interface Locale extends ILocale { | |||
|              */ | ||||
|             "header": string; | ||||
|             /** | ||||
|              * ナビゲーションバーの背景 | ||||
|              * サイドバーの背景 | ||||
|              */ | ||||
|             "navBg": string; | ||||
|             /** | ||||
|              * ナビゲーションバーの文字 | ||||
|              * サイドバーの文字 | ||||
|              */ | ||||
|             "navFg": string; | ||||
|             /** | ||||
|              * ナビゲーションバー文字(アクティブ) | ||||
|              * サイドバー文字(ホバー) | ||||
|              */ | ||||
|             "navHoverFg": string; | ||||
|             /** | ||||
|              * サイドバー文字(アクティブ) | ||||
|              */ | ||||
|             "navActive": string; | ||||
|             /** | ||||
|              * ナビゲーションバーのインジケーター | ||||
|              * サイドバーのインジケーター | ||||
|              */ | ||||
|             "navIndicator": string; | ||||
|             /** | ||||
|  | @ -8279,7 +8271,7 @@ export interface Locale extends ILocale { | |||
|              */ | ||||
|             "mentionMe": string; | ||||
|             /** | ||||
|              * リノート | ||||
|              * Renote | ||||
|              */ | ||||
|             "renote": string; | ||||
|             /** | ||||
|  | @ -8342,6 +8334,10 @@ export interface Locale extends ILocale { | |||
|              * ドライブフォルダーの背景 | ||||
|              */ | ||||
|             "driveFolderBg": string; | ||||
|             /** | ||||
|              * 壁紙のオーバーレイ | ||||
|              */ | ||||
|             "wallpaperOverlay": string; | ||||
|             /** | ||||
|              * バッジ | ||||
|              */ | ||||
|  | @ -8350,6 +8346,14 @@ export interface Locale extends ILocale { | |||
|              * チャットの背景 | ||||
|              */ | ||||
|             "messageBg": string; | ||||
|             /** | ||||
|              * アクセント (暗め) | ||||
|              */ | ||||
|             "accentDarken": string; | ||||
|             /** | ||||
|              * アクセント (明るめ) | ||||
|              */ | ||||
|             "accentLighten": string; | ||||
|             /** | ||||
|              * 強調された文字 | ||||
|              */ | ||||
|  |  | |||
|  | @ -424,7 +424,6 @@ antennaExcludeBots: "Botアカウントを除外" | |||
| antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
| notifyAntenna: "新しいノートを通知する" | ||||
| withFileAntenna: "ファイルが添付されたノートのみ" | ||||
| hideNotesInSensitiveChannel: "センシティブなチャンネルのノートを非表示" | ||||
| enableServiceworker: "ブラウザへのプッシュ通知を有効にする" | ||||
| antennaUsersDescription: "ユーザー名を改行で区切って指定します" | ||||
| caseSensitive: "大文字小文字を区別する" | ||||
|  | @ -1340,7 +1339,6 @@ compress: "圧縮" | |||
| right: "右" | ||||
| bottom: "下" | ||||
| top: "上" | ||||
| embed: "埋め込み" | ||||
| 
 | ||||
| _chat: | ||||
|   noMessagesYet: "まだメッセージはありません" | ||||
|  | @ -1418,7 +1416,6 @@ _settings: | |||
|   showNavbarSubButtons: "ナビゲーションバーに副ボタンを表示" | ||||
|   ifOn: "オンのとき" | ||||
|   ifOff: "オフのとき" | ||||
|   enableSyncThemesBetweenDevices: "デバイス間でインストールしたテーマを同期" | ||||
| 
 | ||||
|   _chat: | ||||
|     showSenderName: "送信者の名前を表示" | ||||
|  | @ -2164,15 +2161,16 @@ _theme: | |||
|     panel: "パネル" | ||||
|     shadow: "影" | ||||
|     header: "ヘッダー" | ||||
|     navBg: "ナビゲーションバーの背景" | ||||
|     navFg: "ナビゲーションバーの文字" | ||||
|     navActive: "ナビゲーションバー文字(アクティブ)" | ||||
|     navIndicator: "ナビゲーションバーのインジケーター" | ||||
|     navBg: "サイドバーの背景" | ||||
|     navFg: "サイドバーの文字" | ||||
|     navHoverFg: "サイドバー文字(ホバー)" | ||||
|     navActive: "サイドバー文字(アクティブ)" | ||||
|     navIndicator: "サイドバーのインジケーター" | ||||
|     link: "リンク" | ||||
|     hashtag: "ハッシュタグ" | ||||
|     mention: "メンション" | ||||
|     mentionMe: "あなた宛てメンション" | ||||
|     renote: "リノート" | ||||
|     renote: "Renote" | ||||
|     modalBg: "モーダルの背景" | ||||
|     divider: "分割線" | ||||
|     scrollbarHandle: "スクロールバーの取っ手" | ||||
|  | @ -2188,8 +2186,11 @@ _theme: | |||
|     buttonHoverBg: "ボタンの背景 (ホバー)" | ||||
|     inputBorder: "入力ボックスの縁取り" | ||||
|     driveFolderBg: "ドライブフォルダーの背景" | ||||
|     wallpaperOverlay: "壁紙のオーバーレイ" | ||||
|     badge: "バッジ" | ||||
|     messageBg: "チャットの背景" | ||||
|     accentDarken: "アクセント (暗め)" | ||||
|     accentLighten: "アクセント (明るめ)" | ||||
|     fgHighlighted: "強調された文字" | ||||
| 
 | ||||
| _sfx: | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
| 	"name": "misskey", | ||||
| 	"version": "2025.4.0-beta.1", | ||||
| 	"version": "2025.4.0-alpha.0", | ||||
| 	"codename": "nasubi", | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
|  |  | |||
|  | @ -1,16 +0,0 @@ | |||
| /* | ||||
|  * SPDX-FileCopyrightText: syuilo and misskey-project | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| export class AddAntennaHideNotesInSensitiveChannel1736230492103 { | ||||
|     name = 'AddAntennaHideNotesInSensitiveChannel1736230492103' | ||||
| 
 | ||||
|     async up(queryRunner) { | ||||
|         await queryRunner.query(`ALTER TABLE "antenna" ADD "hideNotesInSensitiveChannel" boolean NOT NULL DEFAULT false`); | ||||
|     } | ||||
| 
 | ||||
|     async down(queryRunner) { | ||||
|         await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "hideNotesInSensitiveChannel"`); | ||||
|     } | ||||
| } | ||||
|  | @ -186,7 +186,6 @@ | |||
| 	"devDependencies": { | ||||
| 		"@jest/globals": "29.7.0", | ||||
| 		"@nestjs/platform-express": "10.4.15", | ||||
| 		"@sentry/vue": "9.8.0", | ||||
| 		"@simplewebauthn/types": "12.0.0", | ||||
| 		"@swc/jest": "0.2.37", | ||||
| 		"@types/accepts": "1.3.7", | ||||
|  |  | |||
|  | @ -7,8 +7,7 @@ import * as fs from 'node:fs'; | |||
| import { fileURLToPath } from 'node:url'; | ||||
| import { dirname, resolve } from 'node:path'; | ||||
| import * as yaml from 'js-yaml'; | ||||
| import type * as Sentry from '@sentry/node'; | ||||
| import type * as SentryVue from '@sentry/vue'; | ||||
| import * as Sentry from '@sentry/node'; | ||||
| import type { RedisOptions } from 'ioredis'; | ||||
| 
 | ||||
| type RedisOptionsSource = Partial<RedisOptions> & { | ||||
|  | @ -63,12 +62,7 @@ type Source = { | |||
| 		scope?: 'local' | 'global' | string[]; | ||||
| 	}; | ||||
| 	sentryForBackend?: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; }; | ||||
| 	sentryForFrontend?: { | ||||
| 		options: Partial<SentryVue.BrowserOptions> & { dsn: string }; | ||||
| 		vueIntegration?: SentryVue.VueIntegrationOptions | null; | ||||
| 		browserTracingIntegration?: Parameters<typeof SentryVue.browserTracingIntegration>[0] | null; | ||||
| 		replayIntegration?: Parameters<typeof SentryVue.replayIntegration>[0] | null; | ||||
| 	}; | ||||
| 	sentryForFrontend?: { options: Partial<Sentry.NodeOptions> }; | ||||
| 
 | ||||
| 	publishTarballInsteadOfProvideRepositoryUrl?: boolean; | ||||
| 
 | ||||
|  | @ -204,12 +198,7 @@ export type Config = { | |||
| 	redisForTimelines: RedisOptions & RedisOptionsSource; | ||||
| 	redisForReactions: RedisOptions & RedisOptionsSource; | ||||
| 	sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined; | ||||
| 	sentryForFrontend: { | ||||
| 		options: Partial<SentryVue.BrowserOptions> & { dsn: string }; | ||||
| 		vueIntegration?: SentryVue.VueIntegrationOptions | null; | ||||
| 		browserTracingIntegration?: Parameters<typeof SentryVue.browserTracingIntegration>[0] | null; | ||||
| 		replayIntegration?: Parameters<typeof SentryVue.replayIntegration>[0] | null; | ||||
| 	} | undefined; | ||||
| 	sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined; | ||||
| 	perChannelMaxNoteCacheCount: number; | ||||
| 	perUserNotificationsMaxCount: number; | ||||
| 	deactivateAntennaThreshold: number; | ||||
|  |  | |||
|  | @ -114,8 +114,6 @@ export class AntennaService implements OnApplicationShutdown { | |||
| 		if (note.visibility === 'specified') return false; | ||||
| 		if (note.visibility === 'followers') return false; | ||||
| 
 | ||||
| 		if (antenna.hideNotesInSensitiveChannel && note.channel?.isSensitive) return false; | ||||
| 
 | ||||
| 		if (antenna.excludeBots && noteUser.isBot) return false; | ||||
| 
 | ||||
| 		if (antenna.localOnly && noteUser.host != null) return false; | ||||
|  |  | |||
|  | @ -99,7 +99,7 @@ export class ChatService { | |||
| 		text?: string | null; | ||||
| 		file?: MiDriveFile | null; | ||||
| 		uri?: string | null; | ||||
| 	}): Promise<Packed<'ChatMessageLiteFor1on1'>> { | ||||
| 	}): Promise<Packed<'ChatMessageLite'>> { | ||||
| 		if (fromUser.id === toUser.id) { | ||||
| 			throw new Error('yourself'); | ||||
| 		} | ||||
|  | @ -210,16 +210,10 @@ export class ChatService { | |||
| 		text?: string | null; | ||||
| 		file?: MiDriveFile | null; | ||||
| 		uri?: string | null; | ||||
| 	}): Promise<Packed<'ChatMessageLiteForRoom'>> { | ||||
| 		const memberships = (await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id })).map(m => ({ | ||||
| 			userId: m.userId, | ||||
| 			isMuted: m.isMuted, | ||||
| 		})).concat({ // ownerはmembershipレコードを作らないため
 | ||||
| 			userId: toRoom.ownerId, | ||||
| 			isMuted: false, | ||||
| 		}); | ||||
| 	}): Promise<Packed<'ChatMessageLite'>> { | ||||
| 		const memberships = await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id }); | ||||
| 
 | ||||
| 		if (!memberships.some(member => member.userId === fromUser.id)) { | ||||
| 		if (toRoom.ownerId !== fromUser.id && !memberships.some(member => member.userId === fromUser.id)) { | ||||
| 			throw new Error('you are not a member of the room'); | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,13 +7,13 @@ import { Inject, Injectable } from '@nestjs/common'; | |||
| import { ulid } from 'ulid'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import type { Config } from '@/config.js'; | ||||
| import { genAid, isSafeAidT, parseAid, parseAidFull } from '@/misc/id/aid.js'; | ||||
| import { genAidx, isSafeAidxT, parseAidx, parseAidxFull } from '@/misc/id/aidx.js'; | ||||
| import { genMeid, isSafeMeidT, parseMeid, parseMeidFull } from '@/misc/id/meid.js'; | ||||
| import { genMeidg, isSafeMeidgT, parseMeidg, parseMeidgFull } from '@/misc/id/meidg.js'; | ||||
| import { genObjectId, isSafeObjectIdT, parseObjectId, parseObjectIdFull } from '@/misc/id/object-id.js'; | ||||
| import { genAid, isSafeAidT, parseAid } from '@/misc/id/aid.js'; | ||||
| import { genAidx, isSafeAidxT, parseAidx } from '@/misc/id/aidx.js'; | ||||
| import { genMeid, isSafeMeidT, parseMeid } from '@/misc/id/meid.js'; | ||||
| import { genMeidg, isSafeMeidgT, parseMeidg } from '@/misc/id/meidg.js'; | ||||
| import { genObjectId, isSafeObjectIdT, parseObjectId } from '@/misc/id/object-id.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { parseUlid, parseUlidFull } from '@/misc/id/ulid.js'; | ||||
| import { parseUlid } from '@/misc/id/ulid.js'; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class IdService { | ||||
|  | @ -70,18 +70,4 @@ export class IdService { | |||
| 			default: throw new Error('unrecognized id generation method'); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Note: additional is at most 64 bits
 | ||||
| 	@bindThis | ||||
| 	public parseFull(id: string): { date: number; additional: bigint; } { | ||||
| 		switch (this.method) { | ||||
| 			case 'aid': return parseAidFull(id); | ||||
| 			case 'aidx': return parseAidxFull(id); | ||||
| 			case 'objectid': return parseObjectIdFull(id); | ||||
| 			case 'meid': return parseMeidFull(id); | ||||
| 			case 'meidg': return parseMeidgFull(id); | ||||
| 			case 'ulid': return parseUlidFull(id); | ||||
| 			default: throw new Error('unrecognized id generation method'); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -532,10 +532,7 @@ export class NoteCreateService implements OnApplicationShutdown { | |||
| 
 | ||||
| 		this.pushToTl(note, user); | ||||
| 
 | ||||
| 		this.antennaService.addNoteToAntennas({ | ||||
| 			...note, | ||||
| 			channel: data.channel ?? null, | ||||
| 		}, user); | ||||
| 		this.antennaService.addNoteToAntennas(note, user); | ||||
| 
 | ||||
| 		if (data.reply) { | ||||
| 			this.saveReply(data.reply, note); | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ import { setTimeout } from 'node:timers/promises'; | |||
| import * as Redis from 'ioredis'; | ||||
| import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; | ||||
| import { In } from 'typeorm'; | ||||
| import { ReplyError } from 'ioredis'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import type { UsersRepository } from '@/models/_.js'; | ||||
| import type { MiUser } from '@/models/User.js'; | ||||
|  | @ -20,7 +19,7 @@ import { IdService } from '@/core/IdService.js'; | |||
| import { CacheService } from '@/core/CacheService.js'; | ||||
| import type { Config } from '@/config.js'; | ||||
| import { UserListService } from '@/core/UserListService.js'; | ||||
| import { FilterUnionByProperty, groupedNotificationTypes, obsoleteNotificationTypes } from '@/types.js'; | ||||
| import type { FilterUnionByProperty } from '@/types.js'; | ||||
| import { trackPromise } from '@/misc/promise-tracker.js'; | ||||
| 
 | ||||
| @Injectable() | ||||
|  | @ -146,36 +145,21 @@ export class NotificationService implements OnApplicationShutdown { | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		const createdAt = new Date(); | ||||
| 		let notification: FilterUnionByProperty<MiNotification, 'type', T>; | ||||
| 		let redisId: string; | ||||
| 		const notification = { | ||||
| 			id: this.idService.gen(), | ||||
| 			createdAt: new Date(), | ||||
| 			type: type, | ||||
| 			...(notifierId ? { | ||||
| 				notifierId, | ||||
| 			} : {}), | ||||
| 			...data, | ||||
| 		} as any as FilterUnionByProperty<MiNotification, 'type', T>; | ||||
| 
 | ||||
| 		do { | ||||
| 			notification = { | ||||
| 				id: this.idService.gen(), | ||||
| 				createdAt, | ||||
| 				type: type, | ||||
| 				...(notifierId ? { | ||||
| 					notifierId, | ||||
| 				} : {}), | ||||
| 				...data, | ||||
| 			} as unknown as FilterUnionByProperty<MiNotification, 'type', T>; | ||||
| 
 | ||||
| 			try { | ||||
| 				redisId = (await this.redisClient.xadd( | ||||
| 					`notificationTimeline:${notifieeId}`, | ||||
| 					'MAXLEN', '~', this.config.perUserNotificationsMaxCount.toString(), | ||||
| 					this.toXListId(notification.id), | ||||
| 					'data', JSON.stringify(notification)))!; | ||||
| 			} catch (e) { | ||||
| 				// The ID specified in XADD is equal or smaller than the target stream top item で失敗することがあるのでリトライ
 | ||||
| 				if (e instanceof ReplyError) continue; | ||||
| 				throw e; | ||||
| 			} | ||||
| 
 | ||||
| 			break; | ||||
| 			// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | ||||
| 		} while (true); | ||||
| 		const redisIdPromise = this.redisClient.xadd( | ||||
| 			`notificationTimeline:${notifieeId}`, | ||||
| 			'MAXLEN', '~', this.config.perUserNotificationsMaxCount.toString(), | ||||
| 			'*', | ||||
| 			'data', JSON.stringify(notification)); | ||||
| 
 | ||||
| 		const packed = await this.notificationEntityService.pack(notification, notifieeId, {}); | ||||
| 
 | ||||
|  | @ -189,7 +173,7 @@ export class NotificationService implements OnApplicationShutdown { | |||
| 		const interval = notification.type === 'test' ? 0 : 2000; | ||||
| 		setTimeout(interval, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => { | ||||
| 			const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`); | ||||
| 			if (latestReadNotificationId && (latestReadNotificationId >= redisId)) return; | ||||
| 			if (latestReadNotificationId && (latestReadNotificationId >= (await redisIdPromise)!)) return; | ||||
| 
 | ||||
| 			this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed); | ||||
| 			this.pushNotificationService.pushNotification(notifieeId, 'notification', packed); | ||||
|  | @ -244,79 +228,6 @@ export class NotificationService implements OnApplicationShutdown { | |||
| 		this.#shutdownController.abort(); | ||||
| 	} | ||||
| 
 | ||||
| 	private toXListId(id: string): string { | ||||
| 		const { date, additional } = this.idService.parseFull(id); | ||||
| 		return date.toString() + '-' + additional.toString(); | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public async getNotifications( | ||||
| 		userId: MiUser['id'], | ||||
| 		{ | ||||
| 			sinceId, | ||||
| 			untilId, | ||||
| 			limit = 20, | ||||
| 			includeTypes, | ||||
| 			excludeTypes, | ||||
| 		}: { | ||||
| 			sinceId?: string, | ||||
| 			untilId?: string, | ||||
| 			limit?: number, | ||||
| 			// any extra types are allowed, those are no-op
 | ||||
| 			includeTypes?: (MiNotification['type'] | string)[], | ||||
| 			excludeTypes?: (MiNotification['type'] | string)[], | ||||
| 		}, | ||||
| 	): Promise<MiNotification[]> { | ||||
| 		let sinceTime = sinceId ? this.toXListId(sinceId) : null; | ||||
| 		let untilTime = untilId ? this.toXListId(untilId) : null; | ||||
| 
 | ||||
| 		let notifications: MiNotification[]; | ||||
| 		for (;;) { | ||||
| 			let notificationsRes: [id: string, fields: string[]][]; | ||||
| 
 | ||||
| 			// sinceidのみの場合は古い順、そうでない場合は新しい順。 QueryService.makePaginationQueryも参照
 | ||||
| 			if (sinceTime && !untilTime) { | ||||
| 				notificationsRes = await this.redisClient.xrange( | ||||
| 					`notificationTimeline:${userId}`, | ||||
| 					'(' + sinceTime, | ||||
| 					'+', | ||||
| 					'COUNT', limit); | ||||
| 			} else { | ||||
| 				notificationsRes = await this.redisClient.xrevrange( | ||||
| 					`notificationTimeline:${userId}`, | ||||
| 					untilTime ? '(' + untilTime : '+', | ||||
| 					sinceTime ? '(' + sinceTime : '-', | ||||
| 					'COUNT', limit); | ||||
| 			} | ||||
| 
 | ||||
| 			if (notificationsRes.length === 0) { | ||||
| 				return []; | ||||
| 			} | ||||
| 
 | ||||
| 			notifications = notificationsRes.map(x => JSON.parse(x[1][1])) as MiNotification[]; | ||||
| 
 | ||||
| 			if (includeTypes && includeTypes.length > 0) { | ||||
| 				notifications = notifications.filter(notification => includeTypes.includes(notification.type)); | ||||
| 			} else if (excludeTypes && excludeTypes.length > 0) { | ||||
| 				notifications = notifications.filter(notification => !excludeTypes.includes(notification.type)); | ||||
| 			} | ||||
| 
 | ||||
| 			if (notifications.length !== 0) { | ||||
| 				// 通知が1件以上ある場合は返す
 | ||||
| 				break; | ||||
| 			} | ||||
| 
 | ||||
| 			// フィルタしたことで通知が0件になった場合、次のページを取得する
 | ||||
| 			if (sinceId && !untilId) { | ||||
| 				sinceTime = notificationsRes[notificationsRes.length - 1][0]; | ||||
| 			} else { | ||||
| 				untilTime = notificationsRes[notificationsRes.length - 1][0]; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return notifications; | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public onApplicationShutdown(signal?: string | undefined): void { | ||||
| 		this.dispose(); | ||||
|  |  | |||
|  | @ -41,7 +41,6 @@ export class AntennaEntityService { | |||
| 			excludeBots: antenna.excludeBots, | ||||
| 			withReplies: antenna.withReplies, | ||||
| 			withFile: antenna.withFile, | ||||
| 			hideNotesInSensitiveChannel: antenna.hideNotesInSensitiveChannel, | ||||
| 			isActive: antenna.isActive, | ||||
| 			hasUnreadNote: false, // TODO
 | ||||
| 			notify: false, // 後方互換性のため
 | ||||
|  |  | |||
|  | @ -128,7 +128,7 @@ export class ChatEntityService { | |||
| 				packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>; | ||||
| 			}; | ||||
| 		}, | ||||
| 	): Promise<Packed<'ChatMessageLiteFor1on1'>> { | ||||
| 	): Promise<Packed<'ChatMessageLite'>> { | ||||
| 		const packedFiles = options?._hint_?.packedFiles; | ||||
| 
 | ||||
| 		const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src }); | ||||
|  | @ -147,7 +147,7 @@ export class ChatEntityService { | |||
| 			createdAt: this.idService.parse(message.id).date.toISOString(), | ||||
| 			text: message.text, | ||||
| 			fromUserId: message.fromUserId, | ||||
| 			toUserId: message.toUserId!, | ||||
| 			toUserId: message.toUserId, | ||||
| 			fileId: message.fileId, | ||||
| 			file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, | ||||
| 			reactions, | ||||
|  | @ -177,7 +177,7 @@ export class ChatEntityService { | |||
| 				packedUsers: Map<MiUser['id'], Packed<'UserLite'>>; | ||||
| 			}; | ||||
| 		}, | ||||
| 	): Promise<Packed<'ChatMessageLiteForRoom'>> { | ||||
| 	): Promise<Packed<'ChatMessageLite'>> { | ||||
| 		const packedFiles = options?._hint_?.packedFiles; | ||||
| 		const packedUsers = options?._hint_?.packedUsers; | ||||
| 
 | ||||
|  | @ -199,7 +199,7 @@ export class ChatEntityService { | |||
| 			text: message.text, | ||||
| 			fromUserId: message.fromUserId, | ||||
| 			fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId), | ||||
| 			toRoomId: message.toRoomId!, | ||||
| 			toRoomId: message.toRoomId, | ||||
| 			fileId: message.fileId, | ||||
| 			file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, | ||||
| 			reactions, | ||||
|  |  | |||
|  | @ -127,7 +127,6 @@ export class MetaEntityService { | |||
| 
 | ||||
| 			policies: { ...DEFAULT_POLICIES, ...instance.policies }, | ||||
| 
 | ||||
| 			sentryForFrontend: this.config.sentryForFrontend ?? null, | ||||
| 			mediaProxy: this.config.mediaProxy, | ||||
| 			enableUrlPreview: instance.urlPreviewEnabled, | ||||
| 			noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local', | ||||
|  |  | |||
|  | @ -1,40 +0,0 @@ | |||
| /* | ||||
|  * SPDX-FileCopyrightText: syuilo and misskey-project | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| function parseBigIntChunked(str: string, base: number, chunkSize: number, powerOfChunkSize: bigint): bigint { | ||||
| 	const chunks = []; | ||||
| 	while (str.length > 0) { | ||||
| 		chunks.unshift(str.slice(-chunkSize)); | ||||
| 		str = str.slice(0, -chunkSize); | ||||
| 	} | ||||
| 	let result = 0n; | ||||
| 	for (const chunk of chunks) { | ||||
| 		result *= powerOfChunkSize; | ||||
| 		const int = parseInt(chunk, base); | ||||
| 		if (Number.isNaN(int)) { | ||||
| 			throw new Error('Invalid base36 string'); | ||||
| 		} | ||||
| 		result += BigInt(int); | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| export function parseBigInt36(str: string): bigint { | ||||
| 	// log_36(Number.MAX_SAFE_INTEGER) => 10.251599391715352
 | ||||
| 	// so we process 10 chars at once
 | ||||
| 	return parseBigIntChunked(str, 36, 10, 36n ** 10n); | ||||
| } | ||||
| 
 | ||||
| export function parseBigInt16(str: string): bigint { | ||||
| 	// log_16(Number.MAX_SAFE_INTEGER) => 13.25
 | ||||
| 	// so we process 13 chars at once
 | ||||
| 	return parseBigIntChunked(str, 16, 13, 16n ** 13n); | ||||
| } | ||||
| 
 | ||||
| export function parseBigInt32(str: string): bigint { | ||||
| 	// log_32(Number.MAX_SAFE_INTEGER) => 10.6
 | ||||
| 	// so we process 10 chars at once
 | ||||
| 	return parseBigIntChunked(str, 32, 10, 32n ** 10n); | ||||
| } | ||||
|  | @ -7,7 +7,6 @@ | |||
| // 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ2の[ノイズ文字列]
 | ||||
| 
 | ||||
| import * as crypto from 'node:crypto'; | ||||
| import { parseBigInt36 } from '@/misc/bigint.js'; | ||||
| 
 | ||||
| export const aidRegExp = /^[0-9a-z]{10}$/; | ||||
| 
 | ||||
|  | @ -36,12 +35,6 @@ export function parseAid(id: string): { date: Date; } { | |||
| 	return { date: new Date(time) }; | ||||
| } | ||||
| 
 | ||||
| export function parseAidFull(id: string): { date: number; additional: bigint; } { | ||||
| 	const date = parseInt(id.slice(0, 8), 36) + TIME2000; | ||||
| 	const additional = parseBigInt36(id.slice(8, 10)); | ||||
| 	return { date, additional }; | ||||
| } | ||||
| 
 | ||||
| export function isSafeAidT(t: number): boolean { | ||||
| 	return t > TIME2000; | ||||
| } | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ | |||
| // https://misskey.m544.net/notes/71899acdcc9859ec5708ac24
 | ||||
| 
 | ||||
| import { customAlphabet } from 'nanoid'; | ||||
| import { parseBigInt36 } from '@/misc/bigint.js'; | ||||
| 
 | ||||
| export const aidxRegExp = /^[0-9a-z]{16}$/; | ||||
| 
 | ||||
|  | @ -17,7 +16,6 @@ const TIME2000 = 946684800000; | |||
| const TIME_LENGTH = 8; | ||||
| const NODE_LENGTH = 4; | ||||
| const NOISE_LENGTH = 4; | ||||
| const AIDX_LENGTH = TIME_LENGTH + NODE_LENGTH + NOISE_LENGTH; | ||||
| 
 | ||||
| const nodeId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', NODE_LENGTH)(); | ||||
| let counter = 0; | ||||
|  | @ -44,12 +42,6 @@ export function parseAidx(id: string): { date: Date; } { | |||
| 	return { date: new Date(time) }; | ||||
| } | ||||
| 
 | ||||
| export function parseAidxFull(id: string): { date: number; additional: bigint; } { | ||||
| 	const date = parseInt(id.slice(0, TIME_LENGTH), 36) + TIME2000; | ||||
| 	const additional = parseBigInt36(id.slice(TIME_LENGTH, AIDX_LENGTH)); | ||||
| 	return { date, additional }; | ||||
| } | ||||
| 
 | ||||
| export function isSafeAidxT(t: number): boolean { | ||||
| 	return t > TIME2000; | ||||
| } | ||||
|  |  | |||
|  | @ -3,8 +3,6 @@ | |||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| import { parseBigInt16 } from '@/misc/bigint.js'; | ||||
| 
 | ||||
| const CHARS = '0123456789abcdef'; | ||||
| 
 | ||||
| // same as object-id
 | ||||
|  | @ -41,13 +39,6 @@ export function parseMeid(id: string): { date: Date; } { | |||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function parseMeidFull(id: string): { date: number; additional: bigint; } { | ||||
| 	return { | ||||
| 		date: parseInt(id.slice(0, 12), 16) - 0x800000000000, | ||||
| 		additional: parseBigInt16(id.slice(12, 24)), | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function isSafeMeidT(t: number): boolean { | ||||
| 	return t > 0; | ||||
| } | ||||
|  |  | |||
|  | @ -3,8 +3,6 @@ | |||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| import { parseBigInt16 } from '@/misc/bigint.js'; | ||||
| 
 | ||||
| const CHARS = '0123456789abcdef'; | ||||
| 
 | ||||
| //  4bit Fixed hex value 'g'
 | ||||
|  | @ -41,13 +39,6 @@ export function parseMeidg(id: string): { date: Date; } { | |||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function parseMeidgFull(id: string): { date: number; additional: bigint; } { | ||||
| 	return { | ||||
| 		date: parseInt(id.slice(1, 12), 16), | ||||
| 		additional: parseBigInt16(id.slice(12, 24)), | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function isSafeMeidgT(t: number): boolean { | ||||
| 	return t > 0; | ||||
| } | ||||
|  |  | |||
|  | @ -3,8 +3,6 @@ | |||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| import { parseBigInt16 } from '@/misc/bigint.js'; | ||||
| 
 | ||||
| const CHARS = '0123456789abcdef'; | ||||
| 
 | ||||
| // same as meid
 | ||||
|  | @ -41,13 +39,6 @@ export function parseObjectId(id: string): { date: Date; } { | |||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function parseObjectIdFull(id: string): { date: number; additional: bigint; } { | ||||
| 	return { | ||||
| 		date: parseInt(id.slice(0, 8), 16) * 1000, | ||||
| 		additional: parseBigInt16(id.slice(8, 24)), | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function isSafeObjectIdT(t: number): boolean { | ||||
| 	return t > 0; | ||||
| } | ||||
|  |  | |||
|  | @ -5,27 +5,15 @@ | |||
| 
 | ||||
| // Crockford's Base32
 | ||||
| // https://github.com/ulid/spec#encoding
 | ||||
| import { parseBigInt32 } from '@/misc/bigint.js'; | ||||
| 
 | ||||
| const CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; | ||||
| 
 | ||||
| export const ulidRegExp = /^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$/; | ||||
| 
 | ||||
| function parseBase32(timestamp: string) { | ||||
| export function parseUlid(id: string): { date: Date; } { | ||||
| 	const timestamp = id.slice(0, 10); | ||||
| 	let time = 0; | ||||
| 	for (let i = 0; i < timestamp.length; i++) { | ||||
| 	for (let i = 0; i < 10; i++) { | ||||
| 		time = time * 32 + CHARS.indexOf(timestamp[i]); | ||||
| 	} | ||||
| 	return time; | ||||
| } | ||||
| 
 | ||||
| export function parseUlid(id: string): { date: Date; } { | ||||
| 	return { date: new Date(parseBase32(id.slice(0, 10))) }; | ||||
| } | ||||
| 
 | ||||
| export function parseUlidFull(id: string): { date: number; additional: bigint; } { | ||||
| 	return { | ||||
| 		date: parseBase32(id.slice(0, 10)), | ||||
| 		additional: parseBigInt32(id.slice(10, 26)), | ||||
| 	}; | ||||
| 	return { date: new Date(time) }; | ||||
| } | ||||
|  |  | |||
|  | @ -63,7 +63,7 @@ import { | |||
| } from '@/models/json-schema/meta.js'; | ||||
| import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js'; | ||||
| import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js'; | ||||
| import { packedChatMessageSchema, packedChatMessageLiteSchema, packedChatMessageLiteForRoomSchema, packedChatMessageLiteFor1on1Schema } from '@/models/json-schema/chat-message.js'; | ||||
| import { packedChatMessageSchema, packedChatMessageLiteSchema } from '@/models/json-schema/chat-message.js'; | ||||
| import { packedChatRoomSchema } from '@/models/json-schema/chat-room.js'; | ||||
| import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-invitation.js'; | ||||
| import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js'; | ||||
|  | @ -126,8 +126,6 @@ export const refs = { | |||
| 	AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema, | ||||
| 	ChatMessage: packedChatMessageSchema, | ||||
| 	ChatMessageLite: packedChatMessageLiteSchema, | ||||
| 	ChatMessageLiteFor1on1: packedChatMessageLiteFor1on1Schema, | ||||
| 	ChatMessageLiteForRoom: packedChatMessageLiteForRoomSchema, | ||||
| 	ChatRoom: packedChatRoomSchema, | ||||
| 	ChatRoomInvitation: packedChatRoomInvitationSchema, | ||||
| 	ChatRoomMembership: packedChatRoomMembershipSchema, | ||||
|  |  | |||
|  | @ -100,9 +100,4 @@ export class MiAntenna { | |||
| 		default: false, | ||||
| 	}) | ||||
| 	public localOnly: boolean; | ||||
| 
 | ||||
| 	@Column('boolean', { | ||||
| 		default: false, | ||||
| 	}) | ||||
| 	public hideNotesInSensitiveChannel: boolean; | ||||
| } | ||||
|  |  | |||
|  | @ -100,10 +100,5 @@ export const packedAntennaSchema = { | |||
| 			optional: false, nullable: false, | ||||
| 			default: false, | ||||
| 		}, | ||||
| 		hideNotesInSensitiveChannel: { | ||||
| 			type: 'boolean', | ||||
| 			optional: false, nullable: false, | ||||
| 			default: false, | ||||
| 		}, | ||||
| 	}, | ||||
| } as const; | ||||
|  |  | |||
|  | @ -72,7 +72,7 @@ export const packedChatMessageSchema = { | |||
| 					}, | ||||
| 					user: { | ||||
| 						type: 'object', | ||||
| 						optional: false, nullable: false, | ||||
| 						optional: true, nullable: true, | ||||
| 						ref: 'UserLite', | ||||
| 					}, | ||||
| 				}, | ||||
|  | @ -144,113 +144,3 @@ export const packedChatMessageLiteSchema = { | |||
| 		}, | ||||
| 	}, | ||||
| } as const; | ||||
| 
 | ||||
| export const packedChatMessageLiteFor1on1Schema = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		id: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: false, | ||||
| 		}, | ||||
| 		createdAt: { | ||||
| 			type: 'string', | ||||
| 			format: 'date-time', | ||||
| 			optional: false, nullable: false, | ||||
| 		}, | ||||
| 		fromUserId: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: false, | ||||
| 		}, | ||||
| 		toUserId: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: false, | ||||
| 		}, | ||||
| 		text: { | ||||
| 			type: 'string', | ||||
| 			optional: true, nullable: true, | ||||
| 		}, | ||||
| 		fileId: { | ||||
| 			type: 'string', | ||||
| 			optional: true, nullable: true, | ||||
| 		}, | ||||
| 		file: { | ||||
| 			type: 'object', | ||||
| 			optional: true, nullable: true, | ||||
| 			ref: 'DriveFile', | ||||
| 		}, | ||||
| 		reactions: { | ||||
| 			type: 'array', | ||||
| 			optional: false, nullable: false, | ||||
| 			items: { | ||||
| 				type: 'object', | ||||
| 				optional: false, nullable: false, | ||||
| 				properties: { | ||||
| 					reaction: { | ||||
| 						type: 'string', | ||||
| 						optional: false, nullable: false, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| } as const; | ||||
| 
 | ||||
| export const packedChatMessageLiteForRoomSchema = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		id: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: false, | ||||
| 		}, | ||||
| 		createdAt: { | ||||
| 			type: 'string', | ||||
| 			format: 'date-time', | ||||
| 			optional: false, nullable: false, | ||||
| 		}, | ||||
| 		fromUserId: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: false, | ||||
| 		}, | ||||
| 		fromUser: { | ||||
| 			type: 'object', | ||||
| 			optional: false, nullable: false, | ||||
| 			ref: 'UserLite', | ||||
| 		}, | ||||
| 		toRoomId: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: false, | ||||
| 		}, | ||||
| 		text: { | ||||
| 			type: 'string', | ||||
| 			optional: true, nullable: true, | ||||
| 		}, | ||||
| 		fileId: { | ||||
| 			type: 'string', | ||||
| 			optional: true, nullable: true, | ||||
| 		}, | ||||
| 		file: { | ||||
| 			type: 'object', | ||||
| 			optional: true, nullable: true, | ||||
| 			ref: 'DriveFile', | ||||
| 		}, | ||||
| 		reactions: { | ||||
| 			type: 'array', | ||||
| 			optional: false, nullable: false, | ||||
| 			items: { | ||||
| 				type: 'object', | ||||
| 				optional: false, nullable: false, | ||||
| 				properties: { | ||||
| 					reaction: { | ||||
| 						type: 'string', | ||||
| 						optional: false, nullable: false, | ||||
| 					}, | ||||
| 					user: { | ||||
| 						type: 'object', | ||||
| 						optional: false, nullable: false, | ||||
| 						ref: 'UserLite', | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| } as const; | ||||
|  |  | |||
|  | @ -211,38 +211,6 @@ export const packedMetaLiteSchema = { | |||
| 			type: 'boolean', | ||||
| 			optional: false, nullable: false, | ||||
| 		}, | ||||
| 		sentryForFrontend: { | ||||
| 			type: 'object', | ||||
| 			optional: false, nullable: true, | ||||
| 			properties: { | ||||
| 				options: { | ||||
| 					type: 'object', | ||||
| 					optional: false, nullable: false, | ||||
| 					properties: { | ||||
| 						dsn: { | ||||
| 							type: 'string', | ||||
| 							optional: false, nullable: false, | ||||
| 						}, | ||||
| 					}, | ||||
| 					additionalProperties: true, | ||||
| 				}, | ||||
| 				vueIntegration: { | ||||
| 					type: 'object', | ||||
| 					optional: true, nullable: true, | ||||
| 					additionalProperties: true, | ||||
| 				}, | ||||
| 				browserTracingIntegration: { | ||||
| 					type: 'object', | ||||
| 					optional: true, nullable: true, | ||||
| 					additionalProperties: true, | ||||
| 				}, | ||||
| 				replayIntegration: { | ||||
| 					type: 'object', | ||||
| 					optional: true, nullable: true, | ||||
| 					additionalProperties: true, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		mediaProxy: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: false, | ||||
|  |  | |||
|  | @ -73,7 +73,6 @@ export const paramDef = { | |||
| 		excludeBots: { type: 'boolean' }, | ||||
| 		withReplies: { type: 'boolean' }, | ||||
| 		withFile: { type: 'boolean' }, | ||||
| 		hideNotesInSensitiveChannel: { type: 'boolean' }, | ||||
| 	}, | ||||
| 	required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'], | ||||
| } as const; | ||||
|  | @ -134,7 +133,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				excludeBots: ps.excludeBots, | ||||
| 				withReplies: ps.withReplies, | ||||
| 				withFile: ps.withFile, | ||||
| 				hideNotesInSensitiveChannel: ps.hideNotesInSensitiveChannel, | ||||
| 			}); | ||||
| 
 | ||||
| 			this.globalEventService.publishInternalEvent('antennaCreated', antenna); | ||||
|  |  | |||
|  | @ -108,9 +108,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				.leftJoinAndSelect('reply.user', 'replyUser') | ||||
| 				.leftJoinAndSelect('renote.user', 'renoteUser'); | ||||
| 
 | ||||
| 			// NOTE: センシティブ除外の設定はこのエンドポイントでは無視する。
 | ||||
| 			// https://github.com/misskey-dev/misskey/pull/15346#discussion_r1929950255
 | ||||
| 
 | ||||
| 			this.queryService.generateVisibilityQuery(query, me); | ||||
| 			this.queryService.generateMutedUserQueryForNotes(query, me); | ||||
| 			this.queryService.generateBlockedUserQueryForNotes(query, me); | ||||
|  |  | |||
|  | @ -72,7 +72,6 @@ export const paramDef = { | |||
| 		excludeBots: { type: 'boolean' }, | ||||
| 		withReplies: { type: 'boolean' }, | ||||
| 		withFile: { type: 'boolean' }, | ||||
| 		hideNotesInSensitiveChannel: { type: 'boolean' }, | ||||
| 	}, | ||||
| 	required: ['antennaId'], | ||||
| } as const; | ||||
|  | @ -130,7 +129,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				excludeBots: ps.excludeBots, | ||||
| 				withReplies: ps.withReplies, | ||||
| 				withFile: ps.withFile, | ||||
| 				hideNotesInSensitiveChannel: ps.hideNotesInSensitiveChannel, | ||||
| 				isActive: true, | ||||
| 				lastUsedAt: new Date(), | ||||
| 			}); | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ export const meta = { | |||
| 	res: { | ||||
| 		type: 'object', | ||||
| 		optional: false, nullable: false, | ||||
| 		ref: 'ChatMessageLiteForRoom', | ||||
| 		ref: 'ChatMessageLite', | ||||
| 	}, | ||||
| 
 | ||||
| 	errors: { | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ export const meta = { | |||
| 	res: { | ||||
| 		type: 'object', | ||||
| 		optional: false, nullable: false, | ||||
| 		ref: 'ChatMessageLiteFor1on1', | ||||
| 		ref: 'ChatMessageLite', | ||||
| 	}, | ||||
| 
 | ||||
| 	errors: { | ||||
|  |  | |||
|  | @ -23,7 +23,7 @@ export const meta = { | |||
| 		items: { | ||||
| 			type: 'object', | ||||
| 			optional: false, nullable: false, | ||||
| 			ref: 'ChatMessageLiteForRoom', | ||||
| 			ref: 'ChatMessageLite', | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ export const meta = { | |||
| 		items: { | ||||
| 			type: 'object', | ||||
| 			optional: false, nullable: false, | ||||
| 			ref: 'ChatMessageLiteFor1on1', | ||||
| 			ref: 'ChatMessageLite', | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,12 +7,7 @@ import { In } from 'typeorm'; | |||
| import * as Redis from 'ioredis'; | ||||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import type { NotesRepository } from '@/models/_.js'; | ||||
| import { | ||||
| 	obsoleteNotificationTypes, | ||||
| 	groupedNotificationTypes, | ||||
| 	FilterUnionByProperty, | ||||
| 	notificationTypes, | ||||
| } from '@/types.js'; | ||||
| import { obsoleteNotificationTypes, groupedNotificationTypes, FilterUnionByProperty } from '@/types.js'; | ||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||
| import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js'; | ||||
| import { NotificationService } from '@/core/NotificationService.js'; | ||||
|  | @ -52,10 +47,10 @@ export const paramDef = { | |||
| 		markAsRead: { type: 'boolean', default: true }, | ||||
| 		// 後方互換のため、廃止された通知タイプも受け付ける
 | ||||
| 		includeTypes: { type: 'array', items: { | ||||
| 			type: 'string', enum: [...notificationTypes, ...obsoleteNotificationTypes], | ||||
| 			type: 'string', enum: [...groupedNotificationTypes, ...obsoleteNotificationTypes], | ||||
| 		} }, | ||||
| 		excludeTypes: { type: 'array', items: { | ||||
| 			type: 'string', enum: [...notificationTypes, ...obsoleteNotificationTypes], | ||||
| 			type: 'string', enum: [...groupedNotificationTypes, ...obsoleteNotificationTypes], | ||||
| 		} }, | ||||
| 	}, | ||||
| 	required: [], | ||||
|  | @ -79,20 +74,31 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				return []; | ||||
| 			} | ||||
| 			// excludeTypes に全指定されている場合はクエリしない
 | ||||
| 			if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) { | ||||
| 			if (groupedNotificationTypes.every(type => ps.excludeTypes?.includes(type))) { | ||||
| 				return []; | ||||
| 			} | ||||
| 
 | ||||
| 			const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof groupedNotificationTypes[number][]; | ||||
| 			const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof groupedNotificationTypes[number][]; | ||||
| 
 | ||||
| 			const notifications = await this.notificationService.getNotifications(me.id, { | ||||
| 				sinceId: ps.sinceId, | ||||
| 				untilId: ps.untilId, | ||||
| 				limit: ps.limit, | ||||
| 				includeTypes, | ||||
| 				excludeTypes, | ||||
| 			}); | ||||
| 			const limit = (ps.limit + EXTRA_LIMIT) + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
 | ||||
| 			const notificationsRes = await this.redisClient.xrevrange( | ||||
| 				`notificationTimeline:${me.id}`, | ||||
| 				ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+', | ||||
| 				ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : '-', | ||||
| 				'COUNT', limit); | ||||
| 
 | ||||
| 			if (notificationsRes.length === 0) { | ||||
| 				return []; | ||||
| 			} | ||||
| 
 | ||||
| 			let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId && x !== ps.sinceId) as MiNotification[]; | ||||
| 
 | ||||
| 			if (includeTypes && includeTypes.length > 0) { | ||||
| 				notifications = notifications.filter(notification => includeTypes.includes(notification.type)); | ||||
| 			} else if (excludeTypes && excludeTypes.length > 0) { | ||||
| 				notifications = notifications.filter(notification => !excludeTypes.includes(notification.type)); | ||||
| 			} | ||||
| 
 | ||||
| 			if (notifications.length === 0) { | ||||
| 				return []; | ||||
|  |  | |||
|  | @ -82,13 +82,52 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][]; | ||||
| 			const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][]; | ||||
| 
 | ||||
| 			const notifications = await this.notificationService.getNotifications(me.id, { | ||||
| 				sinceId: ps.sinceId, | ||||
| 				untilId: ps.untilId, | ||||
| 				limit: ps.limit, | ||||
| 				includeTypes, | ||||
| 				excludeTypes, | ||||
| 			}); | ||||
| 			let sinceTime = ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime().toString() : null; | ||||
| 			let untilTime = ps.untilId ? this.idService.parse(ps.untilId).date.getTime().toString() : null; | ||||
| 
 | ||||
| 			let notifications: MiNotification[]; | ||||
| 			for (;;) { | ||||
| 				let notificationsRes: [id: string, fields: string[]][]; | ||||
| 
 | ||||
| 				// sinceidのみの場合は古い順、そうでない場合は新しい順。 QueryService.makePaginationQueryも参照
 | ||||
| 				if (sinceTime && !untilTime) { | ||||
| 					notificationsRes = await this.redisClient.xrange( | ||||
| 						`notificationTimeline:${me.id}`, | ||||
| 						'(' + sinceTime, | ||||
| 						'+', | ||||
| 						'COUNT', ps.limit); | ||||
| 				} else { | ||||
| 					notificationsRes = await this.redisClient.xrevrange( | ||||
| 						`notificationTimeline:${me.id}`, | ||||
| 						untilTime ? '(' + untilTime : '+', | ||||
| 						sinceTime ? '(' + sinceTime : '-', | ||||
| 						'COUNT', ps.limit); | ||||
| 				} | ||||
| 
 | ||||
| 				if (notificationsRes.length === 0) { | ||||
| 					return []; | ||||
| 				} | ||||
| 
 | ||||
| 				notifications = notificationsRes.map(x => JSON.parse(x[1][1])) as MiNotification[]; | ||||
| 
 | ||||
| 				if (includeTypes && includeTypes.length > 0) { | ||||
| 					notifications = notifications.filter(notification => includeTypes.includes(notification.type)); | ||||
| 				} else if (excludeTypes && excludeTypes.length > 0) { | ||||
| 					notifications = notifications.filter(notification => !excludeTypes.includes(notification.type)); | ||||
| 				} | ||||
| 
 | ||||
| 				if (notifications.length !== 0) { | ||||
| 					// 通知が1件以上ある場合は返す
 | ||||
| 					break; | ||||
| 				} | ||||
| 
 | ||||
| 				// フィルタしたことで通知が0件になった場合、次のページを取得する
 | ||||
| 				if (ps.sinceId && !ps.untilId) { | ||||
| 					sinceTime = notificationsRes[notificationsRes.length - 1][0]; | ||||
| 				} else { | ||||
| 					untilTime = notificationsRes[notificationsRes.length - 1][0]; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// Mark all as read
 | ||||
| 			if (ps.markAsRead) { | ||||
|  |  | |||
|  | @ -50,7 +50,6 @@ class GlobalTimelineChannel extends Channel { | |||
| 
 | ||||
| 		if (note.visibility !== 'public') return; | ||||
| 		if (note.channelId != null) return; | ||||
| 		if (note.user.requireSigninToViewContents && this.user == null) return; | ||||
| 
 | ||||
| 		if (isRenotePacked(note) && !isQuotePacked(note) && !this.withRenotes) return; | ||||
| 
 | ||||
|  |  | |||
|  | @ -53,7 +53,6 @@ class LocalTimelineChannel extends Channel { | |||
| 		if (note.user.host !== null) return; | ||||
| 		if (note.visibility !== 'public') return; | ||||
| 		if (note.channelId != null) return; | ||||
| 		if (note.user.requireSigninToViewContents && this.user == null) return; | ||||
| 
 | ||||
| 		// 関係ない返信は除外
 | ||||
| 		if (note.reply && this.user && !this.following[note.userId]?.withReplies && !this.withReplies) { | ||||
|  |  | |||
|  | @ -146,7 +146,6 @@ describe('アンテナ', () => { | |||
| 			caseSensitive: false, | ||||
| 			createdAt: new Date(response.createdAt).toISOString(), | ||||
| 			excludeKeywords: [['']], | ||||
| 			hideNotesInSensitiveChannel: false, | ||||
| 			hasUnreadNote: false, | ||||
| 			isActive: true, | ||||
| 			keywords: [['keyword']], | ||||
|  | @ -218,8 +217,6 @@ describe('アンテナ', () => { | |||
| 		{ parameters: () => ({ withReplies: true }) }, | ||||
| 		{ parameters: () => ({ withFile: false }) }, | ||||
| 		{ parameters: () => ({ withFile: true }) }, | ||||
| 		{ parameters: () => ({ hideNotesInSensitiveChannel: false }) }, | ||||
| 		{ parameters: () => ({ hideNotesInSensitiveChannel: true }) }, | ||||
| 	]; | ||||
| 	test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => { | ||||
| 		const response = await successfulApiCall({ | ||||
|  | @ -629,42 +626,6 @@ describe('アンテナ', () => { | |||
| 			assert.deepStrictEqual(response, expected); | ||||
| 		}); | ||||
| 
 | ||||
| 		test('が取得できること(センシティブチャンネルのノートを除く)', async () => { | ||||
| 			const keyword = 'キーワード'; | ||||
| 			const antenna = await successfulApiCall({ | ||||
| 				endpoint: 'antennas/create', | ||||
| 				parameters: { ...defaultParam, keywords: [[keyword]], hideNotesInSensitiveChannel: true }, | ||||
| 				user: alice, | ||||
| 			}); | ||||
| 			const nonSensitiveChannel = await successfulApiCall({ | ||||
| 				endpoint: 'channels/create', | ||||
| 				parameters: { name: 'test', isSensitive: false }, | ||||
| 				user: alice, | ||||
| 			}); | ||||
| 			const sensitiveChannel = await successfulApiCall({ | ||||
| 				endpoint: 'channels/create', | ||||
| 				parameters: { name: 'test', isSensitive: true }, | ||||
| 				user: alice, | ||||
| 			}); | ||||
| 
 | ||||
| 			const noteInLocal = await post(bob, { text: `test ${keyword}` }); | ||||
| 			const noteInNonSensitiveChannel = await post(bob, { text: `test ${keyword}`, channelId: nonSensitiveChannel.id }); | ||||
| 			await post(bob, { text: `test ${keyword}`, channelId: sensitiveChannel.id }); | ||||
| 
 | ||||
| 			const response = await successfulApiCall({ | ||||
| 				endpoint: 'antennas/notes', | ||||
| 				parameters: { antennaId: antenna.id }, | ||||
| 				user: alice, | ||||
| 			}); | ||||
| 			// 最後に投稿したものが先頭に来る。
 | ||||
| 			const expected = [ | ||||
| 				noteInNonSensitiveChannel, | ||||
| 				noteInLocal, | ||||
| 			]; | ||||
| 			assert.deepStrictEqual(response, expected); | ||||
| 		}); | ||||
| 
 | ||||
| 
 | ||||
| 		test.skip('が取得でき、日付指定のPaginationに一貫性があること', async () => { }); | ||||
| 		test.each([ | ||||
| 			{ label: 'ID指定', offsetBy: 'id' }, | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ | |||
| 		header: ':alpha<0.7<@panel', | ||||
| 		navBg: '@panel', | ||||
| 		navFg: '@fg', | ||||
| 		navHoverFg: ':lighten<17<@fg', | ||||
| 		navActive: '@accent', | ||||
| 		navIndicator: '@indicator', | ||||
| 		link: '#44a4c1', | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ | |||
| 		header: ':alpha<0.7<@panel', | ||||
| 		navBg: '@panel', | ||||
| 		navFg: '@fg', | ||||
| 		navHoverFg: ':darken<17<@fg', | ||||
| 		navActive: '@accent', | ||||
| 		navIndicator: '@indicator', | ||||
| 		link: '#44a4c1', | ||||
|  |  | |||
|  | @ -7,9 +7,9 @@ | |||
| 		bg: '#232125', | ||||
| 		fg: '#efdab9', | ||||
| 		link: '#78b0a0', | ||||
| 		warn: '#ffd152', | ||||
| 		warn: '#ecb637', | ||||
| 		badge: '#31b1ce', | ||||
| 		error: '#ff6652', | ||||
| 		error: '#ec4137', | ||||
| 		focus: ':alpha<0.3<@accent', | ||||
| 		navBg: '@panel', | ||||
| 		navFg: '@fg', | ||||
|  | @ -24,13 +24,14 @@ | |||
| 		hashtag: '#ff9156', | ||||
| 		mention: '#ffd152', | ||||
| 		modalBg: 'rgba(0, 0, 0, 0.5)', | ||||
| 		success: '#78b07f', | ||||
| 		success: '#86b300', | ||||
| 		indicator: '@accent', | ||||
| 		mentionMe: '#fb5d38', | ||||
| 		messageBg: '@bg', | ||||
| 		navActive: '@accent', | ||||
| 		infoWarnBg: '#42321c', | ||||
| 		infoWarnFg: '#ffbd3e', | ||||
| 		navHoverFg: ':lighten<17<@fg', | ||||
| 		dateLabelFg: '@fg', | ||||
| 		inputBorder: 'rgba(255, 255, 255, 0.1)', | ||||
| 		inputBorderHover: 'rgba(255, 255, 255, 0.2)', | ||||
|  |  | |||
|  | @ -22,8 +22,5 @@ | |||
| 		mentionMe: 'rgb(212, 210, 76)', | ||||
| 		hashtag: '#5bcbb0', | ||||
| 		link: '@accent', | ||||
| 		success: '@accent', | ||||
| 		warn: 'rgb(255, 213, 82)', | ||||
| 		error: 'rgb(255, 105, 82)', | ||||
| 	}, | ||||
| } | ||||
|  |  | |||
|  | @ -22,8 +22,5 @@ | |||
| 		mentionMe: '#de6161', | ||||
| 		hashtag: '#68bad0', | ||||
| 		link: '#a1c758', | ||||
| 		error: '#ce5441', | ||||
| 		warn: '#d0b868', | ||||
| 		success: '#a1c758', | ||||
| 	}, | ||||
| } | ||||
|  |  | |||
|  | @ -42,6 +42,7 @@ | |||
| 		fgOnWhite: '@accent', | ||||
| 		infoWarnBg: '#42321c', | ||||
| 		infoWarnFg: '#ffbd3e', | ||||
| 		navHoverFg: ':lighten<17<@fg', | ||||
| 		codeBoolean: '#c59eff', | ||||
| 		dateLabelFg: '@fg', | ||||
| 		inputBorder: 'rgba(255, 255, 255, 0.1)', | ||||
|  |  | |||
|  | @ -13,18 +13,18 @@ | |||
| 		fgHighlighted: '#6bc9a0', | ||||
| 		fgOnWhite: '@accent', | ||||
| 		divider: '#cfcfcf', | ||||
| 		panel: '#ebe7e5', | ||||
| 		panel: '@X14', | ||||
| 		panelHeaderBg: '@panel', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 		header: ':alpha<0.7<@panel', | ||||
| 		navBg: '#ebe7e5', | ||||
| 		navBg: '@X14', | ||||
| 		renote: '#229e92', | ||||
| 		mention: '#da6d35', | ||||
| 		mentionMe: '#d44c4c', | ||||
| 		hashtag: '#4cb8d4', | ||||
| 		link: '@accent', | ||||
| 		buttonGradateB: ':hue<-70<@accent', | ||||
| 		success: '@accent', | ||||
| 		error: '#da5635', | ||||
| 		success: '#86b300', | ||||
|     X14: '#ebe7e5' | ||||
| 	}, | ||||
| } | ||||
|  |  | |||
|  | @ -18,8 +18,5 @@ | |||
| 		mention: '@accent', | ||||
| 		mentionMe: 'rgb(170, 149, 98)', | ||||
| 		hashtag: '@accent', | ||||
| 		error: '#db9184', | ||||
| 		warn: '#dbc184', | ||||
| 		success: '#a3c975', | ||||
| 	}, | ||||
| } | ||||
|  |  | |||
|  | @ -43,6 +43,7 @@ | |||
| 		fgOnWhite: '@accent', | ||||
| 		infoWarnBg: '#42321c', | ||||
| 		infoWarnFg: '#ffbd3e', | ||||
| 		navHoverFg: ':lighten<17<@fg', | ||||
| 		codeBoolean: '#c59eff', | ||||
| 		dateLabelFg: '@fg', | ||||
| 		inputBorder: 'rgba(255, 255, 255, 0.1)', | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ | |||
| 		navActive: '@accent', | ||||
| 		infoWarnBg: '#fff0db', | ||||
| 		infoWarnFg: '#8f6e31', | ||||
| 		navHoverFg: ':darken<17<@fg', | ||||
| 		dateLabelFg: '@fg', | ||||
| 		inputBorder: 'rgba(0, 0, 0, 0.1)', | ||||
| 		inputBorderHover: 'rgba(0, 0, 0, 0.2)', | ||||
|  |  | |||
|  | @ -83,7 +83,7 @@ queueMicrotask(() => { | |||
| 			widgets(app); | ||||
| 			misskeyOS = os; | ||||
| 			if (isChromatic()) { | ||||
| 				prefer.commit('animation', false); | ||||
| 				prefer.set('animation', false); | ||||
| 			} | ||||
| 		}); | ||||
| 	}); | ||||
|  |  | |||
|  | @ -25,7 +25,6 @@ | |||
| 		"@rollup/plugin-json": "6.1.0", | ||||
| 		"@rollup/plugin-replace": "6.0.2", | ||||
| 		"@rollup/pluginutils": "5.1.4", | ||||
| 		"@sentry/vue": "9.8.0", | ||||
| 		"@syuilo/aiscript": "0.19.0", | ||||
| 		"@tabler/icons-webfont": "3.31.0", | ||||
| 		"@twemoji/parser": "15.1.1", | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| 
 | ||||
| import { computed, watch, version as vueVersion } from 'vue'; | ||||
| import { compareVersions } from 'compare-versions'; | ||||
| import { version, lang, updateLocale, locale, apiUrl } from '@@/js/config.js'; | ||||
| import { version, lang, updateLocale, locale } from '@@/js/config.js'; | ||||
| import defaultLightTheme from '@@/themes/l-light.json5'; | ||||
| import defaultDarkTheme from '@@/themes/d-green-lime.json5'; | ||||
| import type { App } from 'vue'; | ||||
|  | @ -291,41 +291,6 @@ export async function common(createVue: () => Promise<App<Element>>) { | |||
| 		return root; | ||||
| 	})(); | ||||
| 
 | ||||
| 	if (instance.sentryForFrontend) { | ||||
| 		const Sentry = await import('@sentry/vue'); | ||||
| 		Sentry.init({ | ||||
| 			app, | ||||
| 			integrations: [ | ||||
| 				...(instance.sentryForFrontend.vueIntegration !== undefined ? [ | ||||
| 					Sentry.vueIntegration(instance.sentryForFrontend.vueIntegration ?? undefined), | ||||
| 				] : []), | ||||
| 				...(instance.sentryForFrontend.browserTracingIntegration !== undefined ? [ | ||||
| 					Sentry.browserTracingIntegration(instance.sentryForFrontend.browserTracingIntegration ?? undefined), | ||||
| 				] : []), | ||||
| 				...(instance.sentryForFrontend.replayIntegration !== undefined ? [ | ||||
| 					Sentry.replayIntegration(instance.sentryForFrontend.replayIntegration ?? undefined), | ||||
| 				] : []), | ||||
| 			], | ||||
| 
 | ||||
| 			// Set tracesSampleRate to 1.0 to capture 100%
 | ||||
| 			tracesSampleRate: 1.0, | ||||
| 
 | ||||
| 			// Set `tracePropagationTargets` to control for which URLs distributed tracing should be enabled
 | ||||
| 			...(instance.sentryForFrontend.browserTracingIntegration !== undefined ? { | ||||
| 				tracePropagationTargets: [apiUrl], | ||||
| 			} : {}), | ||||
| 
 | ||||
| 			// Capture Replay for 10% of all sessions,
 | ||||
| 			// plus for 100% of sessions with an error
 | ||||
| 			...(instance.sentryForFrontend.replayIntegration !== undefined ? { | ||||
| 				replaysSessionSampleRate: 0.1, | ||||
| 				replaysOnErrorSampleRate: 1.0, | ||||
| 			} : {}), | ||||
| 
 | ||||
| 			...instance.sentryForFrontend.options, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	app.mount(rootEl); | ||||
| 
 | ||||
| 	// boot.jsのやつを解除
 | ||||
|  |  | |||
|  | @ -39,7 +39,6 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 			<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch> | ||||
| 			<MkSwitch v-model="caseSensitive">{{ i18n.ts.caseSensitive }}</MkSwitch> | ||||
| 			<MkSwitch v-model="withFile">{{ i18n.ts.withFileAntenna }}</MkSwitch> | ||||
| 			<MkSwitch v-model="hideNotesInSensitiveChannel">{{ i18n.ts.hideNotesInSensitiveChannel }}</MkSwitch> | ||||
| 		</div> | ||||
| 		<div :class="$style.actions"> | ||||
| 			<div class="_buttons"> | ||||
|  | @ -87,7 +86,6 @@ const initialAntenna = deepMerge<PartialAllowedAntenna>(props.antenna ?? {}, { | |||
| 	caseSensitive: false, | ||||
| 	localOnly: false, | ||||
| 	withFile: false, | ||||
| 	hideNotesInSensitiveChannel: false, | ||||
| 	isActive: true, | ||||
| 	hasUnreadNote: false, | ||||
| 	notify: false, | ||||
|  | @ -110,7 +108,6 @@ const localOnly = ref<boolean>(initialAntenna.localOnly); | |||
| const excludeBots = ref<boolean>(initialAntenna.excludeBots); | ||||
| const withReplies = ref<boolean>(initialAntenna.withReplies); | ||||
| const withFile = ref<boolean>(initialAntenna.withFile); | ||||
| const hideNotesInSensitiveChannel = ref<boolean>(initialAntenna.hideNotesInSensitiveChannel); | ||||
| const userLists = ref<Misskey.entities.UserList[] | null>(null); | ||||
| 
 | ||||
| watch(() => src.value, async () => { | ||||
|  | @ -127,7 +124,6 @@ async function saveAntenna() { | |||
| 		excludeBots: excludeBots.value, | ||||
| 		withReplies: withReplies.value, | ||||
| 		withFile: withFile.value, | ||||
| 		hideNotesInSensitiveChannel: hideNotesInSensitiveChannel.value, | ||||
| 		caseSensitive: caseSensitive.value, | ||||
| 		localOnly: localOnly.value, | ||||
| 		users: users.value.trim().split('\n').map(x => x.trim()), | ||||
|  |  | |||
|  | @ -215,14 +215,6 @@ onUnmounted(() => { | |||
| .content { | ||||
| 	--MI-stickyTop: 0px; | ||||
| 
 | ||||
| 	/* | ||||
| 	理屈は知らないけど、ここでbackgroundを設定しておかないと | ||||
| 	スクロールコンテナーが少なくともChromeにおいて | ||||
| 	main thread scrolling になってしまい、パフォーマンスが(多分)落ちる。 | ||||
| 	backgroundが透明だと裏側を描画しないといけなくなるとかそういう理由かもしれない | ||||
| 	*/ | ||||
| 	background: var(--MI_THEME-panel); | ||||
| 
 | ||||
| 	&.omitted { | ||||
| 		position: relative; | ||||
| 		max-height: var(--maxHeight); | ||||
|  |  | |||
|  | @ -12,13 +12,11 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 		<path transform="scale(.26458)" d="m439.77 247.19c-43.673 0-78.832 35.157-78.832 78.83v249.98h407.06v-328.81z" :fill="themeVariables.panel"/> | ||||
| 	</g> | ||||
| 	<circle cx="32" cy="83" r="21" :fill="themeVariables.accentedBg"/> | ||||
| 	<g> | ||||
| 		<rect x="120" y="88" width="40" height="6" ry="3" :fill="themeVariables.fg"/> | ||||
| 		<rect x="170" y="88" width="20" height="6" ry="3" :fill="themeVariables.mention"/> | ||||
| 		<rect x="120" y="108" width="20" height="6" ry="3" :fill="themeVariables.hashtag"/> | ||||
| 		<rect x="150" y="108" width="40" height="6" ry="3" :fill="themeVariables.fg"/> | ||||
| 		<rect x="120" y="128" width="40" height="6" ry="3" :fill="themeVariables.fg"/> | ||||
| 		<rect x="170" y="128" width="20" height="6" ry="3" :fill="themeVariables.link"/> | ||||
| 	<circle cx="136" cy="106" r="23" :fill="themeVariables.fg" fill-opacity="0.5"/> | ||||
| 	<g :fill="themeVariables.fg" fill-rule="evenodd"> | ||||
| 		<rect x="171" y="88" width="48" height="6" ry="3"/> | ||||
| 		<rect x="171" y="108" width="48" height="6" ry="3"/> | ||||
| 		<rect x="171" y="128" width="48" height="6" ry="3"/> | ||||
| 	</g> | ||||
| 	<path d="m65.498 40.892h137.7" :stroke="themeVariables.divider" stroke-width="0.75"/> | ||||
| 	<g transform="matrix(.60823 0 0 .60823 25.45 75.755)" fill="none" :stroke="themeVariables.accent" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"> | ||||
|  | @ -55,23 +53,14 @@ const themeVariables = ref<{ | |||
| 	bg: string; | ||||
| 	panel: string; | ||||
| 	fg: string; | ||||
| 	mention: string; | ||||
| 	hashtag: string; | ||||
| 	link: string; | ||||
| 	divider: string; | ||||
| 	accent: string; | ||||
| 	accentedBg: string; | ||||
| 	navBg: string; | ||||
| 	success: string; | ||||
| 	warn: string; | ||||
| 	error: string; | ||||
| }>({ | ||||
| 	bg: 'var(--MI_THEME-bg)', | ||||
| 	panel: 'var(--MI_THEME-panel)', | ||||
| 	fg: 'var(--MI_THEME-fg)', | ||||
| 	mention: 'var(--MI_THEME-mention)', | ||||
| 	hashtag: 'var(--MI_THEME-hashtag)', | ||||
| 	link: 'var(--MI_THEME-link)', | ||||
| 	divider: 'var(--MI_THEME-divider)', | ||||
| 	accent: 'var(--MI_THEME-accent)', | ||||
| 	accentedBg: 'var(--MI_THEME-accentedBg)', | ||||
|  | @ -97,9 +86,6 @@ watch(() => props.theme, (theme) => { | |||
| 		bg: compiled.bg ?? 'var(--MI_THEME-bg)', | ||||
| 		panel: compiled.panel ?? 'var(--MI_THEME-panel)', | ||||
| 		fg: compiled.fg ?? 'var(--MI_THEME-fg)', | ||||
| 		mention: compiled.mention ?? 'var(--MI_THEME-mention)', | ||||
| 		hashtag: compiled.hashtag ?? 'var(--MI_THEME-hashtag)', | ||||
| 		link: compiled.link ?? 'var(--MI_THEME-link)', | ||||
| 		divider: compiled.divider ?? 'var(--MI_THEME-divider)', | ||||
| 		accent: compiled.accent ?? 'var(--MI_THEME-accent)', | ||||
| 		accentedBg: compiled.accentedBg ?? 'var(--MI_THEME-accentedBg)', | ||||
|  |  | |||
|  | @ -249,7 +249,6 @@ async function close(skip: boolean) { | |||
| 
 | ||||
| .pageFooter { | ||||
| 	position: sticky; | ||||
| 	z-index: 1; | ||||
| 	bottom: 0; | ||||
| 	left: 0; | ||||
| 	flex-shrink: 0; | ||||
|  |  | |||
|  | @ -25,8 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 		<div :class="$style.footer"> | ||||
| 			<button class="_textButton" style="color: currentColor;" @click="showMenu"><i class="ti ti-dots-circle-horizontal"></i></button> | ||||
| 			<MkTime :class="$style.time" :time="message.createdAt"/> | ||||
| 			<MkA v-if="isSearchResult && 'toRoom' in message && message.toRoom != null" :to="`/chat/room/${message.toRoomId}`">{{ message.toRoom.name }}</MkA> | ||||
| 			<MkA v-if="isSearchResult && 'toUser' in message && message.toUser != null && isMe" :to="`/chat/user/${message.toUserId}`">@{{ message.toUser.username }}</MkA> | ||||
| 			<MkA v-if="isSearchResult && 'toRoom' in message && message.toRoom != null" :to="`/chat/room/${message.toRoomId}`">{{ message.toRoom?.name }}</MkA> | ||||
| 			<MkA v-if="isSearchResult && 'toUser' in message && message.toUser != null && isMe" :to="`/chat/user/${message.toUserId}`">@{{ message.toUser?.username }}</MkA> | ||||
| 		</div> | ||||
| 		<TransitionGroup | ||||
| 			:enterActiveClass="prefer.s.animation ? $style.transition_reaction_enterActive : ''" | ||||
|  | @ -36,8 +36,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 			:moveClass="prefer.s.animation ? $style.transition_reaction_move : ''" | ||||
| 			tag="div" :class="$style.reactions" | ||||
| 		> | ||||
| 			<div v-for="record in message.reactions" :key="record.reaction + record.user.id" :class="[$style.reaction, record.user.id === $i.id ? $style.reactionMy : null]" @click="onReactionClick(record)"> | ||||
| 				<MkAvatar :user="record.user" :link="false" :class="$style.reactionAvatar"/> | ||||
| 			<div v-for="record in message.reactions" :key="record.reaction + record.user?.id" :class="[$style.reaction, record.user?.id === $i.id ? $style.reactionMy : null]" @click="onReactionClick(record)"> | ||||
| 				<MkAvatar :user="record.user!" :link="false" :class="$style.reactionAvatar"/> | ||||
| 				<MkReactionIcon | ||||
| 					:withTooltip="true" | ||||
| 					:reaction="record.reaction.replace(/^:(\w+):$/, ':$1@.:')" | ||||
|  | @ -57,7 +57,6 @@ import * as Misskey from 'misskey-js'; | |||
| import { url } from '@@/js/config.js'; | ||||
| import { isLink } from '@@/js/is-link.js'; | ||||
| import type { MenuItem } from '@/types/menu.js'; | ||||
| import type { NormalizedChatMessage } from './room.vue'; | ||||
| import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js'; | ||||
| import MkUrlPreview from '@/components/MkUrlPreview.vue'; | ||||
| import { ensureSignin } from '@/i.js'; | ||||
|  | @ -77,7 +76,7 @@ import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js'; | |||
| const $i = ensureSignin(); | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
| 	message: NormalizedChatMessage | Misskey.entities.ChatMessage; | ||||
| 	message: Misskey.entities.ChatMessageLite | Misskey.entities.ChatMessage; | ||||
| 	isSearchResult?: boolean; | ||||
| }>(); | ||||
| 
 | ||||
|  | @ -112,13 +111,13 @@ function react(ev: MouseEvent) { | |||
| function onReactionClick(record: Misskey.entities.ChatMessage['reactions'][0]) { | ||||
| 	if (!$i.policies.canChat) return; | ||||
| 
 | ||||
| 	if (record.user.id === $i.id) { | ||||
| 	if (record.user?.id === $i.id) { | ||||
| 		misskeyApi('chat/messages/unreact', { | ||||
| 			messageId: props.message.id, | ||||
| 			reaction: record.reaction, | ||||
| 		}); | ||||
| 	} else { | ||||
| 		if (!props.message.reactions.some(r => r.user.id === $i.id && r.reaction === record.reaction)) { | ||||
| 		if (!props.message.reactions.some(r => r.user?.id === $i.id && r.reaction === record.reaction)) { | ||||
| 			sound.playMisskeySfx('reaction'); | ||||
| 			misskeyApi('chat/messages/react', { | ||||
| 				messageId: props.message.id, | ||||
|  |  | |||
|  | @ -14,8 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 	<hr v-if="memberships.length > 0"> | ||||
| 
 | ||||
| 	<div v-for="membership in memberships" :key="membership.id" :class="$style.membership"> | ||||
| 		<MkA :class="$style.membershipBody" :to="`${userPage(membership.user!)}`"> | ||||
| 			<MkUserCardMini :user="membership.user!"/> | ||||
| 		<MkA v-if="membership.user != null" :class="$style.membershipBody" :to="`${userPage(membership.user)}`"> | ||||
| 			<MkUserCardMini :user="membership.user"/> | ||||
| 		</MkA> | ||||
| 	</div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -110,7 +110,7 @@ const props = defineProps<{ | |||
| 	roomId?: string; | ||||
| }>(); | ||||
| 
 | ||||
| export type NormalizedChatMessage = Omit<Misskey.entities.ChatMessageLite, 'fromUser' | 'reactions'> & { | ||||
| type NormalizedChatMessage = Omit<Misskey.entities.ChatMessageLite, 'fromUser' | 'reactions'> & { | ||||
| 	fromUser: Misskey.entities.UserLite; | ||||
| 	reactions: (Misskey.entities.ChatMessageLite['reactions'][number] & { | ||||
| 		user: Misskey.entities.UserLite; | ||||
|  | @ -178,10 +178,10 @@ async function initialize() { | |||
| 		connection.value = useStream().useChannel('chatUser', { | ||||
| 			otherId: user.value.id, | ||||
| 		}); | ||||
| 		connection.value.on('message', onMessage); | ||||
| 		connection.value.on('deleted', onDeleted); | ||||
| 		connection.value.on('react', onReact); | ||||
| 		connection.value.on('unreact', onUnreact); | ||||
| 		connection.value?.on('message', onMessage); | ||||
| 		connection.value?.on('deleted', onDeleted); | ||||
| 		connection.value?.on('react', onReact); | ||||
| 		connection.value?.on('unreact', onUnreact); | ||||
| 	} else { | ||||
| 		const [r, m] = await Promise.all([ | ||||
| 			misskeyApi('chat/rooms/show', { roomId: props.roomId }), | ||||
|  | @ -272,10 +272,10 @@ function onReact(ctx: Parameters<Misskey.Channels['chatUser']['events']['react'] | |||
| 				reaction: ctx.reaction, | ||||
| 				user: message.fromUserId === $i.id ? user.value! : $i, | ||||
| 			}); | ||||
| 		} else { | ||||
| 		} else if (ctx.user != null) { | ||||
| 			message.reactions.push({ | ||||
| 				reaction: ctx.reaction, | ||||
| 				user: ctx.user!, | ||||
| 				user: ctx.user, | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | @ -283,7 +283,7 @@ function onReact(ctx: Parameters<Misskey.Channels['chatUser']['events']['react'] | |||
| 
 | ||||
| function onUnreact(ctx: Parameters<Misskey.Channels['chatUser']['events']['unreact']>[0] | Parameters<Misskey.Channels['chatRoom']['events']['unreact']>[0]) { | ||||
| 	const message = messages.value.find(m => m.id === ctx.messageId); | ||||
| 	if (message) { | ||||
| 	if (message && ctx.user != null) { | ||||
| 		const index = message.reactions.findIndex(r => r.reaction === ctx.reaction && r.user.id === ctx.user!.id); | ||||
| 		if (index !== -1) { | ||||
| 			message.reactions.splice(index, 1); | ||||
|  | @ -318,14 +318,12 @@ async function inviteUser() { | |||
| 
 | ||||
| 	const invitee = await os.selectUser({ includeSelf: false, localOnly: true }); | ||||
| 	os.apiWithDialog('chat/rooms/invitations/create', { | ||||
| 		roomId: room.value.id, | ||||
| 		roomId: room.value?.id, | ||||
| 		userId: invitee.id, | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| async function leaveRoom() { | ||||
| 	if (room.value == null) return; | ||||
| 
 | ||||
| 	const { canceled } = await os.confirm({ | ||||
| 		type: 'warning', | ||||
| 		text: i18n.ts.areYouSure, | ||||
|  | @ -333,7 +331,7 @@ async function leaveRoom() { | |||
| 	if (canceled) return; | ||||
| 
 | ||||
| 	misskeyApi('chat/rooms/leave', { | ||||
| 		roomId: room.value.id, | ||||
| 		roomId: room.value?.id, | ||||
| 	}); | ||||
| 	router.push('/chat'); | ||||
| } | ||||
|  |  | |||
|  | @ -150,7 +150,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{ | |||
| 			}, | ||||
| 		}, { | ||||
| 			icon: 'ti ti-code', | ||||
| 			text: i18n.ts.embed, | ||||
| 			text: i18n.ts.genEmbedCode, | ||||
| 			action: () => { | ||||
| 				genEmbedCode('clips', clip.value!.id); | ||||
| 			}, | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 	<div class="_gaps_m"> | ||||
| 		<SearchMarker :keywords="['sync', 'profiles', 'devices']"> | ||||
| 			<MkSwitch :modelValue="profilesSyncEnabled" @update:modelValue="changeProfilesSyncEnabled"> | ||||
| 				<template #label><i class="ti ti-cloud-cog"></i> <SearchLabel>{{ i18n.ts._deck.enableSyncBetweenDevicesForProfiles }}</SearchLabel></template> | ||||
| 				<template #label><SearchLabel>{{ i18n.ts._deck.enableSyncBetweenDevicesForProfiles }}</SearchLabel></template> | ||||
| 			</MkSwitch> | ||||
| 		</SearchMarker> | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 			<div class="_gaps_m"> | ||||
| 				<SearchMarker :keywords="['sync', 'palettes', 'devices']"> | ||||
| 					<MkSwitch :modelValue="palettesSyncEnabled" @update:modelValue="changePalettesSyncEnabled"> | ||||
| 						<template #label><i class="ti ti-cloud-cog"></i> <SearchLabel>{{ i18n.ts._emojiPalette.enableSyncBetweenDevicesForPalettes }}</SearchLabel></template> | ||||
| 						<template #label><SearchLabel>{{ i18n.ts._emojiPalette.enableSyncBetweenDevicesForPalettes }}</SearchLabel></template> | ||||
| 					</MkSwitch> | ||||
| 				</SearchMarker> | ||||
| 			</div> | ||||
|  |  | |||
|  | @ -181,12 +181,6 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 			</template> | ||||
| 		</div> | ||||
| 
 | ||||
| 		<SearchMarker :keywords="['sync', 'themes', 'devices']"> | ||||
| 			<MkSwitch :modelValue="themesSyncEnabled" @update:modelValue="changeThemesSyncEnabled"> | ||||
| 				<template #label><i class="ti ti-cloud-cog"></i> <SearchLabel>{{ i18n.ts._settings.enableSyncThemesBetweenDevices }}</SearchLabel></template> | ||||
| 			</MkSwitch> | ||||
| 		</SearchMarker> | ||||
| 
 | ||||
| 		<FormSection> | ||||
| 			<div class="_formLinksGrid"> | ||||
| 				<FormLink to="/settings/theme/manage"><template #icon><i class="ti ti-tool"></i></template>{{ i18n.ts._theme.manage }}<template #suffix>{{ themesCount }}</template></FormLink> | ||||
|  | @ -270,20 +264,6 @@ watch(syncDeviceDarkMode, () => { | |||
| 	} | ||||
| }); | ||||
| 
 | ||||
| const themesSyncEnabled = ref(prefer.isSyncEnabled('themes')); | ||||
| 
 | ||||
| function changeThemesSyncEnabled(value: boolean) { | ||||
| 	if (value) { | ||||
| 		prefer.enableSync('themes').then((res) => { | ||||
| 			if (res == null) return; | ||||
| 			if (res.enabled) themesSyncEnabled.value = true; | ||||
| 		}); | ||||
| 	} else { | ||||
| 		prefer.disableSync('themes'); | ||||
| 		themesSyncEnabled.value = false; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| const headerActions = computed(() => []); | ||||
| 
 | ||||
| const headerTabs = computed(() => []); | ||||
|  |  | |||
|  | @ -56,7 +56,7 @@ const headerActions = computed(() => [{ | |||
| 	label: i18n.ts.more, | ||||
| 	handler: (ev: MouseEvent) => { | ||||
| 		os.popupMenu([{ | ||||
| 			text: i18n.ts.embed, | ||||
| 			text: i18n.ts.genEmbedCode, | ||||
| 			icon: 'ti ti-code', | ||||
| 			action: () => { | ||||
| 				genEmbedCode('tags', props.tag); | ||||
|  |  | |||
|  | @ -221,7 +221,7 @@ function more() { | |||
| 
 | ||||
| 	&:hover { | ||||
| 		text-decoration: none; | ||||
| 		color: light-dark(hsl(from var(--MI_THEME-navFg) h s calc(l - 17)), hsl(from var(--MI_THEME-navFg) h s calc(l + 17))); | ||||
| 		color: var(--MI_THEME-navHoverFg); | ||||
| 	} | ||||
| 
 | ||||
| 	&.active { | ||||
|  |  | |||
|  | @ -149,7 +149,7 @@ onMounted(() => { | |||
| 
 | ||||
| 				&:hover { | ||||
| 					text-decoration: none; | ||||
| 					color: light-dark(hsl(from var(--MI_THEME-navFg) h s calc(l - 17)), hsl(from var(--MI_THEME-navFg) h s calc(l + 17))); | ||||
| 					color: var(--MI_THEME-navHoverFg); | ||||
| 				} | ||||
| 
 | ||||
| 				&.active { | ||||
|  |  | |||
|  | @ -456,7 +456,7 @@ function menuEdit() { | |||
| 
 | ||||
| 		&:hover { | ||||
| 			text-decoration: none; | ||||
| 			color: light-dark(hsl(from var(--MI_THEME-navFg) h s calc(l - 17)), hsl(from var(--MI_THEME-navFg) h s calc(l + 17))); | ||||
| 			color: var(--MI_THEME-navHoverFg); | ||||
| 		} | ||||
| 
 | ||||
| 		&.active { | ||||
|  |  | |||
|  | @ -12,15 +12,15 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 
 | ||||
| 		<XAnnouncements v-if="$i"/> | ||||
| 		<XStatusBars/> | ||||
| 
 | ||||
| 		<div :class="$style.columnsWrapper"> | ||||
| 		  <!-- passive: https://bugs.webkit.org/show_bug.cgi?id=281300 --> | ||||
| 			<div ref="columnsEl" :class="[$style.columns, { [$style.center]: prefer.r['deck.columnAlign'].value === 'center', [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu" @wheel.passive.self="onWheel"> | ||||
| 			<div ref="columnsEl" :class="[$style.columns, { [$style.center]: prefer.r['deck.columnAlign'].value === 'center', [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu" @wheel.self="onWheel"> | ||||
| 				<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため --> | ||||
| 				<section | ||||
| 					v-for="ids in layout" | ||||
| 					:class="$style.section" | ||||
| 					:style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }" | ||||
| 					@wheel.passive.self="onWheel" | ||||
| 					@wheel.self="onWheel" | ||||
| 				> | ||||
| 					<component | ||||
| 						:is="columnComponents[columns.find(c => c.id === id)!.type] ?? XTlColumn" | ||||
|  | @ -168,8 +168,7 @@ window.addEventListener('resize', () => { | |||
| 	isMobile.value = window.innerWidth <= 500; | ||||
| }); | ||||
| 
 | ||||
| // ポインターイベント非対応用に初期値はUAから出す | ||||
| const snapScroll = ref(deviceKind === 'smartphone' || deviceKind === 'tablet'); | ||||
| const snapScroll = deviceKind === 'smartphone' || deviceKind === 'tablet'; | ||||
| const withWallpaper = prefer.s['deck.wallpaper'] != null; | ||||
| const drawerMenuShowing = ref(false); | ||||
| const gap = prefer.r['deck.columnGap']; | ||||
|  | @ -220,16 +219,7 @@ const onContextmenu = (ev) => { | |||
| 	}], ev); | ||||
| }; | ||||
| 
 | ||||
| // タッチでスクロールしてるときはスナップスクロールを有効にする | ||||
| function pointerEvent(ev: PointerEvent) { | ||||
| 	snapScroll.value = ev.pointerType === 'touch'; | ||||
| } | ||||
| 
 | ||||
| window.document.addEventListener('pointerdown', pointerEvent, { passive: true }); | ||||
| 
 | ||||
| function onWheel(ev: WheelEvent) { | ||||
|   // WheelEvent はマウスからしか発火しないのでスナップスクロールは無効化する | ||||
|   snapScroll.value = false; | ||||
| 	if (ev.deltaX === 0 && columnsEl.value != null) { | ||||
| 		columnsEl.value.scrollLeft += ev.deltaY; | ||||
| 	} | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 		@dragstart="onDragstart" | ||||
| 		@dragend="onDragend" | ||||
| 		@contextmenu.prevent.stop="onContextmenu" | ||||
| 		@wheel.passive="emit('headerWheel', $event)" | ||||
| 		@wheel="emit('headerWheel', $event)" | ||||
| 	> | ||||
| 		<svg viewBox="0 0 256 128" :class="$style.tabShape"> | ||||
| 			<g transform="matrix(6.2431,0,0,6.2431,-677.417,-29.3839)"> | ||||
|  |  | |||
|  | @ -37,11 +37,6 @@ export const searchIndexes: SearchIndexItem[] = [ | |||
| 				label: i18n.ts.themeForDarkMode, | ||||
| 				keywords: ['dark', 'theme'], | ||||
| 			}, | ||||
| 			{ | ||||
| 				id: 'jwW5HULqA', | ||||
| 				label: i18n.ts._settings.enableSyncThemesBetweenDevices, | ||||
| 				keywords: ['sync', 'themes', 'devices'], | ||||
| 			}, | ||||
| 		], | ||||
| 		label: i18n.ts.theme, | ||||
| 		keywords: ['theme'], | ||||
|  |  | |||
|  | @ -331,7 +331,7 @@ export function getNoteMenu(props: { | |||
| 				}, | ||||
| 			}); | ||||
| 		} else { | ||||
| 			menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.embed)); | ||||
| 			menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode)); | ||||
| 		} | ||||
| 
 | ||||
| 		if (isSupportShare()) { | ||||
|  | @ -489,7 +489,7 @@ export function getNoteMenu(props: { | |||
| 				}, | ||||
| 			}); | ||||
| 		} else { | ||||
| 			menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.embed)); | ||||
| 			menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -198,7 +198,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router | |||
| 	} else { | ||||
| 		menuItems.push({ | ||||
| 			icon: 'ti ti-code', | ||||
| 			text: i18n.ts.embed, | ||||
| 			text: i18n.ts.genEmbedCode, | ||||
| 			type: 'parent', | ||||
| 			children: [{ | ||||
| 				text: i18n.ts.noteOfThisUser, | ||||
|  |  | |||
|  | @ -9,9 +9,9 @@ import type { toHiragana as toHiraganaType } from 'wanakana'; | |||
| let toHiragana: typeof toHiraganaType = (str?: string) => str ?? ''; | ||||
| let isWanakanaLoaded = false; | ||||
| 
 | ||||
| /** | ||||
| /**  | ||||
|  * ローマ字変換のセットアップ(日本語以外の環境で読み込まないのでlazy-loading) | ||||
|  * | ||||
|  *  | ||||
|  * ここの比較系関数を使う際は事前に呼び出す必要がある | ||||
|  */ | ||||
| export async function initIntlString(forceWanakana = false) { | ||||
|  | @ -82,17 +82,16 @@ export function normalizeStringWithHiragana(str: string) { | |||
| 
 | ||||
| /** aとbが同じかどうか */ | ||||
| export function compareStringEquals(a: string, b: string) { | ||||
| 	if (a === b) return true; // まったく同じ場合はtrue。なお、ノーマライズ前後で文字数が変化することがあるため、文字数が違うからといってfalseにはできない
 | ||||
| 	if (normalizeString(a) === normalizeString(b)) return true; | ||||
| 	if (normalizeStringWithHiragana(a) === normalizeStringWithHiragana(b)) return true; | ||||
| 	return false; | ||||
| 	return ( | ||||
| 		normalizeString(a) === normalizeString(b) || | ||||
| 		normalizeStringWithHiragana(a) === normalizeStringWithHiragana(b) | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| /** baseにqueryが含まれているかどうか */ | ||||
| export function compareStringIncludes(base: string, query: string) { | ||||
| 	if (base === query) return true; // まったく同じ場合は含まれていると考えてよいのでtrue
 | ||||
| 	if (base.includes(query)) return true; | ||||
| 	if (normalizeString(base).includes(normalizeString(query))) return true; | ||||
| 	if (normalizeStringWithHiragana(base).includes(normalizeStringWithHiragana(query))) return true; | ||||
| 	return false; | ||||
| 	return ( | ||||
| 		normalizeString(base).includes(normalizeString(query)) || | ||||
| 		normalizeStringWithHiragana(base).includes(normalizeStringWithHiragana(query)) | ||||
| 	); | ||||
| } | ||||
|  |  | |||
|  | @ -1007,12 +1007,6 @@ type ChatMessage = components['schemas']['ChatMessage']; | |||
| // @public (undocumented) | ||||
| type ChatMessageLite = components['schemas']['ChatMessageLite']; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| type ChatMessageLiteFor1on1 = components['schemas']['ChatMessageLiteFor1on1']; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| type ChatMessageLiteForRoom = components['schemas']['ChatMessageLiteForRoom']; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| type ChatMessagesCreateToRoomRequest = operations['chat___messages___create-to-room']['requestBody']['content']['application/json']; | ||||
| 
 | ||||
|  | @ -2140,8 +2134,6 @@ declare namespace entities { | |||
|         AbuseReportNotificationRecipient, | ||||
|         ChatMessage, | ||||
|         ChatMessageLite, | ||||
|         ChatMessageLiteFor1on1, | ||||
|         ChatMessageLiteForRoom, | ||||
|         ChatRoom, | ||||
|         ChatRoomInvitation, | ||||
|         ChatRoomMembership | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| { | ||||
| 	"type": "module", | ||||
| 	"name": "misskey-js", | ||||
| 	"version": "2025.4.0-beta.1", | ||||
| 	"version": "2025.4.0-alpha.0", | ||||
| 	"description": "Misskey SDK for JavaScript", | ||||
| 	"license": "MIT", | ||||
| 	"main": "./built/index.js", | ||||
|  |  | |||
|  | @ -56,8 +56,6 @@ export type SystemWebhook = components['schemas']['SystemWebhook']; | |||
| export type AbuseReportNotificationRecipient = components['schemas']['AbuseReportNotificationRecipient']; | ||||
| export type ChatMessage = components['schemas']['ChatMessage']; | ||||
| export type ChatMessageLite = components['schemas']['ChatMessageLite']; | ||||
| export type ChatMessageLiteFor1on1 = components['schemas']['ChatMessageLiteFor1on1']; | ||||
| export type ChatMessageLiteForRoom = components['schemas']['ChatMessageLiteForRoom']; | ||||
| export type ChatRoom = components['schemas']['ChatRoom']; | ||||
| export type ChatRoomInvitation = components['schemas']['ChatRoomInvitation']; | ||||
| export type ChatRoomMembership = components['schemas']['ChatRoomMembership']; | ||||
|  |  | |||
|  | @ -4898,8 +4898,6 @@ export type components = { | |||
|       hasUnreadNote: boolean; | ||||
|       /** @default false */ | ||||
|       notify: boolean; | ||||
|       /** @default false */ | ||||
|       hideNotesInSensitiveChannel: boolean; | ||||
|     }; | ||||
|     Clip: { | ||||
|       /** | ||||
|  | @ -5311,21 +5309,6 @@ export type components = { | |||
|       enableEmail: boolean; | ||||
|       enableServiceWorker: boolean; | ||||
|       translatorAvailable: boolean; | ||||
|       sentryForFrontend: ({ | ||||
|         options: { | ||||
|           dsn: string; | ||||
|           [key: string]: unknown; | ||||
|         }; | ||||
|         vueIntegration?: { | ||||
|           [key: string]: unknown; | ||||
|         } | null; | ||||
|         browserTracingIntegration?: { | ||||
|           [key: string]: unknown; | ||||
|         } | null; | ||||
|         replayIntegration?: { | ||||
|           [key: string]: unknown; | ||||
|         } | null; | ||||
|       }) | null; | ||||
|       mediaProxy: string; | ||||
|       enableUrlPreview: boolean; | ||||
|       backgroundImageUrl: string | null; | ||||
|  | @ -5406,10 +5389,10 @@ export type components = { | |||
|       fileId?: string | null; | ||||
|       file?: components['schemas']['DriveFile'] | null; | ||||
|       isRead?: boolean; | ||||
|       reactions: { | ||||
|       reactions: ({ | ||||
|           reaction: string; | ||||
|           user: components['schemas']['UserLite']; | ||||
|         }[]; | ||||
|           user?: components['schemas']['UserLite'] | null; | ||||
|         })[]; | ||||
|     }; | ||||
|     ChatMessageLite: { | ||||
|       id: string; | ||||
|  | @ -5427,34 +5410,6 @@ export type components = { | |||
|           user?: components['schemas']['UserLite'] | null; | ||||
|         })[]; | ||||
|     }; | ||||
|     ChatMessageLiteFor1on1: { | ||||
|       id: string; | ||||
|       /** Format: date-time */ | ||||
|       createdAt: string; | ||||
|       fromUserId: string; | ||||
|       toUserId: string; | ||||
|       text?: string | null; | ||||
|       fileId?: string | null; | ||||
|       file?: components['schemas']['DriveFile'] | null; | ||||
|       reactions: { | ||||
|           reaction: string; | ||||
|         }[]; | ||||
|     }; | ||||
|     ChatMessageLiteForRoom: { | ||||
|       id: string; | ||||
|       /** Format: date-time */ | ||||
|       createdAt: string; | ||||
|       fromUserId: string; | ||||
|       fromUser: components['schemas']['UserLite']; | ||||
|       toRoomId: string; | ||||
|       text?: string | null; | ||||
|       fileId?: string | null; | ||||
|       file?: components['schemas']['DriveFile'] | null; | ||||
|       reactions: { | ||||
|           reaction: string; | ||||
|           user: components['schemas']['UserLite']; | ||||
|         }[]; | ||||
|     }; | ||||
|     ChatRoom: { | ||||
|       id: string; | ||||
|       /** Format: date-time */ | ||||
|  | @ -11335,7 +11290,6 @@ export type operations = { | |||
|           excludeBots?: boolean; | ||||
|           withReplies: boolean; | ||||
|           withFile: boolean; | ||||
|           hideNotesInSensitiveChannel?: boolean; | ||||
|         }; | ||||
|       }; | ||||
|     }; | ||||
|  | @ -11617,7 +11571,6 @@ export type operations = { | |||
|           excludeBots?: boolean; | ||||
|           withReplies?: boolean; | ||||
|           withFile?: boolean; | ||||
|           hideNotesInSensitiveChannel?: boolean; | ||||
|         }; | ||||
|       }; | ||||
|     }; | ||||
|  | @ -14095,7 +14048,7 @@ export type operations = { | |||
|       /** @description OK (with results) */ | ||||
|       200: { | ||||
|         content: { | ||||
|           'application/json': components['schemas']['ChatMessageLiteForRoom']; | ||||
|           'application/json': components['schemas']['ChatMessageLite']; | ||||
|         }; | ||||
|       }; | ||||
|       /** @description Client error */ | ||||
|  | @ -14158,7 +14111,7 @@ export type operations = { | |||
|       /** @description OK (with results) */ | ||||
|       200: { | ||||
|         content: { | ||||
|           'application/json': components['schemas']['ChatMessageLiteFor1on1']; | ||||
|           'application/json': components['schemas']['ChatMessageLite']; | ||||
|         }; | ||||
|       }; | ||||
|       /** @description Client error */ | ||||
|  | @ -14333,7 +14286,7 @@ export type operations = { | |||
|       /** @description OK (with results) */ | ||||
|       200: { | ||||
|         content: { | ||||
|           'application/json': components['schemas']['ChatMessageLiteForRoom'][]; | ||||
|           'application/json': components['schemas']['ChatMessageLite'][]; | ||||
|         }; | ||||
|       }; | ||||
|       /** @description Client error */ | ||||
|  | @ -14561,7 +14514,7 @@ export type operations = { | |||
|       /** @description OK (with results) */ | ||||
|       200: { | ||||
|         content: { | ||||
|           'application/json': components['schemas']['ChatMessageLiteFor1on1'][]; | ||||
|           'application/json': components['schemas']['ChatMessageLite'][]; | ||||
|         }; | ||||
|       }; | ||||
|       /** @description Client error */ | ||||
|  | @ -21779,8 +21732,8 @@ export type operations = { | |||
|           untilId?: string; | ||||
|           /** @default true */ | ||||
|           markAsRead?: boolean; | ||||
|           includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; | ||||
|           excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; | ||||
|           includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; | ||||
|           excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; | ||||
|         }; | ||||
|       }; | ||||
|     }; | ||||
|  |  | |||
|  | @ -444,9 +444,6 @@ importers: | |||
|       '@nestjs/platform-express': | ||||
|         specifier: 10.4.15 | ||||
|         version: 10.4.15(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.12) | ||||
|       '@sentry/vue': | ||||
|         specifier: 9.8.0 | ||||
|         version: 9.8.0(vue@3.5.13(typescript@5.8.2)) | ||||
|       '@simplewebauthn/types': | ||||
|         specifier: 12.0.0 | ||||
|         version: 12.0.0 | ||||
|  | @ -712,9 +709,6 @@ importers: | |||
|       '@rollup/pluginutils': | ||||
|         specifier: 5.1.4 | ||||
|         version: 5.1.4(rollup@4.36.0) | ||||
|       '@sentry/vue': | ||||
|         specifier: 9.8.0 | ||||
|         version: 9.8.0(vue@3.5.13(typescript@5.8.2)) | ||||
|       '@syuilo/aiscript': | ||||
|         specifier: 0.19.0 | ||||
|         version: 0.19.0 | ||||
|  | @ -3561,34 +3555,10 @@ packages: | |||
|   '@sec-ant/readable-stream@0.4.1': | ||||
|     resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} | ||||
| 
 | ||||
|   '@sentry-internal/browser-utils@9.8.0': | ||||
|     resolution: {integrity: sha512-7aQDeU9ogMLKnEBFM/vvgMMgZDkfMhoZCtX8kq65gn33L4X2B8sI5oyUj2QJtXaRSsiUjbdCaquDLqZBCaLQHA==} | ||||
|     engines: {node: '>=18'} | ||||
| 
 | ||||
|   '@sentry-internal/feedback@9.8.0': | ||||
|     resolution: {integrity: sha512-xWiCJkD8ROuy2pnojuRLcLI6sezK399gasA5ZL4MCXdkryqZYs55Ef2Ofj4z0RdUc8gMUb81+LTqwbmbfTqNlQ==} | ||||
|     engines: {node: '>=18'} | ||||
| 
 | ||||
|   '@sentry-internal/replay-canvas@9.8.0': | ||||
|     resolution: {integrity: sha512-/6ELOnyCOItvqv2Os29JhE8ydDds3xibMQ+FomsSkClQdC4bbc/L74nm/fdXVpJkMswtjksiTwZo1nYTS3JsIw==} | ||||
|     engines: {node: '>=18'} | ||||
| 
 | ||||
|   '@sentry-internal/replay@9.8.0': | ||||
|     resolution: {integrity: sha512-YJhhNnrsufYVIX9s5lNSFFQrBJjUtn5AxvrcnN0fvLymNg3Y73GOUpFmhTxyELjQneKiOViClxjoWSVAN7sqQA==} | ||||
|     engines: {node: '>=18'} | ||||
| 
 | ||||
|   '@sentry/browser@9.8.0': | ||||
|     resolution: {integrity: sha512-iFM4PGLc6qCb0GaHnA5Uy09k25RXVSepAgS574cm1CH7II1wrRjTozKnPKROW89WDMuxoTOL7Tk7qPGCyWmA4g==} | ||||
|     engines: {node: '>=18'} | ||||
| 
 | ||||
|   '@sentry/core@8.55.0': | ||||
|     resolution: {integrity: sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==} | ||||
|     engines: {node: '>=14.18'} | ||||
| 
 | ||||
|   '@sentry/core@9.8.0': | ||||
|     resolution: {integrity: sha512-EnN2yLWCbWjooWBPzwlXdZoJG/Bqn3ymbuXX++DUJuBGjSmtixQeTf/hKeVzj4zbib3BbbYsNBasRVjq8Rk5ng==} | ||||
|     engines: {node: '>=18'} | ||||
| 
 | ||||
|   '@sentry/node@8.55.0': | ||||
|     resolution: {integrity: sha512-h10LJLDTRAzYgay60Oy7moMookqqSZSviCWkkmHZyaDn+4WURnPp5SKhhfrzPRQcXKrweiOwDSHBgn1tweDssg==} | ||||
|     engines: {node: '>=14.18'} | ||||
|  | @ -3609,16 +3579,6 @@ packages: | |||
|     engines: {node: '>=14.18'} | ||||
|     hasBin: true | ||||
| 
 | ||||
|   '@sentry/vue@9.8.0': | ||||
|     resolution: {integrity: sha512-E+27lL+aU8HjDo3DD3TlgStTIxBZHVqz6jZcL0/tig/JldpFRetO77terRHNfSVlPc0m3aNXuARu7G438f7ZlQ==} | ||||
|     engines: {node: '>=18'} | ||||
|     peerDependencies: | ||||
|       pinia: 2.x || 3.x | ||||
|       vue: 2.x || 3.x | ||||
|     peerDependenciesMeta: | ||||
|       pinia: | ||||
|         optional: true | ||||
| 
 | ||||
|   '@shikijs/core@3.2.1': | ||||
|     resolution: {integrity: sha512-FhsdxMWYu/C11sFisEp7FMGBtX/OSSbnXZDMBhGuUDBNTdsoZlMSgQv5f90rwvzWAdWIW6VobD+G3IrazxA6dQ==} | ||||
| 
 | ||||
|  | @ -13685,36 +13645,8 @@ snapshots: | |||
| 
 | ||||
|   '@sec-ant/readable-stream@0.4.1': {} | ||||
| 
 | ||||
|   '@sentry-internal/browser-utils@9.8.0': | ||||
|     dependencies: | ||||
|       '@sentry/core': 9.8.0 | ||||
| 
 | ||||
|   '@sentry-internal/feedback@9.8.0': | ||||
|     dependencies: | ||||
|       '@sentry/core': 9.8.0 | ||||
| 
 | ||||
|   '@sentry-internal/replay-canvas@9.8.0': | ||||
|     dependencies: | ||||
|       '@sentry-internal/replay': 9.8.0 | ||||
|       '@sentry/core': 9.8.0 | ||||
| 
 | ||||
|   '@sentry-internal/replay@9.8.0': | ||||
|     dependencies: | ||||
|       '@sentry-internal/browser-utils': 9.8.0 | ||||
|       '@sentry/core': 9.8.0 | ||||
| 
 | ||||
|   '@sentry/browser@9.8.0': | ||||
|     dependencies: | ||||
|       '@sentry-internal/browser-utils': 9.8.0 | ||||
|       '@sentry-internal/feedback': 9.8.0 | ||||
|       '@sentry-internal/replay': 9.8.0 | ||||
|       '@sentry-internal/replay-canvas': 9.8.0 | ||||
|       '@sentry/core': 9.8.0 | ||||
| 
 | ||||
|   '@sentry/core@8.55.0': {} | ||||
| 
 | ||||
|   '@sentry/core@9.8.0': {} | ||||
| 
 | ||||
|   '@sentry/node@8.55.0': | ||||
|     dependencies: | ||||
|       '@opentelemetry/api': 1.9.0 | ||||
|  | @ -13774,12 +13706,6 @@ snapshots: | |||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
| 
 | ||||
|   '@sentry/vue@9.8.0(vue@3.5.13(typescript@5.8.2))': | ||||
|     dependencies: | ||||
|       '@sentry/browser': 9.8.0 | ||||
|       '@sentry/core': 9.8.0 | ||||
|       vue: 3.5.13(typescript@5.8.2) | ||||
| 
 | ||||
|   '@shikijs/core@3.2.1': | ||||
|     dependencies: | ||||
|       '@shikijs/types': 3.2.1 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue