Merge branch 'develop' into feat-frontend-expand-profile-links
This commit is contained in:
commit
df290d7e38
|
@ -11,6 +11,7 @@
|
||||||
- データベースの肥大化を防止することが可能です
|
- データベースの肥大化を防止することが可能です
|
||||||
- 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。
|
- 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。
|
||||||
- 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。
|
- 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。
|
||||||
|
- データベースサイズへの効果が見られない場合はautovacuumが有効になっているか確認してください
|
||||||
- サーバーの初期設定が完了するまでは連合がオンにならないようになりました
|
- サーバーの初期設定が完了するまでは連合がオンにならないようになりました
|
||||||
- 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました
|
- 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました
|
||||||
- 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました
|
- 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました
|
||||||
|
@ -22,8 +23,8 @@
|
||||||
- Enhance: ユーザー検索をロールポリシーで制限できるように
|
- Enhance: ユーザー検索をロールポリシーで制限できるように
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: AiScriptが1.0に更新されました
|
- Feat: AiScriptが1.1.0に更新されました
|
||||||
- プラグインは1.0に対応したものが必要です
|
- プラグインは1.xに対応したものが必要です
|
||||||
- Playはそのまま動作しますが、新規に作られるプリセットは1.0になります
|
- Playはそのまま動作しますが、新規に作られるプリセットは1.0になります
|
||||||
- 以前のバージョンから無効化されていた note_view_interruptor が有効になりました
|
- 以前のバージョンから無効化されていた note_view_interruptor が有効になりました
|
||||||
- Feat: セーフモード
|
- Feat: セーフモード
|
||||||
|
@ -33,12 +34,14 @@
|
||||||
- URLに`?safemode=true`を付ける
|
- URLに`?safemode=true`を付ける
|
||||||
- PWAのショートカットで Safemode を選択して起動する
|
- PWAのショートカットで Safemode を選択して起動する
|
||||||
- Feat: ページのタブバーを下部に表示できるように
|
- Feat: ページのタブバーを下部に表示できるように
|
||||||
|
- Feat: (実験的)iOSでの触覚フィードバックを有効にできるように
|
||||||
- Enhance: 「自動でもっと見る」オプションが有効になり、安定性が向上しました
|
- Enhance: 「自動でもっと見る」オプションが有効になり、安定性が向上しました
|
||||||
- Enhance: コントロールパネルを検索できるように
|
- Enhance: コントロールパネルを検索できるように
|
||||||
- Enhance: トルコ語 (tr-TR) に対応
|
- Enhance: トルコ語 (tr-TR) に対応
|
||||||
- Enhance: 不必要な翻訳データを読み込まなくなり、パフォーマンスが向上しました
|
- Enhance: 不必要な翻訳データを読み込まなくなり、パフォーマンスが向上しました
|
||||||
- Enhance: 画像エフェクトのパラメータ名の多言語対応
|
- Enhance: 画像エフェクトのパラメータ名の多言語対応
|
||||||
- Enhance: 依存ソフトウェアの更新
|
- Enhance: 依存ソフトウェアの更新
|
||||||
|
- Enhance: ノートを非表示にする相対期間を1ヶ月単位で自由に指定できるように
|
||||||
- Enhance: プロフィールへのリンクをユーザーポップアップのアバターとバナーに追加
|
- Enhance: プロフィールへのリンクをユーザーポップアップのアバターとバナーに追加
|
||||||
- Enhance: ユーザーのノート、フォロー、フォロワーページへのリンクをユーザーポップアップに追加
|
- Enhance: ユーザーのノート、フォロー、フォロワーページへのリンクをユーザーポップアップに追加
|
||||||
- Fix: 投稿フォームでファイルのアップロードが中止または失敗した際のハンドリングを修正
|
- Fix: 投稿フォームでファイルのアップロードが中止または失敗した際のハンドリングを修正
|
||||||
|
|
|
@ -4234,6 +4234,10 @@ export interface Locale extends ILocale {
|
||||||
* プリセットから選択
|
* プリセットから選択
|
||||||
*/
|
*/
|
||||||
"selectFromPresets": string;
|
"selectFromPresets": string;
|
||||||
|
/**
|
||||||
|
* カスタム
|
||||||
|
*/
|
||||||
|
"custom": string;
|
||||||
/**
|
/**
|
||||||
* 実績
|
* 実績
|
||||||
*/
|
*/
|
||||||
|
@ -8836,6 +8840,10 @@ export interface Locale extends ILocale {
|
||||||
* 日
|
* 日
|
||||||
*/
|
*/
|
||||||
"day": string;
|
"day": string;
|
||||||
|
/**
|
||||||
|
* ヶ月
|
||||||
|
*/
|
||||||
|
"month": string;
|
||||||
};
|
};
|
||||||
"_2fa": {
|
"_2fa": {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1054,6 +1054,7 @@ permissionDeniedError: "操作が拒否されました"
|
||||||
permissionDeniedErrorDescription: "このアカウントにはこの操作を行うための権限がありません。"
|
permissionDeniedErrorDescription: "このアカウントにはこの操作を行うための権限がありません。"
|
||||||
preset: "プリセット"
|
preset: "プリセット"
|
||||||
selectFromPresets: "プリセットから選択"
|
selectFromPresets: "プリセットから選択"
|
||||||
|
custom: "カスタム"
|
||||||
achievements: "実績"
|
achievements: "実績"
|
||||||
gotInvalidResponseError: "サーバーの応答が無効です"
|
gotInvalidResponseError: "サーバーの応答が無効です"
|
||||||
gotInvalidResponseErrorDescription: "サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。"
|
gotInvalidResponseErrorDescription: "サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。"
|
||||||
|
@ -2321,6 +2322,7 @@ _time:
|
||||||
minute: "分"
|
minute: "分"
|
||||||
hour: "時間"
|
hour: "時間"
|
||||||
day: "日"
|
day: "日"
|
||||||
|
month: "ヶ月"
|
||||||
|
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "既に設定は完了しています。"
|
alreadyRegistered: "既に設定は完了しています。"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.8.0-alpha.11",
|
"version": "2025.8.0-beta.0",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class PageCountInNote1755168347001 {
|
||||||
|
name = 'PageCountInNote1755168347001'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "note" ADD "pageCount" smallint NOT NULL DEFAULT '0'`);
|
||||||
|
|
||||||
|
// Update existing notes
|
||||||
|
// block_list CTE collects all page blocks on the pages including child blocks in the section blocks.
|
||||||
|
// The clipped_notes CTE counts how many distinct pages each note block is referenced in.
|
||||||
|
// Finally, we update the note table with the count of pages for each referenced note.
|
||||||
|
await queryRunner.query(`
|
||||||
|
WITH RECURSIVE block_list AS (
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
page.id as page_id,
|
||||||
|
block as block
|
||||||
|
FROM page
|
||||||
|
CROSS JOIN LATERAL jsonb_array_elements(page.content) block
|
||||||
|
WHERE block->>'type' = 'note' OR block->>'type' = 'section'
|
||||||
|
)
|
||||||
|
UNION ALL
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
block_list.page_id,
|
||||||
|
child_block AS block
|
||||||
|
FROM LATERAL (
|
||||||
|
SELECT page_id, block
|
||||||
|
FROM block_list
|
||||||
|
WHERE block_list.block->>'type' = 'section'
|
||||||
|
) block_list
|
||||||
|
CROSS JOIN LATERAL jsonb_array_elements(block_list.block->'children') child_block
|
||||||
|
WHERE child_block->>'type' = 'note' OR child_block->>'type' = 'section'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
clipped_notes AS (
|
||||||
|
SELECT
|
||||||
|
(block->>'note') AS note_id,
|
||||||
|
COUNT(distinct block_list.page_id) AS count
|
||||||
|
FROM block_list
|
||||||
|
WHERE block_list.block->>'type' = 'note'
|
||||||
|
GROUP BY block->>'note'
|
||||||
|
)
|
||||||
|
UPDATE note
|
||||||
|
SET "pageCount" = clipped_notes.count
|
||||||
|
FROM clipped_notes
|
||||||
|
WHERE note.id = clipped_notes.note_id;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "pageCount"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -78,6 +78,7 @@ import { ChannelFollowingService } from './ChannelFollowingService.js';
|
||||||
import { ChatService } from './ChatService.js';
|
import { ChatService } from './ChatService.js';
|
||||||
import { RegistryApiService } from './RegistryApiService.js';
|
import { RegistryApiService } from './RegistryApiService.js';
|
||||||
import { ReversiService } from './ReversiService.js';
|
import { ReversiService } from './ReversiService.js';
|
||||||
|
import { PageService } from './PageService.js';
|
||||||
|
|
||||||
import { ChartLoggerService } from './chart/ChartLoggerService.js';
|
import { ChartLoggerService } from './chart/ChartLoggerService.js';
|
||||||
import FederationChart from './chart/charts/federation.js';
|
import FederationChart from './chart/charts/federation.js';
|
||||||
|
@ -227,6 +228,7 @@ const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService',
|
||||||
const $ChatService: Provider = { provide: 'ChatService', useExisting: ChatService };
|
const $ChatService: Provider = { provide: 'ChatService', useExisting: ChatService };
|
||||||
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
|
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
|
||||||
const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
|
const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
|
||||||
|
const $PageService: Provider = { provide: 'PageService', useExisting: PageService };
|
||||||
|
|
||||||
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
|
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
|
||||||
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
|
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
|
||||||
|
@ -379,6 +381,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
ChatService,
|
ChatService,
|
||||||
RegistryApiService,
|
RegistryApiService,
|
||||||
ReversiService,
|
ReversiService,
|
||||||
|
PageService,
|
||||||
|
|
||||||
ChartLoggerService,
|
ChartLoggerService,
|
||||||
FederationChart,
|
FederationChart,
|
||||||
|
@ -527,6 +530,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$ChatService,
|
$ChatService,
|
||||||
$RegistryApiService,
|
$RegistryApiService,
|
||||||
$ReversiService,
|
$ReversiService,
|
||||||
|
$PageService,
|
||||||
|
|
||||||
$ChartLoggerService,
|
$ChartLoggerService,
|
||||||
$FederationChart,
|
$FederationChart,
|
||||||
|
@ -676,6 +680,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
ChatService,
|
ChatService,
|
||||||
RegistryApiService,
|
RegistryApiService,
|
||||||
ReversiService,
|
ReversiService,
|
||||||
|
PageService,
|
||||||
|
|
||||||
FederationChart,
|
FederationChart,
|
||||||
NotesChart,
|
NotesChart,
|
||||||
|
@ -822,6 +827,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$ChatService,
|
$ChatService,
|
||||||
$RegistryApiService,
|
$RegistryApiService,
|
||||||
$ReversiService,
|
$ReversiService,
|
||||||
|
$PageService,
|
||||||
|
|
||||||
$FederationChart,
|
$FederationChart,
|
||||||
$NotesChart,
|
$NotesChart,
|
||||||
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { DataSource, In, Not } from 'typeorm';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import {
|
||||||
|
type NotesRepository,
|
||||||
|
MiPage,
|
||||||
|
type PagesRepository,
|
||||||
|
MiDriveFile,
|
||||||
|
type UsersRepository,
|
||||||
|
MiNote,
|
||||||
|
} from '@/models/_.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import type { MiUser } from '@/models/User.js';
|
||||||
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
|
||||||
|
export interface PageBody {
|
||||||
|
title: string;
|
||||||
|
name: string;
|
||||||
|
summary: string | null;
|
||||||
|
content: Array<Record<string, any>>;
|
||||||
|
variables: Array<Record<string, any>>;
|
||||||
|
script: string;
|
||||||
|
eyeCatchingImage?: MiDriveFile | null;
|
||||||
|
font: string;
|
||||||
|
alignCenter: boolean;
|
||||||
|
hideTitleWhenPinned: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PageService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.db)
|
||||||
|
private db: DataSource,
|
||||||
|
|
||||||
|
@Inject(DI.pagesRepository)
|
||||||
|
private pagesRepository: PagesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.notesRepository)
|
||||||
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
private roleService: RoleService,
|
||||||
|
private moderationLogService: ModerationLogService,
|
||||||
|
private idService: IdService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async create(
|
||||||
|
me: MiUser,
|
||||||
|
body: PageBody,
|
||||||
|
): Promise<MiPage> {
|
||||||
|
await this.pagesRepository.findBy({
|
||||||
|
userId: me.id,
|
||||||
|
name: body.name,
|
||||||
|
}).then(result => {
|
||||||
|
if (result.length > 0) {
|
||||||
|
throw new IdentifiableError('1a79e38e-3d83-4423-845b-a9d83ff93b61');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = await this.pagesRepository.insertOne(new MiPage({
|
||||||
|
id: this.idService.gen(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
title: body.title,
|
||||||
|
name: body.name,
|
||||||
|
summary: body.summary,
|
||||||
|
content: body.content,
|
||||||
|
variables: body.variables,
|
||||||
|
script: body.script,
|
||||||
|
eyeCatchingImageId: body.eyeCatchingImage ? body.eyeCatchingImage.id : null,
|
||||||
|
userId: me.id,
|
||||||
|
visibility: 'public',
|
||||||
|
alignCenter: body.alignCenter,
|
||||||
|
hideTitleWhenPinned: body.hideTitleWhenPinned,
|
||||||
|
font: body.font,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const referencedNotes = this.collectReferencedNotes(page.content);
|
||||||
|
if (referencedNotes.length > 0) {
|
||||||
|
await this.notesRepository.increment({ id: In(referencedNotes) }, 'pageCount', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async update(
|
||||||
|
me: MiUser,
|
||||||
|
pageId: MiPage['id'],
|
||||||
|
body: Partial<PageBody>,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.db.transaction(async (transaction) => {
|
||||||
|
const page = await transaction.findOne(MiPage, {
|
||||||
|
where: {
|
||||||
|
id: pageId,
|
||||||
|
},
|
||||||
|
lock: { mode: 'for_no_key_update' },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (page == null) {
|
||||||
|
throw new IdentifiableError('66aefd3c-fdb2-4a71-85ae-cc18bea85d3f');
|
||||||
|
}
|
||||||
|
if (page.userId !== me.id) {
|
||||||
|
throw new IdentifiableError('d0017699-8256-46f1-aed4-bc03bed73616');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.name != null) {
|
||||||
|
await transaction.findBy(MiPage, {
|
||||||
|
id: Not(pageId),
|
||||||
|
userId: me.id,
|
||||||
|
name: body.name,
|
||||||
|
}).then(result => {
|
||||||
|
if (result.length > 0) {
|
||||||
|
throw new IdentifiableError('d05bfe24-24b6-4ea2-a3ec-87cc9bf4daa4');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await transaction.update(MiPage, page.id, {
|
||||||
|
updatedAt: new Date(),
|
||||||
|
title: body.title,
|
||||||
|
name: body.name,
|
||||||
|
summary: body.summary === undefined ? page.summary : body.summary,
|
||||||
|
content: body.content,
|
||||||
|
variables: body.variables,
|
||||||
|
script: body.script,
|
||||||
|
alignCenter: body.alignCenter,
|
||||||
|
hideTitleWhenPinned: body.hideTitleWhenPinned,
|
||||||
|
font: body.font,
|
||||||
|
eyeCatchingImageId: body.eyeCatchingImage === undefined ? undefined : (body.eyeCatchingImage?.id ?? null),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("page.content", page.content);
|
||||||
|
|
||||||
|
if (body.content != null) {
|
||||||
|
const beforeReferencedNotes = this.collectReferencedNotes(page.content);
|
||||||
|
const afterReferencedNotes = this.collectReferencedNotes(body.content);
|
||||||
|
|
||||||
|
const removedNotes = beforeReferencedNotes.filter(noteId => !afterReferencedNotes.includes(noteId));
|
||||||
|
const addedNotes = afterReferencedNotes.filter(noteId => !beforeReferencedNotes.includes(noteId));
|
||||||
|
|
||||||
|
if (removedNotes.length > 0) {
|
||||||
|
await transaction.decrement(MiNote, { id: In(removedNotes) }, 'pageCount', 1);
|
||||||
|
}
|
||||||
|
if (addedNotes.length > 0) {
|
||||||
|
await transaction.increment(MiNote, { id: In(addedNotes) }, 'pageCount', 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async delete(me: MiUser, pageId: MiPage['id']): Promise<void> {
|
||||||
|
await this.db.transaction(async (transaction) => {
|
||||||
|
const page = await transaction.findOne(MiPage, {
|
||||||
|
where: {
|
||||||
|
id: pageId,
|
||||||
|
},
|
||||||
|
lock: { mode: 'pessimistic_write' }, // same lock level as DELETE
|
||||||
|
});
|
||||||
|
|
||||||
|
if (page == null) {
|
||||||
|
throw new IdentifiableError('66aefd3c-fdb2-4a71-85ae-cc18bea85d3f');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await this.roleService.isModerator(me) && page.userId !== me.id) {
|
||||||
|
throw new IdentifiableError('d0017699-8256-46f1-aed4-bc03bed73616');
|
||||||
|
}
|
||||||
|
|
||||||
|
await transaction.delete(MiPage, page.id);
|
||||||
|
|
||||||
|
if (page.userId !== me.id) {
|
||||||
|
const user = await this.usersRepository.findOneByOrFail({ id: page.userId });
|
||||||
|
this.moderationLogService.log(me, 'deletePage', {
|
||||||
|
pageId: page.id,
|
||||||
|
pageUserId: page.userId,
|
||||||
|
pageUserUsername: user.username,
|
||||||
|
page,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const referencedNotes = this.collectReferencedNotes(page.content);
|
||||||
|
if (referencedNotes.length > 0) {
|
||||||
|
await transaction.decrement(MiNote, { id: In(referencedNotes) }, 'pageCount', 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
collectReferencedNotes(content: MiPage['content']): string[] {
|
||||||
|
const referencingNotes = new Set<string>();
|
||||||
|
const recursiveCollect = (content: unknown[]) => {
|
||||||
|
for (const contentElement of content) {
|
||||||
|
if (typeof contentElement === 'object'
|
||||||
|
&& contentElement !== null
|
||||||
|
&& 'type' in contentElement) {
|
||||||
|
if (contentElement.type === 'note'
|
||||||
|
&& 'note' in contentElement
|
||||||
|
&& typeof contentElement.note === 'string') {
|
||||||
|
referencingNotes.add(contentElement.note);
|
||||||
|
}
|
||||||
|
if (contentElement.type === 'section'
|
||||||
|
&& 'children' in contentElement
|
||||||
|
&& Array.isArray(contentElement.children)) {
|
||||||
|
recursiveCollect(contentElement.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
recursiveCollect(content);
|
||||||
|
return [...referencingNotes];
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,6 +85,7 @@ function generateDummyNote(override?: Partial<MiNote>): MiNote {
|
||||||
renoteCount: 10,
|
renoteCount: 10,
|
||||||
repliesCount: 5,
|
repliesCount: 5,
|
||||||
clippedCount: 0,
|
clippedCount: 0,
|
||||||
|
pageCount: 0,
|
||||||
reactions: {},
|
reactions: {},
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
uri: null,
|
uri: null,
|
||||||
|
|
|
@ -114,6 +114,13 @@ export class MiNote {
|
||||||
})
|
})
|
||||||
public clippedCount: number;
|
public clippedCount: number;
|
||||||
|
|
||||||
|
// The number of note page blocks referencing this note.
|
||||||
|
// This column is used by Remote Note Cleaning and manually updated rather than automatically with triggers.
|
||||||
|
@Column('smallint', {
|
||||||
|
default: 0,
|
||||||
|
})
|
||||||
|
public pageCount: number;
|
||||||
|
|
||||||
@Column('jsonb', {
|
@Column('jsonb', {
|
||||||
default: {},
|
default: {},
|
||||||
})
|
})
|
||||||
|
|
|
@ -82,9 +82,11 @@ export class CleanRemoteNotesProcessorService {
|
||||||
const removalCriteria = [
|
const removalCriteria = [
|
||||||
'note."id" < :newestLimit',
|
'note."id" < :newestLimit',
|
||||||
'note."clippedCount" = 0',
|
'note."clippedCount" = 0',
|
||||||
|
'note."pageCount" = 0',
|
||||||
'note."userHost" IS NOT NULL',
|
'note."userHost" IS NOT NULL',
|
||||||
'NOT EXISTS (SELECT 1 FROM user_note_pining WHERE "noteId" = note."id")',
|
'NOT EXISTS (SELECT 1 FROM user_note_pining WHERE "noteId" = note."id")',
|
||||||
'NOT EXISTS (SELECT 1 FROM note_favorite WHERE "noteId" = note."id")',
|
'NOT EXISTS (SELECT 1 FROM note_favorite WHERE "noteId" = note."id")',
|
||||||
|
'NOT EXISTS (SELECT 1 FROM note_reaction INNER JOIN "user" ON note_reaction."userId" = "user".id WHERE note_reaction."noteId" = note."id" AND "user"."host" IS NULL)',
|
||||||
].join(' AND ');
|
].join(' AND ');
|
||||||
|
|
||||||
const minId = (await this.notesRepository.createQueryBuilder('note')
|
const minId = (await this.notesRepository.createQueryBuilder('note')
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { MoreThan } from 'typeorm';
|
import { MoreThan } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
import type { DriveFilesRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
|
@ -14,6 +14,7 @@ import type { MiNote } from '@/models/Note.js';
|
||||||
import { EmailService } from '@/core/EmailService.js';
|
import { EmailService } from '@/core/EmailService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { SearchService } from '@/core/SearchService.js';
|
import { SearchService } from '@/core/SearchService.js';
|
||||||
|
import { PageService } from '@/core/PageService.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
import type { DbUserDeleteJobData } from '../types.js';
|
import type { DbUserDeleteJobData } from '../types.js';
|
||||||
|
@ -35,7 +36,11 @@ export class DeleteAccountProcessorService {
|
||||||
@Inject(DI.driveFilesRepository)
|
@Inject(DI.driveFilesRepository)
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.pagesRepository)
|
||||||
|
private pagesRepository: PagesRepository,
|
||||||
|
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
|
private pageService: PageService,
|
||||||
private emailService: EmailService,
|
private emailService: EmailService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
|
@ -112,6 +117,28 @@ export class DeleteAccountProcessorService {
|
||||||
this.logger.succ('All of files deleted');
|
this.logger.succ('All of files deleted');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// delete pages. Necessary for decrementing pageCount of notes.
|
||||||
|
while (true) {
|
||||||
|
const pages = await this.pagesRepository.find({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
take: 100,
|
||||||
|
order: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pages.length === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (const page of pages) {
|
||||||
|
await this.pageService.delete(user, page.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{ // Send email notification
|
{ // Send email notification
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||||
if (profile.email && profile.emailVerified) {
|
if (profile.email && profile.emailVerified) {
|
||||||
|
|
|
@ -5,12 +5,13 @@
|
||||||
|
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { DriveFilesRepository, PagesRepository } from '@/models/_.js';
|
import type { DriveFilesRepository, MiDriveFile, PagesRepository } from '@/models/_.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { pageNameSchema } from '@/models/Page.js';
|
||||||
import { MiPage, pageNameSchema } from '@/models/Page.js';
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { PageEntityService } from '@/core/entities/PageEntityService.js';
|
import { PageEntityService } from '@/core/entities/PageEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { PageService } from '@/core/PageService.js';
|
||||||
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -77,11 +78,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
@Inject(DI.driveFilesRepository)
|
@Inject(DI.driveFilesRepository)
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
|
private pageService: PageService,
|
||||||
private pageEntityService: PageEntityService,
|
private pageEntityService: PageEntityService,
|
||||||
private idService: IdService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
let eyeCatchingImage = null;
|
let eyeCatchingImage: MiDriveFile | null = null;
|
||||||
if (ps.eyeCatchingImageId != null) {
|
if (ps.eyeCatchingImageId != null) {
|
||||||
eyeCatchingImage = await this.driveFilesRepository.findOneBy({
|
eyeCatchingImage = await this.driveFilesRepository.findOneBy({
|
||||||
id: ps.eyeCatchingImageId,
|
id: ps.eyeCatchingImageId,
|
||||||
|
@ -102,24 +103,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const page = await this.pagesRepository.insertOne(new MiPage({
|
try {
|
||||||
id: this.idService.gen(),
|
const page = await this.pageService.create(me, {
|
||||||
updatedAt: new Date(),
|
...ps,
|
||||||
title: ps.title,
|
eyeCatchingImage,
|
||||||
name: ps.name,
|
summary: ps.summary ?? null,
|
||||||
summary: ps.summary,
|
});
|
||||||
content: ps.content,
|
|
||||||
variables: ps.variables,
|
|
||||||
script: ps.script,
|
|
||||||
eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null,
|
|
||||||
userId: me.id,
|
|
||||||
visibility: 'public',
|
|
||||||
alignCenter: ps.alignCenter,
|
|
||||||
hideTitleWhenPinned: ps.hideTitleWhenPinned,
|
|
||||||
font: ps.font,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return await this.pageEntityService.pack(page);
|
return await this.pageEntityService.pack(page);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof IdentifiableError && err.id === '1a79e38e-3d83-4423-845b-a9d83ff93b61') {
|
||||||
|
throw new ApiError(meta.errors.nameAlreadyExists);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { PagesRepository, UsersRepository } from '@/models/_.js';
|
import type { MiDriveFile, PagesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
|
import { PageService } from '@/core/PageService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['pages'],
|
tags: ['pages'],
|
||||||
|
@ -44,36 +46,17 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.pagesRepository)
|
private pageService: PageService,
|
||||||
private pagesRepository: PagesRepository,
|
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
|
||||||
private usersRepository: UsersRepository,
|
|
||||||
|
|
||||||
private moderationLogService: ModerationLogService,
|
|
||||||
private roleService: RoleService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const page = await this.pagesRepository.findOneBy({ id: ps.pageId });
|
try {
|
||||||
|
await this.pageService.delete(me, ps.pageId);
|
||||||
if (page == null) {
|
} catch (err) {
|
||||||
throw new ApiError(meta.errors.noSuchPage);
|
if (err instanceof IdentifiableError) {
|
||||||
|
if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage);
|
||||||
|
if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied);
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
if (!await this.roleService.isModerator(me) && page.userId !== me.id) {
|
|
||||||
throw new ApiError(meta.errors.accessDenied);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.pagesRepository.delete(page.id);
|
|
||||||
|
|
||||||
if (page.userId !== me.id) {
|
|
||||||
const user = await this.usersRepository.findOneByOrFail({ id: page.userId });
|
|
||||||
this.moderationLogService.log(me, 'deletePage', {
|
|
||||||
pageId: page.id,
|
|
||||||
pageUserId: page.userId,
|
|
||||||
pageUserUsername: user.username,
|
|
||||||
page,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import { Not } from 'typeorm';
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { PagesRepository, DriveFilesRepository } from '@/models/_.js';
|
import type { DriveFilesRepository, MiDriveFile } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { pageNameSchema } from '@/models/Page.js';
|
import { pageNameSchema } from '@/models/Page.js';
|
||||||
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
|
import { PageService } from '@/core/PageService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['pages'],
|
tags: ['pages'],
|
||||||
|
@ -75,24 +76,17 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.pagesRepository)
|
|
||||||
private pagesRepository: PagesRepository,
|
|
||||||
|
|
||||||
@Inject(DI.driveFilesRepository)
|
@Inject(DI.driveFilesRepository)
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
|
private pageService: PageService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const page = await this.pagesRepository.findOneBy({ id: ps.pageId });
|
try {
|
||||||
if (page == null) {
|
let eyeCatchingImage: MiDriveFile | null | undefined | string = ps.eyeCatchingImageId;
|
||||||
throw new ApiError(meta.errors.noSuchPage);
|
if (eyeCatchingImage != null) {
|
||||||
}
|
eyeCatchingImage = await this.driveFilesRepository.findOneBy({
|
||||||
if (page.userId !== me.id) {
|
id: eyeCatchingImage,
|
||||||
throw new ApiError(meta.errors.accessDenied);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.eyeCatchingImageId != null) {
|
|
||||||
const eyeCatchingImage = await this.driveFilesRepository.findOneBy({
|
|
||||||
id: ps.eyeCatchingImageId,
|
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -101,31 +95,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.name != null) {
|
await this.pageService.update(me, ps.pageId, {
|
||||||
await this.pagesRepository.findBy({
|
...ps,
|
||||||
id: Not(ps.pageId),
|
eyeCatchingImage,
|
||||||
userId: me.id,
|
});
|
||||||
name: ps.name,
|
} catch (err) {
|
||||||
}).then(result => {
|
if (err instanceof IdentifiableError) {
|
||||||
if (result.length > 0) {
|
if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage);
|
||||||
throw new ApiError(meta.errors.nameAlreadyExists);
|
if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied);
|
||||||
|
if (err.id === 'd05bfe24-24b6-4ea2-a3ec-87cc9bf4daa4') throw new ApiError(meta.errors.nameAlreadyExists);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.pagesRepository.update(page.id, {
|
|
||||||
updatedAt: new Date(),
|
|
||||||
title: ps.title,
|
|
||||||
name: ps.name,
|
|
||||||
summary: ps.summary === undefined ? page.summary : ps.summary,
|
|
||||||
content: ps.content,
|
|
||||||
variables: ps.variables,
|
|
||||||
script: ps.script,
|
|
||||||
alignCenter: ps.alignCenter,
|
|
||||||
hideTitleWhenPinned: ps.hideTitleWhenPinned,
|
|
||||||
font: ps.font,
|
|
||||||
eyeCatchingImageId: ps.eyeCatchingImageId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ describe('NoteCreateService', () => {
|
||||||
renoteCount: 0,
|
renoteCount: 0,
|
||||||
repliesCount: 0,
|
repliesCount: 0,
|
||||||
clippedCount: 0,
|
clippedCount: 0,
|
||||||
|
pageCount: 0,
|
||||||
reactions: {},
|
reactions: {},
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
uri: null,
|
uri: null,
|
||||||
|
|
|
@ -23,6 +23,7 @@ const base: MiNote = {
|
||||||
renoteCount: 0,
|
renoteCount: 0,
|
||||||
repliesCount: 0,
|
repliesCount: 0,
|
||||||
clippedCount: 0,
|
clippedCount: 0,
|
||||||
|
pageCount: 0,
|
||||||
reactions: {},
|
reactions: {},
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
uri: null,
|
uri: null,
|
||||||
|
|
|
@ -281,6 +281,24 @@ describe('CleanRemoteNotesProcessorService', () => {
|
||||||
expect(remainingNote).not.toBeNull();
|
expect(remainingNote).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ページ
|
||||||
|
test('should not delete note that is embedded in a page', async () => {
|
||||||
|
const job = createMockJob();
|
||||||
|
|
||||||
|
// Create old remote note that is embedded in a page
|
||||||
|
const clippedNote = await createNote({
|
||||||
|
pageCount: 1, // Embedded in a page
|
||||||
|
}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000);
|
||||||
|
|
||||||
|
const result = await service.process(job as any);
|
||||||
|
|
||||||
|
expect(result.deletedCount).toBe(0);
|
||||||
|
expect(result.skipped).toBe(false);
|
||||||
|
|
||||||
|
const remainingNote = await notesRepository.findOneBy({ id: clippedNote.id });
|
||||||
|
expect(remainingNote).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
// 古いreply, renoteが含まれている時の挙動
|
// 古いreply, renoteが含まれている時の挙動
|
||||||
test('should handle reply/renote relationships correctly', async () => {
|
test('should handle reply/renote relationships correctly', async () => {
|
||||||
const job = createMockJob();
|
const job = createMockJob();
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
"@rollup/plugin-replace": "6.0.2",
|
"@rollup/plugin-replace": "6.0.2",
|
||||||
"@rollup/pluginutils": "5.2.0",
|
"@rollup/pluginutils": "5.2.0",
|
||||||
"@sentry/vue": "10.0.0",
|
"@sentry/vue": "10.0.0",
|
||||||
"@syuilo/aiscript": "1.0.0",
|
"@syuilo/aiscript": "1.1.0",
|
||||||
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
||||||
"@twemoji/parser": "16.0.0",
|
"@twemoji/parser": "16.0.0",
|
||||||
"@vitejs/plugin-vue": "6.0.1",
|
"@vitejs/plugin-vue": "6.0.1",
|
||||||
|
@ -52,6 +52,7 @@
|
||||||
"icons-subsetter": "workspace:*",
|
"icons-subsetter": "workspace:*",
|
||||||
"idb-keyval": "6.2.2",
|
"idb-keyval": "6.2.2",
|
||||||
"insert-text-at-cursor": "0.3.0",
|
"insert-text-at-cursor": "0.3.0",
|
||||||
|
"ios-haptics": "0.1.0",
|
||||||
"is-file-animated": "1.0.2",
|
"is-file-animated": "1.0.2",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"magic-string": "0.30.17",
|
"magic-string": "0.30.17",
|
||||||
|
|
|
@ -141,6 +141,7 @@ import { $i } from '@/i.js';
|
||||||
import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js';
|
import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
import { haptic } from '@/utility/haptic.js';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -431,6 +432,8 @@ function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef,
|
||||||
const key = getKey(emoji);
|
const key = getKey(emoji);
|
||||||
emit('chosen', key);
|
emit('chosen', key);
|
||||||
|
|
||||||
|
haptic();
|
||||||
|
|
||||||
// 最近使った絵文字更新
|
// 最近使った絵文字更新
|
||||||
if (!pinned.value?.includes(key)) {
|
if (!pinned.value?.includes(key)) {
|
||||||
let recents = store.s.recentlyUsedEmojis;
|
let recents = store.s.recentlyUsedEmojis;
|
||||||
|
|
|
@ -46,6 +46,7 @@ import { claimAchievement } from '@/utility/achievements.js';
|
||||||
import { pleaseLogin } from '@/utility/please-login.js';
|
import { pleaseLogin } from '@/utility/please-login.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
import { haptic } from '@/utility/haptic.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
user: Misskey.entities.UserDetailed,
|
user: Misskey.entities.UserDetailed,
|
||||||
|
@ -84,6 +85,8 @@ async function onClick() {
|
||||||
|
|
||||||
wait.value = true;
|
wait.value = true;
|
||||||
|
|
||||||
|
haptic();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isFollowing.value) {
|
if (isFollowing.value) {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
|
||||||
import { getScrollContainer } from '@@/js/scroll.js';
|
import { getScrollContainer } from '@@/js/scroll.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { isHorizontalSwipeSwiping } from '@/utility/touch.js';
|
import { isHorizontalSwipeSwiping } from '@/utility/touch.js';
|
||||||
|
import { haptic } from '@/utility/haptic.js';
|
||||||
|
|
||||||
const SCROLL_STOP = 10;
|
const SCROLL_STOP = 10;
|
||||||
const MAX_PULL_DISTANCE = Infinity;
|
const MAX_PULL_DISTANCE = Infinity;
|
||||||
|
@ -203,6 +204,8 @@ function moving(event: MouseEvent | TouchEvent) {
|
||||||
pullDistance.value = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE);
|
pullDistance.value = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE);
|
||||||
|
|
||||||
isPulledEnough.value = pullDistance.value >= FIRE_THRESHOLD;
|
isPulledEnough.value = pullDistance.value >= FIRE_THRESHOLD;
|
||||||
|
|
||||||
|
if (isPulledEnough.value) haptic();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -38,6 +38,7 @@ import { prefer } from '@/preferences.js';
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
import { noteEvents } from '@/composables/use-note-capture.js';
|
import { noteEvents } from '@/composables/use-note-capture.js';
|
||||||
import { mute as muteEmoji, unmute as unmuteEmoji, checkMuted as isEmojiMuted } from '@/utility/emoji-mute.js';
|
import { mute as muteEmoji, unmute as unmuteEmoji, checkMuted as isEmojiMuted } from '@/utility/emoji-mute.js';
|
||||||
|
import { haptic } from '@/utility/haptic.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
noteId: Misskey.entities.Note['id'];
|
noteId: Misskey.entities.Note['id'];
|
||||||
|
@ -80,6 +81,7 @@ async function toggleReaction() {
|
||||||
|
|
||||||
if (oldReaction !== props.reaction) {
|
if (oldReaction !== props.reaction) {
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
haptic();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mock) {
|
if (mock) {
|
||||||
|
@ -118,6 +120,7 @@ async function toggleReaction() {
|
||||||
}
|
}
|
||||||
|
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
haptic();
|
||||||
|
|
||||||
if (mock) {
|
if (mock) {
|
||||||
emit('reactionToggled', props.reaction, (props.count + 1));
|
emit('reactionToggled', props.reaction, (props.count + 1));
|
||||||
|
|
|
@ -30,6 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { toRefs } from 'vue';
|
import { toRefs } from 'vue';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import XButton from '@/components/MkSwitch.button.vue';
|
import XButton from '@/components/MkSwitch.button.vue';
|
||||||
|
import { haptic } from '@/utility/haptic.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: boolean | Ref<boolean>;
|
modelValue: boolean | Ref<boolean>;
|
||||||
|
@ -48,6 +49,8 @@ const toggle = () => {
|
||||||
if (props.disabled) return;
|
if (props.disabled) return;
|
||||||
emit('update:modelValue', !checked.value);
|
emit('update:modelValue', !checked.value);
|
||||||
emit('change', !checked.value);
|
emit('change', !checked.value);
|
||||||
|
|
||||||
|
haptic();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -292,6 +292,9 @@ const patronsWithIcon = [{
|
||||||
}, {
|
}, {
|
||||||
name: 'NigN',
|
name: 'NigN',
|
||||||
icon: 'https://assets.misskey-hub.net/patrons/1ccaef8e73ec4a50b59ff7cd688ceb84.jpg',
|
icon: 'https://assets.misskey-hub.net/patrons/1ccaef8e73ec4a50b59ff7cd688ceb84.jpg',
|
||||||
|
}, {
|
||||||
|
name: 'しゃどかの',
|
||||||
|
icon: 'https://assets.misskey-hub.net/patrons/5bec3c6b402942619e03f7a2ae76d69e.jpg',
|
||||||
}];
|
}];
|
||||||
|
|
||||||
const patrons = [
|
const patrons = [
|
||||||
|
|
|
@ -88,7 +88,7 @@ let choices = [
|
||||||
]
|
]
|
||||||
|
|
||||||
// シードが「PlayID+ユーザーID+今日の日付」である乱数生成器を用意
|
// シードが「PlayID+ユーザーID+今日の日付」である乱数生成器を用意
|
||||||
let random = Math:gen_rng(\`{THIS_ID}{USER_ID}{Date:year()}{Date:month()}{Date:day()}\`, { algorithm: 'rc4_legacy' })
|
let random = Math:gen_rng(\`{THIS_ID}{USER_ID}{Date:year()}{Date:month()}{Date:day()}\`)
|
||||||
|
|
||||||
// ランダムに選択肢を選ぶ
|
// ランダムに選択肢を選ぶ
|
||||||
let chosen = choices[random(0, (choices.len - 1))]
|
let chosen = choices[random(0, (choices.len - 1))]
|
||||||
|
|
|
@ -99,6 +99,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSwitch v-model="enableFolderPageView">
|
<MkSwitch v-model="enableFolderPageView">
|
||||||
<template #label>Enable folder page view</template>
|
<template #label>Enable folder page view</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="enableHapticFeedback">
|
||||||
|
<template #label>Enable haptic feedback</template>
|
||||||
|
</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</SearchMarker>
|
</SearchMarker>
|
||||||
|
@ -173,6 +176,7 @@ const skipNoteRender = prefer.model('skipNoteRender');
|
||||||
const devMode = prefer.model('devMode');
|
const devMode = prefer.model('devMode');
|
||||||
const stackingRouterView = prefer.model('experimental.stackingRouterView');
|
const stackingRouterView = prefer.model('experimental.stackingRouterView');
|
||||||
const enableFolderPageView = prefer.model('experimental.enableFolderPageView');
|
const enableFolderPageView = prefer.model('experimental.enableFolderPageView');
|
||||||
|
const enableHapticFeedback = prefer.model('experimental.enableHapticFeedback');
|
||||||
|
|
||||||
watch(skipNoteRender, () => {
|
watch(skipNoteRender, () => {
|
||||||
suggestReload();
|
suggestReload();
|
||||||
|
|
|
@ -125,16 +125,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option>
|
<option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
|
|
||||||
<MkSelect v-if="makeNotesFollowersOnlyBefore_type === 'relative'" v-model="makeNotesFollowersOnlyBefore">
|
<MkSelect v-if="makeNotesFollowersOnlyBefore_type === 'relative'" v-model="makeNotesFollowersOnlyBefore_selection">
|
||||||
<option :value="-3600">{{ i18n.ts.oneHour }}</option>
|
<option v-for="preset in makeNotesFollowersOnlyBefore_presets" :value="preset.value">{{ preset.label }}</option>
|
||||||
<option :value="-86400">{{ i18n.ts.oneDay }}</option>
|
<option value="custom">{{ i18n.ts.custom }}</option>
|
||||||
<option :value="-259200">{{ i18n.ts.threeDays }}</option>
|
|
||||||
<option :value="-604800">{{ i18n.ts.oneWeek }}</option>
|
|
||||||
<option :value="-2592000">{{ i18n.ts.oneMonth }}</option>
|
|
||||||
<option :value="-7776000">{{ i18n.ts.threeMonths }}</option>
|
|
||||||
<option :value="-31104000">{{ i18n.ts.oneYear }}</option>
|
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
|
|
||||||
|
<MkInput
|
||||||
|
v-if="makeNotesFollowersOnlyBefore_type === 'relative' && makeNotesFollowersOnlyBefore_isCustomMode"
|
||||||
|
v-model="makeNotesFollowersOnlyBefore_customMonths"
|
||||||
|
type="number"
|
||||||
|
:min="1"
|
||||||
|
>
|
||||||
|
<template #suffix>{{ i18n.ts._time.month }}</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
<MkInput
|
<MkInput
|
||||||
v-if="makeNotesFollowersOnlyBefore_type === 'absolute'"
|
v-if="makeNotesFollowersOnlyBefore_type === 'absolute'"
|
||||||
:modelValue="formatDateTimeString(new Date(makeNotesFollowersOnlyBefore * 1000), 'yyyy-MM-dd')"
|
:modelValue="formatDateTimeString(new Date(makeNotesFollowersOnlyBefore * 1000), 'yyyy-MM-dd')"
|
||||||
|
@ -162,16 +166,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option>
|
<option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
|
|
||||||
<MkSelect v-if="makeNotesHiddenBefore_type === 'relative'" v-model="makeNotesHiddenBefore">
|
<MkSelect v-if="makeNotesHiddenBefore_type === 'relative'" v-model="makeNotesHiddenBefore_selection">
|
||||||
<option :value="-3600">{{ i18n.ts.oneHour }}</option>
|
<option v-for="preset in makeNotesHiddenBefore_presets" :value="preset.value">{{ preset.label }}</option>
|
||||||
<option :value="-86400">{{ i18n.ts.oneDay }}</option>
|
<option value="custom">{{ i18n.ts.custom }}</option>
|
||||||
<option :value="-259200">{{ i18n.ts.threeDays }}</option>
|
|
||||||
<option :value="-604800">{{ i18n.ts.oneWeek }}</option>
|
|
||||||
<option :value="-2592000">{{ i18n.ts.oneMonth }}</option>
|
|
||||||
<option :value="-7776000">{{ i18n.ts.threeMonths }}</option>
|
|
||||||
<option :value="-31104000">{{ i18n.ts.oneYear }}</option>
|
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
|
|
||||||
|
<MkInput
|
||||||
|
v-if="makeNotesHiddenBefore_type === 'relative' && makeNotesHiddenBefore_isCustomMode"
|
||||||
|
v-model="makeNotesHiddenBefore_customMonths"
|
||||||
|
type="number"
|
||||||
|
:min="1"
|
||||||
|
>
|
||||||
|
<template #suffix>{{ i18n.ts._time.month }}</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
<MkInput
|
<MkInput
|
||||||
v-if="makeNotesHiddenBefore_type === 'absolute'"
|
v-if="makeNotesHiddenBefore_type === 'absolute'"
|
||||||
:modelValue="formatDateTimeString(new Date(makeNotesHiddenBefore * 1000), 'yyyy-MM-dd')"
|
:modelValue="formatDateTimeString(new Date(makeNotesHiddenBefore * 1000), 'yyyy-MM-dd')"
|
||||||
|
@ -241,6 +249,37 @@ const makeNotesFollowersOnlyBefore_type = computed(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const makeNotesFollowersOnlyBefore_presets = [
|
||||||
|
{ label: i18n.ts.oneHour, value: -3600 },
|
||||||
|
{ label: i18n.ts.oneDay, value: -86400 },
|
||||||
|
{ label: i18n.ts.threeDays, value: -259200 },
|
||||||
|
{ label: i18n.ts.oneWeek, value: -604800 },
|
||||||
|
{ label: i18n.ts.oneMonth, value: -2592000 },
|
||||||
|
{ label: i18n.ts.threeMonths, value: -7776000 },
|
||||||
|
{ label: i18n.ts.oneYear, value: -31104000 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const makeNotesFollowersOnlyBefore_isCustomMode = ref(
|
||||||
|
makeNotesFollowersOnlyBefore.value != null &&
|
||||||
|
makeNotesFollowersOnlyBefore.value < 0 &&
|
||||||
|
!makeNotesFollowersOnlyBefore_presets.some((preset) => preset.value === makeNotesFollowersOnlyBefore.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
const makeNotesFollowersOnlyBefore_selection = computed({
|
||||||
|
get: () => makeNotesFollowersOnlyBefore_isCustomMode.value ? 'custom' : makeNotesFollowersOnlyBefore.value,
|
||||||
|
set(value) {
|
||||||
|
makeNotesFollowersOnlyBefore_isCustomMode.value = value === 'custom';
|
||||||
|
if (value !== 'custom') makeNotesFollowersOnlyBefore.value = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const makeNotesFollowersOnlyBefore_customMonths = computed({
|
||||||
|
get: () => makeNotesFollowersOnlyBefore.value ? Math.abs(makeNotesFollowersOnlyBefore.value) / (30 * 24 * 60 * 60) : null,
|
||||||
|
set(value) {
|
||||||
|
if (value != null && value > 0) makeNotesFollowersOnlyBefore.value = -Math.abs(Math.floor(Number(value))) * 30 * 24 * 60 * 60;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const makeNotesHiddenBefore_type = computed(() => {
|
const makeNotesHiddenBefore_type = computed(() => {
|
||||||
if (makeNotesHiddenBefore.value == null) {
|
if (makeNotesHiddenBefore.value == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -251,6 +290,37 @@ const makeNotesHiddenBefore_type = computed(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const makeNotesHiddenBefore_presets = [
|
||||||
|
{ label: i18n.ts.oneHour, value: -3600 },
|
||||||
|
{ label: i18n.ts.oneDay, value: -86400 },
|
||||||
|
{ label: i18n.ts.threeDays, value: -259200 },
|
||||||
|
{ label: i18n.ts.oneWeek, value: -604800 },
|
||||||
|
{ label: i18n.ts.oneMonth, value: -2592000 },
|
||||||
|
{ label: i18n.ts.threeMonths, value: -7776000 },
|
||||||
|
{ label: i18n.ts.oneYear, value: -31104000 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const makeNotesHiddenBefore_isCustomMode = ref(
|
||||||
|
makeNotesHiddenBefore.value != null &&
|
||||||
|
makeNotesHiddenBefore.value < 0 &&
|
||||||
|
!makeNotesHiddenBefore_presets.some((preset) => preset.value === makeNotesHiddenBefore.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
const makeNotesHiddenBefore_selection = computed({
|
||||||
|
get: () => makeNotesHiddenBefore_isCustomMode.value ? 'custom' : makeNotesHiddenBefore.value,
|
||||||
|
set(value) {
|
||||||
|
makeNotesHiddenBefore_isCustomMode.value = value === 'custom';
|
||||||
|
if (value !== 'custom') makeNotesHiddenBefore.value = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const makeNotesHiddenBefore_customMonths = computed({
|
||||||
|
get: () => makeNotesHiddenBefore.value ? Math.abs(makeNotesHiddenBefore.value) / (30 * 24 * 60 * 60) : null,
|
||||||
|
set(value) {
|
||||||
|
if (value != null && value > 0) makeNotesHiddenBefore.value = -Math.abs(Math.floor(Number(value))) * 30 * 24 * 60 * 60;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
watch([makeNotesFollowersOnlyBefore, makeNotesHiddenBefore], () => {
|
watch([makeNotesFollowersOnlyBefore, makeNotesHiddenBefore], () => {
|
||||||
save();
|
save();
|
||||||
});
|
});
|
||||||
|
|
|
@ -498,4 +498,7 @@ export const PREF_DEF = definePreferences({
|
||||||
'experimental.enableFolderPageView': {
|
'experimental.enableFolderPageView': {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
'experimental.enableHapticFeedback': {
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { haptic as _haptic } from 'ios-haptics';
|
||||||
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
|
export function haptic() {
|
||||||
|
if (prefer.s['experimental.enableHapticFeedback']) {
|
||||||
|
_haptic();
|
||||||
|
}
|
||||||
|
}
|
|
@ -320,7 +320,7 @@ describe('AiScript UI API', () => {
|
||||||
const { root, get, outputs } = await exe(`
|
const { root, get, outputs } = await exe(`
|
||||||
let text_input = Ui:C:textInput({
|
let text_input = Ui:C:textInput({
|
||||||
onInput: print
|
onInput: print
|
||||||
"default": 'a'
|
default: 'a'
|
||||||
label: 'b'
|
label: 'b'
|
||||||
caption: 'c'
|
caption: 'c'
|
||||||
}, 'id')
|
}, 'id')
|
||||||
|
@ -361,7 +361,7 @@ describe('AiScript UI API', () => {
|
||||||
const { root, get, outputs } = await exe(`
|
const { root, get, outputs } = await exe(`
|
||||||
let textarea = Ui:C:textarea({
|
let textarea = Ui:C:textarea({
|
||||||
onInput: print
|
onInput: print
|
||||||
"default": 'a'
|
default: 'a'
|
||||||
label: 'b'
|
label: 'b'
|
||||||
caption: 'c'
|
caption: 'c'
|
||||||
}, 'id')
|
}, 'id')
|
||||||
|
@ -402,7 +402,7 @@ describe('AiScript UI API', () => {
|
||||||
const { root, get, outputs } = await exe(`
|
const { root, get, outputs } = await exe(`
|
||||||
let number_input = Ui:C:numberInput({
|
let number_input = Ui:C:numberInput({
|
||||||
onInput: print
|
onInput: print
|
||||||
"default": 1
|
default: 1
|
||||||
label: 'a'
|
label: 'a'
|
||||||
caption: 'b'
|
caption: 'b'
|
||||||
}, 'id')
|
}, 'id')
|
||||||
|
@ -564,7 +564,7 @@ describe('AiScript UI API', () => {
|
||||||
const { root, get, outputs } = await exe(`
|
const { root, get, outputs } = await exe(`
|
||||||
let switch = Ui:C:switch({
|
let switch = Ui:C:switch({
|
||||||
onChange: print
|
onChange: print
|
||||||
"default": false
|
default: false
|
||||||
label: 'a'
|
label: 'a'
|
||||||
caption: 'b'
|
caption: 'b'
|
||||||
}, 'id')
|
}, 'id')
|
||||||
|
@ -609,7 +609,7 @@ describe('AiScript UI API', () => {
|
||||||
{ text: 'B', value: 'b' }
|
{ text: 'B', value: 'b' }
|
||||||
]
|
]
|
||||||
onChange: print
|
onChange: print
|
||||||
"default": 'a'
|
default: 'a'
|
||||||
label: 'c'
|
label: 'c'
|
||||||
caption: 'd'
|
caption: 'd'
|
||||||
}, 'id')
|
}, 'id')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2025.8.0-alpha.11",
|
"version": "2025.8.0-beta.0",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
|
|
@ -731,8 +731,8 @@ importers:
|
||||||
specifier: 10.0.0
|
specifier: 10.0.0
|
||||||
version: 10.0.0(vue@3.5.18(typescript@5.9.2))
|
version: 10.0.0(vue@3.5.18(typescript@5.9.2))
|
||||||
'@syuilo/aiscript':
|
'@syuilo/aiscript':
|
||||||
specifier: 1.0.0
|
specifier: 1.1.0
|
||||||
version: 1.0.0
|
version: 1.1.0
|
||||||
'@syuilo/aiscript-0-19-0':
|
'@syuilo/aiscript-0-19-0':
|
||||||
specifier: npm:@syuilo/aiscript@^0.19.0
|
specifier: npm:@syuilo/aiscript@^0.19.0
|
||||||
version: '@syuilo/aiscript@0.19.0'
|
version: '@syuilo/aiscript@0.19.0'
|
||||||
|
@ -811,6 +811,9 @@ importers:
|
||||||
insert-text-at-cursor:
|
insert-text-at-cursor:
|
||||||
specifier: 0.3.0
|
specifier: 0.3.0
|
||||||
version: 0.3.0
|
version: 0.3.0
|
||||||
|
ios-haptics:
|
||||||
|
specifier: 0.1.0
|
||||||
|
version: 0.1.0
|
||||||
is-file-animated:
|
is-file-animated:
|
||||||
specifier: 1.0.2
|
specifier: 1.0.2
|
||||||
version: 1.0.2
|
version: 1.0.2
|
||||||
|
@ -4193,8 +4196,8 @@ packages:
|
||||||
'@syuilo/aiscript@0.19.0':
|
'@syuilo/aiscript@0.19.0':
|
||||||
resolution: {integrity: sha512-ZWG4s1m6RrFjE7NeIMaxFz769YO1jW5ReTrOROrEO4IHheOrjxxJ/Ffe2TUNqX9/XxDloMwfWplKhfSzx8LGMA==}
|
resolution: {integrity: sha512-ZWG4s1m6RrFjE7NeIMaxFz769YO1jW5ReTrOROrEO4IHheOrjxxJ/Ffe2TUNqX9/XxDloMwfWplKhfSzx8LGMA==}
|
||||||
|
|
||||||
'@syuilo/aiscript@1.0.0':
|
'@syuilo/aiscript@1.1.0':
|
||||||
resolution: {integrity: sha512-m+Dxx0g2pDI198OCj/OJgiJnE4ajlbOFAMyh84FmbY1S8ss/MHytxY82dCnMZj5WVt7VE7a1rtW7biuRRfuyaA==}
|
resolution: {integrity: sha512-3S6+tWC6f8WD8nnCgXSkqzPlEL9iKH9cfjERrk3OEuVQy+qP3wSLRszjy9FEHeYtvg90erUCGhTuYUy4XCNnmg==}
|
||||||
|
|
||||||
'@szmarczak/http-timer@5.0.1':
|
'@szmarczak/http-timer@5.0.1':
|
||||||
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
|
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
|
||||||
|
@ -7179,6 +7182,9 @@ packages:
|
||||||
resolution: {integrity: sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==}
|
resolution: {integrity: sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==}
|
||||||
engines: {node: '>=12.22.0'}
|
engines: {node: '>=12.22.0'}
|
||||||
|
|
||||||
|
ios-haptics@0.1.0:
|
||||||
|
resolution: {integrity: sha512-Fk0RApBYJeZNZ9pW3Wx3WcunhdLlpEnVNy/BOn85tx39eZDOHLGhXEb7medoIURGBUjXatOZf5Ozy0+OG466YA==}
|
||||||
|
|
||||||
ip-address@9.0.5:
|
ip-address@9.0.5:
|
||||||
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
|
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
|
@ -14450,7 +14456,7 @@ snapshots:
|
||||||
stringz: 2.1.0
|
stringz: 2.1.0
|
||||||
uuid: 9.0.1
|
uuid: 9.0.1
|
||||||
|
|
||||||
'@syuilo/aiscript@1.0.0':
|
'@syuilo/aiscript@1.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
seedrandom: 3.0.5
|
seedrandom: 3.0.5
|
||||||
stringz: 2.1.0
|
stringz: 2.1.0
|
||||||
|
@ -18221,6 +18227,8 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
ios-haptics@0.1.0: {}
|
||||||
|
|
||||||
ip-address@9.0.5:
|
ip-address@9.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
jsbn: 1.1.0
|
jsbn: 1.1.0
|
||||||
|
|
Loading…
Reference in New Issue