mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-08-20 18:13:37 +00:00
merge from misskey-develop
This commit is contained in:
commit
dab9b518e4
54 changed files with 614 additions and 118 deletions
|
@ -197,6 +197,11 @@ id: 'aidx'
|
||||||
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
#sentryForFrontend:
|
#sentryForFrontend:
|
||||||
|
# vueIntegration:
|
||||||
|
# tracingOptions:
|
||||||
|
# trackComponents: true
|
||||||
|
# browserTracingIntegration:
|
||||||
|
# replayIntegration:
|
||||||
# options:
|
# options:
|
||||||
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
|
|
|
@ -244,6 +244,11 @@ id: 'aidx'
|
||||||
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
#sentryForFrontend:
|
#sentryForFrontend:
|
||||||
|
# vueIntegration:
|
||||||
|
# tracingOptions:
|
||||||
|
# trackComponents: true
|
||||||
|
# browserTracingIntegration:
|
||||||
|
# replayIntegration:
|
||||||
# options:
|
# options:
|
||||||
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
|
|
|
@ -284,6 +284,11 @@ id: 'aidx'
|
||||||
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
#sentryForFrontend:
|
#sentryForFrontend:
|
||||||
|
# vueIntegration:
|
||||||
|
# tracingOptions:
|
||||||
|
# trackComponents: true
|
||||||
|
# browserTracingIntegration:
|
||||||
|
# replayIntegration:
|
||||||
# options:
|
# options:
|
||||||
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,11 @@ id: 'aidx'
|
||||||
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
#sentryForFrontend:
|
#sentryForFrontend:
|
||||||
|
# vueIntegration:
|
||||||
|
# tracingOptions:
|
||||||
|
# trackComponents: true
|
||||||
|
# browserTracingIntegration:
|
||||||
|
# replayIntegration:
|
||||||
# options:
|
# options:
|
||||||
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,12 @@
|
||||||
- メッセージにはリアクションも可能です
|
- メッセージにはリアクションも可能です
|
||||||
- Enhance: セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
|
- Enhance: セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
|
||||||
- Misskeyネイティブでダッシュボードを実装予定です
|
- Misskeyネイティブでダッシュボードを実装予定です
|
||||||
|
- Enhance: フロントエンドのエラートラッキングができるように
|
||||||
|
- `.config/default.yml`中の項目`sentryForFrontend`を適宜設定してください。
|
||||||
|
- 外部サービスであるSentryへエラー情報が送信されます。ご利用の地域の法令に従い、適切なプライバシーポリシーを策定の上で運用してください。
|
||||||
- Enhance: ミュートしているユーザーをユーザー検索の結果から除外するように
|
- Enhance: ミュートしているユーザーをユーザー検索の結果から除外するように
|
||||||
|
- Enhance: アンテナでセンシティブなチャンネルのノートを除外できるように `#14177`
|
||||||
|
- Fix: 通知のページネーションで2つ以上読み込めなくなることがある問題を修正
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: 設定の管理が強化されました
|
- Feat: 設定の管理が強化されました
|
||||||
|
@ -56,6 +61,7 @@
|
||||||
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
|
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
|
||||||
- NOTE: 構造上クラシックUIを新しいデザインシステムに移行することが困難なため、クラシックUIが削除されました
|
- NOTE: 構造上クラシックUIを新しいデザインシステムに移行することが困難なため、クラシックUIが削除されました
|
||||||
- デッキUIでカラムを中央寄せにし、メインカラムの左右にウィジェットカラムを配置し、ナビゲーションバーを上部に表示することである程度クラシックUIを再現できます
|
- デッキUIでカラムを中央寄せにし、メインカラムの左右にウィジェットカラムを配置し、ナビゲーションバーを上部に表示することである程度クラシックUIを再現できます
|
||||||
|
- Fix: iPadOSでdeck uiをマウスカーソルによってスクロールできない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance 全体的なパフォーマンス向上
|
- Enhance 全体的なパフォーマンス向上
|
||||||
|
|
|
@ -244,6 +244,11 @@ id: "aidx"
|
||||||
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
#sentryForFrontend:
|
#sentryForFrontend:
|
||||||
|
# vueIntegration:
|
||||||
|
# tracingOptions:
|
||||||
|
# trackComponents: true
|
||||||
|
# browserTracingIntegration:
|
||||||
|
# replayIntegration:
|
||||||
# options:
|
# options:
|
||||||
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
|
|
12
locales/index.d.ts
vendored
12
locales/index.d.ts
vendored
|
@ -1714,6 +1714,10 @@ export interface Locale extends ILocale {
|
||||||
* ファイルが添付されたノートのみ
|
* ファイルが添付されたノートのみ
|
||||||
*/
|
*/
|
||||||
"withFileAntenna": string;
|
"withFileAntenna": string;
|
||||||
|
/**
|
||||||
|
* センシティブなチャンネルのノートを非表示
|
||||||
|
*/
|
||||||
|
"hideNotesInSensitiveChannel": string;
|
||||||
/**
|
/**
|
||||||
* ブラウザへのプッシュ通知を有効にする
|
* ブラウザへのプッシュ通知を有効にする
|
||||||
*/
|
*/
|
||||||
|
@ -5375,6 +5379,10 @@ export interface Locale extends ILocale {
|
||||||
* 上
|
* 上
|
||||||
*/
|
*/
|
||||||
"top": string;
|
"top": string;
|
||||||
|
/**
|
||||||
|
* 埋め込み
|
||||||
|
*/
|
||||||
|
"embed": string;
|
||||||
"_chat": {
|
"_chat": {
|
||||||
/**
|
/**
|
||||||
* まだメッセージはありません
|
* まだメッセージはありません
|
||||||
|
@ -5663,6 +5671,10 @@ export interface Locale extends ILocale {
|
||||||
* オフのとき
|
* オフのとき
|
||||||
*/
|
*/
|
||||||
"ifOff": string;
|
"ifOff": string;
|
||||||
|
/**
|
||||||
|
* デバイス間でインストールしたテーマを同期
|
||||||
|
*/
|
||||||
|
"enableSyncThemesBetweenDevices": string;
|
||||||
"_chat": {
|
"_chat": {
|
||||||
/**
|
/**
|
||||||
* 送信者の名前を表示
|
* 送信者の名前を表示
|
||||||
|
|
|
@ -424,6 +424,7 @@ antennaExcludeBots: "Botアカウントを除外"
|
||||||
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
|
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
|
||||||
notifyAntenna: "新しいノートを通知する"
|
notifyAntenna: "新しいノートを通知する"
|
||||||
withFileAntenna: "ファイルが添付されたノートのみ"
|
withFileAntenna: "ファイルが添付されたノートのみ"
|
||||||
|
hideNotesInSensitiveChannel: "センシティブなチャンネルのノートを非表示"
|
||||||
enableServiceworker: "ブラウザへのプッシュ通知を有効にする"
|
enableServiceworker: "ブラウザへのプッシュ通知を有効にする"
|
||||||
antennaUsersDescription: "ユーザー名を改行で区切って指定します"
|
antennaUsersDescription: "ユーザー名を改行で区切って指定します"
|
||||||
caseSensitive: "大文字小文字を区別する"
|
caseSensitive: "大文字小文字を区別する"
|
||||||
|
@ -1339,6 +1340,7 @@ compress: "圧縮"
|
||||||
right: "右"
|
right: "右"
|
||||||
bottom: "下"
|
bottom: "下"
|
||||||
top: "上"
|
top: "上"
|
||||||
|
embed: "埋め込み"
|
||||||
|
|
||||||
_chat:
|
_chat:
|
||||||
noMessagesYet: "まだメッセージはありません"
|
noMessagesYet: "まだメッセージはありません"
|
||||||
|
@ -1416,6 +1418,7 @@ _settings:
|
||||||
showNavbarSubButtons: "ナビゲーションバーに副ボタンを表示"
|
showNavbarSubButtons: "ナビゲーションバーに副ボタンを表示"
|
||||||
ifOn: "オンのとき"
|
ifOn: "オンのとき"
|
||||||
ifOff: "オフのとき"
|
ifOff: "オフのとき"
|
||||||
|
enableSyncThemesBetweenDevices: "デバイス間でインストールしたテーマを同期"
|
||||||
|
|
||||||
_chat:
|
_chat:
|
||||||
showSenderName: "送信者の名前を表示"
|
showSenderName: "送信者の名前を表示"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "sharkey",
|
"name": "sharkey",
|
||||||
"version": "2025.4.0-beta.0",
|
"version": "2025.4.0-beta.1",
|
||||||
"codename": "shonk",
|
"codename": "shonk",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* 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"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -193,6 +193,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "29.7.0",
|
"@jest/globals": "29.7.0",
|
||||||
"@nestjs/platform-express": "10.4.15",
|
"@nestjs/platform-express": "10.4.15",
|
||||||
|
"@sentry/vue": "9.8.0",
|
||||||
"@simplewebauthn/types": "12.0.0",
|
"@simplewebauthn/types": "12.0.0",
|
||||||
"@swc/jest": "0.2.37",
|
"@swc/jest": "0.2.37",
|
||||||
"@types/accepts": "1.3.7",
|
"@types/accepts": "1.3.7",
|
||||||
|
|
|
@ -8,7 +8,8 @@ import { fileURLToPath } from 'node:url';
|
||||||
import { dirname, resolve } from 'node:path';
|
import { dirname, resolve } from 'node:path';
|
||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
import { globSync } from 'glob';
|
import { globSync } from 'glob';
|
||||||
import * as Sentry from '@sentry/node';
|
import type * as Sentry from '@sentry/node';
|
||||||
|
import type * as SentryVue from '@sentry/vue';
|
||||||
import type { RedisOptions } from 'ioredis';
|
import type { RedisOptions } from 'ioredis';
|
||||||
|
|
||||||
type RedisOptionsSource = Partial<RedisOptions> & {
|
type RedisOptionsSource = Partial<RedisOptions> & {
|
||||||
|
@ -66,7 +67,12 @@ type Source = {
|
||||||
scope?: 'local' | 'global' | string[];
|
scope?: 'local' | 'global' | string[];
|
||||||
};
|
};
|
||||||
sentryForBackend?: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; };
|
sentryForBackend?: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; };
|
||||||
sentryForFrontend?: { options: Partial<Sentry.NodeOptions> };
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
publishTarballInsteadOfProvideRepositoryUrl?: boolean;
|
publishTarballInsteadOfProvideRepositoryUrl?: boolean;
|
||||||
|
|
||||||
|
@ -239,7 +245,12 @@ export type Config = {
|
||||||
redisForReactions: RedisOptions & RedisOptionsSource;
|
redisForReactions: RedisOptions & RedisOptionsSource;
|
||||||
redisForRateLimit: RedisOptions & RedisOptionsSource;
|
redisForRateLimit: RedisOptions & RedisOptionsSource;
|
||||||
sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
|
sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
|
||||||
sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | 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;
|
||||||
perChannelMaxNoteCacheCount: number;
|
perChannelMaxNoteCacheCount: number;
|
||||||
perUserNotificationsMaxCount: number;
|
perUserNotificationsMaxCount: number;
|
||||||
deactivateAntennaThreshold: number;
|
deactivateAntennaThreshold: number;
|
||||||
|
|
|
@ -114,6 +114,8 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
if (note.visibility === 'specified') return false;
|
if (note.visibility === 'specified') return false;
|
||||||
if (note.visibility === 'followers') 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.excludeBots && noteUser.isBot) return false;
|
||||||
|
|
||||||
if (antenna.localOnly && noteUser.host != null) return false;
|
if (antenna.localOnly && noteUser.host != null) return false;
|
||||||
|
|
|
@ -7,13 +7,13 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { ulid } from 'ulid';
|
import { ulid } from 'ulid';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { genAid, isSafeAidT, parseAid } from '@/misc/id/aid.js';
|
import { genAid, isSafeAidT, parseAid, parseAidFull } from '@/misc/id/aid.js';
|
||||||
import { genAidx, isSafeAidxT, parseAidx } from '@/misc/id/aidx.js';
|
import { genAidx, isSafeAidxT, parseAidx, parseAidxFull } from '@/misc/id/aidx.js';
|
||||||
import { genMeid, isSafeMeidT, parseMeid } from '@/misc/id/meid.js';
|
import { genMeid, isSafeMeidT, parseMeid, parseMeidFull } from '@/misc/id/meid.js';
|
||||||
import { genMeidg, isSafeMeidgT, parseMeidg } from '@/misc/id/meidg.js';
|
import { genMeidg, isSafeMeidgT, parseMeidg, parseMeidgFull } from '@/misc/id/meidg.js';
|
||||||
import { genObjectId, isSafeObjectIdT, parseObjectId } from '@/misc/id/object-id.js';
|
import { genObjectId, isSafeObjectIdT, parseObjectId, parseObjectIdFull } from '@/misc/id/object-id.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { parseUlid } from '@/misc/id/ulid.js';
|
import { parseUlid, parseUlidFull } from '@/misc/id/ulid.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class IdService {
|
export class IdService {
|
||||||
|
@ -70,4 +70,18 @@ export class IdService {
|
||||||
default: throw new Error('unrecognized id generation method');
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -595,7 +595,10 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
|
|
||||||
this.pushToTl(note, user);
|
this.pushToTl(note, user);
|
||||||
|
|
||||||
this.antennaService.addNoteToAntennas(note, user);
|
this.antennaService.addNoteToAntennas({
|
||||||
|
...note,
|
||||||
|
channel: data.channel ?? null,
|
||||||
|
}, user);
|
||||||
|
|
||||||
if (data.reply) {
|
if (data.reply) {
|
||||||
this.saveReply(data.reply, note);
|
this.saveReply(data.reply, note);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { setTimeout } from 'node:timers/promises';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
|
import { ReplyError } from 'ioredis';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
import type { UsersRepository } from '@/models/_.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
|
@ -19,7 +20,7 @@ import { IdService } from '@/core/IdService.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { UserListService } from '@/core/UserListService.js';
|
import { UserListService } from '@/core/UserListService.js';
|
||||||
import type { FilterUnionByProperty } from '@/types.js';
|
import { FilterUnionByProperty, groupedNotificationTypes, obsoleteNotificationTypes } from '@/types.js';
|
||||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -145,21 +146,36 @@ export class NotificationService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const notification = {
|
const createdAt = new Date();
|
||||||
|
let notification: FilterUnionByProperty<MiNotification, 'type', T>;
|
||||||
|
let redisId: string;
|
||||||
|
|
||||||
|
do {
|
||||||
|
notification = {
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
createdAt: new Date(),
|
createdAt,
|
||||||
type: type,
|
type: type,
|
||||||
...(notifierId ? {
|
...(notifierId ? {
|
||||||
notifierId,
|
notifierId,
|
||||||
} : {}),
|
} : {}),
|
||||||
...data,
|
...data,
|
||||||
} as any as FilterUnionByProperty<MiNotification, 'type', T>;
|
} as unknown as FilterUnionByProperty<MiNotification, 'type', T>;
|
||||||
|
|
||||||
const redisIdPromise = this.redisClient.xadd(
|
try {
|
||||||
|
redisId = (await this.redisClient.xadd(
|
||||||
`notificationTimeline:${notifieeId}`,
|
`notificationTimeline:${notifieeId}`,
|
||||||
'MAXLEN', '~', this.config.perUserNotificationsMaxCount.toString(),
|
'MAXLEN', '~', this.config.perUserNotificationsMaxCount.toString(),
|
||||||
'*',
|
this.toXListId(notification.id),
|
||||||
'data', JSON.stringify(notification));
|
'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 packed = await this.notificationEntityService.pack(notification, notifieeId, {});
|
const packed = await this.notificationEntityService.pack(notification, notifieeId, {});
|
||||||
|
|
||||||
|
@ -173,7 +189,7 @@ export class NotificationService implements OnApplicationShutdown {
|
||||||
const interval = notification.type === 'test' ? 0 : 2000;
|
const interval = notification.type === 'test' ? 0 : 2000;
|
||||||
setTimeout(interval, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => {
|
setTimeout(interval, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => {
|
||||||
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`);
|
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`);
|
||||||
if (latestReadNotificationId && (latestReadNotificationId >= (await redisIdPromise)!)) return;
|
if (latestReadNotificationId && (latestReadNotificationId >= redisId)) return;
|
||||||
|
|
||||||
this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
|
this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
|
||||||
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
|
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
|
||||||
|
@ -228,6 +244,79 @@ export class NotificationService implements OnApplicationShutdown {
|
||||||
this.#shutdownController.abort();
|
this.#shutdownController.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private toXListId(id: string, offset: number): string {
|
||||||
|
const { date, additional } = this.idService.parseFull(id);
|
||||||
|
return (date + offset).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, 1) : null;
|
||||||
|
let untilTime = untilId ? this.toXListId(untilId, -1) : 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
|
@bindThis
|
||||||
public onApplicationShutdown(signal?: string | undefined): void {
|
public onApplicationShutdown(signal?: string | undefined): void {
|
||||||
this.dispose();
|
this.dispose();
|
||||||
|
|
|
@ -41,6 +41,7 @@ export class AntennaEntityService {
|
||||||
excludeBots: antenna.excludeBots,
|
excludeBots: antenna.excludeBots,
|
||||||
withReplies: antenna.withReplies,
|
withReplies: antenna.withReplies,
|
||||||
withFile: antenna.withFile,
|
withFile: antenna.withFile,
|
||||||
|
hideNotesInSensitiveChannel: antenna.hideNotesInSensitiveChannel,
|
||||||
isActive: antenna.isActive,
|
isActive: antenna.isActive,
|
||||||
hasUnreadNote: false, // TODO
|
hasUnreadNote: false, // TODO
|
||||||
notify: false, // 後方互換性のため
|
notify: false, // 後方互換性のため
|
||||||
|
|
|
@ -139,6 +139,7 @@ export class MetaEntityService {
|
||||||
|
|
||||||
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
||||||
|
|
||||||
|
sentryForFrontend: this.config.sentryForFrontend ?? null,
|
||||||
mediaProxy: this.config.mediaProxy,
|
mediaProxy: this.config.mediaProxy,
|
||||||
enableUrlPreview: instance.urlPreviewEnabled,
|
enableUrlPreview: instance.urlPreviewEnabled,
|
||||||
noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local',
|
noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local',
|
||||||
|
|
40
packages/backend/src/misc/bigint.ts
Normal file
40
packages/backend/src/misc/bigint.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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,6 +7,7 @@
|
||||||
// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ2の[ノイズ文字列]
|
// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ2の[ノイズ文字列]
|
||||||
|
|
||||||
import * as crypto from 'node:crypto';
|
import * as crypto from 'node:crypto';
|
||||||
|
import { parseBigInt36 } from '@/misc/bigint.js';
|
||||||
|
|
||||||
export const aidRegExp = /^[0-9a-z]{10}$/;
|
export const aidRegExp = /^[0-9a-z]{10}$/;
|
||||||
|
|
||||||
|
@ -35,6 +36,12 @@ export function parseAid(id: string): { date: Date; } {
|
||||||
return { date: new Date(time) };
|
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 {
|
export function isSafeAidT(t: number): boolean {
|
||||||
return t > TIME2000;
|
return t > TIME2000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
// https://misskey.m544.net/notes/71899acdcc9859ec5708ac24
|
// https://misskey.m544.net/notes/71899acdcc9859ec5708ac24
|
||||||
|
|
||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
|
import { parseBigInt36 } from '@/misc/bigint.js';
|
||||||
|
|
||||||
export const aidxRegExp = /^[0-9a-z]{16}$/;
|
export const aidxRegExp = /^[0-9a-z]{16}$/;
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ const TIME2000 = 946684800000;
|
||||||
const TIME_LENGTH = 8;
|
const TIME_LENGTH = 8;
|
||||||
const NODE_LENGTH = 4;
|
const NODE_LENGTH = 4;
|
||||||
const NOISE_LENGTH = 4;
|
const NOISE_LENGTH = 4;
|
||||||
|
const AIDX_LENGTH = TIME_LENGTH + NODE_LENGTH + NOISE_LENGTH;
|
||||||
|
|
||||||
const nodeId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', NODE_LENGTH)();
|
const nodeId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', NODE_LENGTH)();
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
|
@ -42,6 +44,12 @@ export function parseAidx(id: string): { date: Date; } {
|
||||||
return { date: new Date(time) };
|
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 {
|
export function isSafeAidxT(t: number): boolean {
|
||||||
return t > TIME2000;
|
return t > TIME2000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { parseBigInt16 } from '@/misc/bigint.js';
|
||||||
|
|
||||||
const CHARS = '0123456789abcdef';
|
const CHARS = '0123456789abcdef';
|
||||||
|
|
||||||
// same as object-id
|
// same as object-id
|
||||||
|
@ -39,6 +41,13 @@ 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 {
|
export function isSafeMeidT(t: number): boolean {
|
||||||
return t > 0;
|
return t > 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { parseBigInt16 } from '@/misc/bigint.js';
|
||||||
|
|
||||||
const CHARS = '0123456789abcdef';
|
const CHARS = '0123456789abcdef';
|
||||||
|
|
||||||
// 4bit Fixed hex value 'g'
|
// 4bit Fixed hex value 'g'
|
||||||
|
@ -39,6 +41,13 @@ 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 {
|
export function isSafeMeidgT(t: number): boolean {
|
||||||
return t > 0;
|
return t > 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { parseBigInt16 } from '@/misc/bigint.js';
|
||||||
|
|
||||||
const CHARS = '0123456789abcdef';
|
const CHARS = '0123456789abcdef';
|
||||||
|
|
||||||
// same as meid
|
// same as meid
|
||||||
|
@ -39,6 +41,13 @@ 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 {
|
export function isSafeObjectIdT(t: number): boolean {
|
||||||
return t > 0;
|
return t > 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,27 @@
|
||||||
|
|
||||||
// Crockford's Base32
|
// Crockford's Base32
|
||||||
// https://github.com/ulid/spec#encoding
|
// https://github.com/ulid/spec#encoding
|
||||||
|
import { parseBigInt32 } from '@/misc/bigint.js';
|
||||||
|
|
||||||
const CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
const CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
||||||
|
|
||||||
export const ulidRegExp = /^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$/;
|
export const ulidRegExp = /^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$/;
|
||||||
|
|
||||||
export function parseUlid(id: string): { date: Date; } {
|
function parseBase32(timestamp: string) {
|
||||||
const timestamp = id.slice(0, 10);
|
|
||||||
let time = 0;
|
let time = 0;
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < timestamp.length; i++) {
|
||||||
time = time * 32 + CHARS.indexOf(timestamp[i]);
|
time = time * 32 + CHARS.indexOf(timestamp[i]);
|
||||||
}
|
}
|
||||||
return { date: new Date(time) };
|
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)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,4 +100,9 @@ export class MiAntenna {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
public localOnly: boolean;
|
public localOnly: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public hideNotesInSensitiveChannel: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,5 +100,10 @@ export const packedAntennaSchema = {
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
hideNotesInSensitiveChannel: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -261,6 +261,38 @@ export const packedMetaLiteSchema = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
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: {
|
mediaProxy: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -79,6 +79,7 @@ export const paramDef = {
|
||||||
excludeBots: { type: 'boolean' },
|
excludeBots: { type: 'boolean' },
|
||||||
withReplies: { type: 'boolean' },
|
withReplies: { type: 'boolean' },
|
||||||
withFile: { type: 'boolean' },
|
withFile: { type: 'boolean' },
|
||||||
|
hideNotesInSensitiveChannel: { type: 'boolean' },
|
||||||
},
|
},
|
||||||
required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'],
|
required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -139,6 +140,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
excludeBots: ps.excludeBots,
|
excludeBots: ps.excludeBots,
|
||||||
withReplies: ps.withReplies,
|
withReplies: ps.withReplies,
|
||||||
withFile: ps.withFile,
|
withFile: ps.withFile,
|
||||||
|
hideNotesInSensitiveChannel: ps.hideNotesInSensitiveChannel,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.globalEventService.publishInternalEvent('antennaCreated', antenna);
|
this.globalEventService.publishInternalEvent('antennaCreated', antenna);
|
||||||
|
|
|
@ -114,6 +114,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.leftJoinAndSelect('reply.user', 'replyUser')
|
.leftJoinAndSelect('reply.user', 'replyUser')
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||||
|
|
||||||
|
// NOTE: センシティブ除外の設定はこのエンドポイントでは無視する。
|
||||||
|
// https://github.com/misskey-dev/misskey/pull/15346#discussion_r1929950255
|
||||||
|
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
this.queryService.generateMutedUserQueryForNotes(query, me);
|
this.queryService.generateMutedUserQueryForNotes(query, me);
|
||||||
this.queryService.generateBlockedUserQueryForNotes(query, me);
|
this.queryService.generateBlockedUserQueryForNotes(query, me);
|
||||||
|
|
|
@ -78,6 +78,7 @@ export const paramDef = {
|
||||||
excludeBots: { type: 'boolean' },
|
excludeBots: { type: 'boolean' },
|
||||||
withReplies: { type: 'boolean' },
|
withReplies: { type: 'boolean' },
|
||||||
withFile: { type: 'boolean' },
|
withFile: { type: 'boolean' },
|
||||||
|
hideNotesInSensitiveChannel: { type: 'boolean' },
|
||||||
},
|
},
|
||||||
required: ['antennaId'],
|
required: ['antennaId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -135,6 +136,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
excludeBots: ps.excludeBots,
|
excludeBots: ps.excludeBots,
|
||||||
withReplies: ps.withReplies,
|
withReplies: ps.withReplies,
|
||||||
withFile: ps.withFile,
|
withFile: ps.withFile,
|
||||||
|
hideNotesInSensitiveChannel: ps.hideNotesInSensitiveChannel,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
lastUsedAt: new Date(),
|
lastUsedAt: new Date(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,12 @@ import { In } from 'typeorm';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { NotesRepository } from '@/models/_.js';
|
import type { NotesRepository } from '@/models/_.js';
|
||||||
import { obsoleteNotificationTypes, groupedNotificationTypes, FilterUnionByProperty } from '@/types.js';
|
import {
|
||||||
|
obsoleteNotificationTypes,
|
||||||
|
groupedNotificationTypes,
|
||||||
|
FilterUnionByProperty,
|
||||||
|
notificationTypes,
|
||||||
|
} from '@/types.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
|
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
|
||||||
import { NotificationService } from '@/core/NotificationService.js';
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
|
@ -47,10 +52,10 @@ export const paramDef = {
|
||||||
markAsRead: { type: 'boolean', default: true },
|
markAsRead: { type: 'boolean', default: true },
|
||||||
// 後方互換のため、廃止された通知タイプも受け付ける
|
// 後方互換のため、廃止された通知タイプも受け付ける
|
||||||
includeTypes: { type: 'array', items: {
|
includeTypes: { type: 'array', items: {
|
||||||
type: 'string', enum: [...groupedNotificationTypes, ...obsoleteNotificationTypes],
|
type: 'string', enum: [...notificationTypes, ...obsoleteNotificationTypes],
|
||||||
} },
|
} },
|
||||||
excludeTypes: { type: 'array', items: {
|
excludeTypes: { type: 'array', items: {
|
||||||
type: 'string', enum: [...groupedNotificationTypes, ...obsoleteNotificationTypes],
|
type: 'string', enum: [...notificationTypes, ...obsoleteNotificationTypes],
|
||||||
} },
|
} },
|
||||||
},
|
},
|
||||||
required: [],
|
required: [],
|
||||||
|
@ -74,31 +79,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
// excludeTypes に全指定されている場合はクエリしない
|
// excludeTypes に全指定されている場合はクエリしない
|
||||||
if (groupedNotificationTypes.every(type => ps.excludeTypes?.includes(type))) {
|
if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof groupedNotificationTypes[number][];
|
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 excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof groupedNotificationTypes[number][];
|
||||||
|
|
||||||
const limit = (ps.limit + EXTRA_LIMIT) + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
const notifications = await this.notificationService.getNotifications(me.id, {
|
||||||
const notificationsRes = await this.redisClient.xrevrange(
|
sinceId: ps.sinceId,
|
||||||
`notificationTimeline:${me.id}`,
|
untilId: ps.untilId,
|
||||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
|
limit: ps.limit,
|
||||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : '-',
|
includeTypes,
|
||||||
'COUNT', limit);
|
excludeTypes,
|
||||||
|
});
|
||||||
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) {
|
if (notifications.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -82,52 +82,13 @@ 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 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 excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
|
||||||
|
|
||||||
let sinceTime = ps.sinceId ? (this.idService.parse(ps.sinceId).date.getTime() + 1).toString() : null;
|
const notifications = await this.notificationService.getNotifications(me.id, {
|
||||||
let untilTime = ps.untilId ? (this.idService.parse(ps.untilId).date.getTime() - 1).toString() : null;
|
sinceId: ps.sinceId,
|
||||||
|
untilId: ps.untilId,
|
||||||
let notifications: MiNotification[];
|
limit: ps.limit,
|
||||||
for (;;) {
|
includeTypes,
|
||||||
let notificationsRes: [id: string, fields: string[]][];
|
excludeTypes,
|
||||||
|
});
|
||||||
// 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
|
// Mark all as read
|
||||||
if (ps.markAsRead) {
|
if (ps.markAsRead) {
|
||||||
|
|
|
@ -146,6 +146,7 @@ describe('アンテナ', () => {
|
||||||
caseSensitive: false,
|
caseSensitive: false,
|
||||||
createdAt: new Date(response.createdAt).toISOString(),
|
createdAt: new Date(response.createdAt).toISOString(),
|
||||||
excludeKeywords: [['']],
|
excludeKeywords: [['']],
|
||||||
|
hideNotesInSensitiveChannel: false,
|
||||||
hasUnreadNote: false,
|
hasUnreadNote: false,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
keywords: [['keyword']],
|
keywords: [['keyword']],
|
||||||
|
@ -217,6 +218,8 @@ describe('アンテナ', () => {
|
||||||
{ parameters: () => ({ withReplies: true }) },
|
{ parameters: () => ({ withReplies: true }) },
|
||||||
{ parameters: () => ({ withFile: false }) },
|
{ parameters: () => ({ withFile: false }) },
|
||||||
{ parameters: () => ({ withFile: true }) },
|
{ parameters: () => ({ withFile: true }) },
|
||||||
|
{ parameters: () => ({ hideNotesInSensitiveChannel: false }) },
|
||||||
|
{ parameters: () => ({ hideNotesInSensitiveChannel: true }) },
|
||||||
];
|
];
|
||||||
test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => {
|
test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => {
|
||||||
const response = await successfulApiCall({
|
const response = await successfulApiCall({
|
||||||
|
@ -626,6 +629,42 @@ describe('アンテナ', () => {
|
||||||
assert.deepStrictEqual(response, expected);
|
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.skip('が取得でき、日付指定のPaginationに一貫性があること', async () => { });
|
||||||
test.each([
|
test.each([
|
||||||
{ label: 'ID指定', offsetBy: 'id' },
|
{ label: 'ID指定', offsetBy: 'id' },
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
bg: '#232125',
|
bg: '#232125',
|
||||||
fg: '#efdab9',
|
fg: '#efdab9',
|
||||||
link: '#78b0a0',
|
link: '#78b0a0',
|
||||||
warn: '#ecb637',
|
warn: '#ffd152',
|
||||||
badge: '#31b1ce',
|
badge: '#31b1ce',
|
||||||
error: '#ec4137',
|
error: '#ff6652',
|
||||||
focus: ':alpha<0.3<@accent',
|
focus: ':alpha<0.3<@accent',
|
||||||
navBg: '@panel',
|
navBg: '@panel',
|
||||||
navFg: '@fg',
|
navFg: '@fg',
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
hashtag: '#ff9156',
|
hashtag: '#ff9156',
|
||||||
mention: '#ffd152',
|
mention: '#ffd152',
|
||||||
modalBg: 'rgba(0, 0, 0, 0.5)',
|
modalBg: 'rgba(0, 0, 0, 0.5)',
|
||||||
success: '#86b300',
|
success: '#78b07f',
|
||||||
indicator: '@accent',
|
indicator: '@accent',
|
||||||
mentionMe: '#fb5d38',
|
mentionMe: '#fb5d38',
|
||||||
messageBg: '@bg',
|
messageBg: '@bg',
|
||||||
|
|
|
@ -22,5 +22,8 @@
|
||||||
mentionMe: 'rgb(212, 210, 76)',
|
mentionMe: 'rgb(212, 210, 76)',
|
||||||
hashtag: '#5bcbb0',
|
hashtag: '#5bcbb0',
|
||||||
link: '@accent',
|
link: '@accent',
|
||||||
|
success: '@accent',
|
||||||
|
warn: 'rgb(255, 213, 82)',
|
||||||
|
error: 'rgb(255, 105, 82)',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,5 +22,8 @@
|
||||||
mentionMe: '#de6161',
|
mentionMe: '#de6161',
|
||||||
hashtag: '#68bad0',
|
hashtag: '#68bad0',
|
||||||
link: '#a1c758',
|
link: '#a1c758',
|
||||||
|
error: '#ce5441',
|
||||||
|
warn: '#d0b868',
|
||||||
|
success: '#a1c758',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"@rollup/plugin-replace": "6.0.2",
|
"@rollup/plugin-replace": "6.0.2",
|
||||||
"@rollup/pluginutils": "5.1.4",
|
"@rollup/pluginutils": "5.1.4",
|
||||||
"@ruffle-rs/ruffle": "0.1.0-nightly.2024.10.15",
|
"@ruffle-rs/ruffle": "0.1.0-nightly.2024.10.15",
|
||||||
|
"@sentry/vue": "9.8.0",
|
||||||
"@syuilo/aiscript": "0.19.0",
|
"@syuilo/aiscript": "0.19.0",
|
||||||
"@transfem-org/sfm-js": "0.24.6",
|
"@transfem-org/sfm-js": "0.24.6",
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { watch, version as vueVersion } from 'vue';
|
import { watch, version as vueVersion } from 'vue';
|
||||||
import { compareVersions } from 'compare-versions';
|
import { compareVersions } from 'compare-versions';
|
||||||
import { version, lang, langsVersion, updateLocale, locale } from '@@/js/config.js';
|
import { version, lang, langsVersion, updateLocale, locale, apiUrl } from '@@/js/config.js';
|
||||||
import defaultLightTheme from '@@/themes/l-light.json5';
|
import defaultLightTheme from '@@/themes/l-light.json5';
|
||||||
import defaultDarkTheme from '@@/themes/d-green-lime.json5';
|
import defaultDarkTheme from '@@/themes/d-green-lime.json5';
|
||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
|
@ -282,6 +282,41 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
||||||
return root;
|
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);
|
app.mount(rootEl);
|
||||||
|
|
||||||
// boot.jsのやつを解除
|
// boot.jsのやつを解除
|
||||||
|
|
|
@ -39,6 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
|
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
|
||||||
<MkSwitch v-model="caseSensitive">{{ i18n.ts.caseSensitive }}</MkSwitch>
|
<MkSwitch v-model="caseSensitive">{{ i18n.ts.caseSensitive }}</MkSwitch>
|
||||||
<MkSwitch v-model="withFile">{{ i18n.ts.withFileAntenna }}</MkSwitch>
|
<MkSwitch v-model="withFile">{{ i18n.ts.withFileAntenna }}</MkSwitch>
|
||||||
|
<MkSwitch v-model="hideNotesInSensitiveChannel">{{ i18n.ts.hideNotesInSensitiveChannel }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.actions">
|
<div :class="$style.actions">
|
||||||
<div class="_buttons">
|
<div class="_buttons">
|
||||||
|
@ -86,6 +87,7 @@ const initialAntenna = deepMerge<PartialAllowedAntenna>(props.antenna ?? {}, {
|
||||||
caseSensitive: false,
|
caseSensitive: false,
|
||||||
localOnly: false,
|
localOnly: false,
|
||||||
withFile: false,
|
withFile: false,
|
||||||
|
hideNotesInSensitiveChannel: false,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
hasUnreadNote: false,
|
hasUnreadNote: false,
|
||||||
notify: false,
|
notify: false,
|
||||||
|
@ -108,6 +110,7 @@ const localOnly = ref<boolean>(initialAntenna.localOnly);
|
||||||
const excludeBots = ref<boolean>(initialAntenna.excludeBots);
|
const excludeBots = ref<boolean>(initialAntenna.excludeBots);
|
||||||
const withReplies = ref<boolean>(initialAntenna.withReplies);
|
const withReplies = ref<boolean>(initialAntenna.withReplies);
|
||||||
const withFile = ref<boolean>(initialAntenna.withFile);
|
const withFile = ref<boolean>(initialAntenna.withFile);
|
||||||
|
const hideNotesInSensitiveChannel = ref<boolean>(initialAntenna.hideNotesInSensitiveChannel);
|
||||||
const userLists = ref<Misskey.entities.UserList[] | null>(null);
|
const userLists = ref<Misskey.entities.UserList[] | null>(null);
|
||||||
|
|
||||||
watch(() => src.value, async () => {
|
watch(() => src.value, async () => {
|
||||||
|
@ -124,6 +127,7 @@ async function saveAntenna() {
|
||||||
excludeBots: excludeBots.value,
|
excludeBots: excludeBots.value,
|
||||||
withReplies: withReplies.value,
|
withReplies: withReplies.value,
|
||||||
withFile: withFile.value,
|
withFile: withFile.value,
|
||||||
|
hideNotesInSensitiveChannel: hideNotesInSensitiveChannel.value,
|
||||||
caseSensitive: caseSensitive.value,
|
caseSensitive: caseSensitive.value,
|
||||||
localOnly: localOnly.value,
|
localOnly: localOnly.value,
|
||||||
users: users.value.trim().split('\n').map(x => x.trim()),
|
users: users.value.trim().split('\n').map(x => x.trim()),
|
||||||
|
|
|
@ -249,6 +249,7 @@ async function close(skip: boolean) {
|
||||||
|
|
||||||
.pageFooter {
|
.pageFooter {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
z-index: 1;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
|
@ -150,7 +150,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-code',
|
icon: 'ti ti-code',
|
||||||
text: i18n.ts.genEmbedCode,
|
text: i18n.ts.embed,
|
||||||
action: () => {
|
action: () => {
|
||||||
genEmbedCode('clips', clip.value!.id);
|
genEmbedCode('clips', clip.value!.id);
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<SearchMarker :keywords="['sync', 'profiles', 'devices']">
|
<SearchMarker :keywords="['sync', 'profiles', 'devices']">
|
||||||
<MkSwitch :modelValue="profilesSyncEnabled" @update:modelValue="changeProfilesSyncEnabled">
|
<MkSwitch :modelValue="profilesSyncEnabled" @update:modelValue="changeProfilesSyncEnabled">
|
||||||
<template #label><SearchLabel>{{ i18n.ts._deck.enableSyncBetweenDevicesForProfiles }}</SearchLabel></template>
|
<template #label><i class="ti ti-cloud-cog"></i> <SearchLabel>{{ i18n.ts._deck.enableSyncBetweenDevicesForProfiles }}</SearchLabel></template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</SearchMarker>
|
</SearchMarker>
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<SearchMarker :keywords="['sync', 'palettes', 'devices']">
|
<SearchMarker :keywords="['sync', 'palettes', 'devices']">
|
||||||
<MkSwitch :modelValue="palettesSyncEnabled" @update:modelValue="changePalettesSyncEnabled">
|
<MkSwitch :modelValue="palettesSyncEnabled" @update:modelValue="changePalettesSyncEnabled">
|
||||||
<template #label><SearchLabel>{{ i18n.ts._emojiPalette.enableSyncBetweenDevicesForPalettes }}</SearchLabel></template>
|
<template #label><i class="ti ti-cloud-cog"></i> <SearchLabel>{{ i18n.ts._emojiPalette.enableSyncBetweenDevicesForPalettes }}</SearchLabel></template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</SearchMarker>
|
</SearchMarker>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -181,6 +181,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</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>
|
<FormSection>
|
||||||
<div class="_formLinksGrid">
|
<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>
|
<FormLink to="/settings/theme/manage"><template #icon><i class="ti ti-tool"></i></template>{{ i18n.ts._theme.manage }}<template #suffix>{{ themesCount }}</template></FormLink>
|
||||||
|
@ -264,6 +270,20 @@ 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 headerActions = computed(() => []);
|
||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
|
@ -56,7 +56,7 @@ const headerActions = computed(() => [{
|
||||||
label: i18n.ts.more,
|
label: i18n.ts.more,
|
||||||
handler: (ev: MouseEvent) => {
|
handler: (ev: MouseEvent) => {
|
||||||
os.popupMenu([{
|
os.popupMenu([{
|
||||||
text: i18n.ts.genEmbedCode,
|
text: i18n.ts.embed,
|
||||||
icon: 'ti ti-code',
|
icon: 'ti ti-code',
|
||||||
action: () => {
|
action: () => {
|
||||||
genEmbedCode('tags', props.tag);
|
genEmbedCode('tags', props.tag);
|
||||||
|
|
|
@ -12,15 +12,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<XAnnouncements v-if="$i"/>
|
<XAnnouncements v-if="$i"/>
|
||||||
<XStatusBars/>
|
<XStatusBars/>
|
||||||
|
|
||||||
<div :class="$style.columnsWrapper">
|
<div :class="$style.columnsWrapper">
|
||||||
<div ref="columnsEl" :class="[$style.columns, { [$style.center]: prefer.r['deck.columnAlign'].value === 'center', [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu" @wheel.self="onWheel">
|
<!-- 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">
|
||||||
<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
|
<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
|
||||||
<section
|
<section
|
||||||
v-for="ids in layout"
|
v-for="ids in layout"
|
||||||
:class="$style.section"
|
: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' }"
|
: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.self="onWheel"
|
@wheel.passive.self="onWheel"
|
||||||
>
|
>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<component
|
<component
|
||||||
|
@ -175,7 +175,8 @@ window.addEventListener('resize', () => {
|
||||||
isMobile.value = window.innerWidth <= 500;
|
isMobile.value = window.innerWidth <= 500;
|
||||||
});
|
});
|
||||||
|
|
||||||
const snapScroll = deviceKind === 'smartphone' || deviceKind === 'tablet';
|
// ポインターイベント非対応用に初期値はUAから出す
|
||||||
|
const snapScroll = ref(deviceKind === 'smartphone' || deviceKind === 'tablet');
|
||||||
const withWallpaper = prefer.s['deck.wallpaper'] != null;
|
const withWallpaper = prefer.s['deck.wallpaper'] != null;
|
||||||
const drawerMenuShowing = ref(false);
|
const drawerMenuShowing = ref(false);
|
||||||
const gap = prefer.r['deck.columnGap'];
|
const gap = prefer.r['deck.columnGap'];
|
||||||
|
@ -226,7 +227,16 @@ const onContextmenu = (ev) => {
|
||||||
}], ev);
|
}], ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// タッチでスクロールしてるときはスナップスクロールを有効にする
|
||||||
|
function pointerEvent(ev: PointerEvent) {
|
||||||
|
snapScroll.value = ev.pointerType === 'touch';
|
||||||
|
}
|
||||||
|
|
||||||
|
window.document.addEventListener('pointerdown', pointerEvent, { passive: true });
|
||||||
|
|
||||||
function onWheel(ev: WheelEvent) {
|
function onWheel(ev: WheelEvent) {
|
||||||
|
// WheelEvent はマウスからしか発火しないのでスナップスクロールは無効化する
|
||||||
|
snapScroll.value = false;
|
||||||
if (ev.deltaX === 0 && columnsEl.value != null) {
|
if (ev.deltaX === 0 && columnsEl.value != null) {
|
||||||
columnsEl.value.scrollLeft += ev.deltaY;
|
columnsEl.value.scrollLeft += ev.deltaY;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@dragstart="onDragstart"
|
@dragstart="onDragstart"
|
||||||
@dragend="onDragend"
|
@dragend="onDragend"
|
||||||
@contextmenu.prevent.stop="onContextmenu"
|
@contextmenu.prevent.stop="onContextmenu"
|
||||||
@wheel="emit('headerWheel', $event)"
|
@wheel.passive="emit('headerWheel', $event)"
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 256 128" :class="$style.tabShape">
|
<svg viewBox="0 0 256 128" :class="$style.tabShape">
|
||||||
<g transform="matrix(6.2431,0,0,6.2431,-677.417,-29.3839)">
|
<g transform="matrix(6.2431,0,0,6.2431,-677.417,-29.3839)">
|
||||||
|
|
|
@ -37,6 +37,11 @@ export const searchIndexes: SearchIndexItem[] = [
|
||||||
label: i18n.ts.themeForDarkMode,
|
label: i18n.ts.themeForDarkMode,
|
||||||
keywords: ['dark', 'theme'],
|
keywords: ['dark', 'theme'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'jwW5HULqA',
|
||||||
|
label: i18n.ts._settings.enableSyncThemesBetweenDevices,
|
||||||
|
keywords: ['sync', 'themes', 'devices'],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
label: i18n.ts.theme,
|
label: i18n.ts.theme,
|
||||||
keywords: ['theme'],
|
keywords: ['theme'],
|
||||||
|
|
|
@ -342,7 +342,7 @@ export function getNoteMenu(props: {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode));
|
menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.embed));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSupportShare()) {
|
if (isSupportShare()) {
|
||||||
|
@ -506,7 +506,7 @@ export function getNoteMenu(props: {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode));
|
menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.embed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -197,7 +197,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
||||||
} else {
|
} else {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
icon: 'ti ti-code',
|
icon: 'ti ti-code',
|
||||||
text: i18n.ts.genEmbedCode,
|
text: i18n.ts.embed,
|
||||||
type: 'parent',
|
type: 'parent',
|
||||||
children: [{
|
children: [{
|
||||||
text: i18n.ts.noteOfThisUser,
|
text: i18n.ts.noteOfThisUser,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2025.4.0-beta.0",
|
"version": "2025.4.0-beta.1",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
|
|
@ -5165,6 +5165,8 @@ export type components = {
|
||||||
hasUnreadNote: boolean;
|
hasUnreadNote: boolean;
|
||||||
/** @default false */
|
/** @default false */
|
||||||
notify: boolean;
|
notify: boolean;
|
||||||
|
/** @default false */
|
||||||
|
hideNotesInSensitiveChannel: boolean;
|
||||||
};
|
};
|
||||||
Clip: {
|
Clip: {
|
||||||
/**
|
/**
|
||||||
|
@ -5593,6 +5595,21 @@ export type components = {
|
||||||
enableEmail: boolean;
|
enableEmail: boolean;
|
||||||
enableServiceWorker: boolean;
|
enableServiceWorker: boolean;
|
||||||
translatorAvailable: 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;
|
mediaProxy: string;
|
||||||
enableUrlPreview: boolean;
|
enableUrlPreview: boolean;
|
||||||
backgroundImageUrl: string | null;
|
backgroundImageUrl: string | null;
|
||||||
|
@ -12096,6 +12113,7 @@ export type operations = {
|
||||||
excludeBots?: boolean;
|
excludeBots?: boolean;
|
||||||
withReplies: boolean;
|
withReplies: boolean;
|
||||||
withFile: boolean;
|
withFile: boolean;
|
||||||
|
hideNotesInSensitiveChannel?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -12407,6 +12425,7 @@ export type operations = {
|
||||||
excludeBots?: boolean;
|
excludeBots?: boolean;
|
||||||
withReplies?: boolean;
|
withReplies?: boolean;
|
||||||
withFile?: boolean;
|
withFile?: boolean;
|
||||||
|
hideNotesInSensitiveChannel?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -23384,8 +23403,8 @@ export type operations = {
|
||||||
untilId?: string;
|
untilId?: string;
|
||||||
/** @default true */
|
/** @default true */
|
||||||
markAsRead?: boolean;
|
markAsRead?: boolean;
|
||||||
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
|
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||||
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
|
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
74
pnpm-lock.yaml
generated
74
pnpm-lock.yaml
generated
|
@ -544,6 +544,9 @@ importers:
|
||||||
'@nestjs/platform-express':
|
'@nestjs/platform-express':
|
||||||
specifier: 10.4.15
|
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)
|
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':
|
'@simplewebauthn/types':
|
||||||
specifier: 12.0.0
|
specifier: 12.0.0
|
||||||
version: 12.0.0
|
version: 12.0.0
|
||||||
|
@ -730,6 +733,9 @@ importers:
|
||||||
'@ruffle-rs/ruffle':
|
'@ruffle-rs/ruffle':
|
||||||
specifier: 0.1.0-nightly.2024.10.15
|
specifier: 0.1.0-nightly.2024.10.15
|
||||||
version: 0.1.0-nightly.2024.10.15
|
version: 0.1.0-nightly.2024.10.15
|
||||||
|
'@sentry/vue':
|
||||||
|
specifier: 9.8.0
|
||||||
|
version: 9.8.0(vue@3.5.13(typescript@5.8.2))
|
||||||
'@syuilo/aiscript':
|
'@syuilo/aiscript':
|
||||||
specifier: 0.19.0
|
specifier: 0.19.0
|
||||||
version: 0.19.0
|
version: 0.19.0
|
||||||
|
@ -3649,10 +3655,34 @@ packages:
|
||||||
'@sec-ant/readable-stream@0.4.1':
|
'@sec-ant/readable-stream@0.4.1':
|
||||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
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':
|
'@sentry/core@8.55.0':
|
||||||
resolution: {integrity: sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==}
|
resolution: {integrity: sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==}
|
||||||
engines: {node: '>=14.18'}
|
engines: {node: '>=14.18'}
|
||||||
|
|
||||||
|
'@sentry/core@9.8.0':
|
||||||
|
resolution: {integrity: sha512-EnN2yLWCbWjooWBPzwlXdZoJG/Bqn3ymbuXX++DUJuBGjSmtixQeTf/hKeVzj4zbib3BbbYsNBasRVjq8Rk5ng==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
'@sentry/node@8.55.0':
|
'@sentry/node@8.55.0':
|
||||||
resolution: {integrity: sha512-h10LJLDTRAzYgay60Oy7moMookqqSZSviCWkkmHZyaDn+4WURnPp5SKhhfrzPRQcXKrweiOwDSHBgn1tweDssg==}
|
resolution: {integrity: sha512-h10LJLDTRAzYgay60Oy7moMookqqSZSviCWkkmHZyaDn+4WURnPp5SKhhfrzPRQcXKrweiOwDSHBgn1tweDssg==}
|
||||||
engines: {node: '>=14.18'}
|
engines: {node: '>=14.18'}
|
||||||
|
@ -3673,6 +3703,16 @@ packages:
|
||||||
engines: {node: '>=14.18'}
|
engines: {node: '>=14.18'}
|
||||||
hasBin: true
|
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':
|
'@shikijs/core@3.2.1':
|
||||||
resolution: {integrity: sha512-FhsdxMWYu/C11sFisEp7FMGBtX/OSSbnXZDMBhGuUDBNTdsoZlMSgQv5f90rwvzWAdWIW6VobD+G3IrazxA6dQ==}
|
resolution: {integrity: sha512-FhsdxMWYu/C11sFisEp7FMGBtX/OSSbnXZDMBhGuUDBNTdsoZlMSgQv5f90rwvzWAdWIW6VobD+G3IrazxA6dQ==}
|
||||||
|
|
||||||
|
@ -13747,8 +13787,36 @@ snapshots:
|
||||||
|
|
||||||
'@sec-ant/readable-stream@0.4.1': {}
|
'@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@8.55.0': {}
|
||||||
|
|
||||||
|
'@sentry/core@9.8.0': {}
|
||||||
|
|
||||||
'@sentry/node@8.55.0':
|
'@sentry/node@8.55.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@opentelemetry/api': 1.9.0
|
'@opentelemetry/api': 1.9.0
|
||||||
|
@ -13808,6 +13876,12 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- 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':
|
'@shikijs/core@3.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@shikijs/types': 3.2.1
|
'@shikijs/types': 3.2.1
|
||||||
|
|
Loading…
Add table
Reference in a new issue