Compare commits

...

18 Commits

Author SHA1 Message Date
tamaina e2044099f2
Merge 4c21f88209 into 218070eb13 2025-09-25 20:33:22 +09:00
かっこかり 218070eb13
fix(frontend): ビルド成果物のファイル名にlocalesのhashを含めるように (#16580) 2025-09-24 17:01:48 +09:00
syuilo 0f8c068e84
feat(frontend): Video compression (#16574)
* wip

* Update CHANGELOG.md

* wip

* wip

* wip

* wip

* Update use-uploader.ts

* Update use-uploader.ts
2025-09-24 09:01:06 +09:00
github-actions[bot] 69d66b89f2 Bump version to 2025.9.1-alpha.1 2025-09-22 10:52:25 +00:00
饺子w (Yumechi) 211365de64
enhance(backend): 設定ファイルにFastifyOptions.trustProxyを追加 (#16567)
* enhance(backend): 設定ファイルにFastifyOptions.trustProxyを追加

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* try harder

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

---------

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2025-09-22 19:45:01 +09:00
syuilo 966127c63e enhance(frontend): 絵文字ピッカーのサイズをより大きくできるように 2025-09-22 19:43:31 +09:00
果物リン 54800971eb
プロフィールの「ユーザーのノートを検索」でローカルのユーザーを検索できないのを修正 (#16570)
* プロフィールの「ユーザーのノートを検索」でローカルのユーザーを検索できないのを修正

* fix lint
2025-09-22 19:21:01 +09:00
syuilo 13d5c6d2b2 refactor MkAnimBg 2025-09-22 19:00:47 +09:00
syuilo 2cff00eedd update pnpm 2025-09-22 18:27:39 +09:00
syuilo 3fc2261041 dev(pnpm): set minimumReleaseAge to 7days
to mitigate supply-chain attack
Resolve #16572
2025-09-22 18:23:43 +09:00
tamaina 4c21f88209 optimise 2025-09-04 11:30:23 +09:00
tamaina b398347744 log range request 2025-09-04 02:47:13 +09:00
tamaina 507c07dc0d remove 2025-09-04 02:31:36 +09:00
tamaina 1d19237777 ✌️ 2025-09-04 01:58:30 +09:00
tamaina 71613c0053 fix 2025-09-04 01:24:56 +09:00
tamaina dea632654a fix 2025-09-04 01:02:17 +09:00
tamaina 510cc6692f clean up 2025-09-03 23:58:20 +09:00
tamaina 2eef7005c8 enhance(backend): FileServerService: HTTP byte range requestを無視するように (production) 2025-09-03 23:30:48 +09:00
25 changed files with 520 additions and 254 deletions

View File

@ -105,6 +105,16 @@ port: 3000
# socket: /path/to/misskey.sock # socket: /path/to/misskey.sock
# chmodSocket: '777' # chmodSocket: '777'
# Proxy trust settings
#
# Changes how the server interpret the origin IP of the request.
#
# Any format supported by Fastify is accepted.
# Default: trust all proxies (i.e. trustProxy: true)
# See: https://fastify.dev/docs/latest/reference/server/#trustproxy
#
# trustProxy: 1
# ┌──────────────────────────┐ # ┌──────────────────────────┐
#───┘ PostgreSQL configuration └──────────────────────────────── #───┘ PostgreSQL configuration └────────────────────────────────

View File

@ -1,19 +1,23 @@
## 2025.9.1 ## 2025.9.1
### NOTE
- pnpm 10.16.0 が必要です
### General ### General
- Enhance: 広告ごとにセンシティブフラグを設定できるようになりました - Enhance: 広告ごとにセンシティブフラグを設定できるようになりました
### Client ### Client
- Feat: アカウントのQRコードを表示・読み取りできるようになりました - Feat: アカウントのQRコードを表示・読み取りできるようになりました
- Feat: 動画を圧縮してアップロードできるようになりました
- Enhance: チャットの日本語名称がダイレクトメッセージに戻るとともに、ベータ版機能ではなくなりました - Enhance: チャットの日本語名称がダイレクトメッセージに戻るとともに、ベータ版機能ではなくなりました
- Enhance: 画像編集にマスクエフェクト(塗りつぶし、ぼかし)を追加 - Enhance: 画像編集にマスクエフェクト(塗りつぶし、ぼかし)を追加
- Enhance: ウォーターマークにアカウントのQRコードを追加できるように - Enhance: ウォーターマークにアカウントのQRコードを追加できるように
- Enhance: 絵文字ピッカーのサイズをより大きくできるように
- Enhance: 時刻計算のための基準値を一か所で管理するようにし、パフォーマンスを向上 - Enhance: 時刻計算のための基準値を一か所で管理するようにし、パフォーマンスを向上
- Fix: iOSで、デバイスがダークモードだと初回読み込み時にエラーになる問題を修正 - Fix: iOSで、デバイスがダークモードだと初回読み込み時にエラーになる問題を修正
### Server ### Server
- - Enhance: ユーザーIPを確実に取得できるために設定ファイルにFastifyOptions.trustProxyを追加しました
## 2025.9.0 ## 2025.9.0

42
locales/index.d.ts vendored
View File

@ -1030,6 +1030,10 @@ export interface Locale extends ILocale {
* *
*/ */
"processing": string; "processing": string;
/**
*
*/
"preprocessing": string;
/** /**
* *
*/ */
@ -5509,6 +5513,14 @@ export interface Locale extends ILocale {
* <br> * <br>
*/ */
"defaultImageCompressionLevel_description": string; "defaultImageCompressionLevel_description": string;
/**
*
*/
"defaultCompressionLevel": string;
/**
* <br>
*/
"defaultCompressionLevel_description": string;
/** /**
* *
*/ */
@ -5541,6 +5553,36 @@ export interface Locale extends ILocale {
* *
*/ */
"createUserSpecifiedNote": string; "createUserSpecifiedNote": string;
"_compression": {
"_quality": {
/**
*
*/
"high": string;
/**
*
*/
"medium": string;
/**
*
*/
"low": string;
};
"_size": {
/**
*
*/
"large": string;
/**
*
*/
"medium": string;
/**
*
*/
"small": string;
};
};
"_order": { "_order": {
/** /**
* *

View File

@ -253,6 +253,7 @@ noteDeleteConfirm: "このノートを削除しますか?"
pinLimitExceeded: "これ以上ピン留めできません" pinLimitExceeded: "これ以上ピン留めできません"
done: "完了" done: "完了"
processing: "処理中" processing: "処理中"
preprocessing: "準備中"
preview: "プレビュー" preview: "プレビュー"
default: "デフォルト" default: "デフォルト"
defaultValueIs: "デフォルト: {value}" defaultValueIs: "デフォルト: {value}"
@ -1372,6 +1373,8 @@ redisplayAllTips: "全ての「ヒントとコツ」を再表示"
hideAllTips: "全ての「ヒントとコツ」を非表示" hideAllTips: "全ての「ヒントとコツ」を非表示"
defaultImageCompressionLevel: "デフォルトの画像圧縮度" defaultImageCompressionLevel: "デフォルトの画像圧縮度"
defaultImageCompressionLevel_description: "低くすると画質を保てますが、ファイルサイズは増加します。<br>高くするとファイルサイズを減らせますが、画質は低下します。" defaultImageCompressionLevel_description: "低くすると画質を保てますが、ファイルサイズは増加します。<br>高くするとファイルサイズを減らせますが、画質は低下します。"
defaultCompressionLevel: "デフォルトの圧縮度"
defaultCompressionLevel_description: "低くすると品質を保てますが、ファイルサイズは増加します。<br>高くするとファイルサイズを減らせますが、品質は低下します。"
inMinutes: "分" inMinutes: "分"
inDays: "日" inDays: "日"
safeModeEnabled: "セーフモードが有効です" safeModeEnabled: "セーフモードが有効です"
@ -1381,6 +1384,16 @@ themeIsDefaultBecauseSafeMode: "セーフモードが有効な間はデフォル
thankYouForTestingBeta: "ベータ版の検証にご協力いただきありがとうございます!" thankYouForTestingBeta: "ベータ版の検証にご協力いただきありがとうございます!"
createUserSpecifiedNote: "ユーザー指定ノートを作成" createUserSpecifiedNote: "ユーザー指定ノートを作成"
_compression:
_quality:
high: "高品質"
medium: "中品質"
low: "低品質"
_size:
large: "サイズ大"
medium: "サイズ中"
small: "サイズ小"
_order: _order:
newest: "新しい順" newest: "新しい順"
oldest: "古い順" oldest: "古い順"

View File

@ -1,12 +1,12 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2025.9.1-alpha.0", "version": "2025.9.1-alpha.1",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/misskey-dev/misskey.git" "url": "https://github.com/misskey-dev/misskey.git"
}, },
"packageManager": "pnpm@10.15.1", "packageManager": "pnpm@10.16.0",
"workspaces": [ "workspaces": [
"packages/frontend-shared", "packages/frontend-shared",
"packages/frontend", "packages/frontend",
@ -76,7 +76,7 @@
"eslint": "9.35.0", "eslint": "9.35.0",
"globals": "16.3.0", "globals": "16.3.0",
"ncp": "2.0.0", "ncp": "2.0.0",
"pnpm": "10.15.1", "pnpm": "10.16.0",
"start-server-and-test": "2.1.0" "start-server-and-test": "2.1.0"
}, },
"optionalDependencies": { "optionalDependencies": {

View File

@ -7,6 +7,7 @@ import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path'; import { dirname, resolve } from 'node:path';
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
import { type FastifyServerOptions } from 'fastify';
import type * as Sentry from '@sentry/node'; import type * as Sentry from '@sentry/node';
import type * as SentryVue from '@sentry/vue'; import type * as SentryVue from '@sentry/vue';
import type { RedisOptions } from 'ioredis'; import type { RedisOptions } from 'ioredis';
@ -27,6 +28,7 @@ type Source = {
url?: string; url?: string;
port?: number; port?: number;
socket?: string; socket?: string;
trustProxy?: FastifyServerOptions['trustProxy'];
chmodSocket?: string; chmodSocket?: string;
disableHsts?: boolean; disableHsts?: boolean;
db: { db: {
@ -118,6 +120,7 @@ export type Config = {
url: string; url: string;
port: number; port: number;
socket: string | undefined; socket: string | undefined;
trustProxy: FastifyServerOptions['trustProxy'];
chmodSocket: string | undefined; chmodSocket: string | undefined;
disableHsts: boolean | undefined; disableHsts: boolean | undefined;
db: { db: {
@ -266,6 +269,7 @@ export function loadConfig(): Config {
url: url.origin, url: url.origin,
port: config.port ?? parseInt(process.env.PORT ?? '', 10), port: config.port ?? parseInt(process.env.PORT ?? '', 10),
socket: config.socket, socket: config.socket,
trustProxy: config.trustProxy,
chmodSocket: config.chmodSocket, chmodSocket: config.chmodSocket,
disableHsts: config.disableHsts, disableHsts: config.disableHsts,
host, host,

View File

@ -10,22 +10,40 @@ export type IImage = {
data: Buffer; data: Buffer;
ext: string | null; ext: string | null;
type: string; type: string;
filename?: string;
size?: number;
}; };
export type IImageStream = { export type IImageStream = {
data: Readable; data: Readable;
ext: string | null; ext: string | null;
type: string; type: string;
filename?: string;
size?: number;
}; };
export type IImageSharp = { export type IImageSharp = {
data: sharp.Sharp; data: sharp.Sharp;
ext: string | null; ext: string | null;
type: string; type: string;
filename?: string;
size?: number;
}; };
export type IImageStreamable = IImage | IImageStream | IImageSharp; export type IImageStreamable = IImage | IImageStream | IImageSharp;
export function getSizeFromIImage(image: IImageStreamable): number | undefined {
if ('size' in image) {
return image.size;
}
if (image.data instanceof Buffer) {
return image.data.length;
}
return;
}
export const webpDefault: sharp.WebpOptions = { export const webpDefault: sharp.WebpOptions = {
quality: 77, quality: 77,
alphaQuality: 95, alphaQuality: 95,

View File

@ -18,7 +18,7 @@ import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
import { StatusError } from '@/misc/status-error.js'; import { StatusError } from '@/misc/status-error.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { DownloadService } from '@/core/DownloadService.js'; import { DownloadService } from '@/core/DownloadService.js';
import { IImageStreamable, ImageProcessingService, webpDefault } from '@/core/ImageProcessingService.js'; import { getSizeFromIImage, type IImageStreamable, ImageProcessingService, webpDefault } from '@/core/ImageProcessingService.js';
import { VideoProcessingService } from '@/core/VideoProcessingService.js'; import { VideoProcessingService } from '@/core/VideoProcessingService.js';
import { InternalStorageService } from '@/core/InternalStorageService.js'; import { InternalStorageService } from '@/core/InternalStorageService.js';
import { contentDisposition } from '@/misc/content-disposition.js'; import { contentDisposition } from '@/misc/content-disposition.js';
@ -117,6 +117,49 @@ export class FileServerService {
return; return;
} }
@bindThis
private processFileAndConvertToIImage(file: Awaited<ReturnType<FileServerService['getStreamAndTypeFromUrl']>>, request: FastifyRequest, reply: FastifyReply): IImageStreamable {
if (typeof file !== 'object' || !file) {
throw new Error('Invalid file');
}
if (request.headers.range && 'file' in file && file.size > 0) {
this.logger.info(`Range request for file ${file.file.id} ${file.fileRole}: ${request.headers.range}`);
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.size - 1;
if (end > file.size) {
end = file.size - 1;
}
const chunksize = end - start + 1;
reply.header('Content-Range', `bytes ${start}-${end}/${file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.code(206);
return {
data: fs.createReadStream(file.path, {
start,
end,
}),
ext: file.ext,
type: file.mime,
size: chunksize,
filename: file.filename,
};
}
return {
data: fs.createReadStream(file.path),
ext: file.ext,
type: file.mime,
size: file.size,
filename: file.filename,
};
}
@bindThis @bindThis
private async sendDriveFile(request: FastifyRequest<{ Params: { key: string; } }>, reply: FastifyReply) { private async sendDriveFile(request: FastifyRequest<{ Params: { key: string; } }>, reply: FastifyReply) {
const key = request.params.key; const key = request.params.key;
@ -135,9 +178,9 @@ export class FileServerService {
} }
try { try {
if (file.state === 'remote') { let image: IImageStreamable | null = null;
let image: IImageStreamable | null = null;
if (file.state === 'remote') {
if (file.fileRole === 'thumbnail') { if (file.fileRole === 'thumbnail') {
if (isMimeImage(file.mime, 'sharp-convertible-image-with-bmp')) { if (isMimeImage(file.mime, 'sharp-convertible-image-with-bmp')) {
reply.header('Cache-Control', 'max-age=31536000, immutable'); reply.header('Cache-Control', 'max-age=31536000, immutable');
@ -172,36 +215,7 @@ export class FileServerService {
} }
if (!image) { if (!image) {
if (request.headers.range && file.file.size > 0) { image = this.processFileAndConvertToIImage(file, request, reply);
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
image = {
data: fs.createReadStream(file.path, {
start,
end,
}),
ext: file.ext,
type: file.mime,
};
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
} else {
image = {
data: fs.createReadStream(file.path),
ext: file.ext,
type: file.mime,
};
}
} }
if ('pipe' in image.data && typeof image.data.pipe === 'function') { if ('pipe' in image.data && typeof image.data.pipe === 'function') {
@ -212,78 +226,31 @@ export class FileServerService {
// image.dataがstreamでないなら直ちにcleanup // image.dataがstreamでないなら直ちにcleanup
file.cleanup(); file.cleanup();
} }
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
reply.header('Content-Length', file.file.size);
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition',
contentDisposition(
'inline',
correctFilename(file.filename, image.ext),
),
);
return image.data;
}
if (file.fileRole !== 'original') {
const filename = rename(file.filename, {
suffix: file.fileRole === 'thumbnail' ? '-thumb' : '-web',
extname: file.ext ? `.${file.ext}` : '.unknown',
}).toString();
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.mime) ? file.mime : 'application/octet-stream');
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition', contentDisposition('inline', filename));
if (request.headers.range && file.file.size > 0) {
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
const fileStream = fs.createReadStream(file.path, {
start,
end,
});
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
return fileStream;
}
return fs.createReadStream(file.path);
} else { } else {
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.file.type) ? file.file.type : 'application/octet-stream'); if (file.fileRole !== 'original') {
reply.header('Content-Length', file.file.size); const filename = rename(file.filename, {
reply.header('Cache-Control', 'max-age=31536000, immutable'); suffix: file.fileRole === 'thumbnail' ? '-thumb' : '-web',
reply.header('Content-Disposition', contentDisposition('inline', file.filename)); extname: file.ext ? `.${file.ext}` : '.unknown',
}).toString();
if (request.headers.range && file.file.size > 0) { image = this.processFileAndConvertToIImage(file, request, reply);
const range = request.headers.range as string; image.filename = filename;
const parts = range.replace(/bytes=/, '').split('-'); } else {
const start = parseInt(parts[0], 10); image = this.processFileAndConvertToIImage(file, request, reply);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
const fileStream = fs.createReadStream(file.path, {
start,
end,
});
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
return fileStream;
} }
return fs.createReadStream(file.path);
} }
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
const size = getSizeFromIImage(image);
if (size) reply.header('Content-Length', size);
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition',
contentDisposition(
'inline',
correctFilename(image.filename ?? file.filename, image.ext),
),
);
return image.data;
} catch (e) { } catch (e) {
if ('cleanup' in file) file.cleanup(); if ('cleanup' in file) file.cleanup();
throw e; throw e;
@ -363,17 +330,31 @@ export class FileServerService {
data: fs.createReadStream(file.path), data: fs.createReadStream(file.path),
ext: file.ext, ext: file.ext,
type: file.mime, type: file.mime,
size: file.size,
}; };
} else { } else if (!('static' in request.query)) {
const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in request.query) })) // animated
const data = (await sharpBmp(file.path, file.mime, { animated: true }))
.resize({ .resize({
height: 'emoji' in request.query ? 128 : 320, height: 'emoji' in request.query ? 128 : 320,
withoutEnlargement: true, withoutEnlargement: true,
}) });
.webp(webpDefault);
// byte range requestになる可能性があるので、Content-Lengthを送信するためにbufferにする
image = {
data: await data.webp(webpDefault).toBuffer(),
ext: 'webp',
type: 'image/webp',
};
} else {
const data = (await sharpBmp(file.path, file.mime, { animated: false }))
.resize({
height: 'emoji' in request.query ? 128 : 320,
withoutEnlargement: true,
});
image = { image = {
data, data: data.webp(webpDefault),
ext: 'webp', ext: 'webp',
type: 'image/webp', type: 'image/webp',
}; };
@ -420,36 +401,7 @@ export class FileServerService {
} }
if (!image) { if (!image) {
if (request.headers.range && file.file && file.file.size > 0) { image = this.processFileAndConvertToIImage(file, request, reply);
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
image = {
data: fs.createReadStream(file.path, {
start,
end,
}),
ext: file.ext,
type: file.mime,
};
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
} else {
image = {
data: fs.createReadStream(file.path),
ext: file.ext,
type: file.mime,
};
}
} }
if ('cleanup' in file) { if ('cleanup' in file) {
@ -464,6 +416,8 @@ export class FileServerService {
} }
reply.header('Content-Type', image.type); reply.header('Content-Type', image.type);
const size = getSizeFromIImage(image);
if (size) reply.header('Content-Length', size);
reply.header('Cache-Control', 'max-age=31536000, immutable'); reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition', reply.header('Content-Disposition',
contentDisposition( contentDisposition(
@ -479,12 +433,7 @@ export class FileServerService {
} }
@bindThis @bindThis
private async getStreamAndTypeFromUrl(url: string): Promise< private async getStreamAndTypeFromUrl(url: string): Promise<Awaited<ReturnType<FileServerService['downloadAndDetectTypeFromUrl']>> | Awaited<ReturnType<FileServerService['getFileFromKey']>>> {
{ state: 'remote'; fileRole?: 'thumbnail' | 'webpublic' | 'original'; file?: MiDriveFile; mime: string; ext: string | null; path: string; cleanup: () => void; filename: string; }
| { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; mime: string; ext: string | null; path: string; }
| '404'
| '204'
> {
if (url.startsWith(`${this.config.url}/files/`)) { if (url.startsWith(`${this.config.url}/files/`)) {
const key = url.replace(`${this.config.url}/files/`, '').split('/').shift(); const key = url.replace(`${this.config.url}/files/`, '').split('/').shift();
if (!key) throw new StatusError('Invalid File Key', 400, 'Invalid File Key'); if (!key) throw new StatusError('Invalid File Key', 400, 'Invalid File Key');
@ -497,17 +446,18 @@ export class FileServerService {
@bindThis @bindThis
private async downloadAndDetectTypeFromUrl(url: string): Promise< private async downloadAndDetectTypeFromUrl(url: string): Promise<
{ state: 'remote'; mime: string; ext: string | null; path: string; cleanup: () => void; filename: string; } { state: 'remote'; mime: string; ext: string | null; size: number; path: string; cleanup: () => void; filename: string; }
> { > {
const [path, cleanup] = await createTemp(); const [path, cleanup] = await createTemp();
try { try {
const { filename } = await this.downloadService.downloadUrl(url, path); const { filename } = await this.downloadService.downloadUrl(url, path);
const { mime, ext } = await this.fileInfoService.detectType(path); const { mime, ext } = await this.fileInfoService.detectType(path);
const { size } = await fs.promises.stat(path);
return { return {
state: 'remote', state: 'remote',
mime, ext, mime, ext, size,
path, cleanup, path, cleanup,
filename, filename,
}; };
@ -519,8 +469,8 @@ export class FileServerService {
@bindThis @bindThis
private async getFileFromKey(key: string): Promise< private async getFileFromKey(key: string): Promise<
{ state: 'remote'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; url: string; mime: string; ext: string | null; path: string; cleanup: () => void; } { state: 'remote'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; url: string; mime: string; ext: string | null; size: number; path: string; cleanup: () => void; }
| { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; mime: string; ext: string | null; path: string; } | { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; mime: string; ext: string | null; size: number; path: string; }
| '404' | '404'
| '204' | '204'
> { > {
@ -539,7 +489,6 @@ export class FileServerService {
if (!file.storedInternal) { if (!file.storedInternal) {
if (!(file.isLink && file.uri)) return '204'; if (!(file.isLink && file.uri)) return '204';
const result = await this.downloadAndDetectTypeFromUrl(file.uri); const result = await this.downloadAndDetectTypeFromUrl(file.uri);
file.size = (await fs.promises.stat(result.path)).size; // DB file.sizeは正確とは限らないので
return { return {
...result, ...result,
url: file.uri, url: file.uri,
@ -553,16 +502,18 @@ export class FileServerService {
if (isThumbnail || isWebpublic) { if (isThumbnail || isWebpublic) {
const { mime, ext } = await this.fileInfoService.detectType(path); const { mime, ext } = await this.fileInfoService.detectType(path);
const { size } = await fs.promises.stat(path);
return { return {
state: 'stored_internal', state: 'stored_internal',
fileRole: isThumbnail ? 'thumbnail' : 'webpublic', fileRole: isThumbnail ? 'thumbnail' : 'webpublic',
file, file,
filename: file.name, filename: file.name,
mime, ext, mime, ext, size,
path, path,
}; };
} }
const { size } = await fs.promises.stat(path);
return { return {
state: 'stored_internal', state: 'stored_internal',
fileRole: 'original', fileRole: 'original',
@ -571,6 +522,7 @@ export class FileServerService {
// 古いファイルは修正前のmimeを持っているのでできるだけ修正してあげる // 古いファイルは修正前のmimeを持っているのでできるだけ修正してあげる
mime: this.fileInfoService.fixMime(file.type), mime: this.fileInfoService.fixMime(file.type),
ext: null, ext: null,
size,
path, path,
}; };
} }

View File

@ -75,7 +75,7 @@ export class ServerService implements OnApplicationShutdown {
@bindThis @bindThis
public async launch(): Promise<void> { public async launch(): Promise<void> {
const fastify = Fastify({ const fastify = Fastify({
trustProxy: true, trustProxy: this.config.trustProxy ?? true,
logger: false, logger: false,
}); });
this.#fastify = fastify; this.#fastify = fastify;

View File

@ -64,6 +64,8 @@ function toBase62(n: number): string {
} }
export function getConfig(): UserConfig { export function getConfig(): UserConfig {
const localesHash = toBase62(hash(JSON.stringify(locales)));
return { return {
base: '/embed_vite/', base: '/embed_vite/',
@ -148,9 +150,9 @@ export function getConfig(): UserConfig {
// dependencies of i18n.ts // dependencies of i18n.ts
'config': ['@@/js/config.js'], 'config': ['@@/js/config.js'],
}, },
entryFileNames: 'scripts/[hash:8].js', entryFileNames: `scripts/${localesHash}-[hash:8].js`,
chunkFileNames: 'scripts/[hash:8].js', chunkFileNames: `scripts/${localesHash}-[hash:8].js`,
assetFileNames: 'assets/[hash:8][extname]', assetFileNames: `assets/${localesHash}-[hash:8][extname]`,
paths(id) { paths(id) {
for (const p of externalPackages) { for (const p of externalPackages) {
if (p.match.test(id)) { if (p.match.test(id)) {

View File

@ -57,6 +57,7 @@
"json5": "2.2.3", "json5": "2.2.3",
"magic-string": "0.30.18", "magic-string": "0.30.18",
"matter-js": "0.20.0", "matter-js": "0.20.0",
"mediabunny": "1.15.1",
"mfm-js": "0.25.0", "mfm-js": "0.25.0",
"misskey-bubble-game": "workspace:*", "misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",

View File

@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, useTemplateRef } from 'vue'; import { onMounted, onUnmounted, useTemplateRef } from 'vue';
import isChromatic from 'chromatic/isChromatic'; import isChromatic from 'chromatic/isChromatic';
import { initShaderProgram } from '@/utility/webgl.js';
const canvasEl = useTemplateRef('canvasEl'); const canvasEl = useTemplateRef('canvasEl');
@ -21,47 +22,6 @@ const props = withDefaults(defineProps<{
focus: 1.0, focus: 1.0,
}); });
function loadShader(gl: WebGLRenderingContext, type: number, source: string) {
const shader = gl.createShader(type);
if (shader == null) return null;
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(
`falied to compile shader: ${gl.getShaderInfoLog(shader)}`,
);
gl.deleteShader(shader);
return null;
}
return shader;
}
function initShaderProgram(gl: WebGLRenderingContext, vsSource: string, fsSource: string) {
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
const shaderProgram = gl.createProgram();
if (vertexShader == null || fragmentShader == null) return null;
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert(
`failed to init shader: ${gl.getProgramInfoLog(
shaderProgram,
)}`,
);
return null;
}
return shaderProgram;
}
let handle: ReturnType<typeof window['requestAnimationFrame']> | null = null; let handle: ReturnType<typeof window['requestAnimationFrame']> | null = null;
onMounted(() => { onMounted(() => {
@ -71,7 +31,7 @@ onMounted(() => {
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
const maybeGl = canvas.getContext('webgl', { premultipliedAlpha: true }); const maybeGl = canvas.getContext('webgl2', { premultipliedAlpha: true });
if (maybeGl == null) return; if (maybeGl == null) return;
const gl = maybeGl; const gl = maybeGl;
@ -82,18 +42,16 @@ onMounted(() => {
const positionBuffer = gl.createBuffer(); const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const shaderProgram = initShaderProgram(gl, ` const shaderProgram = initShaderProgram(gl, `#version 300 es
attribute vec2 vertex; in vec2 position;
uniform vec2 u_scale; uniform vec2 u_scale;
out vec2 in_uv;
varying vec2 v_pos;
void main() { void main() {
gl_Position = vec4(vertex, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
v_pos = vertex / u_scale; in_uv = position / u_scale;
} }
`, ` `, `#version 300 es
precision mediump float; precision mediump float;
vec3 mod289(vec3 x) { vec3 mod289(vec3 x) {
@ -143,6 +101,7 @@ onMounted(() => {
return 130.0 * dot(m, g); return 130.0 * dot(m, g);
} }
in vec2 in_uv;
uniform float u_time; uniform float u_time;
uniform vec2 u_resolution; uniform vec2 u_resolution;
uniform float u_spread; uniform float u_spread;
@ -150,8 +109,7 @@ onMounted(() => {
uniform float u_warp; uniform float u_warp;
uniform float u_focus; uniform float u_focus;
uniform float u_itensity; uniform float u_itensity;
out vec4 out_color;
varying vec2 v_pos;
float circle( in vec2 _pos, in vec2 _origin, in float _radius ) { float circle( in vec2 _pos, in vec2 _origin, in float _radius ) {
float SPREAD = 0.7 * u_spread; float SPREAD = 0.7 * u_spread;
@ -182,13 +140,13 @@ onMounted(() => {
float ratio = u_resolution.x / u_resolution.y; float ratio = u_resolution.x / u_resolution.y;
vec2 uv = vec2( v_pos.x, v_pos.y / ratio ) * 0.5 + 0.5; vec2 uv = vec2( in_uv.x, in_uv.y / ratio ) * 0.5 + 0.5;
vec3 color = vec3( 0.0 ); vec3 color = vec3( 0.0 );
float greenMix = snoise( v_pos * 1.31 + u_time * 0.8 * 0.00017 ) * 0.5 + 0.5; float greenMix = snoise( in_uv * 1.31 + u_time * 0.8 * 0.00017 ) * 0.5 + 0.5;
float purpleMix = snoise( v_pos * 1.26 + u_time * 0.8 * -0.0001 ) * 0.5 + 0.5; float purpleMix = snoise( in_uv * 1.26 + u_time * 0.8 * -0.0001 ) * 0.5 + 0.5;
float orangeMix = snoise( v_pos * 1.34 + u_time * 0.8 * 0.00015 ) * 0.5 + 0.5; float orangeMix = snoise( in_uv * 1.34 + u_time * 0.8 * 0.00015 ) * 0.5 + 0.5;
float alphaOne = 0.35 + 0.65 * pow( snoise( vec2( u_time * 0.00012, uv.x ) ) * 0.5 + 0.5, 1.2 ); float alphaOne = 0.35 + 0.65 * pow( snoise( vec2( u_time * 0.00012, uv.x ) ) * 0.5 + 0.5, 1.2 );
float alphaTwo = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 1561.0 ) * 0.00014, uv.x ) ) * 0.5 + 0.5, 1.2 ); float alphaTwo = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 1561.0 ) * 0.00014, uv.x ) ) * 0.5 + 0.5, 1.2 );
@ -198,10 +156,10 @@ onMounted(() => {
color += vec3( circle( uv, vec2( 0.90 + cos( u_time * 0.000166 ) * 0.06, 0.42 + sin( u_time * 0.000138 ) * 0.06 ), 0.18 ) ) * alphaTwo * ( green * greenMix + purple * purpleMix ); color += vec3( circle( uv, vec2( 0.90 + cos( u_time * 0.000166 ) * 0.06, 0.42 + sin( u_time * 0.000138 ) * 0.06 ), 0.18 ) ) * alphaTwo * ( green * greenMix + purple * purpleMix );
color += vec3( circle( uv, vec2( 0.19 + sin( u_time * 0.000112 ) * 0.06, 0.25 + sin( u_time * 0.000192 ) * 0.06 ), 0.09 ) ) * alphaThree * ( orange * orangeMix ); color += vec3( circle( uv, vec2( 0.19 + sin( u_time * 0.000112 ) * 0.06, 0.25 + sin( u_time * 0.000192 ) * 0.06 ), 0.09 ) ) * alphaThree * ( orange * orangeMix );
color *= u_itensity + 1.0 * pow( snoise( vec2( v_pos.y + u_time * 0.00013, v_pos.x + u_time * -0.00009 ) ) * 0.5 + 0.5, 2.0 ); color *= u_itensity + 1.0 * pow( snoise( vec2( in_uv.y + u_time * 0.00013, in_uv.x + u_time * -0.00009 ) ) * 0.5 + 0.5, 2.0 );
vec3 inverted = vec3( 1.0 ) - color; vec3 inverted = vec3( 1.0 ) - color;
gl_FragColor = vec4( color, max(max(color.x, color.y), color.z) ); out_color = vec4(color, max(max(color.x, color.y), color.z));
} }
`); `);
if (shaderProgram == null) return; if (shaderProgram == null) return;
@ -223,7 +181,7 @@ onMounted(() => {
gl.uniform1f(u_itensity, 0.5); gl.uniform1f(u_itensity, 0.5);
gl.uniform2fv(u_scale, [props.scale, props.scale]); gl.uniform2fv(u_scale, [props.scale, props.scale]);
const vertex = gl.getAttribLocation(shaderProgram, 'vertex'); const vertex = gl.getAttribLocation(shaderProgram, 'position');
gl.enableVertexAttribArray(vertex); gl.enableVertexAttribArray(vertex);
gl.vertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0); gl.vertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0);

View File

@ -530,6 +530,14 @@ defineExpose({
--eachSize: 50px; --eachSize: 50px;
} }
&.s4 {
--eachSize: 55px;
}
&.s5 {
--eachSize: 60px;
}
&.w1 { &.w1 {
width: calc((var(--eachSize) * 5) + (#{$pad} * 2)); width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
--columns: 1fr 1fr 1fr 1fr 1fr; --columns: 1fr 1fr 1fr 1fr 1fr;

View File

@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, useTemplateRef } from 'vue'; import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, useTemplateRef, onUnmounted } from 'vue';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor'; import insertTextAtCursor from 'insert-text-at-cursor';
@ -218,6 +218,10 @@ const uploader = useUploader({
multiple: true, multiple: true,
}); });
onUnmounted(() => {
uploader.dispose();
});
uploader.events.on('itemUploaded', ctx => { uploader.events.on('itemUploaded', ctx => {
files.value.push(ctx.item.uploaded!); files.value.push(ctx.item.uploaded!);
uploader.removeItem(ctx.item); uploader.removeItem(ctx.item);
@ -1300,6 +1304,7 @@ async function canClose() {
defineExpose({ defineExpose({
clear, clear,
abortUploader: () => uploader.abortAll(),
canClose, canClose,
}); });
</script> </script>

View File

@ -54,6 +54,7 @@ function onPosted() {
async function _close() { async function _close() {
const canClose = await form.value?.canClose(); const canClose = await form.value?.canClose();
if (!canClose) return; if (!canClose) return;
form.value?.abortUploader();
modal.value?.close(); modal.value?.close();
} }

View File

@ -10,7 +10,10 @@ SPDX-License-Identifier: AGPL-3.0-only
:key="item.id" :key="item.id"
v-panel v-panel
:class="[$style.item, { [$style.itemWaiting]: item.preprocessing, [$style.itemCompleted]: item.uploaded, [$style.itemFailed]: item.uploadFailed }]" :class="[$style.item, { [$style.itemWaiting]: item.preprocessing, [$style.itemCompleted]: item.uploaded, [$style.itemFailed]: item.uploadFailed }]"
:style="{ '--p': item.progress != null ? `${item.progress.value / item.progress.max * 100}%` : '0%' }" :style="{
'--p': item.progress != null ? `${item.progress.value / item.progress.max * 100}%` : '0%',
'--pp': item.preprocessProgress != null ? `${item.preprocessProgress * 100}%` : '100%',
}"
@contextmenu.prevent.stop="onContextmenu(item, $event)" @contextmenu.prevent.stop="onContextmenu(item, $event)"
> >
<div :class="$style.itemInner"> <div :class="$style.itemInner">
@ -19,11 +22,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div :class="$style.itemThumbnail" :style="{ backgroundImage: `url(${ item.thumbnail })` }" @click="onThumbnailClick(item, $event)"></div> <div :class="$style.itemThumbnail" :style="{ backgroundImage: `url(${ item.thumbnail })` }" @click="onThumbnailClick(item, $event)"></div>
<div :class="$style.itemBody"> <div :class="$style.itemBody">
<div><i v-if="item.isSensitive" style="color: var(--MI_THEME-warn); margin-right: 0.5em;" class="ti ti-eye-exclamation"></i><MkCondensedLine :minScale="2 / 3">{{ item.name }}</MkCondensedLine></div> <div>
<i v-if="item.isSensitive" style="color: var(--MI_THEME-warn); margin-right: 0.5em;" class="ti ti-eye-exclamation"></i>
<MkCondensedLine :minScale="2 / 3">{{ item.name }}</MkCondensedLine>
</div>
<div :class="$style.itemInfo"> <div :class="$style.itemInfo">
<span>{{ item.file.type }}</span> <span>{{ item.file.type }}</span>
<span v-if="item.compressedSize">({{ i18n.tsx._uploader.compressedToX({ x: bytes(item.compressedSize) }) }} = {{ i18n.tsx._uploader.savedXPercent({ x: Math.round((1 - item.compressedSize / item.file.size) * 100) }) }})</span> <span v-if="item.compressedSize">({{ i18n.tsx._uploader.compressedToX({ x: bytes(item.compressedSize) }) }} = {{ i18n.tsx._uploader.savedXPercent({ x: Math.round((1 - item.compressedSize / item.file.size) * 100) }) }})</span>
<span v-else>{{ bytes(item.file.size) }}</span> <span v-else>{{ bytes(item.file.size) }}</span>
<span v-if="item.preprocessing">{{ i18n.ts.preprocessing }}<MkLoading inline em style="margin-left: 0.5em;"/></span>
</div> </div>
<div> <div>
</div> </div>
@ -97,7 +104,7 @@ function onThumbnailClick(item: UploaderItem, ev: MouseEvent) {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: var(--pp, 100%);
height: 100%; height: 100%;
background: linear-gradient(-45deg, transparent 25%, var(--c) 25%,var(--c) 50%, transparent 50%, transparent 75%, var(--c) 75%, var(--c)); background: linear-gradient(-45deg, transparent 25%, var(--c) 25%,var(--c) 50%, transparent 50%, transparent 75%, var(--c) 75%, var(--c));
background-size: 25px 25px; background-size: 25px 25px;

View File

@ -43,6 +43,12 @@ const IMAGE_EDITING_SUPPORTED_TYPES = [
'image/webp', 'image/webp',
]; ];
const VIDEO_COMPRESSION_SUPPORTED_TYPES = [ // TODO
'video/mp4',
'video/quicktime',
'video/x-matroska',
];
const WATERMARK_SUPPORTED_TYPES = IMAGE_EDITING_SUPPORTED_TYPES; const WATERMARK_SUPPORTED_TYPES = IMAGE_EDITING_SUPPORTED_TYPES;
const IMAGE_PREPROCESS_NEEDED_TYPES = [ const IMAGE_PREPROCESS_NEEDED_TYPES = [
@ -51,6 +57,10 @@ const IMAGE_PREPROCESS_NEEDED_TYPES = [
...IMAGE_EDITING_SUPPORTED_TYPES, ...IMAGE_EDITING_SUPPORTED_TYPES,
]; ];
const VIDEO_PREPROCESS_NEEDED_TYPES = [
...VIDEO_COMPRESSION_SUPPORTED_TYPES,
];
const mimeTypeMap = { const mimeTypeMap = {
'image/webp': 'webp', 'image/webp': 'webp',
'image/jpeg': 'jpg', 'image/jpeg': 'jpg',
@ -64,6 +74,7 @@ export type UploaderItem = {
progress: { max: number; value: number } | null; progress: { max: number; value: number } | null;
thumbnail: string | null; thumbnail: string | null;
preprocessing: boolean; preprocessing: boolean;
preprocessProgress: number | null;
uploading: boolean; uploading: boolean;
uploaded: Misskey.entities.DriveFile | null; uploaded: Misskey.entities.DriveFile | null;
uploadFailed: boolean; uploadFailed: boolean;
@ -76,6 +87,7 @@ export type UploaderItem = {
isSensitive?: boolean; isSensitive?: boolean;
caption?: string | null; caption?: string | null;
abort?: (() => void) | null; abort?: (() => void) | null;
abortPreprocess?: (() => void) | null;
}; };
function getCompressionSettings(level: 0 | 1 | 2 | 3) { function getCompressionSettings(level: 0 | 1 | 2 | 3) {
@ -129,11 +141,12 @@ export function useUploader(options: {
progress: null, progress: null,
thumbnail: THUMBNAIL_SUPPORTED_TYPES.includes(file.type) ? window.URL.createObjectURL(file) : null, thumbnail: THUMBNAIL_SUPPORTED_TYPES.includes(file.type) ? window.URL.createObjectURL(file) : null,
preprocessing: false, preprocessing: false,
preprocessProgress: null,
uploading: false, uploading: false,
aborted: false, aborted: false,
uploaded: null, uploaded: null,
uploadFailed: false, uploadFailed: false,
compressionLevel: prefer.s.defaultImageCompressionLevel, compressionLevel: IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(file.type) ? prefer.s.defaultImageCompressionLevel : VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(file.type) ? prefer.s.defaultVideoCompressionLevel : 0,
watermarkPresetId: uploaderFeatures.value.watermark && $i.policies.watermarkAvailable ? prefer.s.defaultWatermarkPresetId : null, watermarkPresetId: uploaderFeatures.value.watermark && $i.policies.watermarkAvailable ? prefer.s.defaultWatermarkPresetId : null,
file: markRaw(file), file: markRaw(file),
}); });
@ -318,7 +331,7 @@ export function useUploader(options: {
} }
if ( if (
IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(item.file.type) && (IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(item.file.type) || VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(item.file.type)) &&
!item.preprocessing && !item.preprocessing &&
!item.uploading && !item.uploading &&
!item.uploaded !item.uploaded
@ -391,6 +404,19 @@ export function useUploader(options: {
removeItem(item); removeItem(item);
}, },
}); });
} else if (item.preprocessing && item.abortPreprocess != null) {
menu.push({
type: 'divider',
}, {
icon: 'ti ti-player-stop',
text: i18n.ts.abort,
danger: true,
action: () => {
if (item.abortPreprocess != null) {
item.abortPreprocess();
}
},
});
} else if (item.uploading) { } else if (item.uploading) {
menu.push({ menu.push({
type: 'divider', type: 'divider',
@ -474,6 +500,10 @@ export function useUploader(options: {
continue; continue;
} }
if (item.abortPreprocess != null) {
item.abortPreprocess();
}
if (item.abort != null) { if (item.abort != null) {
item.abort(); item.abort();
} }
@ -484,18 +514,30 @@ export function useUploader(options: {
async function preprocess(item: UploaderItem): Promise<void> { async function preprocess(item: UploaderItem): Promise<void> {
item.preprocessing = true; item.preprocessing = true;
item.preprocessProgress = null;
try { if (IMAGE_PREPROCESS_NEEDED_TYPES.includes(item.file.type)) {
if (IMAGE_PREPROCESS_NEEDED_TYPES.includes(item.file.type)) { try {
await preprocessForImage(item); await preprocessForImage(item);
} } catch (err) {
} catch (err) { console.error('Failed to preprocess image', err);
console.error('Failed to preprocess image', err);
// nop // nop
}
}
if (VIDEO_PREPROCESS_NEEDED_TYPES.includes(item.file.type)) {
try {
await preprocessForVideo(item);
} catch (err) {
console.error('Failed to preprocess video', err);
// nop
}
} }
item.preprocessing = false; item.preprocessing = false;
item.preprocessProgress = null;
} }
async function preprocessForImage(item: UploaderItem): Promise<void> { async function preprocessForImage(item: UploaderItem): Promise<void> {
@ -564,10 +606,74 @@ export function useUploader(options: {
item.preprocessedFile = markRaw(preprocessedFile); item.preprocessedFile = markRaw(preprocessedFile);
} }
onUnmounted(() => { async function preprocessForVideo(item: UploaderItem): Promise<void> {
let preprocessedFile: Blob | File = item.file;
const needsCompress = item.compressionLevel !== 0 && VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(preprocessedFile.type);
if (needsCompress) {
const mediabunny = await import('mediabunny');
const source = new mediabunny.BlobSource(preprocessedFile);
const input = new mediabunny.Input({
source,
formats: mediabunny.ALL_FORMATS,
});
const output = new mediabunny.Output({
target: new mediabunny.BufferTarget(),
format: new mediabunny.Mp4OutputFormat(),
});
const currentConversion = await mediabunny.Conversion.init({
input,
output,
video: {
//width: 320, // Height will be deduced automatically to retain aspect ratio
bitrate: item.compressionLevel === 1 ? mediabunny.QUALITY_VERY_HIGH : item.compressionLevel === 2 ? mediabunny.QUALITY_MEDIUM : mediabunny.QUALITY_VERY_LOW,
},
audio: {
bitrate: item.compressionLevel === 1 ? mediabunny.QUALITY_VERY_HIGH : item.compressionLevel === 2 ? mediabunny.QUALITY_MEDIUM : mediabunny.QUALITY_VERY_LOW,
},
});
currentConversion.onProgress = newProgress => item.preprocessProgress = newProgress;
item.abortPreprocess = () => {
item.abortPreprocess = null;
currentConversion.cancel();
item.preprocessing = false;
item.preprocessProgress = null;
};
await currentConversion.execute();
item.abortPreprocess = null;
preprocessedFile = new Blob([output.target.buffer!], { type: output.format.mimeType });
item.compressedSize = output.target.buffer!.byteLength;
item.uploadName = `${item.name}.mp4`;
} else {
item.compressedSize = null;
item.uploadName = item.name;
}
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
item.thumbnail = THUMBNAIL_SUPPORTED_TYPES.includes(preprocessedFile.type) ? window.URL.createObjectURL(preprocessedFile) : null;
item.preprocessedFile = markRaw(preprocessedFile);
}
function dispose() {
for (const item of items.value) { for (const item of items.value) {
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail); if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
} }
abortAll();
}
onUnmounted(() => {
dispose();
}); });
return { return {
@ -575,6 +681,7 @@ export function useUploader(options: {
addFiles, addFiles,
removeItem, removeItem,
abortAll, abortAll,
dispose,
upload, upload,
getMenu, getMenu,
uploading: computed(() => items.value.some(item => item.uploading)), uploading: computed(() => items.value.some(item => item.uploading)),

View File

@ -129,13 +129,37 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSelect <MkSelect
v-model="defaultImageCompressionLevel" :items="[ v-model="defaultImageCompressionLevel" :items="[
{ label: i18n.ts.none, value: 0 }, { label: i18n.ts.none, value: 0 },
{ label: i18n.ts.low, value: 1 }, { label: `${i18n.ts.low} (${i18n.ts._compression._quality.high}; ${i18n.ts._compression._size.large})`, value: 1 },
{ label: i18n.ts.medium, value: 2 }, { label: `${i18n.ts.medium} (${i18n.ts._compression._quality.medium}; ${i18n.ts._compression._size.medium})`, value: 2 },
{ label: i18n.ts.high, value: 3 }, { label: `${i18n.ts.high} (${i18n.ts._compression._quality.low}; ${i18n.ts._compression._size.small})`, value: 3 },
]" ]"
> >
<template #label><SearchLabel>{{ i18n.ts.defaultImageCompressionLevel }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.defaultCompressionLevel }}</SearchLabel></template>
<template #caption><div v-html="i18n.ts.defaultImageCompressionLevel_description"></div></template> <template #caption><div v-html="i18n.ts.defaultCompressionLevel_description"></div></template>
</MkSelect>
</MkPreferenceContainer>
</SearchMarker>
</div>
</FormSection>
</SearchMarker>
<SearchMarker :keywords="['video']">
<FormSection>
<template #label><SearchLabel>{{ i18n.ts.video }}</SearchLabel></template>
<div class="_gaps_m">
<SearchMarker :keywords="['default', 'video', 'compression']">
<MkPreferenceContainer k="defaultVideoCompressionLevel">
<MkSelect
v-model="defaultVideoCompressionLevel" :items="[
{ label: i18n.ts.none, value: 0 },
{ label: `${i18n.ts.low} (${i18n.ts._compression._quality.high}; ${i18n.ts._compression._size.large})`, value: 1 },
{ label: `${i18n.ts.medium} (${i18n.ts._compression._quality.medium}; ${i18n.ts._compression._size.medium})`, value: 2 },
{ label: `${i18n.ts.high} (${i18n.ts._compression._quality.low}; ${i18n.ts._compression._size.small})`, value: 3 },
]"
>
<template #label><SearchLabel>{{ i18n.ts.defaultCompressionLevel }}</SearchLabel></template>
<template #caption><div v-html="i18n.ts.defaultCompressionLevel_description"></div></template>
</MkSelect> </MkSelect>
</MkPreferenceContainer> </MkPreferenceContainer>
</SearchMarker> </SearchMarker>
@ -196,6 +220,7 @@ const meterStyle = computed(() => {
const keepOriginalFilename = prefer.model('keepOriginalFilename'); const keepOriginalFilename = prefer.model('keepOriginalFilename');
const defaultWatermarkPresetId = prefer.model('defaultWatermarkPresetId'); const defaultWatermarkPresetId = prefer.model('defaultWatermarkPresetId');
const defaultImageCompressionLevel = prefer.model('defaultImageCompressionLevel'); const defaultImageCompressionLevel = prefer.model('defaultImageCompressionLevel');
const defaultVideoCompressionLevel = prefer.model('defaultVideoCompressionLevel');
const watermarkPresetsSyncEnabled = ref(prefer.isSyncEnabled('watermarkPresets')); const watermarkPresetsSyncEnabled = ref(prefer.isSyncEnabled('watermarkPresets'));

View File

@ -64,6 +64,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<option :value="1">{{ i18n.ts.small }}</option> <option :value="1">{{ i18n.ts.small }}</option>
<option :value="2">{{ i18n.ts.medium }}</option> <option :value="2">{{ i18n.ts.medium }}</option>
<option :value="3">{{ i18n.ts.large }}</option> <option :value="3">{{ i18n.ts.large }}</option>
<option :value="4">{{ i18n.ts.large }}+</option>
<option :value="5">{{ i18n.ts.large }}++</option>
</MkRadios> </MkRadios>
</MkPreferenceContainer> </MkPreferenceContainer>
</SearchMarker> </SearchMarker>
@ -95,11 +97,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<SearchMarker :keywords="['emoji', 'picker', 'style']"> <SearchMarker :keywords="['emoji', 'picker', 'style']">
<MkPreferenceContainer k="emojiPickerStyle"> <MkPreferenceContainer k="emojiPickerStyle">
<MkSelect v-model="emojiPickerStyle" :items="[ <MkSelect
{ label: i18n.ts.auto, value: 'auto' }, v-model="emojiPickerStyle" :items="[
{ label: i18n.ts.popup, value: 'popup' }, { label: i18n.ts.auto, value: 'auto' },
{ label: i18n.ts.drawer, value: 'drawer' }, { label: i18n.ts.popup, value: 'popup' },
]"> { label: i18n.ts.drawer, value: 'drawer' },
]"
>
<template #label><SearchLabel>{{ i18n.ts.style }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.style }}</SearchLabel></template>
<template #caption>{{ i18n.ts.needReloadToApply }}</template> <template #caption>{{ i18n.ts.needReloadToApply }}</template>
</MkSelect> </MkSelect>
@ -116,13 +120,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { genId } from '@/utility/id.js';
import XPalette from './emoji-palette.palette.vue'; import XPalette from './emoji-palette.palette.vue';
import type { MkSelectItem } from '@/components/MkSelect.vue';
import { genId } from '@/utility/id.js';
import MkRadios from '@/components/MkRadios.vue'; import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue'; import FormSection from '@/components/form/section.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import type { MkSelectItem } from '@/components/MkSelect.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';

View File

@ -439,6 +439,9 @@ export const PREF_DEF = definePreferences({
defaultImageCompressionLevel: { defaultImageCompressionLevel: {
default: 2 as 0 | 1 | 2 | 3, default: 2 as 0 | 1 | 2 | 3,
}, },
defaultVideoCompressionLevel: {
default: 2 as 0 | 1 | 2 | 3,
},
'sound.masterVolume': { 'sound.masterVolume': {
default: 0.5, default: 0.5,

View File

@ -230,11 +230,16 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
icon: 'ti ti-search', icon: 'ti ti-search',
text: i18n.ts.searchThisUsersNotes, text: i18n.ts.searchThisUsersNotes,
action: () => { action: () => {
router.push('/search', { const query = {
query: {
username: user.username, username: user.username,
host: user.host ?? undefined, } as { username: string, host?: string };
},
if (user.host !== null) {
query.host = user.host;
}
router.push('/search', {
query
}); });
}, },
}); });

View File

@ -85,6 +85,8 @@ export function toBase62(n: number): string {
} }
export function getConfig(): UserConfig { export function getConfig(): UserConfig {
const localesHash = toBase62(hash(JSON.stringify(locales)));
return { return {
base: '/vite/', base: '/vite/',
@ -188,9 +190,9 @@ export function getConfig(): UserConfig {
// dependencies of i18n.ts // dependencies of i18n.ts
'config': ['@@/js/config.js'], 'config': ['@@/js/config.js'],
}, },
entryFileNames: 'scripts/[hash:8].js', entryFileNames: `scripts/${localesHash}-[hash:8].js`,
chunkFileNames: 'scripts/[hash:8].js', chunkFileNames: `scripts/${localesHash}-[hash:8].js`,
assetFileNames: 'assets/[hash:8][extname]', assetFileNames: `assets/${localesHash}-[hash:8][extname]`,
paths(id) { paths(id) {
for (const p of externalPackages) { for (const p of externalPackages) {
if (p.match.test(id)) { if (p.match.test(id)) {

View File

@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "misskey-js", "name": "misskey-js",
"version": "2025.9.1-alpha.0", "version": "2025.9.1-alpha.1",
"description": "Misskey SDK for JavaScript", "description": "Misskey SDK for JavaScript",
"license": "MIT", "license": "MIT",
"main": "./built/index.js", "main": "./built/index.js",

View File

@ -83,8 +83,8 @@ importers:
specifier: 2.0.0 specifier: 2.0.0
version: 2.0.0 version: 2.0.0
pnpm: pnpm:
specifier: 10.15.1 specifier: 10.16.0
version: 10.15.1 version: 10.16.0
start-server-and-test: start-server-and-test:
specifier: 2.1.0 specifier: 2.1.0
version: 2.1.0 version: 2.1.0
@ -829,6 +829,9 @@ importers:
matter-js: matter-js:
specifier: 0.20.0 specifier: 0.20.0
version: 0.20.0 version: 0.20.0
mediabunny:
specifier: 1.15.1
version: 1.15.1
mfm-js: mfm-js:
specifier: 0.25.0 specifier: 0.25.0
version: 0.25.0 version: 0.25.0
@ -2440,138 +2443,163 @@ packages:
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm64@1.1.0': '@img/sharp-libvips-linux-arm64@1.1.0':
resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.0.5': '@img/sharp-libvips-linux-arm@1.0.5':
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.1.0': '@img/sharp-libvips-linux-arm@1.1.0':
resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.1.0': '@img/sharp-libvips-linux-ppc64@1.1.0':
resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.0.4': '@img/sharp-libvips-linux-s390x@1.0.4':
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.1.0': '@img/sharp-libvips-linux-s390x@1.1.0':
resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.0.4': '@img/sharp-libvips-linux-x64@1.0.4':
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.1.0': '@img/sharp-libvips-linux-x64@1.1.0':
resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.0.4': '@img/sharp-libvips-linuxmusl-arm64@1.0.4':
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-arm64@1.1.0': '@img/sharp-libvips-linuxmusl-arm64@1.1.0':
resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.0.4': '@img/sharp-libvips-linuxmusl-x64@1.0.4':
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.1.0': '@img/sharp-libvips-linuxmusl-x64@1.1.0':
resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.33.5': '@img/sharp-linux-arm64@0.33.5':
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-arm64@0.34.2': '@img/sharp-linux-arm64@0.34.2':
resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==} resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.33.5': '@img/sharp-linux-arm@0.33.5':
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.34.2': '@img/sharp-linux-arm@0.34.2':
resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==} resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.33.5': '@img/sharp-linux-s390x@0.33.5':
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.34.2': '@img/sharp-linux-s390x@0.34.2':
resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==} resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.33.5': '@img/sharp-linux-x64@0.33.5':
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.34.2': '@img/sharp-linux-x64@0.34.2':
resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==} resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.33.5': '@img/sharp-linuxmusl-arm64@0.33.5':
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-arm64@0.34.2': '@img/sharp-linuxmusl-arm64@0.34.2':
resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==} resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.33.5': '@img/sharp-linuxmusl-x64@0.33.5':
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.2': '@img/sharp-linuxmusl-x64@0.34.2':
resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==} resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.33.5': '@img/sharp-wasm32@0.33.5':
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
@ -2893,30 +2921,35 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-arm64-musl@0.1.79': '@napi-rs/canvas-linux-arm64-musl@0.1.79':
resolution: {integrity: sha512-KsrsR3+6uXv70W/1/kY0yRK4/bbdJgA1Vuxw4KyfSc6mjl1DMoYXDAjpBT/5w7AXy6cGG44jm3upvvt/y/dPfg==} resolution: {integrity: sha512-KsrsR3+6uXv70W/1/kY0yRK4/bbdJgA1Vuxw4KyfSc6mjl1DMoYXDAjpBT/5w7AXy6cGG44jm3upvvt/y/dPfg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@napi-rs/canvas-linux-riscv64-gnu@0.1.79': '@napi-rs/canvas-linux-riscv64-gnu@0.1.79':
resolution: {integrity: sha512-EXaENnSJD6au6z4aKN2PpU9eVNWUsRI2cApm8gCa0WSRMaiYXZsFkXQmhB+Vz2pXahOS8BN2Zd8S1IeML/LCtg==} resolution: {integrity: sha512-EXaENnSJD6au6z4aKN2PpU9eVNWUsRI2cApm8gCa0WSRMaiYXZsFkXQmhB+Vz2pXahOS8BN2Zd8S1IeML/LCtg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-gnu@0.1.79': '@napi-rs/canvas-linux-x64-gnu@0.1.79':
resolution: {integrity: sha512-3xZhHlE9e3cd9D7Comy6/TTSs/8PUGXEXymIwYQrA1QxHojAlAOFlVai4rffzXd0bHylZu+/wD76LodvYqF1Yw==} resolution: {integrity: sha512-3xZhHlE9e3cd9D7Comy6/TTSs/8PUGXEXymIwYQrA1QxHojAlAOFlVai4rffzXd0bHylZu+/wD76LodvYqF1Yw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-musl@0.1.79': '@napi-rs/canvas-linux-x64-musl@0.1.79':
resolution: {integrity: sha512-4yv550uCjIEoTFgrpxYZK67nFlDMCQa3LAheM2QrO+B8w1p5w04usIQSCHqHe6aPWlbLQCIqfVcew6/7Q4KuHg==} resolution: {integrity: sha512-4yv550uCjIEoTFgrpxYZK67nFlDMCQa3LAheM2QrO+B8w1p5w04usIQSCHqHe6aPWlbLQCIqfVcew6/7Q4KuHg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@napi-rs/canvas-win32-x64-msvc@0.1.79': '@napi-rs/canvas-win32-x64-msvc@0.1.79':
resolution: {integrity: sha512-sD5qP2njBRnhNlTNFJDdpeCN6aR3qVamLySTwhX3ec8sdfeT/chf/x2dw2UXoIGMoVaVk/y2ifwxBj/h2a2jug==} resolution: {integrity: sha512-sD5qP2njBRnhNlTNFJDdpeCN6aR3qVamLySTwhX3ec8sdfeT/chf/x2dw2UXoIGMoVaVk/y2ifwxBj/h2a2jug==}
@ -3271,36 +3304,42 @@ packages:
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.0': '@parcel/watcher-linux-arm-musl@2.5.0':
resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.0': '@parcel/watcher-linux-arm64-glibc@2.5.0':
resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.0': '@parcel/watcher-linux-arm64-musl@2.5.0':
resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.0': '@parcel/watcher-linux-x64-glibc@2.5.0':
resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.0': '@parcel/watcher-linux-x64-musl@2.5.0':
resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.5.0': '@parcel/watcher-win32-arm64@2.5.0':
resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
@ -3470,111 +3509,133 @@ packages:
resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-gnueabihf@4.50.1': '@rollup/rollup-linux-arm-gnueabihf@4.50.1':
resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==} resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.46.2': '@rollup/rollup-linux-arm-musleabihf@4.46.2':
resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm-musleabihf@4.50.1': '@rollup/rollup-linux-arm-musleabihf@4.50.1':
resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==} resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.46.2': '@rollup/rollup-linux-arm64-gnu@4.46.2':
resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-gnu@4.50.1': '@rollup/rollup-linux-arm64-gnu@4.50.1':
resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==} resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.46.2': '@rollup/rollup-linux-arm64-musl@4.46.2':
resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-musl@4.50.1': '@rollup/rollup-linux-arm64-musl@4.50.1':
resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==} resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-loongarch64-gnu@4.46.2': '@rollup/rollup-linux-loongarch64-gnu@4.46.2':
resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loongarch64-gnu@4.50.1': '@rollup/rollup-linux-loongarch64-gnu@4.50.1':
resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==} resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-gnu@4.46.2': '@rollup/rollup-linux-ppc64-gnu@4.46.2':
resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-gnu@4.50.1': '@rollup/rollup-linux-ppc64-gnu@4.50.1':
resolution: {integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==} resolution: {integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.46.2': '@rollup/rollup-linux-riscv64-gnu@4.46.2':
resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.50.1': '@rollup/rollup-linux-riscv64-gnu@4.50.1':
resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==} resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.46.2': '@rollup/rollup-linux-riscv64-musl@4.46.2':
resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-musl@4.50.1': '@rollup/rollup-linux-riscv64-musl@4.50.1':
resolution: {integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==} resolution: {integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.46.2': '@rollup/rollup-linux-s390x-gnu@4.46.2':
resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.50.1': '@rollup/rollup-linux-s390x-gnu@4.50.1':
resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==} resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.46.2': '@rollup/rollup-linux-x64-gnu@4.46.2':
resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.50.1': '@rollup/rollup-linux-x64-gnu@4.50.1':
resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==} resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.46.2': '@rollup/rollup-linux-x64-musl@4.46.2':
resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-x64-musl@4.50.1': '@rollup/rollup-linux-x64-musl@4.50.1':
resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==} resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-openharmony-arm64@4.50.1': '@rollup/rollup-openharmony-arm64@4.50.1':
resolution: {integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==} resolution: {integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==}
@ -4308,24 +4369,28 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@swc/core-linux-arm64-musl@1.13.5': '@swc/core-linux-arm64-musl@1.13.5':
resolution: {integrity: sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==} resolution: {integrity: sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@swc/core-linux-x64-gnu@1.13.5': '@swc/core-linux-x64-gnu@1.13.5':
resolution: {integrity: sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==} resolution: {integrity: sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@swc/core-linux-x64-musl@1.13.5': '@swc/core-linux-x64-musl@1.13.5':
resolution: {integrity: sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==} resolution: {integrity: sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@swc/core-win32-arm64-msvc@1.13.5': '@swc/core-win32-arm64-msvc@1.13.5':
resolution: {integrity: sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==} resolution: {integrity: sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==}
@ -4549,6 +4614,12 @@ packages:
'@types/doctrine@0.0.9': '@types/doctrine@0.0.9':
resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
'@types/dom-mediacapture-transform@0.1.11':
resolution: {integrity: sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ==}
'@types/dom-webcodecs@0.1.13':
resolution: {integrity: sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==}
'@types/eslint@7.29.0': '@types/eslint@7.29.0':
resolution: {integrity: sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==} resolution: {integrity: sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==}
@ -8140,6 +8211,9 @@ packages:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
mediabunny@1.15.1:
resolution: {integrity: sha512-+eRTVzd3E4LuGYZzPSQcPzuGdAIljohSlzYTX358XsfLM2qH1lQIBYa+erx7wzVcGQLRNjdV7x7ZS0EpK04DfA==}
meilisearch@0.52.0: meilisearch@0.52.0:
resolution: {integrity: sha512-RqPsB4a78sXf/ATB7PIVvKCG7yf0y1M+uCj8Z9Wku44WmCy3iz0C1PHjVV5xphQolo09CdhdyFoRxHQSJkOdpg==} resolution: {integrity: sha512-RqPsB4a78sXf/ATB7PIVvKCG7yf0y1M+uCj8Z9Wku44WmCy3iz0C1PHjVV5xphQolo09CdhdyFoRxHQSJkOdpg==}
@ -9010,8 +9084,8 @@ packages:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
pnpm@10.15.1: pnpm@10.16.0:
resolution: {integrity: sha512-NOU4wym1VTAUyo6PRTWZf5YYCh0PYUM5NXRJk1NQ2STiL4YUaCGRJk7DPRRirCFWGv+X9rsYBlNRwWLH6PbeZw==} resolution: {integrity: sha512-gGbnsDQhe3AKmk27OgBQYdZBuhMKiZFSE6ELPKSRnBnAN77IBmr9xVm4ljX9uAaxbqZz8kaPuyiqv6E8U+P3aQ==}
engines: {node: '>=18.12'} engines: {node: '>=18.12'}
hasBin: true hasBin: true
@ -9914,24 +9988,28 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
slacc-linux-arm64-musl@0.0.10: slacc-linux-arm64-musl@0.0.10:
resolution: {integrity: sha512-3lUX7752f6Okn54aONioaA+9M5TvifqXBAart+u2lNXEdWmmh003cVSU2Vcwg7nJ9lLHtju2DkDmKKfJjFuShA==} resolution: {integrity: sha512-3lUX7752f6Okn54aONioaA+9M5TvifqXBAart+u2lNXEdWmmh003cVSU2Vcwg7nJ9lLHtju2DkDmKKfJjFuShA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
slacc-linux-x64-gnu@0.0.10: slacc-linux-x64-gnu@0.0.10:
resolution: {integrity: sha512-BxxvylF9zlOLRLCpiyMvKTIUpdLlpetNBJ+DSMDh5+Ggq+AmQz2NUGawmcBJw58F8nMCj9TpWLlGNWc2AuY+JQ==} resolution: {integrity: sha512-BxxvylF9zlOLRLCpiyMvKTIUpdLlpetNBJ+DSMDh5+Ggq+AmQz2NUGawmcBJw58F8nMCj9TpWLlGNWc2AuY+JQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
slacc-linux-x64-musl@0.0.10: slacc-linux-x64-musl@0.0.10:
resolution: {integrity: sha512-TYJi8LOtJiTFcZvka4du7bMjF9Bz1RHRwyLnScr5E5yjjgoLRrsvgSu7bxp87xH+rgJ3CdEwE3w3Ux8EiewHpA==} resolution: {integrity: sha512-TYJi8LOtJiTFcZvka4du7bMjF9Bz1RHRwyLnScr5E5yjjgoLRrsvgSu7bxp87xH+rgJ3CdEwE3w3Ux8EiewHpA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
slacc-win32-arm64-msvc@0.0.10: slacc-win32-arm64-msvc@0.0.10:
resolution: {integrity: sha512-1CHPLiDB4exzFyT5ndtJDsRRhBxNg8mGz6I6eJEMjelGkJR2KZPT9LZuby/1bS/bcVOr7zuJvGNfbEGBeHRwPQ==} resolution: {integrity: sha512-1CHPLiDB4exzFyT5ndtJDsRRhBxNg8mGz6I6eJEMjelGkJR2KZPT9LZuby/1bS/bcVOr7zuJvGNfbEGBeHRwPQ==}
@ -10959,6 +11037,9 @@ packages:
vue-component-type-helpers@3.0.6: vue-component-type-helpers@3.0.6:
resolution: {integrity: sha512-6CRM8X7EJqWCJOiKPvSLQG+hJPb/Oy2gyJx3pLjUEhY7PuaCthQu3e0zAGI1lqUBobrrk9IT0K8sG2GsCluxoQ==} resolution: {integrity: sha512-6CRM8X7EJqWCJOiKPvSLQG+hJPb/Oy2gyJx3pLjUEhY7PuaCthQu3e0zAGI1lqUBobrrk9IT0K8sG2GsCluxoQ==}
vue-component-type-helpers@3.1.0-alpha.0:
resolution: {integrity: sha512-K1guwS1Oy0gNfBdIdIn8JMkUV+S38sriR1zf5dP+KkPS7/r5nHnPZUL74meY2CYlxYBH4qSQ+k7bpHfwiRvaMg==}
vue-demi@0.14.7: vue-demi@0.14.7:
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -14869,7 +14950,7 @@ snapshots:
storybook: 9.1.5(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.1(@types/node@22.18.1)(typescript@5.9.2))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.5(@types/node@22.18.1)(sass@1.92.1)(terser@5.44.0)(tsx@4.20.5)) storybook: 9.1.5(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.1(@types/node@22.18.1)(typescript@5.9.2))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.5(@types/node@22.18.1)(sass@1.92.1)(terser@5.44.0)(tsx@4.20.5))
type-fest: 2.19.0 type-fest: 2.19.0
vue: 3.5.21(typescript@5.9.2) vue: 3.5.21(typescript@5.9.2)
vue-component-type-helpers: 3.0.6 vue-component-type-helpers: 3.1.0-alpha.0
'@stylistic/eslint-plugin@2.13.0(eslint@9.35.0)(typescript@5.9.2)': '@stylistic/eslint-plugin@2.13.0(eslint@9.35.0)(typescript@5.9.2)':
dependencies: dependencies:
@ -15221,6 +15302,12 @@ snapshots:
'@types/doctrine@0.0.9': {} '@types/doctrine@0.0.9': {}
'@types/dom-mediacapture-transform@0.1.11':
dependencies:
'@types/dom-webcodecs': 0.1.13
'@types/dom-webcodecs@0.1.13': {}
'@types/eslint@7.29.0': '@types/eslint@7.29.0':
dependencies: dependencies:
'@types/estree': 1.0.8 '@types/estree': 1.0.8
@ -19668,6 +19755,11 @@ snapshots:
media-typer@0.3.0: {} media-typer@0.3.0: {}
mediabunny@1.15.1:
dependencies:
'@types/dom-mediacapture-transform': 0.1.11
'@types/dom-webcodecs': 0.1.13
meilisearch@0.52.0: {} meilisearch@0.52.0: {}
memoizerific@1.11.3: memoizerific@1.11.3:
@ -20637,7 +20729,7 @@ snapshots:
pngjs@5.0.0: {} pngjs@5.0.0: {}
pnpm@10.15.1: {} pnpm@10.16.0: {}
polished@4.2.2: polished@4.2.2:
dependencies: dependencies:
@ -22745,6 +22837,8 @@ snapshots:
vue-component-type-helpers@3.0.6: {} vue-component-type-helpers@3.0.6: {}
vue-component-type-helpers@3.1.0-alpha.0: {}
vue-demi@0.14.7(vue@3.5.21(typescript@5.9.2)): vue-demi@0.14.7(vue@3.5.21(typescript@5.9.2)):
dependencies: dependencies:
vue: 3.5.21(typescript@5.9.2) vue: 3.5.21(typescript@5.9.2)

View File

@ -30,3 +30,4 @@ onlyBuiltDependencies:
- v-code-diff - v-code-diff
- vue-demi - vue-demi
ignorePatchFailures: false ignorePatchFailures: false
minimumReleaseAge: 10080 # delay 7days to mitigate supply-chain attack