diff options
| -rw-r--r-- | package.json | 4 | ||||
| -rw-r--r-- | packages/frontend/lib/vite-plugin-create-search-index.ts | 609 | ||||
| -rw-r--r-- | packages/frontend/package.json | 3 | ||||
| -rw-r--r-- | packages/frontend/scripts/generate-search-index.ts | 15 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkSuperMenu.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/index.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/utility/autogen/settings-search-index.ts | 993 | ||||
| -rw-r--r-- | packages/frontend/src/utility/settings-search-index.ts | 43 | ||||
| -rw-r--r-- | packages/frontend/src/utility/virtual.d.ts | 18 | ||||
| -rw-r--r-- | packages/frontend/vite.config.ts | 3 | ||||
| -rw-r--r-- | pnpm-lock.yaml | 92 | ||||
| -rw-r--r-- | scripts/dependency-patches/vite.patch | 31 |
12 files changed, 440 insertions, 1375 deletions
diff --git a/package.json b/package.json index b8b2a81751..6fcf6f8620 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "build": "pnpm build-pre && pnpm -r build && pnpm build-assets", "build-storybook": "pnpm --filter frontend build-storybook", "build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api", - "build-frontend-search-index": "pnpm --filter frontend build-search-index", "start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js", "start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js", "init": "pnpm migrate", @@ -85,7 +84,8 @@ "@aiscript-dev/aiscript-languageserver": "-" }, "patchedDependencies": { - "re2": "scripts/dependency-patches/re2.patch" + "re2": "scripts/dependency-patches/re2.patch", + "vite": "scripts/dependency-patches/vite.patch" } } } diff --git a/packages/frontend/lib/vite-plugin-create-search-index.ts b/packages/frontend/lib/vite-plugin-create-search-index.ts index d506e84bb6..99af81fb70 100644 --- a/packages/frontend/lib/vite-plugin-create-search-index.ts +++ b/packages/frontend/lib/vite-plugin-create-search-index.ts @@ -4,77 +4,68 @@ */ import { parse as vueSfcParse } from 'vue/compiler-sfc'; -import type { LogOptions, Plugin } from 'vite'; +import { + createLogger, + EnvironmentModuleGraph, + normalizePath, + type LogErrorOptions, + type LogOptions, + type Plugin, + type PluginOption +} from 'vite'; import fs from 'node:fs'; import { glob } from 'glob'; import JSON5 from 'json5'; -import MagicString from 'magic-string'; +import MagicString, { SourceMap } from 'magic-string'; import path from 'node:path' import { hash, toBase62 } from '../vite.config'; -import { createLogger } from 'vite'; +import { minimatch } from 'minimatch'; +import type { + AttributeNode, CompoundExpressionNode, DirectiveNode, + ElementNode, + RootNode, SimpleExpressionNode, + TemplateChildNode, +} from '@vue/compiler-core'; +import { NodeTypes } from '@vue/compiler-core'; -interface VueAstNode { - type: number; - tag?: string; - loc?: { - start: { offset: number, line: number, column: number }, - end: { offset: number, line: number, column: number }, - source?: string - }; - props?: Array<{ - name: string; - type: number; - value?: { content?: string }; - arg?: { content?: string }; - exp?: { content?: string; loc?: any }; - }>; - children?: VueAstNode[]; - content?: any; - __markerId?: string; - __children?: string[]; -} - -export type AnalysisResult = { +export type AnalysisResult<T = SearchIndexItem> = { filePath: string; - usage: SearchIndexItem[]; + usage: T[]; } -export type SearchIndexItem = { +export type SearchIndexItem = SearchIndexItemLink<SearchIndexItem>; +export type SearchIndexStringItem = SearchIndexItemLink<string>; +export interface SearchIndexItemLink<T> { id: string; path?: string; label: string; keywords: string | string[]; icon?: string; inlining?: string[]; - children?: SearchIndexItem[]; -}; + children?: T[]; +} export type Options = { targetFilePaths: string[], - exportFilePath: string, + mainVirtualModule: string, + modulesToHmrOnUpdate: string[], + fileVirtualModulePrefix?: string, + fileVirtualModuleSuffix?: string, verbose?: boolean, }; -// 関連するノードタイプの定数化 -const NODE_TYPES = { - ELEMENT: 1, - EXPRESSION: 2, - TEXT: 3, - INTERPOLATION: 5, // Mustache -}; - // マーカー関係を表す型 interface MarkerRelation { parentId?: string; markerId: string; - node: VueAstNode; + node: ElementNode; } // ロガー let logger = { info: (msg: string, options?: LogOptions) => { }, warn: (msg: string, options?: LogOptions) => { }, - error: (msg: string, options?: LogOptions) => { }, + error: (msg: string, options?: LogErrorOptions) => { }, }; let loggerInitialized = false; @@ -99,14 +90,11 @@ function initLogger(options: Options) { } } -/** - * 解析結果をTypeScriptファイルとして出力する - */ -function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisResult[]): void { +function collectSearchItemIndexes(analysisResults: AnalysisResult<SearchIndexStringItem>[]): SearchIndexItem[] { logger.info(`Processing ${analysisResults.length} files for output`); // 新しいツリー構造を構築 - const allMarkers = new Map<string, SearchIndexItem>(); + const allMarkers = new Map<string, SearchIndexStringItem>(); // 1. すべてのマーカーを一旦フラットに収集 for (const file of analysisResults) { @@ -115,10 +103,9 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR for (const marker of file.usage) { if (marker.id) { // キーワードとchildren処理を共通化 - const processedMarker = { + const processedMarker: SearchIndexStringItem = { ...marker, keywords: processMarkerProperty(marker.keywords, 'keywords'), - children: processMarkerProperty(marker.children || [], 'children') }; allMarkers.set(marker.id, processedMarker); @@ -143,14 +130,13 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR const { totalMarkers, totalChildren } = countMarkers(resolvedRootMarkers); logger.info(`Total markers in tree: ${totalMarkers} (${resolvedRootMarkers.length} roots + ${totalChildren} nested children)`); - // 6. 結果をTS形式で出力 - writeOutputFile(outputPath, resolvedRootMarkers); + return resolvedRootMarkers; } /** * マーカーのプロパティ(keywordsやchildren)を処理する */ -function processMarkerProperty(propValue: any, propType: 'keywords' | 'children'): any { +function processMarkerProperty(propValue: string | string[], propType: 'keywords' | 'children'): string | string[] { // 文字列の配列表現を解析 if (typeof propValue === 'string' && propValue.startsWith('[') && propValue.endsWith(']')) { try { @@ -169,7 +155,7 @@ function processMarkerProperty(propValue: any, propType: 'keywords' | 'children' /** * 全マーカーから子IDを収集する */ -function collectChildIds(allMarkers: Map<string, SearchIndexItem>): Set<string> { +function collectChildIds(allMarkers: Map<string, SearchIndexStringItem>): Set<string> { const childIds = new Set<string>(); allMarkers.forEach((marker, id) => { @@ -232,10 +218,10 @@ function collectChildIds(allMarkers: Map<string, SearchIndexItem>): Set<string> * ルートマーカー(他の子でないマーカー)を特定する */ function identifyRootMarkers( - allMarkers: Map<string, SearchIndexItem>, + allMarkers: Map<string, SearchIndexStringItem>, childIds: Set<string> -): SearchIndexItem[] { - const rootMarkers: SearchIndexItem[] = []; +): SearchIndexStringItem[] { + const rootMarkers: SearchIndexStringItem[] = []; allMarkers.forEach((marker, id) => { if (!childIds.has(id)) { @@ -251,12 +237,12 @@ function identifyRootMarkers( * 子マーカーの参照をIDから実際のオブジェクトに解決する */ function resolveChildReferences( - rootMarkers: SearchIndexItem[], - allMarkers: Map<string, SearchIndexItem> + rootMarkers: SearchIndexStringItem[], + allMarkers: Map<string, SearchIndexStringItem> ): SearchIndexItem[] { - function resolveChildrenForMarker(marker: SearchIndexItem): SearchIndexItem { + function resolveChildrenForMarker(marker: SearchIndexStringItem): SearchIndexItem { // マーカーのディープコピーを作成 - const resolvedMarker = { ...marker }; + const resolvedMarker: SearchIndexItem = { ...marker, children: [] }; // 明示的に子マーカー配列を作成 const resolvedChildren: SearchIndexItem[] = []; @@ -352,54 +338,18 @@ function countMarkers(markers: SearchIndexItem[]): { totalMarkers: number, total } /** - * 最終的なTypeScriptファイルを出力 - */ -function writeOutputFile(outputPath: string, resolvedRootMarkers: SearchIndexItem[]): void { - try { - const tsOutput = generateTypeScriptCode(resolvedRootMarkers); - fs.writeFileSync(outputPath, tsOutput, 'utf-8'); - // 強制的に出力させるためにViteロガーを使わない - console.log(`Successfully wrote search index to ${outputPath} with ${resolvedRootMarkers.length} root entries`); - } catch (error) { - logger.error('[create-search-index]: error writing output: ', error); - } -} - -/** * TypeScriptコード生成 */ -function generateTypeScriptCode(resolvedRootMarkers: SearchIndexItem[]): string { - return ` -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -// This file was automatically generated by create-search-index. -// Do not edit this file. - -import { i18n } from '@/i18n.js'; - -export type SearchIndexItem = { - id: string; - path?: string; - label: string; - keywords: string[]; - icon?: string; - children?: SearchIndexItem[]; -}; - -export const searchIndexes: SearchIndexItem[] = ${customStringify(resolvedRootMarkers)} as const; - -export type SearchIndex = typeof searchIndexes; -`; +function generateJavaScriptCode(resolvedRootMarkers: SearchIndexItem[]): string { + return `import { i18n } from '@/i18n.js';\n` + + `export const searchIndexes = ${customStringify(resolvedRootMarkers)};\n`; } /** * オブジェクトを特殊な形式の文字列に変換する * i18n参照を保持しつつ適切な形式に変換 */ -function customStringify(obj: any, depth = 0): string { +function customStringify(obj: unknown, depth = 0): string { const INDENT_STR = '\t'; // 配列の処理 @@ -441,7 +391,6 @@ function customStringify(obj: any, depth = 0): string { .filter(([key, value]) => { if (value === undefined) return false; if (key === 'children' && Array.isArray(value) && value.length === 0) return false; - if (key === 'inlining') return false; return true; }) // 各プロパティを変換 @@ -462,7 +411,7 @@ function customStringify(obj: any, depth = 0): string { /** * 特殊プロパティの書式設定 */ -function formatSpecialProperty(key: string, value: any): string { +function formatSpecialProperty(key: string, value: unknown): string { // 値がundefinedの場合は空文字列を返す if (value === undefined) { return '""'; @@ -499,7 +448,7 @@ function formatSpecialProperty(key: string, value: any): string { /** * 配列式の文字列表現を生成 */ -function formatArrayForOutput(items: any[]): string { +function formatArrayForOutput(items: unknown[]): string { return items.map(item => { // i18n.ts. 参照の文字列はそのままJavaScript式として出力 if (typeof item === 'string' && isI18nReference(item)) { @@ -516,17 +465,18 @@ function formatArrayForOutput(items: any[]): string { * 要素ノードからテキスト内容を抽出する * 各抽出方法を分離して可読性を向上 */ -function extractElementText(node: VueAstNode): string | null { +function extractElementText(node: TemplateChildNode): string | null { if (!node) return null; + if (node.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error("Unexpected COMPOUND_EXPRESSION"); - logger.info(`Extracting text from node type=${node.type}, tag=${node.tag || 'unknown'}`); + logger.info(`Extracting text from node type=${node.type}, tag=${'tag' in node ? node.tag : 'unknown'}`); // 1. 直接コンテンツの抽出を試行 const directContent = extractDirectContent(node); if (directContent) return directContent; // 子要素がない場合は終了 - if (!node.children || !Array.isArray(node.children)) { + if (!('children' in node) || !Array.isArray(node.children)) { return null; } @@ -548,12 +498,13 @@ function extractElementText(node: VueAstNode): string | null { /** * ノードから直接コンテンツを抽出 */ -function extractDirectContent(node: VueAstNode): string | null { - if (!node.content) return null; +function extractDirectContent(node: TemplateChildNode): string | null { + if (!('content' in node)) return null; + if (typeof node.content == 'object' && node.content.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error("Unexpected COMPOUND_EXPRESSION"); - const content = typeof node.content === 'string' - ? node.content.trim() - : (node.content.content ? node.content.content.trim() : null); + const content = typeof node.content === 'string' ? node.content.trim() + : node.content.type !== NodeTypes.INTERPOLATION ? node.content.content.trim() + : null; if (!content) return null; @@ -582,9 +533,9 @@ function extractDirectContent(node: VueAstNode): string | null { /** * インターポレーションノード(Mustache)からコンテンツを抽出 */ -function extractInterpolationContent(children: VueAstNode[]): string | null { +function extractInterpolationContent(children: TemplateChildNode[]): string | null { for (const child of children) { - if (child.type === NODE_TYPES.INTERPOLATION) { + if (child.type === NodeTypes.INTERPOLATION) { logger.info(`Found interpolation node (Mustache): ${JSON.stringify(child.content).substring(0, 100)}...`); if (child.content && child.content.type === 4 && child.content.content) { @@ -595,6 +546,7 @@ function extractInterpolationContent(children: VueAstNode[]): string | null { return content; } } else if (child.content && typeof child.content === 'object') { + if (child.content.type == NodeTypes.COMPOUND_EXPRESSION) throw new Error("Unexpected COMPOUND_EXPRESSION"); // オブジェクト形式のcontentを探索 logger.info(`Complex interpolation node: ${JSON.stringify(child.content).substring(0, 100)}...`); @@ -616,10 +568,10 @@ function extractInterpolationContent(children: VueAstNode[]): string | null { /** * 式ノードからコンテンツを抽出 */ -function extractExpressionContent(children: VueAstNode[]): string | null { +function extractExpressionContent(children: TemplateChildNode[]): string | null { // i18n.ts. 参照パターンを持つものを優先 for (const child of children) { - if (child.type === NODE_TYPES.EXPRESSION && child.content) { + if (child.type === NodeTypes.TEXT && child.content) { const expr = child.content.trim(); if (isI18nReference(expr)) { @@ -631,7 +583,7 @@ function extractExpressionContent(children: VueAstNode[]): string | null { // その他の式 for (const child of children) { - if (child.type === NODE_TYPES.EXPRESSION && child.content) { + if (child.type === NodeTypes.TEXT && child.content) { const expr = child.content.trim(); logger.info(`Found expression: ${expr}`); return expr; @@ -644,9 +596,9 @@ function extractExpressionContent(children: VueAstNode[]): string | null { /** * テキストノードからコンテンツを抽出 */ -function extractTextContent(children: VueAstNode[]): string | null { +function extractTextContent(children: TemplateChildNode[]): string | null { for (const child of children) { - if (child.type === NODE_TYPES.TEXT && child.content) { + if (child.type === NodeTypes.COMMENT && child.content) { const text = child.content.trim(); if (text) { @@ -672,16 +624,16 @@ function extractTextContent(children: VueAstNode[]): string | null { /** * 子ノードを再帰的に探索してコンテンツを抽出 */ -function extractNestedContent(children: VueAstNode[]): string | null { +function extractNestedContent(children: TemplateChildNode[]): string | null { for (const child of children) { - if (child.children && Array.isArray(child.children) && child.children.length > 0) { + if ('children' in child && Array.isArray(child.children) && child.children.length > 0) { const nestedContent = extractElementText(child); if (nestedContent) { logger.info(`Found nested content: ${nestedContent}`); return nestedContent; } - } else if (child.type === NODE_TYPES.ELEMENT) { + } else if (child.type === NodeTypes.ELEMENT) { // childrenがなくても内部を調査 const nestedContent = extractElementText(child); @@ -699,16 +651,16 @@ function extractNestedContent(children: VueAstNode[]): string | null { /** * SearchLabelとSearchKeywordを探して抽出する関数 */ -function extractLabelsAndKeywords(nodes: VueAstNode[]): { label: string | null, keywords: any[] } { +function extractLabelsAndKeywords(nodes: TemplateChildNode[]): { label: string | null, keywords: string[] } { let label: string | null = null; - const keywords: any[] = []; + const keywords: string[] = []; logger.info(`Extracting labels and keywords from ${nodes.length} nodes`); // 再帰的にSearchLabelとSearchKeywordを探索(ネストされたSearchMarkerは処理しない) - function findComponents(nodes: VueAstNode[]) { + function findComponents(nodes: TemplateChildNode[]) { for (const node of nodes) { - if (node.type === NODE_TYPES.ELEMENT) { + if (node.type === NodeTypes.ELEMENT) { logger.info(`Checking element: ${node.tag}`); // SearchMarkerの場合は、その子要素は別スコープなのでスキップ @@ -730,11 +682,12 @@ function extractLabelsAndKeywords(nodes: VueAstNode[]): { label: string | null, logger.info(`SearchLabel found but extraction failed, trying direct children inspection`); // バックアップ: 子直接確認 - type=5のMustacheインターポレーションを重点的に確認 - if (node.children && Array.isArray(node.children)) { + { for (const child of node.children) { // Mustacheインターポレーション - if (child.type === NODE_TYPES.INTERPOLATION && child.content) { + if (child.type === NodeTypes.INTERPOLATION && child.content) { // content内の式を取り出す + if (child.content.type == NodeTypes.COMPOUND_EXPRESSION) throw new Error("unexpected COMPOUND_EXPRESSION"); const expression = child.content.content || (child.content.type === 4 ? child.content.content : null) || JSON.stringify(child.content); @@ -747,13 +700,13 @@ function extractLabelsAndKeywords(nodes: VueAstNode[]): { label: string | null, } } // 式ノード - else if (child.type === NODE_TYPES.EXPRESSION && child.content && isI18nReference(child.content)) { + else if (child.type === NodeTypes.TEXT && child.content && isI18nReference(child.content)) { label = child.content.trim(); logger.info(`Found i18n in expression: ${label}`); break; } // テキストノードでもMustache構文を探す - else if (child.type === NODE_TYPES.TEXT && child.content) { + else if (child.type === NodeTypes.COMMENT && child.content) { const mustacheMatch = child.content.trim().match(/^\s*{{\s*(.*?)\s*}}\s*$/); if (mustacheMatch && mustacheMatch[1] && isI18nReference(mustacheMatch[1])) { label = mustacheMatch[1].trim(); @@ -778,11 +731,12 @@ function extractLabelsAndKeywords(nodes: VueAstNode[]): { label: string | null, logger.info(`SearchKeyword found but extraction failed, trying direct children inspection`); // バックアップ: 子直接確認 - type=5のMustacheインターポレーションを重点的に確認 - if (node.children && Array.isArray(node.children)) { + { for (const child of node.children) { // Mustacheインターポレーション - if (child.type === NODE_TYPES.INTERPOLATION && child.content) { + if (child.type === NodeTypes.INTERPOLATION && child.content) { // content内の式を取り出す + if (child.content.type == NodeTypes.COMPOUND_EXPRESSION) throw new Error("unexpected COMPOUND_EXPRESSION"); const expression = child.content.content || (child.content.type === 4 ? child.content.content : null) || JSON.stringify(child.content); @@ -796,14 +750,14 @@ function extractLabelsAndKeywords(nodes: VueAstNode[]): { label: string | null, } } // 式ノード - else if (child.type === NODE_TYPES.EXPRESSION && child.content && isI18nReference(child.content)) { + else if (child.type === NodeTypes.TEXT && child.content && isI18nReference(child.content)) { const keyword = child.content.trim(); keywords.push(keyword); logger.info(`Found i18n keyword in expression: ${keyword}`); break; } // テキストノードでもMustache構文を探す - else if (child.type === NODE_TYPES.TEXT && child.content) { + else if (child.type === NodeTypes.COMMENT && child.content) { const mustacheMatch = child.content.trim().match(/^\s*{{\s*(.*?)\s*}}\s*$/); if (mustacheMatch && mustacheMatch[1] && isI18nReference(mustacheMatch[1])) { const keyword = mustacheMatch[1].trim(); @@ -834,23 +788,22 @@ function extractLabelsAndKeywords(nodes: VueAstNode[]): { label: string | null, function extractUsageInfoFromTemplateAst( - templateAst: any, + templateAst: RootNode | undefined, id: string, -): SearchIndexItem[] { - const allMarkers: SearchIndexItem[] = []; - const markerMap = new Map<string, SearchIndexItem>(); +): SearchIndexStringItem[] { + const allMarkers: SearchIndexStringItem[] = []; + const markerMap = new Map<string, SearchIndexItemLink<string>>(); const childrenIds = new Set<string>(); const normalizedId = id.replace(/\\/g, '/'); if (!templateAst) return allMarkers; // マーカーの基本情報を収集 - function collectMarkers(node: VueAstNode, parentId: string | null = null) { - if (node.type === 1 && node.tag === 'SearchMarker') { + function collectMarkers(node: TemplateChildNode | RootNode, parentId: string | null = null) { + if (node.type === NodeTypes.ELEMENT && node.tag === 'SearchMarker') { // マーカーID取得 - const markerIdProp = node.props?.find((p: any) => p.name === 'markerId'); - const markerId = markerIdProp?.value?.content || - node.__markerId; + const markerIdProp = node.props?.find(p => p.name === 'markerId'); + const markerId = markerIdProp?.type == NodeTypes.ATTRIBUTE ? markerIdProp.value?.content : null; // SearchMarkerにマーカーIDがない場合はエラー if (markerId == null) { @@ -859,7 +812,7 @@ function extractUsageInfoFromTemplateAst( } // マーカー基本情報 - const markerInfo: SearchIndexItem = { + const markerInfo: SearchIndexStringItem = { id: markerId, children: [], label: '', // デフォルト値 @@ -882,7 +835,7 @@ function extractUsageInfoFromTemplateAst( if (bindings.path) markerInfo.path = bindings.path; if (bindings.icon) markerInfo.icon = bindings.icon; if (bindings.label) markerInfo.label = bindings.label; - if (bindings.children) markerInfo.children = bindings.children; + if (bindings.children) markerInfo.children = processMarkerProperty(bindings.children, 'children') as string[]; if (bindings.inlining) { markerInfo.inlining = bindings.inlining; logger.info(`Added inlining ${JSON.stringify(bindings.inlining)} to marker ${markerId}`); @@ -946,19 +899,19 @@ function extractUsageInfoFromTemplateAst( } // 子ノードを処理 - if (node.children && Array.isArray(node.children)) { - node.children.forEach((child: VueAstNode) => { - collectMarkers(child, markerId); - }); + for (const child of node.children) { + collectMarkers(child, markerId); } return markerId; } // SearchMarkerでない場合は再帰的に子ノードを処理 - else if (node.children && Array.isArray(node.children)) { - node.children.forEach((child: VueAstNode) => { - collectMarkers(child, parentId); - }); + else if ('children' in node && Array.isArray(node.children)) { + for (const child of node.children) { + if (typeof child == 'object' && child.type !== NodeTypes.SIMPLE_EXPRESSION) { + collectMarkers(child, parentId); + } + } } return null; @@ -969,16 +922,22 @@ function extractUsageInfoFromTemplateAst( return allMarkers; } +type SpecialBindings = { + inlining: string[]; + keywords: string[] | string; +}; +type Bindings = Partial<Omit<Record<keyof SearchIndexItem, string>, keyof SpecialBindings> & SpecialBindings>; // バインドプロパティの処理を修正する関数 -function extractNodeBindings(node: VueAstNode): Record<keyof SearchIndexItem, any> { - const bindings: Record<string, any> = {}; +function extractNodeBindings(node: TemplateChildNode | RootNode): Bindings { + const bindings: Bindings = {}; - if (!node.props || !Array.isArray(node.props)) return bindings; + if (node.type !== NodeTypes.ELEMENT) return bindings; // バインド式を収集 for (const prop of node.props) { - if (prop.type === 7 && prop.name === 'bind' && prop.arg?.content) { + if (prop.type === NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.arg && 'content' in prop.arg) { const propName = prop.arg.content; + if (prop.exp?.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error('unexpected COMPOUND_EXPRESSION'); const propContent = prop.exp?.content || ''; logger.info(`Processing bind prop ${propName}: ${propContent}`); @@ -1055,7 +1014,7 @@ function extractNodeBindings(node: VueAstNode): Record<keyof SearchIndexItem, an } // 配列式をパースする補助関数(文字列リテラル処理を改善) -function parseArrayExpression(expr: string): any[] { +function parseArrayExpression(expr: string): string[] { try { // 単純なケースはJSON5でパースを試みる return JSON5.parse(expr.replace(/'/g, '"')); @@ -1067,7 +1026,7 @@ function parseArrayExpression(expr: string): any[] { const content = expr.substring(1, expr.length - 1).trim(); if (!content) return []; - const result: any[] = []; + const result: string[] = []; let currentItem = ''; let depth = 0; let inString = false; @@ -1138,37 +1097,16 @@ function parseArrayExpression(expr: string): any[] { } } -export async function analyzeVueProps(options: Options & { - transformedCodeCache: Record<string, string>, -}): Promise<void> { - initLogger(options); - - const allMarkers: SearchIndexItem[] = []; - - // 対象ファイルパスを glob で展開 - const filePaths = options.targetFilePaths.reduce<string[]>((acc, filePathPattern) => { - const matchedFiles = glob.sync(filePathPattern); - return [...acc, ...matchedFiles]; - }, []); - - logger.info(`Found ${filePaths.length} matching files to analyze`); - - for (const filePath of filePaths) { - const absolutePath = path.join(process.cwd(), filePath); - const id = absolutePath.replace(/\\/g, '/'); // 絶対パスに変換 - const code = options.transformedCodeCache[id]; // options 経由でキャッシュ参照 - if (!code) { // キャッシュミスの場合 - logger.error(`Error: No cached code found for: ${id}.`); // エラーログ - throw new Error(`No cached code found for: ${id}.`); // エラーを投げる - } - +export function collectFileMarkers(files: [id: string, code: string][]): AnalysisResult<SearchIndexStringItem> { + const allMarkers: SearchIndexStringItem[] = []; + for (const [id, code] of files) { try { - const { descriptor, errors } = vueSfcParse(options.transformedCodeCache[id], { - filename: filePath, + const { descriptor, errors } = vueSfcParse(code, { + filename: id, }); if (errors.length > 0) { - logger.error(`Compile Error: ${filePath}, ${errors}`); + logger.error(`Compile Error: ${id}, ${errors}`); continue; // エラーが発生したファイルはスキップ } @@ -1176,83 +1114,76 @@ export async function analyzeVueProps(options: Options & { if (fileMarkers && fileMarkers.length > 0) { allMarkers.push(...fileMarkers); // すべてのマーカーを収集 - logger.info(`Successfully extracted ${fileMarkers.length} markers from ${filePath}`); + logger.info(`Successfully extracted ${fileMarkers.length} markers from ${id}`); } else { - logger.info(`No markers found in ${filePath}`); + logger.info(`No markers found in ${id}`); } } catch (error) { - logger.error(`Error analyzing file ${filePath}:`, error); + logger.error(`Error analyzing file ${id}:`, error); } } // 収集したすべてのマーカー情報を使用 - const analysisResult: AnalysisResult[] = [ - { - filePath: "combined-markers", // すべてのファイルのマーカーを1つのエントリとして扱う - usage: allMarkers, - } - ]; - - outputAnalysisResultAsTS(options.exportFilePath, analysisResult); // すべてのマーカー情報を渡す -} - -interface MarkerRelation { - parentId?: string; - markerId: string; - node: VueAstNode; + return { + filePath: "combined-markers", // すべてのファイルのマーカーを1つのエントリとして扱う + usage: allMarkers, + }; } -async function processVueFile( - code: string, - id: string, - options: Options, - transformedCodeCache: Record<string, string> -): Promise<{ +type TransformedCode = { code: string, - map: any, - transformedCodeCache: Record<string, string> -}> { - const normalizedId = id.replace(/\\/g, '/'); // ファイルパスを正規化 + map: SourceMap, +}; - // 開発モード時はコード内容に変更があれば常に再処理する - // コード内容が同じ場合のみキャッシュを使用 - const isDevMode = process.env.NODE_ENV === 'development'; +export class MarkerIdAssigner { + // key: file id + private cache: Map<string, TransformedCode>; - const s = new MagicString(code); // magic-string のインスタンスを作成 + constructor() { + this.cache = new Map(); + } - if (!isDevMode && transformedCodeCache[normalizedId] && transformedCodeCache[normalizedId].includes('markerId=')) { - logger.info(`Using cached version for ${id}`); - return { - code: transformedCodeCache[normalizedId], - map: s.generateMap({ source: id, includeContent: true }), - transformedCodeCache - }; + public onInvalidate(id: string) { + this.cache.delete(id); } - // すでに処理済みのファイルでコードに変更がない場合はキャッシュを返す - if (transformedCodeCache[normalizedId] === code) { - logger.info(`Code unchanged for ${id}, using cached version`); - return { - code: transformedCodeCache[normalizedId], - map: s.generateMap({ source: id, includeContent: true }), - transformedCodeCache - }; + public processFile(id: string, code: string): TransformedCode { + // try cache first + if (this.cache.has(id)) { + return this.cache.get(id)!; + } + const transformed = this.#processImpl(id, code); + this.cache.set(id, transformed); + return transformed; } - const parsed = vueSfcParse(code, { filename: id }); - if (!parsed.descriptor.template) { - return { - code, - map: s.generateMap({ source: id, includeContent: true }), - transformedCodeCache + #processImpl(id: string, code: string): TransformedCode { + const s = new MagicString(code); // magic-string のインスタンスを作成 + + const parsed = vueSfcParse(code, { filename: id }); + if (!parsed.descriptor.template) { + return { + code, + map: s.generateMap({ source: id, includeContent: true }), + }; + } + const ast = parsed.descriptor.template.ast; // テンプレート AST を取得 + const markerRelations: MarkerRelation[] = []; // MarkerRelation 配列を初期化 + + if (!ast) { + return { + code: s.toString(), // 変更後のコードを返す + map: s.generateMap({ source: id, includeContent: true }), // ソースマップも生成 (sourceMap: true が必要) + }; + } + + type SearchMarkerElementNode = ElementNode & { + __markerId?: string, + __children?: string[], }; - } - const ast = parsed.descriptor.template.ast; // テンプレート AST を取得 - const markerRelations: MarkerRelation[] = []; // MarkerRelation 配列を初期化 - if (ast) { - function traverse(node: any, currentParent?: any) { - if (node.type === 1 && node.tag === 'SearchMarker') { + function traverse(node: RootNode | TemplateChildNode | SimpleExpressionNode | CompoundExpressionNode, currentParent?: SearchMarkerElementNode) { + if (node.type === NodeTypes.ELEMENT && node.tag === 'SearchMarker') { // 行番号はコード先頭からの改行数で取得 const lineNumber = code.slice(0, node.loc.start.offset).split('\n').length; // ファイルパスと行番号からハッシュ値を生成 @@ -1261,14 +1192,14 @@ async function processVueFile( const generatedMarkerId = toBase62(hash(`${idKey}:${lineNumber}`)); const props = node.props || []; - const hasMarkerIdProp = props.some((prop: any) => prop.type === 6 && prop.name === 'markerId'); + const hasMarkerIdProp = props.some((prop) => prop.type === NodeTypes.ATTRIBUTE && prop.name === 'markerId'); const nodeMarkerId = hasMarkerIdProp - ? props.find((prop: any) => prop.type === 6 && prop.name === 'markerId')?.value?.content as string + ? props.find((prop): prop is AttributeNode => prop.type === NodeTypes.ATTRIBUTE && prop.name === 'markerId')?.value?.content as string : generatedMarkerId; - node.__markerId = nodeMarkerId; + (node as SearchMarkerElementNode).__markerId = nodeMarkerId; // 子マーカーの場合、親ノードに __children を設定しておく - if (currentParent && currentParent.type === 1 && currentParent.tag === 'SearchMarker') { + if (currentParent) { currentParent.__children = currentParent.__children || []; currentParent.__children.push(nodeMarkerId); } @@ -1313,9 +1244,13 @@ async function processVueFile( } } - const newParent = node.type === 1 && node.tag === 'SearchMarker' ? node : currentParent; - if (node.children && Array.isArray(node.children)) { - node.children.forEach(child => traverse(child, newParent)); + const newParent: SearchMarkerElementNode | undefined = node.type === NodeTypes.ELEMENT && node.tag === 'SearchMarker' ? node : currentParent; + if ('children' in node) { + for (const child of node.children) { + if (typeof child == 'object') { + traverse(child, newParent); + } + } } } @@ -1341,7 +1276,11 @@ async function processVueFile( if (!parentRelation || !parentRelation.node) continue; const parentNode = parentRelation.node; - const childrenProp = parentNode.props?.find((prop: any) => prop.type === 7 && prop.name === 'bind' && prop.arg?.content === 'children'); + const childrenProp = parentNode.props?.find((prop): prop is DirectiveNode => + prop.type === NodeTypes.DIRECTIVE && + prop.name === 'bind' && + prop.arg?.type === NodeTypes.SIMPLE_EXPRESSION && + prop.arg.content === 'children'); // 親ノードの開始位置を特定 const parentNodeStart = parentNode.loc!.start.offset; @@ -1416,53 +1355,64 @@ async function processVueFile( } } } + + return { + code: s.toString(), // 変更後のコードを返す + map: s.generateMap({ source: id, includeContent: true }), // ソースマップも生成 (sourceMap: true が必要) + }; } - const transformedCode = s.toString(); // 変換後のコードを取得 - transformedCodeCache[normalizedId] = transformedCode; // 変換後のコードをキャッシュに保存 + async getOrLoad(id: string) { + // if there already exists a cache, return it + // note cahce will be invalidated on file change so the cache must be up to date + let code = this.getCached(id)?.code; + if (code != null) { + return code; + } - return { - code: transformedCode, // 変更後のコードを返す - map: s.generateMap({ source: id, includeContent: true }), // ソースマップも生成 (sourceMap: true が必要) - transformedCodeCache // キャッシュも返す - }; -} + // if no cache found, read and parse the file + const originalCode = await fs.promises.readFile(id, 'utf-8'); -export async function generateSearchIndex(options: Options, transformedCodeCache: Record<string, string> = {}) { - const filePaths = options.targetFilePaths.reduce<string[]>((acc, filePathPattern) => { - const matchedFiles = glob.sync(filePathPattern); - return [...acc, ...matchedFiles]; - }, []); + // Other code may already parsed the file while we were waiting for the file to be read so re-check the cache + code = this.getCached(id)?.code; + if (code != null) { + return code; + } - for (const filePath of filePaths) { - const id = path.resolve(filePath); // 絶対パスに変換 - const code = fs.readFileSync(filePath, 'utf-8'); // ファイル内容を読み込む - const { transformedCodeCache: newCache } = await processVueFile(code, id, options, transformedCodeCache); // processVueFile 関数を呼び出す - transformedCodeCache = newCache; // キャッシュを更新 + // parse the file + code = this.processFile(id, originalCode)?.code; + return code; } - await analyzeVueProps({ ...options, transformedCodeCache }); // 開発サーバー起動時にも analyzeVueProps を実行 - - return transformedCodeCache; // キャッシュを返す + getCached(id: string) { + return this.cache.get(id); + } } // Rollup プラグインとして export -export default function pluginCreateSearchIndex(options: Options): Plugin { - let transformedCodeCache: Record<string, string> = {}; // キャッシュオブジェクトをプラグインスコープで定義 - const isDevServer = process.env.NODE_ENV === 'development'; // 開発サーバーかどうか +export default function pluginCreateSearchIndex(options: Options): PluginOption { + const assigner = new MarkerIdAssigner(); + return [ + createSearchIndex(options, assigner), + pluginCreateSearchIndexVirtualModule(options, assigner), + ] +} +function createSearchIndex(options: Options, assigner: MarkerIdAssigner): Plugin { initLogger(options); // ロガーを初期化 + const root = normalizePath(process.cwd()); + + function isTargetFile(id: string): boolean { + const relativePath = path.posix.relative(root, id); + return options.targetFilePaths.some(pat => minimatch(relativePath, pat)) + } return { - name: 'createSearchIndex', + name: 'autoAssignMarkerId', enforce: 'pre', - async buildStart() { - if (!isDevServer) { - return; - } - - transformedCodeCache = await generateSearchIndex(options, transformedCodeCache); + watchChange(id) { + assigner.onInvalidate(id); }, async transform(code, id) { @@ -1470,43 +1420,88 @@ export default function pluginCreateSearchIndex(options: Options): Plugin { return; } - // targetFilePaths にマッチするファイルのみ処理を行う - // glob パターンでマッチング - let isMatch = false; // isMatch の初期値を false に設定 - for (const pattern of options.targetFilePaths) { // パターンごとにマッチング確認 - const globbedFiles = glob.sync(pattern); - for (const globbedFile of globbedFiles) { - const normalizedGlobbedFile = path.resolve(globbedFile); // glob 結果を絶対パスに - const normalizedId = path.resolve(id); // id を絶対パスに - if (normalizedGlobbedFile === normalizedId) { // 絶対パス同士で比較 - isMatch = true; - break; // マッチしたらループを抜ける - } - } - if (isMatch) break; // いずれかのパターンでマッチしたら、outer loop も抜ける - } - - if (!isMatch) { + if (!isTargetFile(id)) { return; } - // ファイルの内容が変更された場合は再処理を行う - const normalizedId = id.replace(/\\/g, '/'); - const hasContentChanged = !transformedCodeCache[normalizedId] || transformedCodeCache[normalizedId] !== code; + return assigner.processFile(id, code); + }, + }; +} + +export function pluginCreateSearchIndexVirtualModule(options: Options, asigner: MarkerIdAssigner): Plugin { + const searchIndexPrefix = options.fileVirtualModulePrefix ?? 'search-index-individual:'; + const searchIndexSuffix = options.fileVirtualModuleSuffix ?? '.ts'; + const allSearchIndexFile = options.mainVirtualModule; + const root = normalizePath(process.cwd()); + + function isTargetFile(id: string): boolean { + const relativePath = path.posix.relative(root, id); + return options.targetFilePaths.some(pat => minimatch(relativePath, pat)) + } + + function parseSearchIndexFileId(id: string): string | null { + const noQuery = id.split('?')[0]; + if (noQuery.startsWith(searchIndexPrefix) && noQuery.endsWith(searchIndexSuffix)) { + const filePath = id.slice(searchIndexPrefix.length).slice(0, -searchIndexSuffix.length); + if (isTargetFile(filePath)) { + return filePath; + } + } + return null; + } - const transformed = await processVueFile(code, id, options, transformedCodeCache); - transformedCodeCache = transformed.transformedCodeCache; // キャッシュを更新 + return { + name: 'generateSearchIndexVirtualModule', + // hotUpdate hook を vite:vue よりもあとに実行したいため enforce: post + enforce: 'post', - if (isDevServer && hasContentChanged) { - await analyzeVueProps({ ...options, transformedCodeCache }); // ファイルが変更されたときのみ分析を実行 + async resolveId(id) { + if (id == allSearchIndexFile) { + return '\0' + allSearchIndexFile; } - return transformed; + const searchIndexFilePath = parseSearchIndexFileId(id); + if (searchIndexFilePath != null) { + return id; + } + return undefined; }, - async writeBundle() { - await analyzeVueProps({ ...options, transformedCodeCache }); // ビルド時にも analyzeVueProps を実行 + async load(id) { + if (id == '\0' + allSearchIndexFile) { + const files = await Promise.all(options.targetFilePaths.map(async (filePathPattern) => await glob(filePathPattern))).then(paths => paths.flat()); + let generatedFile = ''; + let arrayElements = ''; + for (let file of files) { + const normalizedRelative = normalizePath(file); + const absoluteId = normalizePath(path.join(process.cwd(), normalizedRelative)) + searchIndexSuffix; + const variableName = normalizedRelative.replace(/[\/.-]/g, '_'); + generatedFile += `import { searchIndexes as ${variableName} } from '${searchIndexPrefix}${absoluteId}';\n`; + arrayElements += ` ...${variableName},\n`; + } + generatedFile += `export let searchIndexes = [\n${arrayElements}];\n`; + return generatedFile; + } + + const searchIndexFilePath = parseSearchIndexFileId(id); + if (searchIndexFilePath != null) { + // call load to update the index file when the file is changed + this.addWatchFile(searchIndexFilePath); + + const code = await asigner.getOrLoad(searchIndexFilePath); + return generateJavaScriptCode(collectSearchItemIndexes([collectFileMarkers([[id, code]])])); + } + return null; }, + + hotUpdate(this: { environment: { moduleGraph: EnvironmentModuleGraph } }, { file, modules }) { + if (isTargetFile(file)) { + const updateMods = options.modulesToHmrOnUpdate.map(id => this.environment.moduleGraph.getModuleById(path.posix.join(root, id))).filter(x => x != null); + return [...modules, ...updateMods]; + } + return modules; + } }; } diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 01dcf09d47..156e6abea2 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -5,7 +5,6 @@ "scripts": { "watch": "vite", "build": "vite build", - "build-search-index": "vite-node --config \"./vite-node.config.ts\" \"./scripts/generate-search-index.ts\"", "storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"", "build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js", "build-storybook": "pnpm build-storybook-pre && storybook build --webpack-stats-json storybook-static", @@ -115,6 +114,7 @@ "@typescript-eslint/eslint-plugin": "8.27.0", "@typescript-eslint/parser": "8.27.0", "@vitest/coverage-v8": "3.0.9", + "@vue/compiler-core": "3.5.13", "@vue/runtime-core": "3.5.13", "acorn": "8.14.1", "cross-env": "7.0.3", @@ -125,6 +125,7 @@ "happy-dom": "17.4.4", "intersection-observer": "0.12.2", "micromatch": "4.0.8", + "minimatch": "10.0.1", "msw": "2.7.3", "msw-storybook-addon": "2.0.4", "nodemon": "3.1.9", diff --git a/packages/frontend/scripts/generate-search-index.ts b/packages/frontend/scripts/generate-search-index.ts deleted file mode 100644 index cbb4bb8c51..0000000000 --- a/packages/frontend/scripts/generate-search-index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { searchIndexes } from '../vite.config.js'; -import { generateSearchIndex } from '../lib/vite-plugin-create-search-index.js'; - -async function main() { - for (const searchIndex of searchIndexes) { - await generateSearchIndex(searchIndex); - } -} - -main(); diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index a094718382..4156fa2732 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -94,7 +94,7 @@ export type SuperMenuDef = { <script lang="ts" setup> import { useTemplateRef, ref, watch, nextTick } from 'vue'; -import type { SearchIndexItem } from '@/utility/autogen/settings-search-index.js'; +import type { SearchIndexItem } from '@/utility/settings-search-index.js'; import MkInput from '@/components/MkInput.vue'; import { i18n } from '@/i18n.js'; import { getScrollContainer } from '@@/js/scroll.js'; diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index 4ed4cdc773..5921a8c812 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -42,7 +42,7 @@ import { instance } from '@/instance.js'; import { definePage, provideMetadataReceiver, provideReactiveMetadata } from '@/page.js'; import * as os from '@/os.js'; import { useRouter } from '@/router.js'; -import { searchIndexes } from '@/utility/autogen/settings-search-index.js'; +import { searchIndexes } from '@/utility/settings-search-index.js'; import { enableAutoBackup, getPreferencesProfileMenu } from '@/preferences/utility.js'; import { store } from '@/store.js'; import { signout } from '@/signout.js'; diff --git a/packages/frontend/src/utility/autogen/settings-search-index.ts b/packages/frontend/src/utility/autogen/settings-search-index.ts deleted file mode 100644 index 7f800d2b70..0000000000 --- a/packages/frontend/src/utility/autogen/settings-search-index.ts +++ /dev/null @@ -1,993 +0,0 @@ - -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -// This file was automatically generated by create-search-index. -// Do not edit this file. - -import { i18n } from '@/i18n.js'; - -export type SearchIndexItem = { - id: string; - path?: string; - label: string; - keywords: string[]; - icon?: string; - children?: SearchIndexItem[]; -}; - -export const searchIndexes: SearchIndexItem[] = [ - { - id: 'flXd1LC7r', - children: [ - { - id: 'hB11H5oul', - label: i18n.ts.syncDeviceDarkMode, - keywords: ['sync', 'device', 'dark', 'light', 'mode'], - }, - { - id: 'fDbLtIKeo', - label: i18n.ts.themeForLightMode, - keywords: ['light', 'theme'], - }, - { - id: 'CsSVILKpX', - label: i18n.ts.themeForDarkMode, - keywords: ['dark', 'theme'], - }, - { - id: 'jwW5HULqA', - label: i18n.ts._settings.enableSyncThemesBetweenDevices, - keywords: ['sync', 'themes', 'devices'], - }, - ], - label: i18n.ts.theme, - keywords: ['theme'], - path: '/settings/theme', - icon: 'ti ti-palette', - }, - { - id: '6fFIRXUww', - children: [ - { - id: 'EcwZE7dCl', - label: i18n.ts.notUseSound, - keywords: ['mute'], - }, - { - id: '9MxYVIf7k', - label: i18n.ts.useSoundOnlyWhenActive, - keywords: ['active', 'mute'], - }, - { - id: '94afQxKat', - label: i18n.ts.masterVolume, - keywords: ['volume', 'master'], - }, - ], - label: i18n.ts.sounds, - keywords: ['sounds', i18n.ts._settings.soundsBanner], - path: '/settings/sounds', - icon: 'ti ti-music', - }, - { - id: '5BjnxMfYV', - children: [ - { - id: '75QPEg57v', - children: [ - { - id: 'CiHijRkGG', - label: i18n.ts.changePassword, - keywords: [], - }, - ], - label: i18n.ts.password, - keywords: ['password'], - }, - { - id: '2fa', - children: [ - { - id: 'qCXM0HtJ7', - label: i18n.ts.totp, - keywords: ['totp', 'app', i18n.ts.totpDescription], - }, - { - id: '3g1RePuD9', - label: i18n.ts.securityKeyAndPasskey, - keywords: ['security', 'key', 'passkey'], - }, - { - id: 'pFRud5u8k', - label: i18n.ts.passwordLessLogin, - keywords: ['password', 'less', 'key', 'passkey', 'login', 'signin', i18n.ts.passwordLessLoginDescription], - }, - ], - label: i18n.ts['2fa'], - keywords: ['2fa'], - }, - ], - label: i18n.ts.security, - keywords: ['security', i18n.ts._settings.securityBanner], - path: '/settings/security', - icon: 'ti ti-lock', - }, - { - id: 'w4L6myH61', - children: [ - { - id: 'ru8DrOn3J', - label: i18n.ts._profile.changeBanner, - keywords: ['banner', 'change'], - }, - { - id: 'CCnD8Apnu', - label: i18n.ts._profile.changeAvatar, - keywords: ['avatar', 'icon', 'change'], - }, - { - id: 'yFEVCJxFX', - label: i18n.ts._profile.name, - keywords: ['name'], - }, - { - id: '2O1S5reaB', - label: i18n.ts._profile.description, - keywords: ['description', 'bio'], - }, - { - id: 'pWi4OLS8g', - label: i18n.ts.location, - keywords: ['location', 'locale'], - }, - { - id: 'oLO5X6Wtw', - label: i18n.ts.birthday, - keywords: ['birthday', 'birthdate', 'age'], - }, - { - id: 'm2trKwPgq', - label: i18n.ts.language, - keywords: ['language', 'locale'], - }, - { - id: 'kfDZxCDp9', - label: i18n.ts._profile.metadataEdit, - keywords: ['metadata'], - }, - { - id: 'uPt3MFymp', - label: i18n.ts._profile.followedMessage, - keywords: ['follow', 'message', i18n.ts._profile.followedMessageDescription], - }, - { - id: 'wuGg0tBjw', - label: i18n.ts.reactionAcceptance, - keywords: ['reaction'], - }, - { - id: 'EezPpmMnf', - children: [ - { - id: 'f2cRLh8ad', - label: i18n.ts.flagAsCat, - keywords: ['cat'], - }, - { - id: 'eVoViiF3h', - label: i18n.ts.flagAsBot, - keywords: ['bot'], - }, - ], - label: i18n.ts.advancedSettings, - keywords: [], - }, - ], - label: i18n.ts.profile, - keywords: ['profile'], - path: '/settings/profile', - icon: 'ti ti-user', - }, - { - id: '2rp9ka5Ht', - children: [ - { - id: 'BhAQiHogN', - label: i18n.ts.makeFollowManuallyApprove, - keywords: ['follow', 'lock', i18n.ts.lockedAccountInfo], - }, - { - id: '4DeWGsPaD', - label: i18n.ts.autoAcceptFollowed, - keywords: ['follow', 'auto', 'accept'], - }, - { - id: 'iaM6zUmO9', - label: i18n.ts.makeReactionsPublic, - keywords: ['reaction', 'public', i18n.ts.makeReactionsPublicDescription], - }, - { - id: '5Q6uhghzV', - label: i18n.ts.followingVisibility, - keywords: ['following', 'visibility'], - }, - { - id: 'pZ9q65FX5', - label: i18n.ts.followersVisibility, - keywords: ['follower', 'visibility'], - }, - { - id: 'DMS4yvAGg', - label: i18n.ts.hideOnlineStatus, - keywords: ['online', 'status', i18n.ts.hideOnlineStatusDescription], - }, - { - id: '8rEsGuN8w', - label: i18n.ts.noCrawle, - keywords: ['crawle', 'index', 'search', i18n.ts.noCrawleDescription], - }, - { - id: 's7LdSpiLn', - label: i18n.ts.preventAiLearning, - keywords: ['crawle', 'ai', i18n.ts.preventAiLearningDescription], - }, - { - id: 'l2Wf1s2ad', - label: i18n.ts.makeExplorable, - keywords: ['explore', i18n.ts.makeExplorableDescription], - }, - { - id: 'xEYlOghao', - label: i18n.ts._chat.chatAllowedUsers, - keywords: ['chat'], - }, - { - id: 'BnOtlyaAh', - children: [ - { - id: 'BzMIVBpL0', - label: i18n.ts._accountSettings.requireSigninToViewContents, - keywords: ['login', 'signin'], - }, - { - id: 'jJUqPqBAv', - label: i18n.ts._accountSettings.makeNotesFollowersOnlyBefore, - keywords: ['follower', i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription], - }, - { - id: 'ra10txIFV', - label: i18n.ts._accountSettings.makeNotesHiddenBefore, - keywords: ['hidden', i18n.ts._accountSettings.makeNotesHiddenBeforeDescription], - }, - ], - label: i18n.ts.lockdown, - keywords: ['lockdown'], - }, - ], - label: i18n.ts.privacy, - keywords: ['privacy', i18n.ts._settings.privacyBanner], - path: '/settings/privacy', - icon: 'ti ti-lock-open', - }, - { - id: '3yCAv0IsZ', - children: [ - { - id: 'AKvDrxSj5', - children: [ - { - id: 'a5b9RjEvq', - label: i18n.ts.uiLanguage, - keywords: ['language'], - }, - { - id: '9ragaff40', - label: i18n.ts.overridedDeviceKind, - keywords: ['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop'], - }, - { - id: 'lfI3yMX9g', - label: i18n.ts.showAvatarDecorations, - keywords: ['avatar', 'icon', 'decoration', 'show'], - }, - { - id: '31Y4IcGEf', - label: i18n.ts.alwaysConfirmFollow, - keywords: ['follow', 'confirm', 'always'], - }, - { - id: '78q2asrLS', - label: i18n.ts.highlightSensitiveMedia, - keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'], - }, - { - id: 'zydOfGYip', - label: i18n.ts.confirmWhenRevealingSensitiveMedia, - keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'], - }, - { - id: 'wqpOC22Zm', - label: i18n.ts.enableAdvancedMfm, - keywords: ['mfm', 'enable', 'show', 'advanced'], - }, - { - id: 'c98gbF9c6', - label: i18n.ts.enableInfiniteScroll, - keywords: ['auto', 'load', 'auto', 'more', 'scroll'], - }, - { - id: '6ANRSOaNg', - label: i18n.ts.emojiStyle, - keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'], - }, - ], - label: i18n.ts.general, - keywords: ['general'], - }, - { - id: '5G6O6qdis', - children: [ - { - id: 'khT3n6byY', - label: i18n.ts.showFixedPostForm, - keywords: ['post', 'form', 'timeline'], - }, - { - id: 'q5ElfNSou', - label: i18n.ts.showFixedPostFormInChannel, - keywords: ['post', 'form', 'timeline', 'channel'], - }, - { - id: '3GcWIaZf8', - label: i18n.ts.collapseRenotes, - keywords: ['renote', i18n.ts.collapseRenotesDescription], - }, - { - id: 'd2H4E5ys6', - label: i18n.ts.showGapBetweenNotesInTimeline, - keywords: ['note', 'timeline', 'gap'], - }, - { - id: '1LHOhDKGW', - label: i18n.ts.disableStreamingTimeline, - keywords: ['disable', 'streaming', 'timeline'], - }, - { - id: 'DSzwvTp7i', - label: i18n.ts.pinnedList, - keywords: ['pinned', 'list'], - }, - { - id: 'ykifk3NHS', - label: i18n.ts.showNoteActionsOnlyHover, - keywords: ['hover', 'show', 'footer', 'action'], - }, - { - id: 'tLGyaQagB', - label: i18n.ts.showClipButtonInNoteFooter, - keywords: ['footer', 'action', 'clip', 'show'], - }, - { - id: '7W6g8Dcqz', - label: i18n.ts.showReactionsCount, - keywords: ['reaction', 'count', 'show'], - }, - { - id: 'uAOoH3LFF', - label: i18n.ts.confirmOnReact, - keywords: ['reaction', 'confirm'], - }, - { - id: 'eCiyZLC8n', - label: i18n.ts.loadRawImages, - keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment'], - }, - { - id: '68u9uRmFP', - label: i18n.ts.useReactionPickerForContextMenu, - keywords: ['reaction', 'picker', 'contextmenu', 'open'], - }, - { - id: 'yxehrHZ6x', - label: i18n.ts.reactionsDisplaySize, - keywords: ['reaction', 'size', 'scale', 'display'], - }, - { - id: 'gi8ILaE2Z', - label: i18n.ts.limitWidthOfReaction, - keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'], - }, - { - id: 'cEQJZ7DQG', - label: i18n.ts.mediaListWithOneImageAppearance, - keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'], - }, - { - id: 'haX4QVulD', - label: i18n.ts.instanceTicker, - keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'], - }, - { - id: 'pneYnQekL', - label: i18n.ts.displayOfSensitiveMedia, - keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'], - }, - ], - label: i18n.ts._settings.timelineAndNote, - keywords: ['timeline', 'note'], - }, - { - id: 'eJ2jme16W', - children: [ - { - id: 'ErMQr6LQk', - label: i18n.ts.keepCw, - keywords: ['remember', 'keep', 'note', 'cw'], - }, - { - id: 'zrJicawH9', - label: i18n.ts.rememberNoteVisibility, - keywords: ['remember', 'keep', 'note', 'visibility'], - }, - { - id: 'BaQfrVO82', - label: i18n.ts.enableQuickAddMfmFunction, - keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'], - }, - { - id: 'C2WYcVM1d', - label: i18n.ts.defaultNoteVisibility, - keywords: ['default', 'note', 'visibility'], - }, - ], - label: i18n.ts.postForm, - keywords: ['post', 'form'], - }, - { - id: 'sQXSA6gik', - children: [ - { - id: 'rICn8stqk', - label: i18n.ts.useGroupedNotifications, - keywords: ['group'], - }, - { - id: 'xFmAg2tDe', - label: i18n.ts.position, - keywords: ['position'], - }, - { - id: 'Ek4Cw3VPq', - label: i18n.ts.stackAxis, - keywords: ['stack', 'axis', 'direction'], - }, - ], - label: i18n.ts.notifications, - keywords: ['notification'], - }, - { - id: 'gDVCqZfxm', - children: [ - { - id: 'ei8Ix3s4S', - label: i18n.ts._settings._chat.showSenderName, - keywords: ['show', 'sender', 'name'], - }, - { - id: '2E7vdIUQd', - label: i18n.ts._settings._chat.sendOnEnter, - keywords: ['send', 'enter', 'newline'], - }, - ], - label: i18n.ts.chat, - keywords: ['chat', 'messaging'], - }, - { - id: '96LnS1sxB', - children: [ - { - id: 'vPQPvmntL', - label: i18n.ts.reduceUiAnimation, - keywords: ['animation', 'motion', 'reduce'], - }, - { - id: 'wfJ91vwzq', - label: i18n.ts.disableShowingAnimatedImages, - keywords: ['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif'], - }, - { - id: '42b1L4xdq', - label: i18n.ts.enableAnimatedMfm, - keywords: ['mfm', 'enable', 'show', 'animated'], - }, - { - id: 'dLkRNHn3k', - label: i18n.ts.enableHorizontalSwipe, - keywords: ['swipe', 'horizontal', 'tab'], - }, - { - id: 'BvooTWFW5', - label: i18n.ts.keepScreenOn, - keywords: ['keep', 'screen', 'display', 'on'], - }, - { - id: 'yzbghkAq0', - label: i18n.ts.useNativeUIForVideoAudioPlayer, - keywords: ['native', 'system', 'video', 'audio', 'player', 'media'], - }, - { - id: 'aSbKFHbOy', - label: i18n.ts._settings.makeEveryTextElementsSelectable, - keywords: ['text', 'selectable'], - }, - { - id: 'bTcAsPvNz', - label: i18n.ts.menuStyle, - keywords: ['menu', 'style', 'popup', 'drawer'], - }, - { - id: 'lSVBaLnyW', - label: i18n.ts._contextMenu.title, - keywords: ['contextmenu', 'system', 'native'], - }, - { - id: 'pec0uMPq5', - label: i18n.ts.fontSize, - keywords: ['font', 'size'], - }, - { - id: 'Eh7vTluDO', - label: i18n.ts.useSystemFont, - keywords: ['font', 'system', 'native'], - }, - ], - label: i18n.ts.accessibility, - keywords: ['accessibility', i18n.ts._settings.accessibilityBanner], - }, - { - id: 'vTRSKf1JA', - children: [ - { - id: '2VjlA02wB', - label: i18n.ts.turnOffToImprovePerformance, - keywords: ['blur'], - }, - { - id: 'f6J0lmg1g', - label: i18n.ts.turnOffToImprovePerformance, - keywords: ['blur', 'modal'], - }, - { - id: 'hQqXhfNg8', - label: i18n.ts.turnOffToImprovePerformance, - keywords: ['sticky'], - }, - ], - label: i18n.ts.performance, - keywords: ['performance'], - }, - { - id: 'utM8dEobb', - label: i18n.ts.dataSaver, - keywords: ['datasaver'], - }, - { - id: 'gOUvwkE9t', - children: [ - { - id: 'iUMUvFURf', - label: i18n.ts.squareAvatars, - keywords: ['avatar', 'icon', 'square'], - }, - { - id: 'ceyPO9Ywi', - label: i18n.ts.seasonalScreenEffect, - keywords: ['effect', 'show'], - }, - { - id: 'ztwIlsXhP', - label: i18n.ts.openImageInNewTab, - keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'], - }, - { - id: 'vLSsQbZEo', - label: i18n.ts.withRepliesByDefaultForNewlyFollowed, - keywords: ['follow', 'replies'], - }, - { - id: 'hQt85bBIX', - label: i18n.ts.whenServerDisconnected, - keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'], - }, - { - id: 'C9SyK2m0', - label: i18n.ts.numberOfPageCache, - keywords: ['cache', 'page'], - }, - { - id: '2U0iVUtfW', - label: i18n.ts.forceShowAds, - keywords: ['ad', 'show'], - }, - { - id: '1rA7ADEXY', - label: i18n.ts.hemisphere, - keywords: [], - }, - { - id: 'vRayx89Rt', - label: i18n.ts.additionalEmojiDictionary, - keywords: ['emoji', 'dictionary', 'additional', 'extra'], - }, - ], - label: i18n.ts.other, - keywords: ['other'], - }, - ], - label: i18n.ts.preferences, - keywords: ['general', 'preferences', i18n.ts._settings.preferencesBanner], - path: '/settings/preferences', - icon: 'ti ti-adjustments', - }, - { - id: 'mwkwtw83Y', - label: i18n.ts.plugins, - keywords: ['plugin', 'addon', 'extension', i18n.ts._settings.pluginBanner], - path: '/settings/plugin', - icon: 'ti ti-plug', - }, - { - id: 'F1uK9ssiY', - children: [ - { - id: 'E0ndmaP6Q', - label: i18n.ts._role.policies, - keywords: ['account', 'info'], - }, - { - id: 'r5SjfwZJc', - label: i18n.ts.rolesAssignedToMe, - keywords: ['roles'], - }, - { - id: 'cm7LrjgaW', - label: i18n.ts.accountMigration, - keywords: ['account', 'move', 'migration'], - }, - { - id: 'ozfqNviP3', - label: i18n.ts.closeAccount, - keywords: ['account', 'close', 'delete', i18n.ts._accountDelete.requestAccountDelete], - }, - { - id: 'tpywgkpxy', - label: i18n.ts.experimentalFeatures, - keywords: ['experimental', 'feature', 'flags'], - }, - { - id: 'zWbGKohZ2', - label: i18n.ts.developer, - keywords: ['developer', 'mode', 'debug'], - }, - ], - label: i18n.ts.other, - keywords: ['other'], - path: '/settings/other', - icon: 'ti ti-dots', - }, - { - id: '9bNikHWzQ', - children: [ - { - id: 't6XtfnRm9', - label: i18n.ts._settings.showNavbarSubButtons, - keywords: ['navbar', 'sidebar', 'toggle', 'button', 'sub'], - }, - ], - label: i18n.ts.navbar, - keywords: ['navbar', 'menu', 'sidebar'], - path: '/settings/navbar', - icon: 'ti ti-list', - }, - { - id: '3icEvyv2D', - children: [ - { - id: 'lO3uFTkPN', - children: [ - { - id: '5JKaXRqyt', - label: i18n.ts.showMutedWord, - keywords: ['show'], - }, - ], - label: i18n.ts.wordMute, - keywords: ['note', 'word', 'soft', 'mute', 'hide'], - }, - { - id: 'fMkjL3dK4', - label: i18n.ts.hardWordMute, - keywords: ['note', 'word', 'hard', 'mute', 'hide'], - }, - { - id: 'cimSzQXN0', - label: i18n.ts.instanceMute, - keywords: ['note', 'server', 'instance', 'host', 'federation', 'mute', 'hide'], - }, - { - id: 'gq8rPy3Du', - label: `${i18n.ts.mutedUsers} (${ i18n.ts.renote })`, - keywords: ['renote', 'mute', 'hide', 'user'], - }, - { - id: 'mh2r7EUbF', - label: i18n.ts.mutedUsers, - keywords: ['note', 'mute', 'hide', 'user'], - }, - { - id: 'AUS1OgHrn', - label: i18n.ts.blockedUsers, - keywords: ['block', 'user'], - }, - ], - label: i18n.ts.muteAndBlock, - keywords: ['mute', 'block', i18n.ts._settings.muteAndBlockBanner], - path: '/settings/mute-block', - icon: 'ti ti-ban', - }, - { - id: 'yR1OSyLiT', - children: [ - { - id: 'yMJzyzOUk', - label: i18n.ts._emojiPalette.enableSyncBetweenDevicesForPalettes, - keywords: ['sync', 'palettes', 'devices'], - }, - { - id: 'wCE09vgZr', - label: i18n.ts._emojiPalette.paletteForMain, - keywords: ['main', 'palette'], - }, - { - id: 'uCzRPrSNx', - label: i18n.ts._emojiPalette.paletteForReaction, - keywords: ['reaction', 'palette'], - }, - { - id: 'hgQr28WUk', - children: [ - { - id: 'fY04NIHSQ', - label: i18n.ts.size, - keywords: ['emoji', 'picker', 'scale', 'size'], - }, - { - id: '3j7vlaL7t', - label: i18n.ts.numberOfColumn, - keywords: ['emoji', 'picker', 'width', 'column', 'size'], - }, - { - id: 'zPX8z1Bcy', - label: i18n.ts.height, - keywords: ['emoji', 'picker', 'height', 'size'], - }, - { - id: '2CSkZa4tl', - label: i18n.ts.style, - keywords: ['emoji', 'picker', 'style'], - }, - ], - label: i18n.ts.emojiPickerDisplay, - keywords: ['emoji', 'picker', 'display'], - }, - ], - label: i18n.ts.emojiPalette, - keywords: ['emoji', 'palette'], - path: '/settings/emoji-palette', - icon: 'ti ti-mood-happy', - }, - { - id: '3Tcxw4Fwl', - children: [ - { - id: 'iIai9O65I', - label: i18n.ts.emailAddress, - keywords: ['email', 'address'], - }, - { - id: 'i6cC6oi0m', - label: i18n.ts.receiveAnnouncementFromInstance, - keywords: ['announcement', 'email'], - }, - { - id: 'C1YTinP11', - label: i18n.ts.emailNotification, - keywords: ['notification', 'email'], - }, - ], - label: i18n.ts.email, - keywords: ['email'], - path: '/settings/email', - icon: 'ti ti-mail', - }, - { - id: 'tnYoppRiv', - children: [ - { - id: 'cN3dsGNxu', - label: i18n.ts.usageAmount, - keywords: ['capacity', 'usage'], - }, - { - id: 'rOAOU2P6C', - label: i18n.ts.statistics, - keywords: ['statistics', 'usage'], - }, - { - id: 'uXGlQXATx', - label: i18n.ts.uploadFolder, - keywords: ['default', 'upload', 'folder'], - }, - { - id: 'goQdtf3dD', - label: i18n.ts.keepOriginalFilename, - keywords: ['keep', 'original', 'filename', i18n.ts.keepOriginalFilenameDescription], - }, - { - id: '83xRo0XJl', - label: i18n.ts.alwaysMarkSensitive, - keywords: ['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file'], - }, - { - id: 'BrBqZL35E', - label: i18n.ts.enableAutoSensitive, - keywords: ['auto', 'nsfw', 'sensitive', 'media', 'file', i18n.ts.enableAutoSensitiveDescription], - }, - ], - label: i18n.ts.drive, - keywords: ['drive', i18n.ts._settings.driveBanner], - path: '/settings/drive', - icon: 'ti ti-cloud', - }, - { - id: 'FfZdOs8y', - children: [ - { - id: 'B1ZU6Ur54', - label: i18n.ts._deck.enableSyncBetweenDevicesForProfiles, - keywords: ['sync', 'profiles', 'devices'], - }, - { - id: 'wWH4pxMQN', - label: i18n.ts._deck.useSimpleUiForNonRootPages, - keywords: ['ui', 'root', 'page'], - }, - { - id: '3LR509BvD', - label: i18n.ts.defaultNavigationBehaviour, - keywords: ['default', 'navigation', 'behaviour', 'window'], - }, - { - id: 'ybU8RLXgm', - label: i18n.ts._deck.alwaysShowMainColumn, - keywords: ['always', 'show', 'main', 'column'], - }, - { - id: 'xRasZyAVl', - label: i18n.ts._deck.columnAlign, - keywords: ['column', 'align'], - }, - { - id: '6qcyPd0oJ', - label: i18n.ts._deck.deckMenuPosition, - keywords: ['menu', 'position'], - }, - { - id: '4zk2Now4S', - label: i18n.ts._deck.navbarPosition, - keywords: ['navbar', 'position'], - }, - { - id: 'CGNtJ2I3n', - label: i18n.ts._deck.columnGap, - keywords: ['column', 'gap', 'margin'], - }, - { - id: 'rxPDMo7bE', - label: i18n.ts.setWallpaper, - keywords: ['wallpaper'], - }, - ], - label: i18n.ts.deck, - keywords: ['deck', 'ui'], - path: '/settings/deck', - icon: 'ti ti-columns', - }, - { - id: 'BlJ2rsw9h', - children: [ - { - id: '9bLU1nIjt', - label: i18n.ts._settings.api, - keywords: ['api', 'app', 'token', 'accessToken'], - }, - { - id: '5VSGOVYR0', - label: i18n.ts._settings.webhook, - keywords: ['webhook'], - }, - ], - label: i18n.ts._settings.serviceConnection, - keywords: ['app', 'service', 'connect', 'webhook', 'api', 'token', i18n.ts._settings.serviceConnectionBanner], - path: '/settings/connect', - icon: 'ti ti-link', - }, - { - id: 'gtaOSdIJB', - label: i18n.ts.avatarDecorations, - keywords: ['avatar', 'icon', 'decoration'], - path: '/settings/avatar-decoration', - icon: 'ti ti-sparkles', - }, - { - id: 'zK6posor9', - label: i18n.ts.accounts, - keywords: ['accounts'], - path: '/settings/accounts', - icon: 'ti ti-users', - }, - { - id: '330Q4mf8E', - children: [ - { - id: 'eGSjUDIKu', - label: i18n.ts._exportOrImport.allNotes, - keywords: ['notes'], - }, - { - id: 'iMDgUVgRu', - label: i18n.ts._exportOrImport.favoritedNotes, - keywords: ['favorite', 'notes'], - }, - { - id: '3y6KgkVbT', - label: i18n.ts._exportOrImport.clips, - keywords: ['clip', 'notes'], - }, - { - id: 'cKiHkj8HE', - label: i18n.ts._exportOrImport.followingList, - keywords: ['following', 'users'], - }, - { - id: '3zzmQXn0t', - label: i18n.ts._exportOrImport.userLists, - keywords: ['user', 'lists'], - }, - { - id: '3ZGXcEqWZ', - label: i18n.ts._exportOrImport.muteList, - keywords: ['mute', 'users'], - }, - { - id: '84oL7B1Dr', - label: i18n.ts._exportOrImport.blockingList, - keywords: ['block', 'users'], - }, - { - id: 'ckqi48Kbl', - label: i18n.ts.antennas, - keywords: ['antennas'], - }, - ], - label: i18n.ts._settings.accountData, - keywords: ['import', 'export', 'data', 'archive', i18n.ts._settings.accountDataBanner], - path: '/settings/account-data', - icon: 'ti ti-package', - }, -] as const; - -export type SearchIndex = typeof searchIndexes; diff --git a/packages/frontend/src/utility/settings-search-index.ts b/packages/frontend/src/utility/settings-search-index.ts new file mode 100644 index 0000000000..22e2407b15 --- /dev/null +++ b/packages/frontend/src/utility/settings-search-index.ts @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { searchIndexes as generated } from 'search-index:settings'; +import type { GeneratedSearchIndexItem } from 'search-index:settings'; + +export type SearchIndexItem = { + id: string; + path?: string; + label: string; + keywords: string[]; + icon?: string; + children?: SearchIndexItem[]; +}; + +const rootMods = new Map(generated.map(item => [item.id, item])); + +function walk(item: GeneratedSearchIndexItem) { + if (item.inlining) { + for (const id of item.inlining) { + const inline = rootMods.get(id); + if (inline) { + (item.children ??= []).push(inline); + rootMods.delete(id); + } else { + console.log('[Settings Search Index] Failed to inline', id); + } + } + } + + for (const child of item.children ?? []) { + walk(child); + } +} + +for (const item of generated) { + walk(item); +} + +export const searchIndexes: SearchIndexItem[] = generated; + diff --git a/packages/frontend/src/utility/virtual.d.ts b/packages/frontend/src/utility/virtual.d.ts new file mode 100644 index 0000000000..59470a1f5e --- /dev/null +++ b/packages/frontend/src/utility/virtual.d.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +declare module 'search-index:settings' { + export type GeneratedSearchIndexItem = { + id: string; + path?: string; + label: string; + keywords: string[]; + icon?: string; + inlining?: string[]; + children?: GeneratedSearchIndexItem[]; + }; + + export const searchIndexes: GeneratedSearchIndexItem[]; +} diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index ec80e71ae4..aa7bf24174 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -24,7 +24,8 @@ const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.s */ export const searchIndexes = [{ targetFilePaths: ['src/pages/settings/*.vue'], - exportFilePath: './src/utility/autogen/settings-search-index.ts', + mainVirtualModule: 'search-index:settings', + modulesToHmrOnUpdate: ['src/pages/settings/index.vue'], verbose: process.env.FRONTEND_SEARCH_INDEX_VERBOSE === 'true', }] satisfies SearchIndexOptions[]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1d60e0f05..0389ccc38b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ patchedDependencies: re2: hash: 018babd22b7ce951bcd10d6246f1e541a7ac7ba212f7fa8985e774ece67d08e1 path: scripts/dependency-patches/re2.patch + vite: + hash: cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29 + path: scripts/dependency-patches/vite.patch importers: @@ -726,7 +729,7 @@ importers: version: 15.1.1 '@vitejs/plugin-vue': specifier: 5.2.3 - version: 5.2.3(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2)) + version: 5.2.3(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2)) '@vue/compiler-sfc': specifier: 3.5.13 version: 3.5.13 @@ -864,7 +867,7 @@ importers: version: 1.13.1(vue@3.5.13(typescript@5.8.2)) vite: specifier: 6.2.4 - version: 6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) + version: 6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) vue: specifier: 3.5.13 version: 3.5.13(typescript@5.8.2) @@ -916,7 +919,7 @@ importers: version: 8.6.7(@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.2) '@storybook/react-vite': specifier: 8.6.7 - version: 8.6.7(@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.36.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.2)(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)) + version: 8.6.7(@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.36.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.2)(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)) '@storybook/test': specifier: 8.6.7 version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) @@ -931,7 +934,7 @@ importers: version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vue@3.5.13(typescript@5.8.2)) '@storybook/vue3-vite': specifier: 8.6.7 - version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2)) + version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2)) '@testing-library/vue': specifier: 8.1.0 version: 8.1.0(@vue/compiler-sfc@3.5.13)(@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2)) @@ -977,6 +980,9 @@ importers: '@vitest/coverage-v8': specifier: 3.0.9 version: 3.0.9(vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)) + '@vue/compiler-core': + specifier: 3.5.13 + version: 3.5.13 '@vue/runtime-core': specifier: 3.5.13 version: 3.5.13 @@ -1007,6 +1013,9 @@ importers: micromatch: specifier: 4.0.8 version: 4.0.8 + minimatch: + specifier: 10.0.1 + version: 10.0.1 msw: specifier: 2.7.3 version: 2.7.3(@types/node@22.13.11)(typescript@5.8.2) @@ -1081,7 +1090,7 @@ importers: version: 15.1.1 '@vitejs/plugin-vue': specifier: 5.2.3 - version: 5.2.3(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2)) + version: 5.2.3(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2)) '@vue/compiler-sfc': specifier: 3.5.13 version: 3.5.13 @@ -1135,7 +1144,7 @@ importers: version: 11.1.0 vite: specifier: 6.2.4 - version: 6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) + version: 6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) vue: specifier: 3.5.13 version: 3.5.13(typescript@5.8.2) @@ -1723,10 +1732,6 @@ packages: resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.7': - resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.8': resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} engines: {node: '>=6.9.0'} @@ -1751,11 +1756,6 @@ packages: resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.24.7': - resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.25.6': resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} engines: {node: '>=6.0.0'} @@ -1850,10 +1850,6 @@ packages: resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} engines: {node: '>=6.9.0'} - '@babel/types@7.24.7': - resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} - engines: {node: '>=6.9.0'} - '@babel/types@7.25.6': resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} engines: {node: '>=6.9.0'} @@ -11836,8 +11832,6 @@ snapshots: dependencies: '@babel/types': 7.25.6 - '@babel/helper-string-parser@7.24.7': {} - '@babel/helper-string-parser@7.24.8': {} '@babel/helper-validator-identifier@7.24.7': {} @@ -11864,10 +11858,6 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/parser@7.24.7': - dependencies: - '@babel/types': 7.25.6 - '@babel/parser@7.25.6': dependencies: '@babel/types': 7.25.6 @@ -11973,12 +11963,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.24.7': - dependencies: - '@babel/helper-string-parser': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - '@babel/types@7.25.6': dependencies: '@babel/helper-string-parser': 7.24.8 @@ -12848,12 +12832,12 @@ snapshots: '@types/yargs': 17.0.19 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.8.2)(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.8.2)(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))': dependencies: glob: 10.4.5 magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.8.2) - vite: 6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) + vite: 6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) optionalDependencies: typescript: 5.8.2 @@ -14369,13 +14353,13 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - '@storybook/builder-vite@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))': + '@storybook/builder-vite@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))': dependencies: '@storybook/csf-plugin': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) browser-assert: 1.2.1 storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) ts-dedent: 2.2.0 - vite: 6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) + vite: 6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) '@storybook/components@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))': dependencies: @@ -14438,11 +14422,11 @@ snapshots: react-dom: 19.0.0(react@19.0.0) storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) - '@storybook/react-vite@8.6.7(@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.36.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.2)(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))': + '@storybook/react-vite@8.6.7(@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.36.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.2)(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.8.2)(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.8.2)(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)) '@rollup/pluginutils': 5.1.4(rollup@4.36.0) - '@storybook/builder-vite': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)) + '@storybook/builder-vite': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)) '@storybook/react': 8.6.7(@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.2) find-up: 5.0.0 magic-string: 0.30.17 @@ -14452,7 +14436,7 @@ snapshots: resolve: 1.22.8 storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) tsconfig-paths: 4.2.0 - vite: 6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) + vite: 6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) optionalDependencies: '@storybook/test': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)) transitivePeerDependencies: @@ -14501,15 +14485,15 @@ snapshots: dependencies: storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) - '@storybook/vue3-vite@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2))': + '@storybook/vue3-vite@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2))': dependencies: - '@storybook/builder-vite': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)) + '@storybook/builder-vite': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)) '@storybook/vue3': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vue@3.5.13(typescript@5.8.2)) find-package-json: 1.2.0 magic-string: 0.30.17 storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5) typescript: 5.8.2 - vite: 6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) + vite: 6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) vue-component-meta: 2.0.16(typescript@5.8.2) vue-docgen-api: 4.75.1(vue@3.5.13(typescript@5.8.2)) transitivePeerDependencies: @@ -15350,9 +15334,9 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-vue@5.2.3(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2))': + '@vitejs/plugin-vue@5.2.3(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2))': dependencies: - vite: 6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) + vite: 6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) vue: 3.5.13(typescript@5.8.2) '@vitest/coverage-v8@3.0.9(vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))': @@ -15387,14 +15371,14 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.0.9(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))': + '@vitest/mocker@3.0.9(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))': dependencies: '@vitest/spy': 3.0.9 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: msw: 2.7.3(@types/node@22.13.11)(typescript@5.8.2) - vite: 6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) + vite: 6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) '@vitest/pretty-format@2.0.5': dependencies: @@ -16554,8 +16538,8 @@ snapshots: constantinople@4.0.1: dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 content-disposition@0.5.4: dependencies: @@ -19134,7 +19118,7 @@ snapshots: '@babel/generator': 7.23.5 '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.5) '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.5) - '@babel/types': 7.24.7 + '@babel/types': 7.25.6 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 @@ -22580,7 +22564,7 @@ snapshots: debug: 4.4.0(supports-color@8.1.1) es-module-lexer: 1.6.0 pathe: 2.0.3 - vite: 6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) + vite: 6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) transitivePeerDependencies: - '@types/node' - jiti @@ -22597,7 +22581,7 @@ snapshots: vite-plugin-turbosnap@1.0.3: {} - vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3): + vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3): dependencies: esbuild: 0.25.1 postcss: 8.5.3 @@ -22616,7 +22600,7 @@ snapshots: vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3): dependencies: '@vitest/expect': 3.0.9 - '@vitest/mocker': 3.0.9(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(vite@6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)) + '@vitest/mocker': 3.0.9(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(vite@6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)) '@vitest/pretty-format': 3.0.9 '@vitest/runner': 3.0.9 '@vitest/snapshot': 3.0.9 @@ -22632,7 +22616,7 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.2.4(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) + vite: 6.2.4(patch_hash=cb4a32b4c98072ee9756b54d2da01411e766907004dac5fb3abc965dac9c6a29)(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) vite-node: 3.0.9(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3) why-is-node-running: 2.3.0 optionalDependencies: @@ -22887,7 +22871,7 @@ snapshots: with@7.0.2: dependencies: '@babel/parser': 7.25.6 - '@babel/types': 7.24.7 + '@babel/types': 7.25.6 assert-never: 1.2.1 babel-walk: 3.0.0-canary-5 diff --git a/scripts/dependency-patches/vite.patch b/scripts/dependency-patches/vite.patch new file mode 100644 index 0000000000..3fe28ea67e --- /dev/null +++ b/scripts/dependency-patches/vite.patch @@ -0,0 +1,31 @@ +diff --git a/dist/node/chunks/dep-DrOo5SEf.js b/dist/node/chunks/dep-DrOo5SEf.js +index 329e68bd27e55a56d815fa6b4de2d615a8c2b343..9d9f58e90ae836f80063b698e307fec436e53e07 100644 +--- a/dist/node/chunks/dep-DrOo5SEf.js ++++ b/dist/node/chunks/dep-DrOo5SEf.js +@@ -45971,7 +45971,7 @@ function importAnalysisPlugin(config) { + let isPartiallySelfAccepting = false; + const importedBindings = enablePartialAccept ? /* @__PURE__ */ new Map() : null; + const toAbsoluteUrl = (url) => path$d.posix.resolve(path$d.posix.dirname(importerModule.url), url); +- const normalizeUrl = async (url, pos, forceSkipImportAnalysis = false) => { ++ const normalizeUrl = async (url, pos, forceSkipImportAnalysis = false, stripBase2 = false) => { + url = stripBase(url, base); + let importerFile = importer; + if (depsOptimizer && moduleListContains(depsOptimizer.options.exclude, url)) { +@@ -46031,7 +46031,7 @@ function importAnalysisPlugin(config) { + e.pos = pos; + throw e; + } +- if (!ssr) url = joinUrlSegments(base, url); ++ if (!ssr && !stripBase2) url = joinUrlSegments(base, url); + return [url, resolved.id]; + }; + const orderedImportedUrls = new Array(imports.length); +@@ -46288,7 +46288,7 @@ See ${colors$1.blue( + const pluginImports = this._addedImports; + if (pluginImports) { + (await Promise.all( +- [...pluginImports].map((id) => normalizeUrl(id, 0, true)) ++ [...pluginImports].map((id) => normalizeUrl(id, 0, true, true)) + )).forEach(([url]) => importedUrls.add(url)); + } + if (ssr && importerModule.isSelfAccepting) { |