mirror of
https://github.com/devon22/obsidian-gridexplorer.git
synced 2026-07-22 05:38:16 +00:00
2.7.11
This commit is contained in:
parent
330c73ba48
commit
63ca91d3bd
13 changed files with 632 additions and 54 deletions
51
docs/custom-mode-example.md
Normal file
51
docs/custom-mode-example.md
Normal file
|
|
@ -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 }));
|
||||
```
|
||||
134
docs/custom-mode-fields.md
Normal file
134
docs/custom-mode-fields.md
Normal file
|
|
@ -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.
|
||||
134
docs/custom-mode-fields_ja.md
Normal file
134
docs/custom-mode-fields_ja.md
Normal file
|
|
@ -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 にアクセスできます
|
||||
---
|
||||
|
||||
この機能を使用すると、複雑なプラグインコードを書かずに、ノートデータを柔軟に表示・計算できます。
|
||||
140
docs/custom-mode-fields_zhTW.md
Normal file
140
docs/custom-mode-fields_zhTW.md
Normal file
|
|
@ -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
|
||||
---
|
||||
|
||||
這個功能讓您能夠更靈活地呈現和計算筆記中的數據,無需編寫複雜的插件代碼即可實現強大的顯示效果。
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
150
src/GridView.ts
150
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}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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': 'Нова нотатка',
|
||||
|
|
|
|||
38
styles.css
38
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"2.7.10": "1.1.0"
|
||||
"2.7.11": "1.1.0"
|
||||
}
|
||||
Loading…
Reference in a new issue