From 63ca91d3bd49fbbd18e208ce247ea344d34171ce Mon Sep 17 00:00:00 2001 From: Devon22 Date: Thu, 31 Jul 2025 23:27:04 +0800 Subject: [PATCH] 2.7.11 --- docs/custom-mode-example.md | 51 +++++++++++ docs/custom-mode-fields.md | 134 ++++++++++++++++++++++++++++ docs/custom-mode-fields_ja.md | 134 ++++++++++++++++++++++++++++ docs/custom-mode-fields_zhTW.md | 140 +++++++++++++++++++++++++++++ manifest.json | 2 +- package-lock.json | 4 +- package.json | 2 +- src/GridView.ts | 150 ++++++++++++++++++++++---------- src/modal/customModeModal.ts | 4 +- src/modal/searchModal.ts | 19 ++++ src/translations.ts | 6 ++ styles.css | 38 +++++++- versions.json | 2 +- 13 files changed, 632 insertions(+), 54 deletions(-) create mode 100644 docs/custom-mode-example.md create mode 100644 docs/custom-mode-fields.md create mode 100644 docs/custom-mode-fields_ja.md create mode 100644 docs/custom-mode-fields_zhTW.md diff --git a/docs/custom-mode-example.md b/docs/custom-mode-example.md new file mode 100644 index 0000000..e93803b --- /dev/null +++ b/docs/custom-mode-example.md @@ -0,0 +1,51 @@ +--- +date: 2025-06-26 +--- +1. My Favorites +``` +return dv.pages("#Favorites").sort(page => page.modified_date ? dv.date(page.modified_date) : page.file.mtime, 'desc'); +``` + +2. On This Day in History +``` +const today = new Date(); +return dv.pages().where(p => { + const pageDate = p.modified_date ? new Date(p.modified_date): new Date(p.file.mtime); + return pageDate.getYear() !== today.getYear() && + pageDate.getMonth() === today.getMonth() && + pageDate.getDate() === today.getDate(); +}); +``` + +3. Backlinks +``` +const currentPage = dv.current(); +if (!currentPage) return []; +return dv.pages().where(p => p.file.outlinks.includes(currentPage.file.link) && p.file.path.split('/').length > 1).sort(page => page.modified_date ? dv.date(page.modified_date) : page.file.mtime, 'desc'); +``` + +4. Random Images +``` +const files = app.vault.getFiles(); +const imageFiles = files.filter(file => + file.path.startsWith("resources/") && + /\.(png|jpg|jpeg|gif|bmp|svg|webp)$/i.test(file.name) +); +return imageFiles + .sort(() => 0.5 - Math.random()) + .slice(0, 10) + .map(f => ({ file: f })); +``` + +5. Random Videos +``` +const files = app.vault.getFiles(); +const videoFiles = files.filter(file => + file.path.startsWith("resources/") && + /\.(mp4|webm|mov|avi|mkv|ogv)$/i.test(file.name) +); +return videoFiles + .sort(() => 0.5 - Math.random()) + .slice(0, 10) + .map(f => ({ file: f })); +``` \ No newline at end of file diff --git a/docs/custom-mode-fields.md b/docs/custom-mode-fields.md new file mode 100644 index 0000000..7fe573b --- /dev/null +++ b/docs/custom-mode-fields.md @@ -0,0 +1,134 @@ +# Grid Explorer Custom Mode Fields Guide + +## Overview + +Grid Explorer supports setting field aliases and performing real-time computations in custom modes. This allows for more flexible display and calculation of note metadata. + +## Basic Syntax + +### Field Aliases + +Use the `|` symbol to set a display name for a field: + +``` +original_field|Display Name +``` + +**Examples:** +- `birthday|Birthday` - Displays the `birthday` field as "Birthday" +- `status|Status` - Displays the `status` field as "Status" + +### Computation Expressions + +Use `{{ ... }}` to wrap JavaScript expressions for computation: + +``` +field_name {{ expression }} +``` + +**Available Variables:** + +| Variable | Description | Example | +|----------|-------------|---------| +| `value` | Current field's raw value | `value` or `this` | +| `metadata` | The complete frontmatter metadata object | `metadata.tags` | +| `app` | Obsidian app instance | `app.vault.getMarkdownFiles()` | +| `dv` | Dataview API instance (requires Dataview plugin) | `dv.pages('#tag')` | + +## Advanced Usage + +### Combining Aliases and Computations + +``` +field_name|Display Name {{ expression }} +``` + +**Basic Example:** +``` +age|Age {{ Math.floor((Date.now() - new Date(value).getTime()) / (1000 * 60 * 60 * 24 * 365.25)) }} +``` + +**With Dataview Example:** +``` +tags|Related Notes {{ dv.pages(value).length }} +``` + +### Multi-field Combinations + +``` +full_name|Full Name {{ (metadata.first_name || '') + ' ' + (metadata.last_name || '') }} +``` + +## Practical Examples + +### 1. Basic Computations + +#### Calculate Age +``` +birthday|Age {{ Math.floor((Date.now() - new Date(value).getTime()) / (1000 * 60 * 60 * 24 * 365.25)) }} +``` + +#### Format Date +``` +date|Date {{ new Date(value).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) }} +``` + +### 2. Advanced Dataview Usage + +#### Count Related Notes +``` +tags|Related Notes {{ dv.pages(value).length }} +``` + +#### Show Incomplete Tasks +``` +file.folder|To-dos {{ dv.pages(`"${value}"`).file.tasks.where(t => !t.completed).length }} +``` + +#### Calculate Tag Frequency +``` +tags|Tag Stats {{ value.map(tag => `${tag}(${dv.pages(tag).length})`).join(', ') }} +``` + +#### Show Last Modified Time +``` +file.mtime|Last Modified {{ dv.date(value).toFormat('yyyy-MM-dd HH:mm') }} +``` + +### 3. Conditional Display + +``` +status|Status {{ value === 'active' ? 'Active' : 'Inactive' }} +``` + +### 4. Progress Calculation + +``` +progress|Progress {{ Math.round((metadata.completed / metadata.total) * 100) + '%' }} +``` + +## Notes + +1. JavaScript code in computation expressions is executed dynamically when displayed +2. If a computation fails, it falls back to the original value +3. For security, avoid using potentially harmful code in expressions +4. Keep expressions simple; use Dataview plugin for complex calculations + +## Troubleshooting + +If computations don't work as expected: + +1. Check expression syntax +2. Use `console.log(value, metadata)` for debugging +3. Verify field names and case sensitivity +4. Check for extra spaces or special characters + +## Advanced Tips + +- Use ternary operators for conditional rendering +- Combine multiple fields in computations +- Use JavaScript built-in functions (like `toLocaleString()`, `toFixed()`) for formatting +- Access Obsidian API using the `app` variable +--- + +This feature allows for flexible display and computation of note data without writing complex plugin code. diff --git a/docs/custom-mode-fields_ja.md b/docs/custom-mode-fields_ja.md new file mode 100644 index 0000000..4089e69 --- /dev/null +++ b/docs/custom-mode-fields_ja.md @@ -0,0 +1,134 @@ +# Grid Explorer カスタムモード フィールドガイド + +## 概要 + +Grid Explorer は、カスタムモードでフィールドのエイリアスを設定し、リアルタイムで計算を実行する機能をサポートしています。これにより、メタデータを柔軟に表示・計算できます。 + +## 基本構文 + +### フィールドエイリアス + +`|` 記号を使用して、フィールドに表示名を設定します: + +``` +元のフィールド名|表示名 +``` + +**例:** +- `birthday|誕生日` - `birthday` フィールドを「誕生日」として表示 +- `status|状態` - `status` フィールドを「状態」として表示 + +### 計算式 + +`{{ ... }}` で JavaScript の式を囲んで計算を実行します: + +``` +フィールド名 {{ 式 }} +``` + +**使用可能な変数:** + +| 変数 | 説明 | 例 | +|------|------|-----| +| `value` | 現在のフィールドの値 | `value` または `this` | +| `metadata` | フロントマターのメタデータオブジェクト | `metadata.tags` | +| `app` | Obsidian アプリケーションインスタンス | `app.vault.getMarkdownFiles()` | +| `dv` | Dataview API インスタンス(Dataview プラグインが必要) | `dv.pages('#tag')` | + +## 高度な使い方 + +### エイリアスと計算の組み合わせ + +``` +フィールド名|表示名 {{ 式 }} +``` + +**基本例:** +``` +birthday|年齢 {{ Math.floor((Date.now() - new Date(value).getTime()) / (1000 * 60 * 60 * 24 * 365.25)) }} +``` + +**Dataview 使用例:** +``` +tags|関連ノート {{ dv.pages(value).length }} +``` + +### 複数フィールドの組み合わせ + +``` +full_name|フルネーム {{ (metadata.first_name || '') + ' ' + (metadata.last_name || '') }} +``` + +## 実践的な例 + +### 1. 基本的な計算 + +#### 年齢の計算 +``` +birthday|年齢 {{ Math.floor((Date.now() - new Date(value).getTime()) / (1000 * 60 * 60 * 24 * 365.25)) }} +``` + +#### 日付のフォーマット +``` +date|日付 {{ new Date(value).toLocaleDateString('ja-JP', { year: 'numeric', month: 'long', day: 'numeric' }) }} +``` + +### 2. Dataview の高度な機能 + +#### 関連ノートのカウント +``` +tags|関連ノート {{ dv.pages(value).length }} +``` + +#### 未完了タスクの表示 +``` +file.folder|ToDo {{ dv.pages(`"${value}"`).file.tasks.where(t => !t.completed).length }} +``` + +#### タグの使用頻度 +``` +tags|タグ統計 {{ value.map(tag => `${tag}(${dv.pages(tag).length})`).join(', ') }} +``` + +#### 最終更新日時の表示 +``` +file.mtime|最終更新 {{ dv.date(value).toFormat('yyyy-MM-dd HH:mm') }} +``` + +### 3. 条件付き表示 + +``` +status|状態 {{ value === 'active' ? '有効' : '無効' }} +``` + +### 4. 進捗率の計算 + +``` +progress|進捗 {{ Math.round((metadata.completed / metadata.total) * 100) + '%' }} +``` + +## 注意事項 + +1. 計算式内の JavaScript コードは表示時に動的に実行されます +2. 計算に失敗した場合は元の値が表示されます +3. セキュリティのため、危険なコードは使用しないでください +4. 複雑な計算には Dataview プラグインの使用を推奨します + +## トラブルシューティング + +計算が期待通りに動作しない場合: + +1. 式の構文を確認してください +2. `console.log(value, metadata)` でデバッグできます +3. フィールド名と大文字小文字を確認してください +4. 余分なスペースや特殊文字を確認してください + +## 高度なヒント + +- 条件付き表示には三項演算子を使用します +- 複数のフィールドを組み合わせて計算します +- 表示のフォーマットには `toLocaleString()` や `toFixed()` などの JavaScript 組み込み関数を使用します +- `app` 変数で Obsidian API にアクセスできます +--- + +この機能を使用すると、複雑なプラグインコードを書かずに、ノートデータを柔軟に表示・計算できます。 diff --git a/docs/custom-mode-fields_zhTW.md b/docs/custom-mode-fields_zhTW.md new file mode 100644 index 0000000..ad3576e --- /dev/null +++ b/docs/custom-mode-fields_zhTW.md @@ -0,0 +1,140 @@ +# Grid Explorer 自訂模式欄位說明 + +## 功能概述 + +Grid Explorer 現在支援在自訂模式中為欄位設定別名,並在顯示時進行即時計算。這讓您能夠以更靈活的方式顯示和計算筆記中的元數據。 + +## 基本語法 + +### 欄位別名 + +使用 `|` 符號為欄位設定顯示名稱: + +``` +原始欄位名稱|顯示名稱 +``` + +**範例:** +- `birthday|生日` - 將 `birthday` 欄位顯示為「生日」 +- `status|狀態` - 將 `status` 欄位顯示為「狀態」 + +### 計算表達式 + +使用 `{{ ... }}` 包裝 JavaScript 表達式來進行計算: + +``` +欄位名稱 {{ 表達式 }} +``` + +**可用變數:** + +在計算表達式中,您可以使用以下變數: + +| 變數 | 說明 | 範例 | +|------|------|------| +| `value` | 當前欄位的原始值 | `value` 或 `this` | +| `metadata` | 整個 frontmatter 元數據對象 | `metadata.tags` | +| `app` | Obsidian 應用程式實例 | `app.vault.getMarkdownFiles()` | +| `dv` | Dataview API 實例(需安裝 Dataview 插件) | `dv.pages('#tag')` | + +## 進階用法 + +### 結合別名與計算 + +您可以同時使用別名和計算表達式: + +``` +欄位名稱|顯示名稱 {{ 表達式 }} +``` + +**基本範例:** +``` +birthday|年齡 {{ Math.floor((Date.now() - new Date(value).getTime()) / (1000 * 60 * 60 * 24 * 365.25)) }} +``` + +**結合 Dataview 範例:** +``` +tags|相關筆記 {{ dv.pages(value).length }} +``` + +### 多欄位組合 + +您可以在計算表達式中引用其他欄位: + +``` +full_name|全名 {{ (metadata.first_name || '') + ' ' + (metadata.last_name || '') }} +``` + +## 實際應用範例 + +### 1. 基本計算 + +#### 計算年齡 +``` +birthday|年齡 {{ Math.floor((Date.now() - new Date(value).getTime()) / (1000 * 60 * 60 * 24 * 365.25)) }} +``` + +#### 格式化日期 +``` +date|日期 {{ new Date(value).toLocaleDateString('zh-TW', { year: 'numeric', month: 'long', day: 'numeric' }) }} +``` + +### 2. 使用 Dataview 的進階功能 + +#### 計算相關筆記數量 +``` +tags|相關筆記 {{ dv.pages(value).length }} +``` + +#### 顯示未完成任務數 +``` +file.folder|待辦事項 {{ dv.pages(`"${value}"`).file.tasks.where(t => !t.completed).length }} +``` + +#### 計算標籤使用頻率 +``` +tags|標籤統計 {{ value.map(tag => `${tag}(${dv.pages(tag).length})`).join(', ') }} +``` + +#### 顯示最近修改時間 +``` +file.mtime|最後修改 {{ dv.date(value).toFormat('yyyy-MM-dd HH:mm') }} +``` + +### 3. 條件顯示 + +``` +status|狀態 {{ value === 'active' ? '啟用' : '停用' }} +``` + +### 4. 計算進度 + +``` +progress|進度 {{ Math.round((metadata.completed / metadata.total) * 100) + '%' }} +``` + +## 注意事項 + +1. 計算表達式中的 JavaScript 代碼會在顯示時動態執行 +2. 如果計算表達式出錯,會自動回退顯示原始值 +3. 為確保安全性,不建議在表達式中使用可能有害的代碼 +4. 表達式應保持簡潔,複雜的計算建議使用 Dataview 插件 + +## 疑難排解 + +如果計算結果不符合預期: + +1. 檢查表達式語法是否正確 +2. 使用 `console.log(value, metadata)` 調試表達式 +3. 確認欄位名稱和大小寫是否正確 +4. 檢查是否有額外的空格或特殊字符 + +## 進階提示 + +- 使用三元運算符進行條件渲染 +- 結合多個欄位進行計算 +- 使用 JavaScript 的內建函數(如 `toLocaleString()`、`toFixed()` 等)格式化輸出 +- 在表達式中使用 `app` 變數訪問 Obsidian API +--- + +這個功能讓您能夠更靈活地呈現和計算筆記中的數據,無需編寫複雜的插件代碼即可實現強大的顯示效果。 diff --git a/manifest.json b/manifest.json index a049956..94521cc 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "gridexplorer", "name": "GridExplorer", - "version": "2.7.10", + "version": "2.7.11", "minAppVersion": "1.1.0", "description": "Browse note files in a grid view.", "author": "Devon22", diff --git a/package-lock.json b/package-lock.json index 507b3f8..cbb724b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gridexplorer", - "version": "2.7.10", + "version": "2.7.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gridexplorer", - "version": "2.7.10", + "version": "2.7.11", "license": "MIT", "devDependencies": { "@types/node": "^16.11.6", diff --git a/package.json b/package.json index 6a1b356..12a54ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gridexplorer", - "version": "2.7.10", + "version": "2.7.11", "description": "Browse note files in a grid view.", "main": "main.js", "scripts": { diff --git a/src/GridView.ts b/src/GridView.ts index b906c21..9c73050 100644 --- a/src/GridView.ts +++ b/src/GridView.ts @@ -39,6 +39,7 @@ export class GridView extends ItemView { folderSortType: string = ''; // 資料夾排序模式 searchQuery: string = ''; // 搜尋關鍵字 searchAllFiles: boolean = true; // 是否搜尋所有筆記 + searchFilesNameOnly: boolean = false; // 是否只搜尋筆記名稱 searchMediaFiles: boolean = false; // 是否搜尋媒體檔案 randomNoteIncludeMedia: boolean = false; // 隨機筆記是否包含圖片和影片 selectedItemIndex: number = -1; // 當前選中的項目索引 @@ -84,6 +85,15 @@ export class GridView extends ItemView { return handleKeyDown(this, event); } }); + + // 監聽 dataview:index-ready + this.registerEvent( + (this.app.metadataCache as any).on('dataview:index-ready', () => { + if (this.sourceMode.startsWith('custom-')) { + this.render(); + } + }) + ); } getViewType() { @@ -1938,15 +1948,31 @@ export class GridView extends ItemView { // 檢查一般搜尋詞 if (normalTerms.length > 0) { - // 檢查檔案名稱是否包含所有一般搜尋字串 - const matchesFileName = normalTerms.every(term => fileName.includes(term)); - - if (!matchesFileName && file.extension === 'md') { - // 如果檔名不匹配且是 Markdown 檔案,檢查內容 - const content = (await this.app.vault.cachedRead(file)).toLowerCase(); - matchesNormalTerms = normalTerms.every(term => content.includes(term)); + if (this.searchFilesNameOnly) { + // 僅檢查檔名 + matchesNormalTerms = normalTerms.every(term => fileName.includes(term)); } else { - matchesNormalTerms = matchesFileName; + // 檢查每個一般搜尋字串是否存在於檔名或(若為 Markdown)檔案內容中 + matchesNormalTerms = true; + let contentLower: string | null = null; + for (const term of normalTerms) { + if (fileName.includes(term)) { + continue; // 此關鍵字已在檔名中找到 + } + + if (file.extension === 'md') { + // 延遲讀取內容,避免不必要的 IO + if (contentLower === null) { + contentLower = (await this.app.vault.cachedRead(file)).toLowerCase(); + } + if (contentLower.includes(term)) { + continue; // 此關鍵字在內容中找到 + } + } + // 若此關鍵字既不在檔名也不在內容中,則不符合 + matchesNormalTerms = false; + break; + } } } @@ -2003,34 +2029,40 @@ export class GridView extends ItemView { ); // 排序檔案 - if (this.sourceMode === 'bookmarks') { - // 保持原始順序 - files.sort((a, b) => { - const indexA = fileIndexMap.get(a) ?? Number.MAX_SAFE_INTEGER; - const indexB = fileIndexMap.get(b) ?? Number.MAX_SAFE_INTEGER; - return indexA - indexB; - }); - } else if (this.sourceMode === 'recent-files') { - // 臨時的排序類型 - const sortType = this.sortType; - this.sortType = 'mtime-desc'; + if (this.searchAllFiles) { + // 搜尋所有檔案時 files = sortFiles(files, this); - this.sortType = sortType; - } else if (this.sourceMode === 'random-note') { - // 臨時的排序類型 - const sortType = this.sortType; - this.sortType = 'random'; - files = sortFiles(files, this); - this.sortType = sortType; - } else if (this.sourceMode.startsWith('custom-')) { - // 保持原始順序 - files.sort((a, b) => { - const indexA = fileIndexMap.get(a) ?? Number.MAX_SAFE_INTEGER; - const indexB = fileIndexMap.get(b) ?? Number.MAX_SAFE_INTEGER; - return indexA - indexB; - }); } else { - files = sortFiles(files, this); + // 非搜尋所有檔案時 + if (this.sourceMode === 'bookmarks') { + // 保持原始順序 + files.sort((a, b) => { + const indexA = fileIndexMap.get(a) ?? Number.MAX_SAFE_INTEGER; + const indexB = fileIndexMap.get(b) ?? Number.MAX_SAFE_INTEGER; + return indexA - indexB; + }); + } else if (this.sourceMode === 'recent-files') { + // 臨時的排序類型 + const sortType = this.sortType; + this.sortType = 'mtime-desc'; + files = sortFiles(files, this); + this.sortType = sortType; + } else if (this.sourceMode === 'random-note') { + // 臨時的排序類型 + const sortType = this.sortType; + this.sortType = 'random'; + files = sortFiles(files, this); + this.sortType = sortType; + } else if (this.sourceMode.startsWith('custom-')) { + // 保持原始順序 + files.sort((a, b) => { + const indexA = fileIndexMap.get(a) ?? Number.MAX_SAFE_INTEGER; + const indexB = fileIndexMap.get(b) ?? Number.MAX_SAFE_INTEGER; + return indexA - indexB; + }); + } else { + files = sortFiles(files, this); + } } // 忽略檔案 @@ -2147,20 +2179,31 @@ export class GridView extends ItemView { // 收集所有欄位值,並處理別名("原始欄位|別名") fieldList.forEach(fieldEntry => { - // 解析欄位與顯示名稱 - // 只從右邊數來的第一個 '|' 進行分割 - // 例如 "State|A|狀態" 會變成 ["State|A", "狀態"] - const lastPipeIndex = fieldEntry.lastIndexOf('|'); - let fieldKey, displayName; + // 解析欄位 (fieldKey)、別名 (labelName)、運算式 (calcExpr) + // 格式示例: + // birthday|年齡 {{ Math.floor(...) }} + // birthday {{ Math.floor(...) }} + // birthday|年齡 + // birthday - if (lastPipeIndex !== -1) { - // 如果找到 '|',則分割成欄位名稱和顯示名稱 - fieldKey = fieldEntry.substring(0, lastPipeIndex).trim(); - displayName = fieldEntry.substring(lastPipeIndex + 1).trim() || fieldKey; + let raw = fieldEntry.trim(); + let calcExpr: string | null = null; + // 先取出運算區塊 {{ ... }} + const calcMatch = raw.match(/\{\{(.*?)\}\}/); + if (calcMatch) { + calcExpr = calcMatch[1]; + raw = raw.substring(0, calcMatch.index).trim(); // 去掉運算部分 + } + // 再處理 alias + const aliasIdx = raw.lastIndexOf('|'); + let fieldKey: string; + let labelName: string; + if (aliasIdx !== -1) { + fieldKey = raw.substring(0, aliasIdx).trim(); + labelName = raw.substring(aliasIdx + 1).trim() || fieldKey; } else { - // 如果沒有 '|',則使用原始欄位名稱 - fieldKey = fieldEntry.trim(); - displayName = fieldKey; + fieldKey = raw; + labelName = fieldKey; } if (metadata?.[fieldKey] !== undefined && metadata?.[fieldKey] !== '' && metadata?.[fieldKey] !== null) { @@ -2172,7 +2215,20 @@ export class GridView extends ItemView { if (Array.isArray(metadata[fieldKey])) { metadata[fieldKey] = metadata[fieldKey].join(', '); } - fieldValues.push(`${displayName}: ${metadata[fieldKey]}`); + let outputValue: string | number | null = metadata[fieldKey]; + if (calcExpr) { + try { + const fn = new Function('value', 'metadata', 'app', 'dv', `return (${calcExpr});`); + + // 獲取 Dataview API + const dvApi = this.app.plugins.plugins.dataview?.api; + + outputValue = fn(metadata[fieldKey], metadata, this.app, dvApi); + } catch (error) { + console.error('GridExplorer: evaluate displayName error', error); + } + } + fieldValues.push(`${labelName}: ${outputValue}`); } }); diff --git a/src/modal/customModeModal.ts b/src/modal/customModeModal.ts index 4254ee7..4c2938e 100644 --- a/src/modal/customModeModal.ts +++ b/src/modal/customModeModal.ts @@ -154,7 +154,9 @@ export class CustomModeModal extends Modal { renderOptions(); - new Setting(contentEl) + const saveSetting = new Setting(contentEl); + saveSetting.settingEl.classList.add('ge-save-footer'); + saveSetting .addButton(button => { button.setButtonText(t('save')) .setCta() diff --git a/src/modal/searchModal.ts b/src/modal/searchModal.ts index 2e4bf1e..d290dbe 100644 --- a/src/modal/searchModal.ts +++ b/src/modal/searchModal.ts @@ -164,6 +164,18 @@ export class SearchModal extends Modal { searchScopeCheckbox.checked = false; } + // 創建只搜尋檔案名稱設定 + const searchNameContainer = searchOptionsContainer.createDiv('ge-search-name-container'); + const searchNameCheckbox = searchNameContainer.createEl('input', { + type: 'checkbox', + cls: 'ge-search-name-checkbox' + }); + searchNameCheckbox.checked = this.gridView.searchFilesNameOnly; + searchNameContainer.createEl('span', { + text: t('search_files_name_only'), + cls: 'ge-search-name-label' + }); + // 創建搜尋媒體檔案設定 const searchMediaFilesContainer = searchOptionsContainer.createDiv('ge-search-media-files-container'); const searchMediaFilesCheckbox = searchMediaFilesContainer.createEl('input', { @@ -189,6 +201,12 @@ export class SearchModal extends Modal { this.gridView.searchAllFiles = !searchScopeCheckbox.checked; } }); + searchNameContainer.addEventListener('click', (e) => { + if (e.target !== searchNameCheckbox) { + searchNameCheckbox.checked = !searchNameCheckbox.checked; + this.gridView.searchFilesNameOnly = !searchNameCheckbox.checked; + } + }); searchMediaFilesContainer.addEventListener('click', (e) => { if (e.target !== searchMediaFilesCheckbox) { searchMediaFilesCheckbox.checked = !searchMediaFilesCheckbox.checked; @@ -302,6 +320,7 @@ export class SearchModal extends Modal { const performSearch = () => { this.gridView.searchQuery = searchInput.value; this.gridView.searchAllFiles = !searchScopeCheckbox.checked; + this.gridView.searchFilesNameOnly = searchNameCheckbox.checked; this.gridView.searchMediaFiles = searchMediaFilesCheckbox.checked; this.gridView.clearSelection(); this.gridView.app.workspace.requestSaveLayout(); diff --git a/src/translations.ts b/src/translations.ts index 55703d8..8a6737c 100644 --- a/src/translations.ts +++ b/src/translations.ts @@ -33,6 +33,7 @@ export const TRANSLATIONS: Translations = { 'search': '搜尋', 'search_placeholder': '搜尋關鍵字', 'search_current_location_only': '僅搜尋目前位置', + 'search_files_name_only': '僅搜尋檔名', 'search_media_files': '搜尋媒體檔案', 'cancel': '取消', 'new_note': '新增筆記', @@ -307,6 +308,7 @@ export const TRANSLATIONS: Translations = { 'search': 'Search', 'search_placeholder': 'Search keyword', 'search_current_location_only': 'Search current location only', + 'search_files_name_only': 'Search files name only', 'search_media_files': 'Search media files', 'cancel': 'Cancel', 'new_note': 'New note', @@ -581,6 +583,7 @@ export const TRANSLATIONS: Translations = { 'search': '搜索', 'search_placeholder': '搜索关键字', 'search_current_location_only': '仅搜索当前位置', + 'search_files_name_only': '仅搜索文件名', 'search_media_files': '搜索媒体文件', 'cancel': '取消', 'new_note': '新建笔记', @@ -853,6 +856,7 @@ export const TRANSLATIONS: Translations = { 'search': '検索', 'search_placeholder': 'キーワード検索', 'search_current_location_only': '現在の場所のみ検索', + 'search_files_name_only': 'ファイル名のみ検索', 'search_media_files': 'メディアファイルを検索', 'cancel': 'キャンセル', 'new_note': '新規ノート', @@ -1126,6 +1130,7 @@ export const TRANSLATIONS: Translations = { 'search': 'Поиск', 'search_placeholder': 'Ключевое слово для поиска', 'search_current_location_only': 'Искать только в текущем расположении', + 'search_files_name_only': 'Искать только в названии файлов', 'search_media_files': 'Искать медиафайлы', 'cancel': 'Отмена', 'new_note': 'Новая заметка', @@ -1398,6 +1403,7 @@ export const TRANSLATIONS: Translations = { 'search': 'Пошук', 'search_placeholder': 'Ключове слово для пошуку', 'search_current_location_only': 'Шукати лише в поточному розташуванні', + 'search_files_name_only': 'Шукати лише в назві файлів', 'search_media_files': 'Шукати медіафайли', 'cancel': 'Скасувати', 'new_note': 'Нова нотатка', diff --git a/styles.css b/styles.css index ae9a072..2b07738 100644 --- a/styles.css +++ b/styles.css @@ -926,6 +926,24 @@ a.ge-current-folder:hover { font-size: var(--font-ui-small); } +/* 只搜尋檔案名稱設定樣式 Search Name Container */ +.ge-search-name-container { + display: flex; + align-items: center; + cursor: pointer; +} + +.ge-search-name-checkbox { + cursor: pointer; +} + +.ge-search-name-label { + cursor: pointer; + user-select: none; + color: var(--text-normal); + font-size: var(--font-ui-small); +} + /* 搜尋媒體檔案設定樣式 Search Media Files Container */ .ge-search-media-files-container { display: flex; @@ -1451,6 +1469,23 @@ a.ge-current-folder:hover { gap: 0.5rem; } +/* Save Footer */ +.ge-save-footer { + position: sticky; + bottom: 0; + background-color: transparent; + padding: 8px 0; + z-index: 10; +} + +.setting-item .ge-save-footer { + border-top: none; +} + +.ge-save-footer .setting-item-control button { + box-shadow: 0 5px 8px rgba(0, 0, 0, 0.4); +} + /* --------------------------------------------------------------------------- */ /* Popup Modal Styles */ @@ -1871,4 +1906,5 @@ a.ge-current-folder:hover { .shortcut-option-icon svg { width: 16px; height: 16px; -} \ No newline at end of file +} + diff --git a/versions.json b/versions.json index b949784..d294f2a 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "2.7.10": "1.1.0" + "2.7.11": "1.1.0" } \ No newline at end of file