Compare commits

..
Author SHA1 Message Date
copilot-swe-agent[bot]andsyuilo 263d476ffe Fix iOS touch position offset in image masking
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-10-25 06:22:35 +00:00
copilot-swe-agent[bot] 3ab64ac688 Initial plan 2025-10-25 06:17:02 +00:00
48 changed files with 930 additions and 985 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "24.10.0"
"version": "22.15.0"
},
"ghcr.io/devcontainers-extra/features/pnpm:2": {
"version": "10.10.0"
@@ -11,7 +11,6 @@ jobs:
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Reply
uses: actions/github-script@v6
+1 -1
View File
@@ -1 +1 @@
24.10.0
22.15.0
+6 -16
View File
@@ -1,24 +1,14 @@
## 2025.11.0
## Unreleased
### General
- Enhance: Node.js 24.10.0をサポートするようになりました
- Enhance: DockerのNode.jsが24.10.0に更新されました
### Client
- Enhance: 管理しているチャンネルの見分けがつきやすくなるように
- Enhance: プッシュ通知を行うための権限確認をより確実に行うように
- Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正
- Fix: ナビゲーションバーのリアルタイムモード切替ボタンの状態をよりわかりやすく表示するように
- Fix: ページのタイトルが長いとき、はみ出る問題を修正
### Server
- Enhance: Remote Notes Cleaningが複雑度が高いノートの処理を中断せずに次のノートから再開するように
## 2025.10.2
-
### Client
- Fix: アプリ内からキャッシュをクリアするとテーマ再適用するまでレンダリングが正しく行われない問題を修正
- Fix: 期限が無期限のアンケートに投票できない問題を修正
### Server
-
## 2025.10.1
+1 -1
View File
@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.4
ARG NODE_VERSION=24.10.0-bookworm
ARG NODE_VERSION=22.15.0-bookworm
# build assets & compile TypeScript
+1 -37
View File
@@ -747,7 +747,7 @@ export interface Locale extends ILocale {
*/
"flagShowTimelineRepliesDescription": string;
/**
* フォロー中ユーザーからのフォロー申請を自動承認
* フォロー中ユーザーからのフォロリクを自動承認
*/
"autoAcceptFollowed": string;
/**
@@ -1226,10 +1226,6 @@ export interface Locale extends ILocale {
* 既読
*/
"messageRead": string;
/**
* すべてのメッセージを既読にする
*/
"readAllChatMessages": string;
/**
* これより過去の履歴はありません
*/
@@ -4110,18 +4106,6 @@ export interface Locale extends ILocale {
* 端末の電池消費量が増加する可能性があります。
*/
"sendPushNotificationReadMessageCaption": string;
/**
* ブラウザの通知設定を許可してください
*/
"pleaseAllowPushNotification": string;
/**
* 通知の送信権限の取得に失敗しました
*/
"browserPushNotificationDisabled": string;
/**
* {serverName}から通知を送信する権限がありません。ブラウザの設定から通知を許可して再度お試しください。
*/
"browserPushNotificationDisabledDescription": ParameterizedString<"serverName">;
/**
* 最大化
*/
@@ -5621,10 +5605,6 @@ export interface Locale extends ILocale {
* 技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。
*/
"deviceInfoDescription": string;
/**
* あなたは管理者です
*/
"youAreAdmin": string;
"_compression": {
"_quality": {
/**
@@ -6017,10 +5997,6 @@ export interface Locale extends ILocale {
* ページのタブバーを下部に表示
*/
"showPageTabBarBottom": string;
/**
* 絵文字ピッカーに固定表示するプリセットをパレットとして登録したり、ピッカーの表示方法をカスタマイズしたりできます。
*/
"emojiPaletteBanner": string;
"_chat": {
/**
* 送信者の名前を表示
@@ -6083,10 +6059,6 @@ export interface Locale extends ILocale {
* 設定のバックアップが見つかりました
*/
"backupFound": string;
/**
* 設定の強制バックアップ
*/
"forceBackup": string;
};
"_accountSettings": {
/**
@@ -10918,14 +10890,6 @@ export interface Locale extends ILocale {
};
};
"_moderationLogTypes": {
/**
* ジョブキューをクリア
*/
"clearQueue": string;
/**
* キューのジョブを再試行
*/
"promoteQueue": string;
/**
* ロールを作成
*/
+1 -10
View File
@@ -182,7 +182,7 @@ flagAsCat: "にゃああああああああああああああ!!!!!!
flagAsCatDescription: "にゃにゃにゃ??"
flagShowTimelineReplies: "タイムラインにノートへの返信を表示する"
flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。"
autoAcceptFollowed: "フォロー中ユーザーからのフォロー申請を自動承認"
autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認"
addAccount: "アカウントを追加"
reloadAccountsList: "アカウントリストの情報を更新"
loginFailed: "ログインに失敗しました"
@@ -302,7 +302,6 @@ uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がか
uploadNFiles: "{n}個のファイルをアップロード"
explore: "みつける"
messageRead: "既読"
readAllChatMessages: "すべてのメッセージを既読にする"
noMoreHistory: "これより過去の履歴はありません"
startChat: "メッセージを送る"
nUsersRead: "{n}人が読みました"
@@ -1023,9 +1022,6 @@ pushNotificationAlreadySubscribed: "プッシュ通知は有効です"
pushNotificationNotSupported: "ブラウザかサーバーがプッシュ通知に非対応"
sendPushNotificationReadMessage: "通知が既読になったらプッシュ通知を削除する"
sendPushNotificationReadMessageCaption: "端末の電池消費量が増加する可能性があります。"
pleaseAllowPushNotification: "ブラウザの通知設定を許可してください"
browserPushNotificationDisabled: "通知の送信権限の取得に失敗しました"
browserPushNotificationDisabledDescription: "{serverName}から通知を送信する権限がありません。ブラウザの設定から通知を許可して再度お試しください。"
windowMaximize: "最大化"
windowMinimize: "最小化"
windowRestore: "元に戻す"
@@ -1400,7 +1396,6 @@ scheduled: "予約"
widgets: "ウィジェット"
deviceInfo: "デバイス情報"
deviceInfoDescription: "技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。"
youAreAdmin: "あなたは管理者です"
_compression:
_quality:
@@ -1508,7 +1503,6 @@ _settings:
showUrlPreview: "URLプレビューを表示する"
showAvailableReactionsFirstInNote: "利用できるリアクションを先頭に表示"
showPageTabBarBottom: "ページのタブバーを下部に表示"
emojiPaletteBanner: "絵文字ピッカーに固定表示するプリセットをパレットとして登録したり、ピッカーの表示方法をカスタマイズしたりできます。"
_chat:
showSenderName: "送信者の名前を表示"
@@ -1529,7 +1523,6 @@ _preferencesBackup:
youNeedToNameYourProfileToEnableAutoBackup: "自動バックアップを有効にするにはプロファイル名の設定が必要です。"
autoPreferencesBackupIsNotEnabledForThisDevice: "このデバイスで設定の自動バックアップは有効になっていません。"
backupFound: "設定のバックアップが見つかりました"
forceBackup: "設定の強制バックアップ"
_accountSettings:
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
@@ -2893,8 +2886,6 @@ _abuseReport:
deleteConfirm: "通知先を削除しますか?"
_moderationLogTypes:
clearQueue: "ジョブキューをクリア"
promoteQueue: "キューのジョブを再試行"
createRole: "ロールを作成"
deleteRole: "ロールを削除"
updateRole: "ロールを更新"
+3 -6
View File
@@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2025.11.0-alpha.0",
"version": "2025.10.1",
"codename": "nasubi",
"repository": {
"type": "git",
@@ -68,7 +68,7 @@
"devDependencies": {
"@misskey-dev/eslint-plugin": "2.1.0",
"@types/js-yaml": "4.0.9",
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@typescript-eslint/eslint-plugin": "8.46.1",
"@typescript-eslint/parser": "8.46.1",
"cross-env": "10.1.0",
@@ -85,9 +85,6 @@
"pnpm": {
"overrides": {
"@aiscript-dev/aiscript-languageserver": "-"
},
"ignoredBuiltDependencies": [
"@sentry-internal/node-cpu-profiler"
]
}
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ const __dirname = path.dirname(__filename);
const args = [];
args.push(...[
...semver.satisfies(process.version, '^20.17.0 || ^22.0.0 || ^24.10.0') ? ['--no-experimental-require-module'] : [],
...semver.satisfies(process.version, '^20.17.0 || ^22.0.0') ? ['--no-experimental-require-module'] : [],
'--experimental-vm-modules',
'--experimental-import-meta-resolve',
path.join(__dirname, 'node_modules/jest/bin/jest.js'),
+6 -6
View File
@@ -4,7 +4,7 @@
"private": true,
"type": "module",
"engines": {
"node": "^22.15.0 || ^24.10.0"
"node": "^22.15.0"
},
"scripts": {
"start": "node ./built/boot/entry.js",
@@ -87,8 +87,8 @@
"@nestjs/core": "11.1.6",
"@nestjs/testing": "11.1.6",
"@peertube/http-signature": "1.7.0",
"@sentry/node": "10.20.0",
"@sentry/profiling-node": "10.20.0",
"@sentry/node": "8.55.0",
"@sentry/profiling-node": "8.55.0",
"@simplewebauthn/server": "12.0.0",
"@sinonjs/fake-timers": "11.3.1",
"@smithy/node-http-handler": "2.5.0",
@@ -117,7 +117,7 @@
"fastify": "5.6.1",
"fastify-raw-body": "5.0.0",
"feed": "4.2.2",
"file-type": "21.0.0",
"file-type": "19.6.0",
"fluent-ffmpeg": "2.1.3",
"form-data": "4.0.4",
"got": "14.5.0",
@@ -191,7 +191,7 @@
"devDependencies": {
"@jest/globals": "29.7.0",
"@nestjs/platform-express": "10.4.20",
"@sentry/vue": "10.20.0",
"@sentry/vue": "9.46.0",
"@simplewebauthn/types": "12.0.0",
"@swc/jest": "0.2.39",
"@types/accepts": "1.3.7",
@@ -210,7 +210,7 @@
"@types/jsrsasign": "10.5.15",
"@types/mime-types": "2.1.4",
"@types/ms": "0.7.34",
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@types/nodemailer": "6.4.20",
"@types/oauth": "0.9.6",
"@types/oauth2orize": "1.11.5",
+2 -2
View File
@@ -20,8 +20,8 @@ import { AiService } from '@/core/AiService.js';
import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { isMimeImage } from '@/misc/is-mime-image.js';
import type { PredictionType } from 'nsfwjs';
import { isMimeImage } from '@/misc/is-mime-image.js';
export type FileInfo = {
size: number;
@@ -339,7 +339,7 @@ export class FileInfoService {
}
@bindThis
public fixMime(mime: string): string {
public fixMime(mime: string | fileType.MimeType): string {
// see https://github.com/misskey-dev/misskey/pull/10686
if (mime === 'audio/x-flac') {
return 'audio/flac';
@@ -51,17 +51,6 @@ export class CleanRemoteNotesProcessorService {
skipped: boolean;
transientErrors: number;
}> {
const getConfig = () => {
return {
enabled: this.meta.enableRemoteNotesCleaning,
maxDuration: this.meta.remoteNotesCleaningMaxProcessingDurationInMinutes * 60 * 1000, // Convert minutes to milliseconds
// The date limit for the newest note to be considered for deletion.
// All notes newer than this limit will always be retained.
newestLimit: this.idService.gen(Date.now() - (1000 * 60 * 60 * 24 * this.meta.remoteNotesCleaningExpiryDaysForEachNotes)),
};
};
const initialConfig = getConfig();
if (!this.meta.enableRemoteNotesCleaning) {
this.logger.info('Remote notes cleaning is disabled, skipping...');
return {
@@ -75,9 +64,13 @@ export class CleanRemoteNotesProcessorService {
this.logger.info('cleaning remote notes...');
const maxDuration = this.meta.remoteNotesCleaningMaxProcessingDurationInMinutes * 60 * 1000; // Convert minutes to milliseconds
const startAt = Date.now();
//#region queries
// The date limit for the newest note to be considered for deletion.
// All notes newer than this limit will always be retained.
const newestLimit = this.idService.gen(Date.now() - (1000 * 60 * 60 * 24 * this.meta.remoteNotesCleaningExpiryDaysForEachNotes));
// The condition for removing the notes.
// The note must be:
@@ -99,7 +92,7 @@ export class CleanRemoteNotesProcessorService {
const minId = (await this.notesRepository.createQueryBuilder('note')
.select('MIN(note.id)', 'minId')
.where({
id: LessThan(initialConfig.newestLimit),
id: LessThan(newestLimit),
userHost: Not(IsNull()),
replyId: IsNull(),
renoteId: IsNull(),
@@ -162,12 +155,12 @@ export class CleanRemoteNotesProcessorService {
// | fff | fff | TRUE |
// | ggg | ggg | FALSE |
//
const candidateNotesQuery = ({ limit }: { limit: number }) => this.db.createQueryBuilder()
const candidateNotesQuery = this.db.createQueryBuilder()
.select(`"${candidateNotesCteName}"."id"`, 'id')
.addSelect('unremovable."id" IS NULL', 'isRemovable')
.addSelect(`BOOL_OR("${candidateNotesCteName}"."isBase")`, 'isBase')
.addCommonTableExpression(
`((SELECT "base".* FROM (${candidateNotesQueryBase.orderBy('note.id', 'ASC').limit(limit).getQuery()}) AS "base") UNION ${candidateNotesQueryInductive.getQuery()})`,
`((SELECT "base".* FROM (${candidateNotesQueryBase.orderBy('note.id', 'ASC').limit(currentLimit).getQuery()}) AS "base") UNION ${candidateNotesQueryInductive.getQuery()})`,
candidateNotesCteName,
{ recursive: true },
)
@@ -185,11 +178,6 @@ export class CleanRemoteNotesProcessorService {
let lowThroughputWarned = false;
let transientErrors = 0;
for (;;) {
const { enabled, maxDuration, newestLimit } = getConfig();
if (!enabled) {
this.logger.info('Remote notes cleaning is disabled, processing stopped...');
break;
}
//#region check time
const batchBeginAt = Date.now();
@@ -217,38 +205,13 @@ export class CleanRemoteNotesProcessorService {
let noteIds = null;
try {
noteIds = await candidateNotesQuery({ limit: currentLimit }).setParameters(
noteIds = await candidateNotesQuery.setParameters(
{ newestLimit, cursorLeft },
).getRawMany<{ id: MiNote['id'], isRemovable: boolean, isBase: boolean }>();
} catch (e) {
if (e instanceof QueryFailedError && e.driverError?.code === '57014') {
// Statement timeout (maybe suddenly hit a large note tree), if possible, reduce the limit and try again
// if not possible, skip the current batch of notes and find the next root note
if (currentLimit <= minimumLimit) {
job.log('Local note tree complexity is too high, finding next root note...');
const idWindow = await this.notesRepository.createQueryBuilder('note')
.select('id')
.where('note.id > :cursorLeft')
.andWhere(removalCriteria)
.andWhere({ replyId: IsNull(), renoteId: IsNull() })
.orderBy('note.id', 'ASC')
.limit(minimumLimit + 1)
.setParameters({ cursorLeft, newestLimit })
.getRawMany<{ id?: MiNote['id'] }>();
job.log(`Skipped note IDs: ${idWindow.slice(0, minimumLimit).map(id => id.id).join(', ')}`);
const lastId = idWindow.at(minimumLimit)?.id;
if (!lastId) {
job.log('No more notes to clean.');
break;
}
cursorLeft = lastId;
continue;
}
if (currentLimit > minimumLimit && e instanceof QueryFailedError && e.driverError?.code === '57014') {
// Statement timeout (maybe suddenly hit a large note tree), reduce the limit and try again
// continuous failures will eventually converge to currentLimit == minimumLimit and then throw
currentLimit = Math.max(minimumLimit, Math.floor(currentLimit * 0.25));
continue;
}
+2 -6
View File
@@ -316,12 +316,8 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO
: new URL(path, new URL('resources/', import.meta.url));
const formData = new FormData();
formData.append(
'file',
blob ?? new Blob([new Uint8Array(await readFile(absPath))]),
basename(absPath.toString()),
);
formData.append('file', blob ??
new File([new Uint8Array(await readFile(absPath))], basename(absPath.toString())));
formData.append('force', 'true');
if (name) {
formData.append('name', name);
+1 -1
View File
@@ -11,7 +11,7 @@
},
"devDependencies": {
"@types/estree": "1.0.8",
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@typescript-eslint/eslint-plugin": "8.46.1",
"@typescript-eslint/parser": "8.46.1",
"rollup": "4.52.4",
+1 -1
View File
@@ -43,7 +43,7 @@
"@testing-library/vue": "8.1.0",
"@types/estree": "1.0.8",
"@types/micromatch": "4.0.9",
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1",
-1
View File
@@ -20,4 +20,3 @@ export const instanceName = (siteName === 'Misskey' || siteName == null) ? host
export const ui = localStorage.getItem('ui');
export const debug = localStorage.getItem('debug') === 'true';
export const isSafeMode = localStorage.getItem('isSafeMode') === 'true';
export const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion)').matches;
+1 -1
View File
@@ -21,7 +21,7 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"devDependencies": {
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@typescript-eslint/eslint-plugin": "8.46.1",
"@typescript-eslint/parser": "8.46.1",
"esbuild": "0.25.10",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

+2 -2
View File
@@ -24,7 +24,7 @@
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.2",
"@rollup/pluginutils": "5.3.0",
"@sentry/vue": "10.20.0",
"@sentry/vue": "10.19.0",
"@syuilo/aiscript": "1.1.2",
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
"@twemoji/parser": "16.0.0",
@@ -109,7 +109,7 @@
"@types/estree": "1.0.8",
"@types/matter-js": "0.20.2",
"@types/micromatch": "4.0.9",
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/sanitize-html": "2.16.0",
"@types/seedrandom": "3.0.8",
+7
View File
@@ -303,6 +303,13 @@ export async function mainBoot() {
});
}
if ('Notification' in window) {
// 許可を得ていなかったらリクエスト
if (Notification.permission === 'default') {
Notification.requestPermission();
}
}
if (store.s.realtimeMode) {
const stream = useStream();
@@ -27,10 +27,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</I18n>
</div>
<div v-if="$i != null && $i.id === channel.userId" style="color: var(--MI_THEME-warn)">
<i class="ti ti-user-star ti-fw"></i>
<span style="margin-left: 4px;">{{ i18n.ts.youAreAdmin }}</span>
</div>
</div>
</div>
<article v-if="channel.description">
@@ -52,7 +48,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
+1 -11
View File
@@ -60,9 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
@drop.prevent.stop="onDrop"
@contextmenu.stop="onContextmenu"
>
<div :class="$style.tipContainer">
<MkTip k="drive"><div v-html="i18n.ts.driveAboutTip"></div></MkTip>
</div>
<MkTip k="drive"><div v-html="i18n.ts.driveAboutTip"></div></MkTip>
<div :class="$style.folders">
<XFolder
@@ -804,10 +802,6 @@ onBeforeUnmount(() => {
}
}
.tipContainer:not(:empty) {
padding: 16px 32px;
}
.folders,
.files {
display: grid;
@@ -817,10 +811,6 @@ onBeforeUnmount(() => {
}
@container (max-width: 600px) {
.tipContainer:not(:empty) {
padding: 16px;
}
.folders,
.files {
padding: 16px;
@@ -539,44 +539,46 @@ defineExpose({
}
&.w1 {
--columns: 5;
width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
--columns: 1fr 1fr 1fr 1fr 1fr;
}
&.w2 {
--columns: 6;
width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
--columns: 1fr 1fr 1fr 1fr 1fr 1fr;
}
&.w3 {
--columns: 7;
width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
}
&.w4 {
--columns: 8;
width: calc((var(--eachSize) * 8) + (#{$pad} * 2));
--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
}
&.w5 {
--columns: 9;
width: calc((var(--eachSize) * 9) + (#{$pad} * 2));
--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
}
&.h1 {
--rows: 4;
height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
}
&.h2 {
--rows: 6;
height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
}
&.h3 {
--rows: 8;
height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
}
&.h4 {
--rows: 10;
height: calc((var(--eachSize) * 10) + (#{$pad} * 2));
}
width: calc((var(--eachSize) * var(--columns)) + (#{$pad} * 2));
height: calc((var(--eachSize) * var(--rows)) + (#{$pad} * 2));
&.asDrawer {
width: 100% !important;
@@ -591,7 +593,7 @@ defineExpose({
> .body {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
grid-template-columns: var(--columns);
font-size: 30px;
> .config {
@@ -633,7 +635,7 @@ defineExpose({
::v-deep(section) {
> .body {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
grid-template-columns: var(--columns);
font-size: 30px;
> .item {
@@ -242,31 +242,27 @@ function onImagePointerdown(ev: PointerEvent) {
const AW = canvasEl.value.clientWidth;
const AH = canvasEl.value.clientHeight;
const BW = imageBitmap.width;
const BH = imageBitmap.height;
const BW = canvasEl.value.width; // Canvas intrinsic width (render width)
const BH = canvasEl.value.height; // Canvas intrinsic height (render height)
let xOffset = 0;
let yOffset = 0;
// Calculate the scale factor for object-fit: contain
const scale = Math.min(AW / BW, AH / BH);
// Calculate the actual rendered size of the canvas content
const renderW = BW * scale;
const renderH = BH * scale;
// Calculate the offset to center the image
const xOffset = (AW - renderW) / 2;
const yOffset = (AH - renderH) / 2;
if (AW / AH < BW / BH) { // 横長
yOffset = AH - BH * (AW / BW);
} else { // 縦長
xOffset = AW - BW * (AH / BH);
}
const rect = canvasEl.value.getBoundingClientRect();
const pointerOffsetX = ev.clientX - rect.left;
const pointerOffsetY = ev.clientY - rect.top;
xOffset /= 2;
yOffset /= 2;
let startX = ev.offsetX - xOffset;
let startY = ev.offsetY - yOffset;
if (AW / AH < BW / BH) { // 横長
startX = startX / (Math.max(AW, AH) / Math.max(BH / BW, 1));
startY = startY / (Math.max(AW, AH) / Math.max(BW / BH, 1));
} else { // 縦長
startX = startX / (Math.min(AW, AH) / Math.max(BH / BW, 1));
startY = startY / (Math.min(AW, AH) / Math.max(BW / BH, 1));
}
// Convert from client coordinates to canvas coordinates
let startX = (pointerOffsetX - xOffset) / scale;
let startY = (pointerOffsetY - yOffset) / scale;
const id = genId();
if (penMode.value === 'fill') {
@@ -311,19 +307,15 @@ function onImagePointerdown(ev: PointerEvent) {
});
}
_move(ev.offsetX, ev.offsetY);
_move(ev.clientX, ev.clientY);
function _move(pointerX: number, pointerY: number) {
let x = pointerX - xOffset;
let y = pointerY - yOffset;
if (AW / AH < BW / BH) { // 横長
x = x / (Math.max(AW, AH) / Math.max(BH / BW, 1));
y = y / (Math.max(AW, AH) / Math.max(BW / BH, 1));
} else { // 縦長
x = x / (Math.min(AW, AH) / Math.max(BH / BW, 1));
y = y / (Math.min(AW, AH) / Math.max(BW / BH, 1));
}
function _move(pointerClientX: number, pointerClientY: number) {
const pointerX = pointerClientX - rect.left;
const pointerY = pointerClientY - rect.top;
// Convert from client coordinates to canvas coordinates
let x = (pointerX - xOffset) / scale;
let y = (pointerY - yOffset) / scale;
const scaleX = Math.abs(x - startX);
const scaleY = Math.abs(y - startY);
@@ -340,7 +332,7 @@ function onImagePointerdown(ev: PointerEvent) {
}
function move(ev: PointerEvent) {
_move(ev.offsetX, ev.offsetY);
_move(ev.clientX, ev.clientY);
}
function up() {
+1 -1
View File
@@ -58,7 +58,7 @@ const remaining = computed(() => {
});
const total = computed(() => sum(props.choices.map(x => x.votes)));
const closed = computed(() => props.expiresAt != null && remaining.value <= 0);
const closed = computed(() => remaining.value <= 0);
const isVoted = computed(() => !props.multiple && props.choices.some(c => c.isVoted));
const timer = computed(() => i18n.tsx._poll[
remaining.value >= 86400 ? 'remainingDays' :
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<header :class="$style.header">
<div :class="$style.headerLeft">
<button v-if="!fixed" :class="$style.cancel" class="_button" @click="cancel"><i class="ti ti-x"></i></button>
<button v-click-anime v-tooltip="i18n.ts.account" :class="$style.account" class="_button" @click="openAccountMenu">
<button v-click-anime v-tooltip="i18n.ts.switchAccount" :class="$style.account" class="_button" @click="openAccountMenu">
<img :class="$style.avatar" :src="(postAccount ?? $i).avatarUrl" style="border-radius: 100%;"/>
</button>
</div>
@@ -42,11 +42,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { ref } from 'vue';
import { instanceName } from '@@/js/config.js';
import { $i } from '@/i.js';
import MkButton from '@/components/MkButton.vue';
import { instance } from '@/instance.js';
import { apiWithDialog, promiseDialog, alert } from '@/os.js';
import { apiWithDialog, promiseDialog } from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { getAccounts } from '@/accounts.js';
@@ -73,28 +72,11 @@ const supported = ref(false);
const pushSubscription = ref<PushSubscription | null>(null);
const pushRegistrationInServer = ref<{ state?: string; key?: string; userId: string; endpoint: string; sendReadMessage: boolean; } | undefined>();
async function subscribe() {
function subscribe() {
if (!registration.value || !supported.value || !instance.swPublickey) return;
if ('Notification' in window) {
let permission = Notification.permission;
if (Notification.permission === 'default') {
permission = await promiseDialog(Notification.requestPermission(), null, null, i18n.ts.pleaseAllowPushNotification);
}
if (permission !== 'granted') {
alert({
type: 'error',
title: i18n.ts.browserPushNotificationDisabled,
text: i18n.tsx.browserPushNotificationDisabledDescription({ serverName: instanceName }),
});
return;
}
}
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
await promiseDialog(registration.value.pushManager.subscribe({
return promiseDialog(registration.value.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(instance.swPublickey),
})
@@ -47,11 +47,9 @@ defineProps<{
<style lang="scss" module>
.root {
display: block;
width: 100%;
&.inline {
display: inline-block;
width: auto;
}
}
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="!thin_ && narrow && props.displayMyAvatar && $i" class="_button" :class="$style.buttonsLeft" @click="openAccountMenu">
<MkAvatar :class="$style.avatar" :user="$i"/>
</div>
<div v-else-if="!thin_ && narrow && !hideTitle" :class="[$style.buttons, $style.buttonsLeft]"></div>
<div v-else-if="!thin_ && narrow && !hideTitle" :class="$style.buttonsLeft"/>
<template v-if="pageMetadata">
<div v-if="!hideTitle" :class="$style.titleContainer" @click="top">
@@ -18,9 +18,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<i v-else-if="pageMetadata.icon" :class="[$style.titleIcon, pageMetadata.icon]"></i>
<div class="_nowrap" :class="$style.title">
<div :class="$style.title">
<MkUserName v-if="pageMetadata.userName" :user="pageMetadata.userName" :nowrap="true"/>
<div v-else-if="pageMetadata.title" class="_nowrap">{{ pageMetadata.title }}</div>
<div v-else-if="pageMetadata.title">{{ pageMetadata.title }}</div>
<div v-if="pageMetadata.subtitle" :class="$style.subtitle">
{{ pageMetadata.subtitle }}
</div>
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<XTabs v-if="!narrow || hideTitle" :class="$style.tabs" :tab="tab" :tabs="tabs" :rootEl="el" @update:tab="key => emit('update:tab', key)" @tabClick="onTabClick"/>
</template>
<div v-if="(!thin_ && narrow && !hideTitle) || (actions && actions.length > 0)" :class="[$style.buttons, $style.buttonsRight]">
<div v-if="(!thin_ && narrow && !hideTitle) || (actions && actions.length > 0)" :class="$style.buttonsRight">
<template v-for="action in actions">
<button v-tooltip.noDelay="action.text" class="_button" :class="[$style.button, { [$style.highlighted]: action.highlighted }]" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
</template>
@@ -155,10 +155,8 @@ onUnmounted(() => {
.upper {
--height: 50px;
--margin: var(--MI-margin);
display: flex;
gap: var(--margin);
align-items: center;
gap: var(--MI-margin);
height: var(--height);
.tabs:first-child {
@@ -171,7 +169,6 @@ onUnmounted(() => {
&.thin {
--height: 40px;
--margin: 8px;
> .buttons {
> .button {
@@ -182,8 +179,12 @@ onUnmounted(() => {
&.slim {
text-align: center;
gap: 0;
.titleContainer {
.tabs:first-child {
margin-left: 0;
}
> .titleContainer {
margin: 0 auto;
max-width: 100%;
}
@@ -196,7 +197,7 @@ onUnmounted(() => {
}
.buttons {
flex-shrink: 0;
--margin: 8px;
display: flex;
align-items: center;
min-width: var(--height);
@@ -206,6 +207,16 @@ onUnmounted(() => {
}
}
.buttonsLeft {
composes: buttons;
margin: 0 var(--margin) 0 0;
}
.buttonsRight {
composes: buttons;
margin: 0 0 0 var(--margin);
}
.avatar {
$size: 32px;
display: inline-block;
@@ -220,7 +231,7 @@ onUnmounted(() => {
align-items: center;
justify-content: center;
height: var(--height);
width: calc(var(--height) - 8px);
width: calc(var(--height) - (var(--margin)));
box-sizing: border-box;
position: relative;
border-radius: 5px;
@@ -243,7 +254,6 @@ onUnmounted(() => {
.titleContainer {
display: flex;
align-items: center;
min-width: 0;
max-width: min(30vw, 400px);
overflow: clip;
white-space: nowrap;
@@ -277,6 +287,9 @@ onUnmounted(() => {
.title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.1;
}
@@ -137,7 +137,6 @@ import FormSplit from '@/components/form/split.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkLink from '@/components/MkLink.vue';
import MkInfo from '@/components/MkInfo.vue';
const initStats = () => misskeyApi('stats', {});
</script>
+1 -2
View File
@@ -6,10 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader>
<div class="_spacer" style="--MI_SPACER-w: 500px;">
<div v-if="instance.ads.length > 0" class="_gaps">
<div class="_gaps">
<MkAd v-for="ad in instance.ads" :key="ad.id" :specify="ad"/>
</div>
<MkResult v-else type="empty"/>
</div>
</PageWithHeader>
</template>
-1
View File
@@ -15,7 +15,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.bannerStatus">
<div><i class="ti ti-users ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
<div><i class="ti ti-pencil ti-fw"></i><I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
<div v-if="$i != null && channel != null && $i.id === channel.userId" style="color: var(--MI_THEME-warn)"><i class="ti ti-user-star ti-fw"></i><span style="margin-left: 4px;">{{ i18n.ts.youAreAdmin }}</span></div>
</div>
<div v-if="channel.isSensitive" :class="$style.sensitiveIndicator">{{ i18n.ts.sensitive }}</div>
<div :class="$style.bannerFade"></div>
+11 -3
View File
@@ -44,8 +44,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkPagination>
</div>
<div v-else-if="tab === 'owned'" class="_gaps">
<MkButton link primary rounded to="/channels/new"><i class="ti ti-plus"></i> {{ i18n.ts.createNew }}</MkButton>
<div v-else-if="tab === 'owned'">
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
<MkPagination v-slot="{items}" :paginator="ownedPaginator">
<div :class="$style.root">
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
@@ -126,7 +126,15 @@ async function search() {
key.value = query + type;
}
const headerActions = computed(() => []);
function create() {
router.push('/channels/new');
}
const headerActions = computed(() => [{
icon: 'ti ti-plus',
text: i18n.ts.create,
handler: create,
}]);
const headerTabs = computed(() => [{
key: 'search',
@@ -5,11 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_spacer" style="--MI_SPACER-w: 700px;">
<div v-if="roles != null && roles.length > 0" class="_gaps_s">
<div class="_gaps_s">
<MkRolePreview v-for="role in roles" :key="role.id" :role="role" :forModeration="false"/>
</div>
<MkLoading v-else-if="loading" />
<MkResult v-else type="empty" :text="i18n.ts.noRole"/>
</div>
</template>
@@ -17,16 +15,12 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkRolePreview from '@/components/MkRolePreview.vue';
import { i18n } from '@/i18n.js';
import { misskeyApi } from '@/utility/misskey-api.js';
const roles = ref<Misskey.entities.Role[] | null>(null);
const loading = ref(true);
misskeyApi('roles/list').then(res => {
roles.value = res.filter(x => x.target === 'manual').sort((a, b) => b.displayOrder - a.displayOrder);
}).finally(() => {
loading.value = false;
});
</script>
@@ -6,10 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<SearchMarker path="/settings/emoji-palette" :label="i18n.ts.emojiPalette" :keywords="['emoji', 'palette']" icon="ti ti-mood-happy">
<div class="_gaps_m">
<MkFeatureBanner icon="/client-assets/artist_palette_3d.png" color="#ff9100">
<SearchText>{{ i18n.ts._settings.emojiPaletteBanner }}</SearchText>
</MkFeatureBanner>
<FormSection first>
<template #label>{{ i18n.ts._emojiPalette.palettes }}</template>
@@ -127,7 +123,6 @@ import { computed, ref, watch } from 'vue';
import XPalette from './emoji-palette.palette.vue';
import type { MkSelectItem } from '@/components/MkSelect.vue';
import { genId } from '@/utility/id.js';
import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue';
@@ -54,9 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #icon><SearchIcon><i class="ti ti-badges"></i></SearchIcon></template>
<template #label><SearchLabel>{{ i18n.ts.rolesAssignedToMe }}</SearchLabel></template>
<div class="_gaps_s">
<MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/>
</div>
<MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/>
</MkFolder>
</SearchMarker>
@@ -136,14 +134,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<hr>
<MkButton @click="forceCloudBackup">Force cloud backup</MkButton>
<hr>
<template v-if="$i.policies.chatAvailability !== 'unavailable'">
<MkButton @click="readAllChatMessages">{{ i18n.ts.readAllChatMessages }}</MkButton>
<MkButton @click="readAllChatMessages">Read all chat messages</MkButton>
<hr>
</template>
<MkButton @click="forceCloudBackup">{{ i18n.ts._preferencesBackup.forceBackup }}</MkButton>
<FormSlot>
<MkButton danger @click="migrate"><i class="ti ti-refresh"></i> {{ i18n.ts.migrateOldSettings }}</MkButton>
<template #caption>{{ i18n.ts.migrateOldSettings_description }}</template>
+3 -4
View File
@@ -5,7 +5,6 @@
import * as Misskey from 'misskey-js';
import { hemisphere } from '@@/js/intl-const.js';
import { prefersReducedMotion } from '@@/js/config.js';
import { definePreferences } from './manager.js';
import type { Theme } from '@/theme.js';
import type { SoundType } from '@/utility/sound.js';
@@ -212,10 +211,10 @@ export const PREF_DEF = definePreferences({
default: false,
},
animation: {
default: !prefersReducedMotion,
default: !window.matchMedia('(prefers-reduced-motion)').matches,
},
animatedMfm: {
default: !prefersReducedMotion,
default: !window.matchMedia('(prefers-reduced-motion)').matches,
},
advancedMfm: {
default: true,
@@ -233,7 +232,7 @@ export const PREF_DEF = definePreferences({
default: false,
},
disableShowingAnimatedImages: {
default: prefersReducedMotion,
default: window.matchMedia('(prefers-reduced-motion)').matches,
},
emojiStyle: {
default: 'twemoji', // twemoji / fluentEmoji / native
+3 -4
View File
@@ -7,7 +7,6 @@ import { markRaw, ref } from 'vue';
import * as Misskey from 'misskey-js';
import lightTheme from '@@/themes/l-light.json5';
import darkTheme from '@@/themes/d-green-lime.json5';
import { prefersReducedMotion } from '@@/js/config.js';
import { hemisphere } from '@@/js/intl-const.js';
import type { DeviceKind } from '@/utility/device-kind.js';
import type { Plugin } from '@/plugin.js';
@@ -221,11 +220,11 @@ export const store = markRaw(new Pizzax('base', {
},
animation: {
where: 'device',
default: !prefersReducedMotion,
default: !window.matchMedia('(prefers-reduced-motion)').matches,
},
animatedMfm: {
where: 'device',
default: !prefersReducedMotion,
default: !window.matchMedia('(prefers-reduced-motion)').matches,
},
advancedMfm: {
where: 'device',
@@ -249,7 +248,7 @@ export const store = markRaw(new Pizzax('base', {
},
disableShowingAnimatedImages: {
where: 'device',
default: prefersReducedMotion,
default: window.matchMedia('(prefers-reduced-motion)').matches,
},
emojiStyle: {
where: 'device',
+2 -4
View File
@@ -11,8 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<img :src="instance.iconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
</button>
<button v-if="!iconOnly" v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode">
<i v-if="store.r.realtimeMode.value" class="ti ti-bolt ti-fw"></i>
<i v-else class="ti ti-bolt-off ti-fw"></i>
<i class="ti ti-bolt ti-fw"></i>
</button>
</div>
<div :class="$style.middle">
@@ -55,8 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<i class="ti ti-apps ti-fw"></i>
</button>
<button v-if="iconOnly" v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode">
<i v-if="store.r.realtimeMode.value" class="ti ti-bolt ti-fw"></i>
<i v-else class="ti ti-bolt-off ti-fw"></i>
<i class="ti ti-bolt ti-fw"></i>
</button>
<button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="() => { os.post(); }">
<i class="ti ti-pencil ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span>
+2 -10
View File
@@ -5,21 +5,13 @@
import _confetti from 'canvas-confetti';
import * as os from '@/os.js';
import { prefer } from '@/preferences.js';
export function confetti(options: { duration?: number; } = {}) {
if (!prefer.s.animation) return;
const duration = options.duration ?? 1000 * 4;
const animationEnd = Date.now() + duration;
const defaults = {
startVelocity: 30,
spread: 360,
ticks: 60,
zIndex: os.claimZIndex('high'),
} satisfies _confetti.Options;
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: os.claimZIndex('high') };
function randomInRange(min: number, max: number) {
function randomInRange(min, max) {
return Math.random() * (max - min) + min;
}
+1 -1
View File
@@ -11,7 +11,7 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"devDependencies": {
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@types/wawoff2": "1.0.2",
"@typescript-eslint/eslint-plugin": "8.46.1",
"@typescript-eslint/parser": "8.46.1"
+3 -5
View File
@@ -7,13 +7,11 @@
"exports": {
".": {
"import": "./built/index.js",
"types": "./built/index.d.ts",
"default": "./built/index.js"
"types": "./built/index.d.ts"
},
"./*": {
"import": "./built/*",
"types": "./built/*",
"default": "./built/*"
"types": "./built/*"
}
},
"scripts": {
@@ -25,7 +23,7 @@
},
"devDependencies": {
"@types/matter-js": "0.20.2",
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@types/seedrandom": "3.0.8",
"@typescript-eslint/eslint-plugin": "8.46.1",
"@typescript-eslint/parser": "8.46.1",
+1 -1
View File
@@ -8,7 +8,7 @@
},
"devDependencies": {
"@readme/openapi-parser": "5.0.2",
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@typescript-eslint/eslint-plugin": "8.46.1",
"@typescript-eslint/parser": "8.46.1",
"openapi-types": "12.1.3",
+4 -6
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
"version": "2025.11.0-alpha.0",
"version": "2025.10.1",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
@@ -9,13 +9,11 @@
"exports": {
".": {
"import": "./built/index.js",
"types": "./built/index.d.ts",
"default": "./built/index.js"
"types": "./built/index.d.ts"
},
"./*": {
"import": "./built/*",
"types": "./built/*",
"default": "./built/*"
"types": "./built/*"
}
},
"scripts": {
@@ -38,7 +36,7 @@
},
"devDependencies": {
"@microsoft/api-extractor": "7.53.1",
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@typescript-eslint/eslint-plugin": "8.46.1",
"@typescript-eslint/parser": "8.46.1",
"@vitest/coverage-v8": "3.2.4",
+3 -5
View File
@@ -7,13 +7,11 @@
"exports": {
".": {
"import": "./built/index.js",
"types": "./built/index.d.ts",
"default": "./built/index.js"
"types": "./built/index.d.ts"
},
"./*": {
"import": "./built/*",
"types": "./built/*",
"default": "./built/*"
"types": "./built/*"
}
},
"scripts": {
@@ -24,7 +22,7 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"devDependencies": {
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@typescript-eslint/eslint-plugin": "8.46.1",
"@typescript-eslint/parser": "8.46.1",
"esbuild": "0.25.10",
+747 -655
View File
File diff suppressed because it is too large Load Diff
+18 -19
View File
@@ -9,14 +9,14 @@
"version": "1.0.0",
"devDependencies": {
"@types/mdast": "4.0.4",
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@vitest/coverage-v8": "3.2.4",
"mdast-util-to-string": "4.0.0",
"remark": "15.0.1",
"remark-parse": "11.0.0",
"typescript": "5.9.3",
"unified": "11.0.5",
"vite": "6.4.1",
"vite": "6.3.6",
"vite-node": "3.2.4",
"vitest": "3.2.4"
}
@@ -940,14 +940,14 @@
"dev": true
},
"node_modules/@types/node": {
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
"version": "22.18.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz",
"integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.16.0"
"undici-types": "~6.21.0"
}
},
"node_modules/@types/unist": {
@@ -1171,9 +1171,9 @@
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1247,11 +1247,10 @@
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -2729,9 +2728,9 @@
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
@@ -2840,9 +2839,9 @@
}
},
"node_modules/vite": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
"dev": true,
"license": "MIT",
"peer": true,
+2 -2
View File
@@ -10,14 +10,14 @@
},
"devDependencies": {
"@types/mdast": "4.0.4",
"@types/node": "24.9.1",
"@types/node": "22.18.10",
"@vitest/coverage-v8": "3.2.4",
"mdast-util-to-string": "4.0.0",
"remark": "15.0.1",
"remark-parse": "11.0.0",
"typescript": "5.9.3",
"unified": "11.0.5",
"vite": "6.4.1",
"vite": "6.3.6",
"vite-node": "3.2.4",
"vitest": "3.2.4"
}