From ca80f4e27395acf7466c1239905e0214aeb4265b Mon Sep 17 00:00:00 2001 From: tai-cha Date: Sun, 2 Mar 2025 09:53:53 +0000 Subject: [PATCH] =?UTF-8?q?=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88=E3=81=9A?= =?UTF-8?q?=E7=9C=81=E7=95=A5=E8=A8=98=E6=B3=95=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/vite-plugin-create-search-index.ts | 612 +++++++++++++++--- .../src/components/global/SearchKeyword.vue | 14 + .../src/components/global/SearchLabel.vue | 14 + packages/frontend/src/components/index.ts | 7 + .../frontend/src/pages/settings/general.vue | 7 +- .../scripts/autogen/settings-search-index.ts | 220 ++++--- 6 files changed, 674 insertions(+), 200 deletions(-) create mode 100644 packages/frontend/src/components/global/SearchKeyword.vue create mode 100644 packages/frontend/src/components/global/SearchLabel.vue diff --git a/packages/frontend/lib/vite-plugin-create-search-index.ts b/packages/frontend/lib/vite-plugin-create-search-index.ts index 2f4b83314a..344faabe70 100644 --- a/packages/frontend/lib/vite-plugin-create-search-index.ts +++ b/packages/frontend/lib/vite-plugin-create-search-index.ts @@ -171,7 +171,7 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR return resolveChildrenReferences(marker); }); - // 特殊なプロパティ変換用の関数 + // 特殊なプロパティ変換用の関数 - i18n参照処理を強化 function formatSpecialProperty(key: string, value: any): string { // 値がundefinedの場合は空文字列を返す if (value === undefined) { @@ -183,55 +183,82 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR return customStringify(value); } - // 文字列でない場合はJSON5で文字列化 - if (typeof value !== 'string') { - return JSON5.stringify(value); + // keywordsが配列の場合、特別に処理 + if (key === 'keywords' && Array.isArray(value)) { + return `[${formatArrayForOutput(value)}]`; } - // i18n.ts 参照を含む場合 - if (value.includes('i18n.ts.')) { - return value; // クォートなしで直接返す - } + // 文字列値の場合の特別処理 + if (typeof value === 'string') { + // i18n.ts 参照を含む場合 - クォートなしでそのまま出力 + if (value.includes('i18n.ts.')) { + logger.info(`Preserving i18n reference in output: ${value}`); + return value; + } - // keywords が配列リテラルの形式の場合 - if (key === 'keywords' && value.startsWith('[') && value.endsWith(']')) { - return value; // クォートなしで直接返す + // keywords が配列リテラルの形式の場合 + if (key === 'keywords' && value.startsWith('[') && value.endsWith(']')) { + return value; + } } // 上記以外は通常の JSON5 文字列として返す return JSON5.stringify(value); } - // オブジェクトをカスタム形式に変換する関数 + // オブジェクトをカスタム形式に変換する関数 - i18n参照処理を強化 function customStringify(obj: any, depth = 0): string { const INDENT_STR = '\t'; + + // 配列の処理 if (Array.isArray(obj)) { if (obj.length === 0) return '[]'; const indent = INDENT_STR.repeat(depth); const childIndent = INDENT_STR.repeat(depth + 1); - const items = obj.map(item => `${childIndent}${customStringify(item, depth + 1)}`).join(',\n'); + + // 配列要素の処理 + const items = obj.map(item => { + // オブジェクト要素 + if (typeof item === 'object' && item !== null) { + return `${childIndent}${customStringify(item, depth + 1)}`; + } + + // i18n参照を含む文字列要素 + if (typeof item === 'string' && item.includes('i18n.ts.')) { + return `${childIndent}${item}`; // クォートなしでそのまま出力 + } + + // その他の要素 + return `${childIndent}${JSON5.stringify(item)}`; + }).join(',\n'); + return `[\n${items},\n${indent}]`; } + // null または非オブジェクト if (obj === null || typeof obj !== 'object') { return JSON5.stringify(obj); } + // オブジェクトの処理 const indent = INDENT_STR.repeat(depth); const childIndent = INDENT_STR.repeat(depth + 1); + const entries = Object.entries(obj) + // 不要なプロパティを除去 .filter(([key, value]) => { - // valueがundefinedの場合は出力しない if (value === undefined) return false; - // childrenが空配列の場合は出力しない if (key === 'children' && Array.isArray(value) && value.length === 0) return false; return true; }) + // 各プロパティを変換 .map(([key, value]) => { - // childrenが配列の場合で要素がある場合のみ特別処理 + // 子要素配列の特殊処理 if (key === 'children' && Array.isArray(value) && value.length > 0) { return `${childIndent}${key}: ${customStringify(value, depth + 1)}`; } + + // ラベルやその他プロパティを処理 return `${childIndent}${key}: ${formatSpecialProperty(key, value)}`; }); @@ -239,6 +266,20 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR return `{\n${entries.join(',\n')},\n${indent}}`; } + // 配列式の文字列表現を修正 - i18n参照を適切に処理 + function formatArrayForOutput(items: any[]): string { + return items.map(item => { + // i18n.ts. 参照の文字列はそのままJavaScript式として出力 + if (typeof item === 'string' && item.includes('i18n.ts.')) { + logger.info(`Preserving i18n reference in array: ${item}`); + return item; // クォートなしでそのまま + } + + // その他の値はJSON5形式で文字列化 + return JSON5.stringify(item); + }).join(', '); + } + // 最終出力用のデバッグ情報を生成 let totalMarkers = resolvedRootMarkers.length; let totalChildren = 0; @@ -266,8 +307,6 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR // This file was automatically generated by create-search-index. // Do not edit this file. -/* eslint-disable @stylistic/comma-spacing */ - import { i18n } from '@/i18n.js'; export type SearchIndexItem = { @@ -282,8 +321,6 @@ export type SearchIndexItem = { export const searchIndexes:SearchIndexItem[] = ${customStringify(resolvedRootMarkers)} as const; export type SearchIndex = typeof searchIndexes; - -/* eslint-enable @stylistic/comma-spacing */ `; try { @@ -294,95 +331,350 @@ export type SearchIndex = typeof searchIndexes; } } +// 要素ノードからテキスト内容を抽出する関数 (Mustache構文のi18n参照にも対応) +function extractElementText(node: any): string | null { + if (!node) return null; + + console.log(`Extracting text from node type=${node.type}, tag=${node.tag || 'unknown'}`); + + // Mustache構文を検出するための正規表現パターン + const mustachePattern = /^\s*{{\s*(.*?)\s*}}\s*$/; + + // childrenが配列でない場合、content直接チェック (インラインテキスト対応) + if (node.content) { + const content = node.content.trim(); + console.log(`Direct node content found: ${content}`); + + // Mustache構文のチェック + const mustacheMatch = content.match(mustachePattern); + if (mustacheMatch && mustacheMatch[1] && mustacheMatch[1].includes('i18n.ts.')) { + const extractedContent = mustacheMatch[1].trim(); + console.log(`Extracted i18n reference from mustache: ${extractedContent}`); + return extractedContent; + } + + // 直接i18n参照を含む場合 + if (content.includes('i18n.ts.')) { + console.log(`Direct i18n reference found: ${content}`); + return content; + } + + // その他のコンテンツ + if (content) { + return content; + } + } + + // childrenがない場合は終了 + if (!node.children || !Array.isArray(node.children)) { + return null; + } + + // Mustacheテンプレート構文の特殊ノード検出 (type=5はインターポレーション) + for (const child of node.children) { + if (child.type === 5) { // インターポレーションノード (Mustache表現) + console.log(`Found interpolation node (Mustache): `, child); + if (child.content && child.content.type === 4 && child.content.content) { + const content = child.content.content.trim(); + console.log(`Interpolation content: ${content}`); + if (content.includes('i18n.ts.')) { + return content; + } + } else if (child.content && typeof child.content === 'object') { + // オブジェクト形式のcontentを再帰的に探索 + console.log(`Complex interpolation node:`, JSON.stringify(child.content).substring(0, 100)); + if (child.content.content) { + const content = child.content.content.trim(); + if (content.includes('i18n.ts.')) { + console.log(`Found i18n reference in complex interpolation: ${content}`); + return content; + } + } + } + } + } + + // 最初のパスで i18n.ts. 参照パターンを持つものを探す (最優先) + for (const child of node.children) { + if (child.type === 2 && child.content) { // 式ノード + const expr = child.content.trim(); + if (expr.includes('i18n.ts.')) { + console.log(`Found i18n reference in expression node: ${expr}`); + return expr; // i18n参照を見つけたら即座に返す + } + } + } + + // 2回目のパスで一般的な式を探す + for (const child of node.children) { + if (child.type === 2 && child.content) { // その他の式ノード + const expr = child.content.trim(); + console.log(`Found expression: ${expr}`); + return expr; + } + } + + // 3回目のパスでテキストノードを探す + for (const child of node.children) { + if (child.type === 3 && child.content) { // テキストノード + const text = child.content.trim(); + if (text) { + console.log(`Found text node: ${text}`); + + // Mustache構文のチェック + const mustacheMatch = text.match(mustachePattern); + if (mustacheMatch && mustacheMatch[1] && mustacheMatch[1].includes('i18n.ts.')) { + console.log(`Extracted i18n ref from text mustache: ${mustacheMatch[1]}`); + return mustacheMatch[1].trim(); + } + + return text; + } + } + } + + // 深さ優先で再帰的に探索 (子の子まで調べる) + for (const child of node.children) { + if (child.children && Array.isArray(child.children) && child.children.length > 0) { + const nestedContent = extractElementText(child); + if (nestedContent) { + console.log(`Found nested content: ${nestedContent}`); + return nestedContent; + } + } else if (child.type === 1) { // 子要素ノード - childrenがなくても内部を調査 + const nestedContent = extractElementText(child); + if (nestedContent) { + console.log(`Found content in childless element: ${nestedContent}`); + return nestedContent; + } + } + } + + return null; +} + +// SearchLabelとSearchKeywordを探して抽出する関数 (スコープを適切に分離) +function extractLabelsAndKeywords(nodes: any[]): { label: string | null, keywords: any[] } { + let label: string | null = null; + const keywords: any[] = []; + + console.log(`Extracting labels and keywords from ${nodes.length} nodes`); + + // 再帰的にSearchLabelとSearchKeywordを探索(ネストされたSearchMarkerは処理しない) + function findComponents(nodes: any[]) { + for (const node of nodes) { + if (node.type === 1) { // Element node + console.log(`Checking element: ${node.tag}`); + + // SearchMarkerの場合は、その子要素は別スコープなのでスキップ + if (node.tag === 'SearchMarker') { + console.log(`Found nested SearchMarker - skipping its content to maintain scope isolation`); + continue; // このSearchMarkerの中身は処理しない (スコープ分離) + } + + // SearchLabelの処理 + if (node.tag === 'SearchLabel') { + console.log(`Found SearchLabel node, structure:`, JSON.stringify(node).substring(0, 200) + '...'); + + // まず完全なノード内容の抽出を試みる + const content = extractElementText(node); + if (content) { + label = content; + console.log(`SearchLabel content extracted: ${content}`); + } else { + console.log(`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 === 5 && child.content) { + // content内の式を取り出す + const expression = child.content.content || + (child.content.type === 4 ? child.content.content : null) || + JSON.stringify(child.content); + + console.log(`Interpolation expression: ${expression}`); + if (typeof expression === 'string' && expression.includes('i18n.ts.')) { + label = expression.trim(); + console.log(`Found i18n in interpolation: ${label}`); + break; + } + } + // 式ノード + else if (child.type === 2 && child.content && child.content.includes('i18n.ts.')) { + label = child.content.trim(); + console.log(`Found i18n in expression: ${label}`); + break; + } + // テキストノードでもMustache構文を探す + else if (child.type === 3 && child.content) { + const mustacheMatch = child.content.trim().match(/^\s*{{\s*(.*?)\s*}}\s*$/); + if (mustacheMatch && mustacheMatch[1] && mustacheMatch[1].includes('i18n.ts.')) { + label = mustacheMatch[1].trim(); + console.log(`Found i18n in text mustache: ${label}`); + break; + } + } + } + } + } + } + // SearchKeywordの処理 + else if (node.tag === 'SearchKeyword') { + console.log(`Found SearchKeyword node`); + + // まず完全なノード内容の抽出を試みる + const content = extractElementText(node); + if (content) { + keywords.push(content); + console.log(`SearchKeyword content extracted: ${content}`); + } else { + console.log(`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 === 5 && child.content) { + // content内の式を取り出す + const expression = child.content.content || + (child.content.type === 4 ? child.content.content : null) || + JSON.stringify(child.content); + + console.log(`Keyword interpolation: ${expression}`); + if (typeof expression === 'string' && expression.includes('i18n.ts.')) { + const keyword = expression.trim(); + keywords.push(keyword); + console.log(`Found i18n keyword in interpolation: ${keyword}`); + break; + } + } + // 式ノード + else if (child.type === 2 && child.content && child.content.includes('i18n.ts.')) { + const keyword = child.content.trim(); + keywords.push(keyword); + console.log(`Found i18n keyword in expression: ${keyword}`); + break; + } + // テキストノードでもMustache構文を探す + else if (child.type === 3 && child.content) { + const mustacheMatch = child.content.trim().match(/^\s*{{\s*(.*?)\s*}}\s*$/); + if (mustacheMatch && mustacheMatch[1] && mustacheMatch[1].includes('i18n.ts.')) { + const keyword = mustacheMatch[1].trim(); + keywords.push(keyword); + console.log(`Found i18n keyword in text mustache: ${keyword}`); + break; + } + } + } + } + } + } + + // 子要素を再帰的に調査(ただしSearchMarkerは除外) + if (node.children && Array.isArray(node.children)) { + findComponents(node.children); + } + } + } + } + + findComponents(nodes); + + // デバッグ情報 + console.log(`Extraction completed: label=${label}, keywords=[${keywords.join(', ')}]`); + return { label, keywords }; +} + function extractUsageInfoFromTemplateAst( templateAst: any, code: string, ): SearchIndexItem[] { - // すべてのマーカー情報を保持するために、結果をトップレベルマーカー+すべての子マーカーを含む配列に変更 const allMarkers: SearchIndexItem[] = []; - // マーカーIDからオブジェクトへのマップ const markerMap = new Map(); - // 子マーカーIDを集約するためのセット const childrenIds = new Set(); - if (!templateAst) { - return allMarkers; - } - - // デバッグ情報 - logger.info('Started extracting markers from AST'); + if (!templateAst) return allMarkers; // マーカーの基本情報を収集 function collectMarkers(node: any, parentId: string | null = null) { - // SearchMarkerコンポーネントの検出 if (node.type === 1 && node.tag === 'SearchMarker') { - // マーカーID生成 (markerId属性またはDOM内に記録されているものを使用) + // マーカーID取得 const markerIdProp = node.props?.find((p: any) => p.name === 'markerId'); const markerId = markerIdProp?.value?.content || - node.__markerId || - `marker-${Math.random().toString(36).substring(2, 10)}`; - - logger.info(`Found SearchMarker with ID: ${markerId}`); + node.__markerId || + `marker-${Math.random().toString(36).substring(2, 10)}`; // マーカー基本情報 const markerInfo: SearchIndexItem = { id: markerId, children: [], - label: '', + label: '', // デフォルト値 keywords: [], }; - // 静的プロパティを抽出 + // 静的プロパティを取得 if (node.props && Array.isArray(node.props)) { - node.props.forEach((prop: any) => { + for (const prop of node.props) { if (prop.type === 6 && prop.name && prop.name !== 'markerId') { - // 静的プロパティの抽出 if (prop.name === 'path') markerInfo.path = prop.value?.content || ''; else if (prop.name === 'icon') markerInfo.icon = prop.value?.content || ''; else if (prop.name === 'label') markerInfo.label = prop.value?.content || ''; - - logger.info(`Static prop ${prop.name}:`, prop.value?.content); } - }); + } } - // バインドプロパティを抽出 - if (node.props && Array.isArray(node.props)) { - node.props.forEach((prop: any) => { - if (prop.type === 7 && prop.name === 'bind' && prop.arg?.content) { - const propName = prop.arg.content; - const propValue = prop.exp?.content || ''; + // バインドプロパティを取得 + const bindings = extractNodeBindings(node); + 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.keywords) { + if (Array.isArray(bindings.keywords)) { + markerInfo.keywords = bindings.keywords; + } else { + markerInfo.keywords = bindings.keywords || []; + } + } - logger.info(`Bind prop ${propName}:`, propValue); + // SearchLabelとSearchKeywordを抽出 (AST全体を探索) + if (node.children && Array.isArray(node.children)) { + console.log(`Processing marker ${markerId} for labels and keywords`); + const extracted = extractLabelsAndKeywords(node.children); - if (propName === 'label') { - markerInfo.label = propValue; - } else if (propName === 'path') { - markerInfo.path = propValue; - } else if (propName === 'icon') { - markerInfo.icon = propValue; - } else if (propName === 'keywords') { - markerInfo.keywords = propValue || '[]'; - } else if (propName === 'children') { - markerInfo.children = propValue || '[]'; - } + // SearchLabelからのラベル取得は最優先で適用 + if (extracted.label) { + markerInfo.label = extracted.label; + console.log(`Using extracted label for ${markerId}: ${extracted.label}`); + } else if (markerInfo.label) { + console.log(`Using existing label for ${markerId}: ${markerInfo.label}`); + } else { + markerInfo.label = 'Unnamed marker'; + console.log(`No label found for ${markerId}, using default`); + } + + // SearchKeywordからのキーワード取得を追加 + if (extracted.keywords.length > 0) { + const existingKeywords = Array.isArray(markerInfo.keywords) ? + [...markerInfo.keywords] : + (markerInfo.keywords ? [markerInfo.keywords] : []); + + // i18n参照のキーワードは最優先で追加 + const combinedKeywords = [...existingKeywords]; + for (const kw of extracted.keywords) { + combinedKeywords.push(kw); + console.log(`Added extracted keyword to ${markerId}: ${kw}`); } - }); + + markerInfo.keywords = combinedKeywords; + } } - // ラベルがない場合はデフォルト値を設定 - if (!markerInfo.label) { - markerInfo.label = 'Unnamed marker'; - } - - // キーワードがない場合はデフォルト値を設定 - if (!markerInfo.keywords || (Array.isArray(markerInfo.keywords) && markerInfo.keywords.length === 0)) { - markerInfo.keywords = '[]'; - } - - // マーカーをマップに保存し、すべてのマーカーリストにも追加 + // マーカーを登録 markerMap.set(markerId, markerInfo); - allMarkers.push(markerInfo); // すべてのマーカーを保持 + allMarkers.push(markerInfo); // 親子関係を記録 if (parentId) { @@ -395,11 +687,10 @@ function extractUsageInfoFromTemplateAst( parent.children = [markerId]; } childrenIds.add(markerId); - logger.info(`Added ${markerId} as child of ${parentId}`); } } - // 子ノードを処理(親は現在のノード) + // 子ノードを処理 if (node.children && Array.isArray(node.children)) { node.children.forEach((child: any) => { collectMarkers(child, markerId); @@ -409,7 +700,7 @@ function extractUsageInfoFromTemplateAst( return markerId; } - // SearchMarkerでない場合は、子ノードを同じ親コンテキストで処理 + // 子ノードを処理 if (node.children && Array.isArray(node.children)) { node.children.forEach((child: any) => { collectMarkers(child, parentId); @@ -419,16 +710,169 @@ function extractUsageInfoFromTemplateAst( return null; } - // AST解析を開始 + // AST解析開始 collectMarkers(templateAst); - - // デバッグ情報 - logger.info(`Found ${markerMap.size} markers, ${childrenIds.size} children`); - - // 重要: すべてのマーカー情報を返す return allMarkers; } +// バインドプロパティの処理を修正する関数 +function extractNodeBindings(node: any): Record { + const bindings: Record = {}; + + if (!node.props || !Array.isArray(node.props)) return bindings; + + // バインド式を収集 + for (const prop of node.props) { + if (prop.type === 7 && prop.name === 'bind' && prop.arg?.content) { + const propName = prop.arg.content; + const propContent = prop.exp?.content || ''; + + logger.info(`Processing bind prop ${propName}: ${propContent}`); + + // keywordsの特殊処理 + if (propName === 'keywords') { + try { + const content = propContent.trim(); + + // 配列式の場合 + if (content.startsWith('[') && content.endsWith(']')) { + // i18n参照や特殊な式を保持するため、各要素を個別に解析 + const elements = parseArrayExpression(content); + if (elements.length > 0) { + bindings.keywords = elements; + logger.info(`Parsed keywords array: ${JSON5.stringify(elements)}`); + } else { + bindings.keywords = []; + logger.info('Empty keywords array'); + } + } + // その他の式(非配列) + else if (content) { + bindings.keywords = content; // 式をそのまま保持 + logger.info(`Keeping keywords as expression: ${content}`); + } else { + bindings.keywords = []; + logger.info('No keywords provided'); + } + } catch (e) { + logger.error(`Failed to parse keywords binding: ${propContent}`, e); + // エラーが起きても何らかの値を設定 + bindings.keywords = propContent || []; + } + } + // その他のプロパティ + else if (propName === 'label') { + // ラベルの場合も式として保持 + bindings[propName] = propContent; + logger.info(`Set label from bind expression: ${propContent}`); + } + else { + bindings[propName] = propContent; + } + } + } + + return bindings; +} + +// 配列式をパースする補助関数(文字列リテラル処理を改善) +function parseArrayExpression(expr: string): any[] { + try { + // 単純なケースはJSON5でパースを試みる + return JSON5.parse(expr.replace(/'/g, '"')); + } catch (e) { + // 複雑なケース(i18n.ts.xxx などの式を含む場合)は手動パース + logger.info(`Complex array expression, trying manual parsing: ${expr}`); + + // "["と"]"を取り除く + const content = expr.substring(1, expr.length - 1).trim(); + if (!content) return []; + + const result: any[] = []; + let currentItem = ''; + let depth = 0; + let inString = false; + let stringChar = ''; + + // カンマで区切る(ただし文字列内や入れ子の配列内のカンマは無視) + for (let i = 0; i < content.length; i++) { + const char = content[i]; + + if (inString) { + if (char === stringChar && content[i - 1] !== '\\') { + inString = false; + } + currentItem += char; + } else if (char === '"' || char === "'") { + inString = true; + stringChar = char; + currentItem += char; + } else if (char === '[') { + depth++; + currentItem += char; + } else if (char === ']') { + depth--; + currentItem += char; + } else if (char === ',' && depth === 0) { + // 項目の区切りを検出 + const trimmed = currentItem.trim(); + + // 純粋な文字列リテラルの場合、実際の値に変換 + if ((trimmed.startsWith("'") && trimmed.endsWith("'")) || + (trimmed.startsWith('"') && trimmed.endsWith('"'))) { + try { + result.push(JSON5.parse(trimmed)); + } catch (err) { + result.push(trimmed); + } + } else { + // それ以外の式はそのまま(i18n.ts.xxx など) + result.push(trimmed); + } + + currentItem = ''; + } else { + currentItem += char; + } + } + + // 最後の項目を処理 + if (currentItem.trim()) { + const trimmed = currentItem.trim(); + + // 純粋な文字列リテラルの場合、実際の値に変換 + if ((trimmed.startsWith("'") && trimmed.endsWith("'")) || + (trimmed.startsWith('"') && trimmed.endsWith('"'))) { + try { + result.push(JSON5.parse(trimmed)); + } catch (err) { + result.push(trimmed); + } + } else { + // それ以外の式はそのまま(i18n.ts.xxx など) + result.push(trimmed); + } + } + + logger.info(`Parsed complex array expression: ${expr} -> ${JSON.stringify(result)}`); + return result; + } +} + +// 配列式の文字列表現を修正 - i18n参照を適切に処理 +function formatArrayForOutput(items: any[]): string { + return items.map(item => { + // i18n.ts. 参照の文字列はそのままJavaScript式として出力 + if (typeof item === 'string' && item.includes('i18n.ts.')) { + logger.info(`Preserving i18n reference in array: ${item}`); + return item; // クォートなしでそのまま + } + + // その他の値はJSON5形式で文字列化 + return JSON5.stringify(item); + }).join(', '); +} + export async function analyzeVueProps(options: { targetFilePaths: string[], exportFilePath: string, @@ -466,7 +910,7 @@ export async function analyzeVueProps(options: { }); if (errors.length) { - logger.error(`Compile Error: ${filePath}`, errors); + logger.error(`Compile Error: ${filePath}, ${errors}`); continue; // エラーが発生したファイルはスキップ } @@ -578,7 +1022,7 @@ async function processVueFile( s.appendRight(endOfStartTag, ` markerId="${generatedMarkerId}"`); logger.info(`Adding markerId="${generatedMarkerId}" to ${id}:${lineNumber}`); } else { - logger.warn(`markerId already exists in ${id}:${lineNumber}`); + logger.info(`markerId already exists in ${id}:${lineNumber}`); } } } diff --git a/packages/frontend/src/components/global/SearchKeyword.vue b/packages/frontend/src/components/global/SearchKeyword.vue new file mode 100644 index 0000000000..27a284faf0 --- /dev/null +++ b/packages/frontend/src/components/global/SearchKeyword.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/packages/frontend/src/components/global/SearchLabel.vue b/packages/frontend/src/components/global/SearchLabel.vue new file mode 100644 index 0000000000..27a284faf0 --- /dev/null +++ b/packages/frontend/src/components/global/SearchLabel.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 5cba647c51..ebbad3e5b8 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -25,6 +25,9 @@ import MkFooterSpacer from './global/MkFooterSpacer.vue'; import MkStickyContainer from './global/MkStickyContainer.vue'; import MkLazy from './global/MkLazy.vue'; import SearchMarker from './global/SearchMarker.vue'; +import SearchLabel from './global/SearchLabel.vue'; +import SearchKeyword from './global/SearchKeyword.vue'; + import type { App } from 'vue'; export default function(app: App) { @@ -56,6 +59,8 @@ export const components = { MkStickyContainer: MkStickyContainer, MkLazy: MkLazy, SearchMarker: SearchMarker, + SearchLabel: SearchLabel, + SearchKeyword: SearchKeyword, }; declare module '@vue/runtime-core' { @@ -82,5 +87,7 @@ declare module '@vue/runtime-core' { MkStickyContainer: typeof MkStickyContainer; MkLazy: typeof MkLazy; SearchMarker: typeof SearchMarker; + SearchLabel: typeof SearchLabel; + SearchKeyword: typeof SearchKeyword; } } diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index fc5b98e0b7..a9e57558ff 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -73,12 +73,11 @@ SPDX-License-Identifier: AGPL-3.0-only
- - + + diff --git a/packages/frontend/src/scripts/autogen/settings-search-index.ts b/packages/frontend/src/scripts/autogen/settings-search-index.ts index 810a2659bc..03d43b3d12 100644 --- a/packages/frontend/src/scripts/autogen/settings-search-index.ts +++ b/packages/frontend/src/scripts/autogen/settings-search-index.ts @@ -7,8 +7,6 @@ // This file was automatically generated by create-search-index. // Do not edit this file. -/* eslint-disable @stylistic/comma-spacing */ - import { i18n } from '@/i18n.js'; export type SearchIndexItem = { @@ -32,22 +30,22 @@ export const searchIndexes:SearchIndexItem[] = [ { id: 'gAvOWkPnm', label: i18n.ts._profile.description, - keywords: ['description','bio'], + keywords: ['description', 'bio'], }, { id: 'jDQ9ug9JI', label: i18n.ts.location, - keywords: ['location','locale'], + keywords: ['location', 'locale'], }, { id: '4IINpCs7w', label: i18n.ts.birthday, - keywords: ['birthday','birthdate','age'], + keywords: ['birthday', 'birthdate', 'age'], }, { id: 'nrKSnXwYN', label: i18n.ts.language, - keywords: ['language','locale'], + keywords: ['language', 'locale'], }, { id: 'xpFKUEupm', @@ -98,7 +96,7 @@ export const searchIndexes:SearchIndexItem[] = [ { id: 'yGVh4afrC', label: i18n.ts.autoAcceptFollowed, - keywords: ['follow','auto','accept'], + keywords: ['follow', 'auto', 'accept'], }, { id: 'vomfIAjG2', @@ -108,12 +106,12 @@ export const searchIndexes:SearchIndexItem[] = [ { id: 'BakhCOlGl', label: i18n.ts.followingVisibility, - keywords: ['following','visibility'], + keywords: ['following', 'visibility'], }, { id: 'nALctPZes', label: i18n.ts.followersVisibility, - keywords: ['follower','visibility'], + keywords: ['follower', 'visibility'], }, { id: 'g39hgvANq', @@ -160,17 +158,17 @@ export const searchIndexes:SearchIndexItem[] = [ { id: '6AAT3LUIL', label: i18n.ts.rememberNoteVisibility, - keywords: ['remember','keep','note','visibility'], + keywords: ['remember', 'keep', 'note', 'visibility'], }, { id: '8d0FCNIgm', label: i18n.ts.defaultNoteVisibility, - keywords: ['default','note','visibility'], + keywords: ['default', 'note', 'visibility'], }, { id: 'o3hisYwlV', label: i18n.ts.keepCw, - keywords: ['remember','keep','note','cw'], + keywords: ['remember', 'keep', 'note', 'cw'], }, ], label: i18n.ts.privacy, @@ -191,36 +189,36 @@ export const searchIndexes:SearchIndexItem[] = [ }, ], label: i18n.ts.wordMute, - keywords: ['note','word','soft','mute','hide'], + keywords: ['note', 'word', 'soft', 'mute', 'hide'], }, { id: 'evlmsuKtu', label: i18n.ts.hardWordMute, - keywords: ['note','word','hard','mute','hide'], + keywords: ['note', 'word', 'hard', 'mute', 'hide'], }, { id: 'zLHWO9mBm', label: i18n.ts.instanceMute, - keywords: ['note','server','instance','host','federation','mute','hide'], + keywords: ['note', 'server', 'instance', 'host', 'federation', 'mute', 'hide'], }, { id: 's3IxYcSKj', label: `${i18n.ts.mutedUsers} (${ i18n.ts.renote })`, - keywords: ['renote','mute','hide','user'], + keywords: ['renote', 'mute', 'hide', 'user'], }, { id: 'iBnGIbcys', label: i18n.ts.mutedUsers, - keywords: ['note','mute','hide','user'], + keywords: ['note', 'mute', 'hide', 'user'], }, { id: 'g6aGcN8X5', label: i18n.ts.blockedUsers, - keywords: ['block','user'], + keywords: ['block', 'user'], }, ], label: i18n.ts.muteAndBlock, - keywords: ['mute','block'], + keywords: ['mute', 'block'], path: '/settings/mute-block', icon: 'ti ti-ban', }, @@ -235,22 +233,22 @@ export const searchIndexes:SearchIndexItem[] = [ { id: '3zpvHzV5g', label: i18n.ts.overridedDeviceKind, - keywords: ['device','type','kind','smartphone','tablet','desktop'], + keywords: ['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop'], }, { id: '1QvSeZtNe', label: i18n.ts.showFixedPostForm, - keywords: ['post','form','timeline'], + keywords: ['post', 'form', 'timeline'], }, { id: 'oVjAZ97m1', label: i18n.ts.showFixedPostFormInChannel, - keywords: ['post','form','timeline','channel'], + keywords: ['post', 'form', 'timeline', 'channel'], }, { id: '6sIzeMWoB', label: i18n.ts.pinnedList, - keywords: ['pinned','list'], + keywords: ['pinned', 'list'], }, { id: '6HbFzMsfZ', @@ -261,238 +259,238 @@ export const searchIndexes:SearchIndexItem[] = [ keywords: ['renote', i18n.ts.collapseRenotesDescription], }, { - id: 'r8RoaDGyB', + id: 'ge9OZWVVN', label: i18n.ts.showNoteActionsOnlyHover, - keywords: ['hover','show','footer','action'], + keywords: ['hover', 'show', 'footer', 'action'], }, { - id: 'dhe5ltCVy', + id: 'qkpSLdjU0', label: i18n.ts.showClipButtonInNoteFooter, - keywords: ['footer','action','clip','show'], + keywords: ['footer', 'action', 'clip', 'show'], }, { - id: 'lJijzarvh', + id: 'A3lCFvhIW', label: i18n.ts.enableAdvancedMfm, - keywords: ['mfm','enable','show','advanced'], + keywords: ['mfm', 'enable', 'show', 'advanced'], }, { - id: 'plFY7Zr0Y', + id: 'AhvTBCdWA', label: i18n.ts.enableAnimatedMfm, - keywords: ['mfm','enable','show','animated'], + keywords: ['mfm', 'enable', 'show', 'animated'], }, { - id: 'uCWeJdWGo', + id: 'hsXMJyRt9', label: i18n.ts.enableQuickAddMfmFunction, - keywords: ['mfm','enable','show','advanced','picker','form','function','fn'], + keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'], }, { - id: 'jojGm1s0T', + id: 'caZ2i1vUp', label: i18n.ts.showReactionsCount, - keywords: ['reaction','count','show'], + keywords: ['reaction', 'count', 'show'], }, { - id: 'gob3LGyDO', + id: 'zeBSHyLvX', label: i18n.ts.showGapBetweenNotesInTimeline, - keywords: ['note','timeline','gap'], + keywords: ['note', 'timeline', 'gap'], }, { - id: 'v5IaTjjUK', + id: 'iCXH6Slng', label: i18n.ts.loadRawImages, - keywords: ['image','photo','picture','media','thumbnail','quality','raw','attachment'], + keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment'], }, { - id: '9rlymuz2e', + id: 'dLhN3FVue', label: i18n.ts.reactionsDisplaySize, - keywords: ['reaction','size','scale','display'], + keywords: ['reaction', 'size', 'scale', 'display'], }, { - id: 'a6JkO6iHN', + id: 'vmlTGQcw9', label: i18n.ts.limitWidthOfReaction, - keywords: ['reaction','size','scale','display','width','limit'], + keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'], }, { - id: 'B2C8Chpn3', + id: 'urTAUjuCD', label: i18n.ts.instanceTicker, - keywords: ['ticker','information','label','instance','server','host','federation'], + keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'], }, { - id: 'lXGQcJA0m', + id: 'jEMNPc04n', label: i18n.ts.displayOfSensitiveMedia, - keywords: ['attachment','image','photo','picture','media','thumbnail','nsfw','sensitive','display','show','hide','visibility'], + keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'], }, { - id: 'fI9unfBG9', + id: 'dbVdQ8uQ6', label: i18n.ts.mediaListWithOneImageAppearance, - keywords: ['attachment','image','photo','picture','media','thumbnail','list','size','height'], + keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'], }, ], label: i18n.ts.displayOfNote, - keywords: ['note','display'], + keywords: ['note', 'display'], }, { - id: 'ksPnvPfsq', + id: 'reaXNqM8S', children: [ { - id: 'rlKh8qQSy', + id: 'x5ekE7znD', label: i18n.ts.useGroupedNotifications, keywords: ['group'], }, { - id: 'mZoLcRxn9', + id: 'iUWlBX9ZI', label: i18n.ts.position, keywords: ['position'], }, { - id: 'mu8Fghr9P', + id: 'vrBikwOLp', label: i18n.ts.stackAxis, - keywords: ['stack','axis','direction'], + keywords: ['stack', 'axis', 'direction'], }, ], label: i18n.ts.notificationDisplay, - keywords: ['notification','display'], + keywords: ['notification', 'display'], }, { - id: 'sUAe1tYeV', + id: 'ufhXubmEr', children: [ { - id: 'pXk2Bq5c7', + id: 'fgl7X4XsD', label: i18n.ts.reduceUiAnimation, - keywords: ['animation','motion','reduce'], + keywords: ['animation', 'motion', 'reduce'], }, { - id: 'l92lxxESG', + id: 'bjKigdnsy', label: i18n.ts.useBlurEffect, keywords: ['blur'], }, { - id: '5fw6zkK39', + id: 'CGoeiZtv2', label: i18n.ts.useBlurEffectForModal, - keywords: ['blur','modal'], + keywords: ['blur', 'modal'], }, { - id: 'm3kpvUE3f', + id: 'bnzqN2Uv5', label: i18n.ts.disableShowingAnimatedImages, - keywords: ['disable','animation','image','photo','picture','media','thumbnail','gif'], + keywords: ['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif'], }, { - id: 'EEHGMwhkC', + id: '9LEabU98l', label: i18n.ts.highlightSensitiveMedia, - keywords: ['highlight','sensitive','nsfw','image','photo','picture','media','thumbnail'], + keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'], }, { - id: 'hBnm0B8aI', + id: 'aiKchQcTg', label: i18n.ts.squareAvatars, - keywords: ['avatar','icon','square'], + keywords: ['avatar', 'icon', 'square'], }, { - id: 'm9Q5z11L8', + id: '370ytj4BG', label: i18n.ts.showAvatarDecorations, - keywords: ['avatar','icon','decoration','show'], + keywords: ['avatar', 'icon', 'decoration', 'show'], }, { - id: 'pHvwcwF4c', + id: 'sNxpyIsU0', label: i18n.ts.useSystemFont, - keywords: ['font','system','native'], + keywords: ['font', 'system', 'native'], }, { - id: '3lcbrBHYZ', + id: 'lNM8JR8Fq', label: i18n.ts.forceShowAds, - keywords: ['ad','show'], + keywords: ['ad', 'show'], }, { - id: '523Bx87JU', + id: 'rDKuKXq3L', label: i18n.ts.seasonalScreenEffect, - keywords: ['effect','show'], + keywords: ['effect', 'show'], }, { - id: '7dFSaWz4u', + id: 'eTBtusX71', label: i18n.ts.useNativeUIForVideoAudioPlayer, - keywords: ['native','system','video','audio','player','media'], + keywords: ['native', 'system', 'video', 'audio', 'player', 'media'], }, { - id: 'q3Vv5zIZu', + id: 'c2IIsJe2s', label: i18n.ts.menuStyle, - keywords: ['menu','style','popup','drawer'], + keywords: ['menu', 'style', 'popup', 'drawer'], }, { - id: 'd1Gno6GaT', + id: 'zeg5SUJfO', label: i18n.ts.emojiStyle, - keywords: ['emoji','style','native','system','fluent','twemoji'], + keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'], }, { - id: '3sV67CRuN', + id: 'dSCrbhyrx', label: i18n.ts.fontSize, - keywords: ['font','size'], + keywords: ['font', 'size'], }, ], label: i18n.ts.appearance, keywords: ['appearance'], }, { - id: 'AOASYDlhv', + id: 'onFBs0tYX', children: [ { - id: 'rwvOv068t', + id: 'gmuZO8DML', label: i18n.ts.openImageInNewTab, - keywords: ['image','photo','picture','media','thumbnail','new','tab'], + keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'], }, { - id: '1XB7XWwbI', + id: '5pZfWlbB2', label: i18n.ts.useReactionPickerForContextMenu, - keywords: ['reaction','picker','contextmenu','open'], + keywords: ['reaction', 'picker', 'contextmenu', 'open'], }, { - id: '4fjitJYAl', + id: 'pihxkFGES', label: i18n.ts.enableInfiniteScroll, - keywords: ['load','auto','more'], + keywords: ['load', 'auto', 'more'], }, { - id: 'f2Nk4GdTa', + id: '1QcQVuU3X', label: i18n.ts.keepScreenOn, - keywords: ['keep','screen','display','on'], + keywords: ['keep', 'screen', 'display', 'on'], }, { - id: '3D54jQsGn', + id: 'DPsw4YfoC', label: i18n.ts.disableStreamingTimeline, - keywords: ['disable','streaming','timeline'], + keywords: ['disable', 'streaming', 'timeline'], }, { - id: '6MKp7i7iH', + id: 'nXcDddZr9', label: i18n.ts.enableHorizontalSwipe, - keywords: ['swipe','horizontal','tab'], + keywords: ['swipe', 'horizontal', 'tab'], }, { - id: 'eSvGS9DsY', + id: 'sq5mQo7Th', label: i18n.ts.alwaysConfirmFollow, - keywords: ['follow','confirm','always'], + keywords: ['follow', 'confirm', 'always'], }, { - id: 'kGYY8LCdK', + id: '9IuNcZ9Ct', label: i18n.ts.confirmWhenRevealingSensitiveMedia, - keywords: ['sensitive','nsfw','media','image','photo','picture','attachment','confirm'], + keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'], }, { - id: 'fdOu3BDKL', + id: '4BhdUdrlf', label: i18n.ts.confirmOnReact, - keywords: ['reaction','confirm'], + keywords: ['reaction', 'confirm'], }, { - id: 'isbDLeTjm', + id: 'qqA6jS0Fy', label: i18n.ts.whenServerDisconnected, - keywords: ['server','disconnect','reconnect','reload','streaming'], + keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'], }, { - id: 'F5werzQkF', + id: 'EPnMDBcWn', label: i18n.ts._contextMenu.title, - keywords: ['contextmenu','system','native'], + keywords: ['contextmenu', 'system', 'native'], }, { - id: 'CPNGOZAoV', + id: 'iHo9Udj7J', label: i18n.ts.numberOfPageCache, - keywords: ['cache','page'], + keywords: ['cache', 'page'], }, { - id: '5XHiLyaol', + id: '8KHaBRiOz', label: i18n.ts.dataSaver, keywords: ['datasaver'], }, @@ -508,5 +506,3 @@ export const searchIndexes:SearchIndexItem[] = [ ] as const; export type SearchIndex = typeof searchIndexes; - -/* eslint-enable @stylistic/comma-spacing */