diff --git a/manifest.json b/manifest.json index f4f2893..ee7043f 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "gridexplorer", "name": "GridExplorer", - "version": "1.8.2", + "version": "1.8.4", "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 fb5d40b..a8c914c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gridexplorer", - "version": "1.8.2", + "version": "1.8.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gridexplorer", - "version": "1.8.2", + "version": "1.8.4", "license": "MIT", "devDependencies": { "@types/node": "^16.11.6", diff --git a/package.json b/package.json index 553a51b..8b0a6d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gridexplorer", - "version": "1.8.2", + "version": "1.8.4", "description": "Browse note files in a grid view.", "main": "main.js", "scripts": { diff --git a/src/FileWatcher.ts b/src/FileWatcher.ts index 334789e..234ec52 100644 --- a/src/FileWatcher.ts +++ b/src/FileWatcher.ts @@ -20,6 +20,7 @@ export class FileWatcher { return; } + //新增檔案 this.plugin.registerEvent( this.app.vault.on('create', (file) => { if (file instanceof TFile) { @@ -35,6 +36,7 @@ export class FileWatcher { }) ); + //刪除檔案 this.plugin.registerEvent( this.app.vault.on('delete', (file) => { if (file instanceof TFile) { diff --git a/src/GridView.ts b/src/GridView.ts index 6b69d89..214cfcb 100644 --- a/src/GridView.ts +++ b/src/GridView.ts @@ -1,4 +1,4 @@ -import { App, WorkspaceLeaf, ItemView, Modal, TFolder, TFile, Menu, Notice, Setting} from 'obsidian'; +import { WorkspaceLeaf, ItemView, TFolder, TFile, Menu, Notice } from 'obsidian'; import { setIcon, getFrontMatterInfo } from 'obsidian'; import { showFolderSelectionModal } from './FolderSelectionModal'; import { findFirstImageInNote } from './mediaUtils'; @@ -15,6 +15,8 @@ export class GridView extends ItemView { sourcePath: string; sortType: string; searchQuery: string; + searchAllFiles: boolean; + searchType: string; selectedItemIndex: number = -1; // 當前選中的項目索引 gridItems: HTMLElement[] = []; // 存儲所有網格項目的引用 hasKeyboardFocus: boolean = false; // 是否有鍵盤焦點 @@ -29,6 +31,7 @@ export class GridView extends ItemView { this.sourcePath = ''; // 用於資料夾模式的路徑 this.sortType = this.plugin.settings.defaultSortType; // 使用設定中的預設排序模式 this.searchQuery = ''; // 搜尋關鍵字 + this.searchAllFiles = true; // 根據設定決定是否註冊檔案變更監聽器 if (this.plugin.settings.enableFileWatcher) { @@ -322,7 +325,7 @@ export class GridView extends ItemView { } // 添加回上層按鈕(僅在資料夾模式且不在根目錄時顯示) - if (this.sourceMode === 'folder' && this.sourcePath !== '/') { + if (this.sourceMode === 'folder' && this.sourcePath !== '/' && this.searchQuery === '') { const upButton = headerButtonsDiv.createEl('button', { attr: { 'aria-label': t('go_up') } }); upButton.addEventListener('click', () => { const parentPath = this.sourcePath.split('/').slice(0, -1).join('/') || '/'; @@ -453,6 +456,7 @@ export class GridView extends ItemView { clearButton.addEventListener('click', (e) => { e.stopPropagation(); // 防止觸發搜尋文字的點擊事件 this.searchQuery = ''; + this.searchAllFiles = true; this.clearSelection(); this.render(); // 通知 Obsidian 保存視圖狀態 @@ -493,9 +497,15 @@ export class GridView extends ItemView { container.empty(); container.addClass('ge-grid-container'); container.style.setProperty('--grid-item-width', this.plugin.settings.gridItemWidth + 'px'); + if (this.plugin.settings.gridItemHeight === 0) { + container.style.setProperty('--grid-item-height', '100%'); + } else { + container.style.setProperty('--grid-item-height', this.plugin.settings.gridItemHeight + 'px'); + } container.style.setProperty('--image-area-width', this.plugin.settings.imageAreaWidth + 'px'); container.style.setProperty('--image-area-height', this.plugin.settings.imageAreaHeight + 'px'); - + container.style.setProperty('--title-font-size', this.plugin.settings.titleFontSize + 'em'); + // 重置網格項目數組 this.gridItems = []; @@ -550,7 +560,7 @@ export class GridView extends ItemView { return false; } } - + return true; }) .sort((a, b) => a.name.localeCompare(b.name)); @@ -581,8 +591,13 @@ export class GridView extends ItemView { loadingDiv.setText(t('searching')); // 取得 vault 中所有支援的檔案 - const allFiles = this.app.vault.getFiles(); - + let allFiles: TFile[] = []; + if (this.searchAllFiles) { + allFiles = this.app.vault.getFiles(); + } else { + allFiles = await this.getFiles(); + } + // 根據設定過濾檔案 const filteredFiles = allFiles.filter(file => { // 非媒體檔案(Markdown、PDF、Canvas)始終包含 @@ -648,14 +663,22 @@ export class GridView extends ItemView { if (!(file instanceof TFile)) return; // 載入預覽內容 + let imageUrl: string | null = ''; const contentArea = fileEl.querySelector('.ge-content-area') as Element; if (!contentArea.hasAttribute('data-loaded')) { // 根據檔案類型處理 if (file.extension === 'md') { + let summaryLength = this.plugin.settings.summaryLength; + if (summaryLength < 50) { + summaryLength = 100; + this.plugin.settings.summaryLength = 100; + this.plugin.saveSettings(); + } + // Markdown 檔案顯示內容預覽 const content = await this.app.vault.cachedRead(file); const frontMatterInfo = getFrontMatterInfo(content); - const contentWithoutFrontmatter = content.substring(frontMatterInfo.contentStart).slice(0, 500); + const contentWithoutFrontmatter = content.substring(frontMatterInfo.contentStart).slice(0, summaryLength+summaryLength); let contentWithoutMediaLinks = contentWithoutFrontmatter.replace(/```[\s\S]*?```||!?(?:\[[^\]]*\]\([^)]+\)|\[\[[^\]]+\]\])/g, '').trim(); //把開頭的標題整行刪除 @@ -664,10 +687,12 @@ export class GridView extends ItemView { } // 只取前100個字符作為預覽 - const preview = contentWithoutMediaLinks.slice(0, 100) + (contentWithoutMediaLinks.length > 100 ? '...' : ''); + const preview = contentWithoutMediaLinks.slice(0, summaryLength) + (contentWithoutMediaLinks.length > summaryLength ? '...' : ''); // 創建預覽內容 const contentEl = contentArea.createEl('p', { text: preview.trim() }); + + imageUrl = await findFirstImageInNote(this.app, content); } else { // 其他檔案顯示副檔名 const contentEl = contentArea.createEl('p', { text: file.extension.toUpperCase() }); @@ -700,7 +725,6 @@ export class GridView extends ItemView { imageArea.setAttribute('data-loaded', 'true'); } else if (file.extension === 'md') { // Markdown 檔案尋找內部圖片 - const imageUrl = await findFirstImageInNote(this.app, file); if (imageUrl) { const img = imageArea.createEl('img'); img.src = imageUrl; @@ -1081,7 +1105,8 @@ export class GridView extends ItemView { sourceMode: this.sourceMode, sourcePath: this.sourcePath, sortType: this.sortType, - searchQuery: this.searchQuery + searchQuery: this.searchQuery, + searchAllFiles: this.searchAllFiles } }; } @@ -1093,6 +1118,7 @@ export class GridView extends ItemView { this.sourcePath = state.state.sourcePath || null; this.sortType = state.state.sortType || 'mtime-desc'; this.searchQuery = state.state.searchQuery || ''; + this.searchAllFiles = state.state.searchAllFiles ?? true; this.render(); } } diff --git a/src/SearchModal.ts b/src/SearchModal.ts index 3f21ae3..555f02b 100644 --- a/src/SearchModal.ts +++ b/src/SearchModal.ts @@ -49,6 +49,31 @@ export class SearchModal extends Modal { searchInput.focus(); }); + // 創建搜尋範圍設定 + const searchScopeContainer = contentEl.createDiv('ge-search-scope-container'); + const searchScopeCheckbox = searchScopeContainer.createEl('input', { + type: 'checkbox', + cls: 'ge-search-scope-checkbox' + }); + searchScopeCheckbox.checked = !this.gridView.searchAllFiles; + const searchScopeLabel = searchScopeContainer.createEl('span', { + text: t('search_current_location_only'), + cls: 'ge-search-scope-label' + }); + + // 點擊容器時切換勾選框狀態 + searchScopeContainer.addEventListener('click', (e) => { + if (e.target !== searchScopeCheckbox) { + searchScopeCheckbox.checked = !searchScopeCheckbox.checked; + this.gridView.searchAllFiles = !searchScopeCheckbox.checked; + } + }); + + // 勾選框變更時更新搜尋範圍 + searchScopeCheckbox.addEventListener('change', () => { + this.gridView.searchAllFiles = !searchScopeCheckbox.checked; + }); + // 創建按鈕容器 const buttonContainer = contentEl.createDiv('ge-button-container'); @@ -65,6 +90,7 @@ export class SearchModal extends Modal { // 綁定搜尋事件 const performSearch = () => { this.gridView.searchQuery = searchInput.value; + this.gridView.searchAllFiles = !searchScopeCheckbox.checked; this.gridView.clearSelection(); this.gridView.render(); // 通知 Obsidian 保存視圖狀態 diff --git a/src/mediaUtils.ts b/src/mediaUtils.ts index 48f45ae..457cc14 100644 --- a/src/mediaUtils.ts +++ b/src/mediaUtils.ts @@ -1,10 +1,9 @@ import { App, TFile } from 'obsidian'; // 尋找筆記中的第一張圖片 -export async function findFirstImageInNote(app: App, file: TFile) { +export async function findFirstImageInNote(app: App, content: string) { try { - const content = await app.vault.cachedRead(file); - const internalMatch = content.match(/(?:!\[\[(.*?\.(?:jpg|jpeg|png|gif|webp))(?:\|.*?)?\]\]|!\[(.*?)\]\(\s*(\S+?(?:\.(?:jpg|jpeg|png|gif|webp)|format=(?:jpg|jpeg|png|gif|webp))[^\s)]*)\s*(?:\s+["'][^"']*["'])?\s*\))/gi); + const internalMatch = content.match(/(?:!\[\[(.*?\.(?:jpg|jpeg|png|gif|webp))(?:\|.*?)?\]\]|!\[(.*?)\]\(\s*(\S+?(?:\.(?:jpg|jpeg|png|gif|webp)|format=(?:jpg|jpeg|png|gif|webp))[^\s)]*)\s*(?:\s+["'][^"']*["'])?\s*\))/i); if (internalMatch) { return processMediaLink(app, internalMatch[0]); } else { diff --git a/src/settings.ts b/src/settings.ts index 824c12e..25ebca5 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,4 +1,4 @@ -import { App, PluginSettingTab, Setting } from 'obsidian'; +import { App, PluginSettingTab, Setting, Notice } from 'obsidian'; import { t } from './translations'; import GridExplorerPlugin from '../main'; @@ -7,8 +7,11 @@ export interface GallerySettings { ignoredFolderPatterns: string[]; defaultSortType: string; gridItemWidth: number; + gridItemHeight: number; imageAreaWidth: number; imageAreaHeight: number; + titleFontSize: number; + summaryLength: number; enableFileWatcher: boolean; showMediaFiles: boolean; searchMediaFiles: boolean; @@ -21,8 +24,11 @@ export const DEFAULT_SETTINGS: GallerySettings = { ignoredFolderPatterns: [], // 預設以字串忽略的資料夾模式 defaultSortType: 'mtime-desc', // 預設排序模式:修改時間倒序 gridItemWidth: 300, // 網格項目寬度,預設 300 + gridItemHeight: 0, // 網格項目高度,預設 0 imageAreaWidth: 100, // 圖片區域寬度,預設 100 imageAreaHeight: 100, // 圖片區域高度,預設 100 + titleFontSize: 1.1, // 筆記標題的字型大小,預設 1.1 + summaryLength: 100, // 筆記摘要的字數,預設 100 enableFileWatcher: true, // 預設啟用檔案監控 showMediaFiles: false, // 預設顯示圖片和影片 searchMediaFiles: false, // 預設搜尋時也包含圖片和影片 @@ -42,6 +48,19 @@ export class GridExplorerSettingTab extends PluginSettingTab { const { containerEl } = this; containerEl.empty(); + // 回復預設值按鈕 + new Setting(containerEl) + .setName(t('reset_to_default')) + .setDesc(t('reset_to_default_desc')) + .addButton(button => button + .setButtonText(t('reset')) + .onClick(async () => { + this.plugin.settings = { ...DEFAULT_SETTINGS }; + await this.plugin.saveSettings(); + this.display(); + new Notice(t('settings_reset_notice')); + })); + // 媒體檔案設定區域 containerEl.createEl('h3', { text: t('media_files_settings') }); @@ -133,7 +152,7 @@ export class GridExplorerSettingTab extends PluginSettingTab { .setDesc(t('grid_item_width_desc')) .addSlider(slider => { slider - .setLimits(200, 600, 50) + .setLimits(200, 600, 10) .setValue(this.plugin.settings.gridItemWidth) .setDynamicTooltip() .onChange(async (value) => { @@ -142,6 +161,21 @@ export class GridExplorerSettingTab extends PluginSettingTab { }); }); + // 網格項目高度設定 + new Setting(containerEl) + .setName(t('grid_item_height')) + .setDesc(t('grid_item_height_desc')) + .addSlider(slider => { + slider + .setLimits(0, 600, 10) + .setValue(this.plugin.settings.gridItemHeight) + .setDynamicTooltip() + .onChange(async (value) => { + this.plugin.settings.gridItemHeight = value; + await this.plugin.saveSettings(); + }); + }); + // 圖片區域寬度設定 new Setting(containerEl) .setName(t('image_area_width')) @@ -172,6 +206,37 @@ export class GridExplorerSettingTab extends PluginSettingTab { }); }); + //筆記標題的字型大小 + new Setting(containerEl) + .setName(t('title_font_size')) + .setDesc(t('title_font_size_desc')) + .addSlider(slider => { + slider + .setLimits(1, 1.5, 0.05) + .setValue(this.plugin.settings.titleFontSize) + .setDynamicTooltip() + .onChange(async (value) => { + this.plugin.settings.titleFontSize = value; + await this.plugin.saveSettings(); + }); + }); + + + // 筆記摘要的字數設定 + new Setting(containerEl) + .setName(t('summary_length')) + .setDesc(t('summary_length_desc')) + .addSlider(slider => { + slider + .setLimits(50, 600, 50) + .setValue(this.plugin.settings.summaryLength) + .setDynamicTooltip() + .onChange(async (value) => { + this.plugin.settings.summaryLength = value; + await this.plugin.saveSettings(); + }); + }); + // 忽略的資料夾設定 const ignoredFoldersContainer = containerEl.createDiv('ignored-folders-container'); diff --git a/src/translations.ts b/src/translations.ts index eaa1abe..8a6f3b0 100644 --- a/src/translations.ts +++ b/src/translations.ts @@ -31,6 +31,7 @@ export const TRANSLATIONS: Translations = { 'no_backlinks': '沒有反向連結', 'search': '搜尋', 'search_placeholder': '搜尋關鍵字', + 'search_current_location_only': '僅搜尋目前位置', 'cancel': '取消', 'new_note': '新增筆記', 'untitled': '未命名', @@ -77,12 +78,21 @@ export const TRANSLATIONS: Translations = { 'default_sort_type_desc': '設定開啟網格視圖時的預設排序模式', 'grid_item_width': '網格項目寬度', 'grid_item_width_desc': '設定網格項目的寬度', + 'grid_item_height': '網格項目高度', + 'grid_item_height_desc': '設定網格項目的高度 (設為0時為自動調整)', 'image_area_width': '圖片區域寬度', 'image_area_width_desc': '設定圖片預覽區域的寬度', 'image_area_height': '圖片區域高度', 'image_area_height_desc': '設定圖片預覽區域的高度', + 'title_font_size': '標題字體大小', + 'title_font_size_desc': '設定標題字體的大小', + 'summary_length': '摘要長度', + 'summary_length_desc': '設定摘要的長度', 'enable_file_watcher': '啟用檔案監控', 'enable_file_watcher_desc': '啟用後會自動偵測檔案變更並更新視圖,關閉後需手動點擊重新整理按鈕', + 'reset_to_default' : '重置為預設值', + 'reset_to_default_desc': '將所有設定重置為預設值', + 'settings_reset_notice': '設定值已重置為預設值', // 選擇資料夾對話框 'select_folders': '選擇資料夾', @@ -107,6 +117,7 @@ export const TRANSLATIONS: Translations = { 'no_backlinks': 'No backlinks', 'search': 'Search', 'search_placeholder': 'Search keyword', + 'search_current_location_only': 'Search current location only', 'cancel': 'Cancel', 'new_note': 'New note', 'untitled': 'Untitled', @@ -153,12 +164,22 @@ export const TRANSLATIONS: Translations = { 'default_sort_type_desc': 'Set the default sorting method when opening Grid View', 'grid_item_width': 'Grid item width', 'grid_item_width_desc': 'Set the width of grid items', + 'grid_item_height': 'Grid item height', + 'grid_item_height_desc': 'Set the height of grid items (set to 0 to automatically adjust)', 'image_area_width': 'Image area width', 'image_area_width_desc': 'Set the width of the image preview area', 'image_area_height': 'Image area height', 'image_area_height_desc': 'Set the height of the image preview area', + 'title_font_size': 'Title font size', + 'title_font_size_desc': 'Set the size of the title font', + 'summary_length': 'Summary length', + 'summary_length_desc': 'Set the length of the summary', 'enable_file_watcher': 'Enable file watcher', 'enable_file_watcher_desc': 'When enabled, the view will automatically update when files change. If disabled, you need to click the refresh button manually', + 'reset_to_default': 'Reset to default', + 'reset_to_default_desc': 'Reset all settings to default values', + 'settings_reset_notice': 'Settings have been reset to default values', + // Select Folder Dialog 'select_folders': 'Select folder', @@ -182,7 +203,8 @@ export const TRANSLATIONS: Translations = { 'go_up': '返回上层文件夹', 'no_backlinks': '没有反向链接', 'search': '搜索', - 'search_placeholder': '搜索关键词', + 'search_placeholder': '搜索关键字', + 'search_current_location_only': '仅搜索当前位置', 'cancel': '取消', 'new_note': '新建笔记', 'untitled': '未命名', @@ -229,12 +251,21 @@ export const TRANSLATIONS: Translations = { 'default_sort_type_desc': '设置打开网格视图时的默认排序模式', 'grid_item_width': '网格项目宽度', 'grid_item_width_desc': '设置网格项目的宽度', + 'grid_item_height': '网格项目高度', + 'grid_item_height_desc': '设置网格项目的高度(设置为0时将自动调整)', 'image_area_width': '图片区域宽度', 'image_area_width_desc': '设置图片预览区域的宽度', 'image_area_height': '图片区域高度', 'image_area_height_desc': '设置图片预览区域的高度', + 'title_font_size': '标题字体大小', + 'title_font_size_desc': '设置标题字体的大小', + 'summary_length': '摘要长度', + 'summary_length_desc': '设置摘要的长度', 'enable_file_watcher': '启用文件监控', 'enable_file_watcher_desc': '启用后会自动检测文件变更并更新视图,关闭后需手动点击刷新按钮', + 'reset_to_default': '重置为默认', + 'reset_to_default_desc': '重置所有设置为默认值', + 'settings_reset_notice': '设置值已重置为默认值', // 选择文件夹对话框 'select_folders': '选择文件夹', @@ -258,7 +289,8 @@ export const TRANSLATIONS: Translations = { 'go_up': '上のフォルダへ', 'no_backlinks': 'バックリンクなし', 'search': '検索', - 'search_placeholder': 'キーワード検索', + 'search_placeholder': '検索キーワード', + 'search_current_location_only': '現在の場所のみ検索', 'cancel': 'キャンセル', 'new_note': '新規ノート', 'untitled': '無題', @@ -305,12 +337,21 @@ export const TRANSLATIONS: Translations = { 'default_sort_type_desc': 'グリッドビューを開いたときのデフォルトの並び替えを設定', 'grid_item_width': 'グリッドアイテムの幅', 'grid_item_width_desc': 'グリッドアイテムの幅を設定', + 'grid_item_height': 'グリッドアイテムの高さ', + 'grid_item_height_desc': 'グリッドアイテムの高さを設定(0を設定すると自動調整されます)', 'image_area_width': '画像エリアの幅', 'image_area_width_desc': '画像プレビューエリアの幅を設定', 'image_area_height': '画像エリアの高さ', 'image_area_height_desc': '画像プレビューエリアの高さを設定', + 'title_font_size': 'タイトルフォントサイズ', + 'title_font_size_desc': 'タイトルフォントのサイズを設定', + 'summary_length': '要約の長さ', + 'summary_length_desc': '要約の長さを設定', 'enable_file_watcher': 'ファイル監視を有効にする', 'enable_file_watcher_desc': '有効にすると、ファイルの変更を自動的に検出してビューを更新します。無効にすると、手動で更新ボタンをクリックする必要があります', + 'reset_to_default': 'デフォルトに戻す', + 'reset_to_default_desc': 'すべての設定をデフォルト値に戻', + 'settings_reset_notice': '設定値がデフォルト値にリセットされました', // フォルダ選択ダイアログ 'select_folders': 'フォルダを選択', diff --git a/styles.css b/styles.css index 90a351b..982c799 100644 --- a/styles.css +++ b/styles.css @@ -43,7 +43,7 @@ transition: transform 0.2s, box-shadow 0.2s; display: flex; gap: 14px; - height: 100%; + height: var(--grid-item-height); } .ge-grid-item:hover { @@ -95,7 +95,7 @@ .ge-title { margin: 0 0 0 3px; - font-size: var(--h5-size); + font-size: var(--title-font-size); color: var(--text-normal); overflow: hidden; text-overflow: ellipsis; @@ -113,6 +113,7 @@ display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; + height: 100%; } .ge-content-area p:empty { @@ -146,6 +147,7 @@ padding: 7px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); transition: all 0.2s ease; + height: 100%; } .ge-grid-item.ge-folder-item:hover { @@ -230,7 +232,7 @@ /* 搜尋對話框樣式 */ .ge-search-container { - margin-bottom: 16px; + margin-bottom: 8px; position: relative; display: flex; align-items: center; @@ -257,6 +259,25 @@ color: var(--text-muted); } +/* 搜尋範圍設定樣式 */ +.ge-search-scope-container { + display: flex; + align-items: center; + margin: 4px 0; + cursor: pointer; + padding: 4px; +} + +.ge-search-scope-checkbox { + cursor: pointer; +} + +.ge-search-scope-label { + cursor: pointer; + user-select: none; + color: var(--text-normal); +} + .ge-button-container { display: flex; gap: 8px; @@ -656,3 +677,4 @@ border: 2px dashed var(--interactive-accent); transform: scale(1.05); } + diff --git a/versions.json b/versions.json index 8976fbb..bc94b62 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "1.8.2": "1.1.0" + "1.8.4": "1.1.0" } \ No newline at end of file