From 5003d6d7a8e6f0b71d7e0b8167b236310b241bb6 Mon Sep 17 00:00:00 2001 From: Devon22 Date: Sat, 14 Feb 2026 00:06:54 +0800 Subject: [PATCH] 3.1.0 --- manifest.json | 2 +- package-lock.json | 4 +- package.json | 2 +- src/GridView.ts | 78 +++++++++++++++++----- src/renderHeaderButton.ts | 135 +++++++++++++------------------------- src/renderModePath.ts | 61 +++++++++++++++-- src/translations/en.ts | 1 + src/translations/ja.ts | 3 +- src/translations/ko.ts | 1 + src/translations/ru.ts | 3 +- src/translations/uk.ts | 1 + src/translations/zh-TW.ts | 1 + src/translations/zh.ts | 1 + src/utils/fileUtils.ts | 87 ++++++++++++++---------- versions.json | 2 +- 15 files changed, 232 insertions(+), 150 deletions(-) diff --git a/manifest.json b/manifest.json index 780e05e..e440e54 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "gridexplorer", "name": "GridExplorer", - "version": "3.0.9", + "version": "3.1.0", "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 6611406..9016984 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gridexplorer", - "version": "3.0.9", + "version": "3.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gridexplorer", - "version": "3.0.9", + "version": "3.1.0", "license": "MIT", "dependencies": { "@esbuild/linux-x64": "0.17.3", diff --git a/package.json b/package.json index e0d10c6..acad984 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gridexplorer", - "version": "3.0.9", + "version": "3.1.0", "description": "Browse note files in a grid view.", "main": "main.js", "scripts": { diff --git a/src/GridView.ts b/src/GridView.ts index e66357f..1b3a20f 100644 --- a/src/GridView.ts +++ b/src/GridView.ts @@ -59,6 +59,7 @@ export class GridView extends ItemView { pinnedList: string[] = []; // 置頂清單 taskFilter: string = 'uncompleted'; // 任務分類 hideHeaderElements: boolean = false; // 是否隱藏標題列元素(模式名稱和按鈕) + bookmarkGroupId: string = 'all'; // 書籤群組 ID customOptionIndex: number = -1; // 自訂模式選項索引 baseCardLayout: 'horizontal' | 'vertical' = 'horizontal'; // 使用者在設定或 UI 中選擇的基礎卡片樣式(不受資料夾臨時覆蓋影響) cardLayout: 'horizontal' | 'vertical' = 'horizontal'; // 目前實際使用的卡片樣式(可能被資料夾 metadata 臨時覆蓋) @@ -252,7 +253,8 @@ export class GridView extends ItemView { searchQuery?: string, searchCurrentLocationOnly?: boolean, searchFilesNameOnly?: boolean, - searchMediaFiles?: boolean + searchMediaFiles?: boolean, + bookmarkGroupId?: string ) { // 如果新的狀態與當前狀態相同,則不進行任何操作 @@ -261,7 +263,8 @@ export class GridView extends ItemView { this.searchQuery === searchQuery && this.searchCurrentLocationOnly === searchCurrentLocationOnly && this.searchFilesNameOnly === searchFilesNameOnly && - this.searchMediaFiles === searchMediaFiles) { + this.searchMediaFiles === searchMediaFiles && + (bookmarkGroupId === undefined || this.bookmarkGroupId === bookmarkGroupId)) { return; } @@ -337,6 +340,9 @@ export class GridView extends ItemView { if (searchMediaFiles !== undefined) { this.searchMediaFiles = searchMediaFiles; } + if (bookmarkGroupId !== undefined) { + this.bookmarkGroupId = bookmarkGroupId; + } // 通知 Obsidian 保存視圖狀態 this.app.workspace.requestSaveLayout(); @@ -1991,19 +1997,6 @@ export class GridView extends ItemView { // 在網格視圖中直接顯示筆記 async showNoteInGrid(file: TFile) { - // 隱藏移動端導航欄 - if (Platform.isPhone) { - const mobileNavbar = document.querySelector('.mobile-navbar') as HTMLElement; - if (mobileNavbar) { - if (!document.body.classList.contains('is-floating-nav')) { - mobileNavbar.style.transform = 'translateY(100%)'; - } else { - mobileNavbar.style.transform = 'translateY(200%)'; - } - mobileNavbar.style.transition = 'transform 0.3s ease-out'; - } - } - // 關閉之前的筆記顯示 if (this.isShowingNote) { this.hideNoteInGrid(); @@ -2062,6 +2055,59 @@ export class GridView extends ItemView { noteContent.style.padding = '15px'; } + // 在移動端添加滾動監聽,根據滾動方向控制導航欄顯示/隱藏 + if (Platform.isPhone) { + let lastScrollTop = 0; + const handleScroll = () => { + const mobileNavbar = document.querySelector('.mobile-navbar') as HTMLElement; + if (!mobileNavbar) return; + + const currentScrollTop = scrollContainer.scrollTop; + + // 往上捲(滾動位置增加)時隱藏導航欄 + if (currentScrollTop > lastScrollTop && currentScrollTop > 50) { + if (!document.body.classList.contains('is-floating-nav')) { + mobileNavbar.style.transform = 'translateY(100%)'; + } else { + mobileNavbar.style.transform = 'translateY(200%)'; + } + mobileNavbar.style.transition = 'transform 0.3s ease-out'; + } + // 往下捲(滾動位置減少)時顯示導航欄 + else if (currentScrollTop < lastScrollTop) { + mobileNavbar.style.transform = 'translateY(0)'; + mobileNavbar.style.transition = 'transform 0.3s ease-in'; + } + + lastScrollTop = currentScrollTop; + }; + + scrollContainer.addEventListener('scroll', handleScroll); + + // 監聽分頁切換事件,當離開當前視圖時恢復導航欄 + const handleActiveLeafChange = () => { + const activeView = this.app.workspace.getActiveViewOfType(GridView); + // 如果當前活動視圖不是這個 GridView 實例,或者不在顯示筆記狀態 + if (activeView !== this || !this.isShowingNote) { + const navbar = document.querySelector('.mobile-navbar') as HTMLElement; + if (navbar) { + navbar.style.transform = 'translateY(0)'; + navbar.style.transition = 'transform 0.3s ease-in'; + } + } + }; + + // 註冊事件監聽器 + this.registerEvent( + this.app.workspace.on('active-leaf-change', handleActiveLeafChange) + ); + + // 儲存滾動事件清理函數 + this.eventCleanupFunctions.push(() => { + scrollContainer.removeEventListener('scroll', handleScroll); + }); + } + // 取得 Metadata (Frontmatter) const fileCache = this.app.metadataCache.getFileCache(file); const frontmatter = fileCache?.frontmatter; @@ -2304,6 +2350,7 @@ export class GridView extends ItemView { showNoteTags: this.showNoteTags, recentSources: this.recentSources, futureSources: this.futureSources, + bookmarkGroupId: this.bookmarkGroupId, } }; } @@ -2329,6 +2376,7 @@ export class GridView extends ItemView { this.showNoteTags = state.state.showNoteTags ?? this.plugin.settings.showNoteTags; this.recentSources = state.state.recentSources ?? []; this.futureSources = state.state.futureSources ?? []; + this.bookmarkGroupId = state.state.bookmarkGroupId ?? 'all'; await this.render(); } } diff --git a/src/renderHeaderButton.ts b/src/renderHeaderButton.ts index 7c810a2..d68d1df 100644 --- a/src/renderHeaderButton.ts +++ b/src/renderHeaderButton.ts @@ -34,7 +34,7 @@ export function renderHeaderButton(gridView: GridView) { backButton.addEventListener('click', async (event) => { event.preventDefault(); event.stopPropagation(); - + // 如果有歷史記錄 if (gridView.recentSources.length > 0) { // 將當前狀態推入 futureSources 以便前進 @@ -54,7 +54,7 @@ export function renderHeaderButton(gridView: GridView) { // 取得最近一筆歷史記錄 const lastSource = JSON.parse(gridView.recentSources[0]); gridView.recentSources.shift(); // 從歷史記錄中移除 - + // 設定來源及搜尋狀態(不記錄到歷史) await gridView.setSource( lastSource.mode, @@ -119,7 +119,7 @@ export function renderHeaderButton(gridView: GridView) { // 只有在有歷史記錄時才顯示右鍵選單 if (gridView.recentSources.length > 0) { event.preventDefault(); - + const menu = new Menu(); // 添加歷史記錄 @@ -127,11 +127,11 @@ export function renderHeaderButton(gridView: GridView) { try { const sourceInfo = JSON.parse(sourceInfoStr); const { mode, path } = sourceInfo; - + // 根據模式顯示圖示和文字 let displayText = ''; let icon = ''; - + switch (mode) { case 'folder': displayText = path || '/'; @@ -190,7 +190,7 @@ export function renderHeaderButton(gridView: GridView) { displayText += `: "${sourceInfo.searchQuery}"`; } } - + // 添加歷史記錄到選單 menu.addItem((item) => { item @@ -213,7 +213,7 @@ export function renderHeaderButton(gridView: GridView) { if (clickedIndex !== -1) { const newerHistory = gridView.recentSources.slice(0, clickedIndex); const forwardStack = [...newerHistory].reverse(); - gridView.futureSources = [...forwardStack, currentKey, ...gridView.futureSources]; + gridView.futureSources = [...forwardStack, currentKey, ...gridView.futureSources]; if (gridView.futureSources.length > 10) { gridView.futureSources.length = 10; } @@ -242,7 +242,7 @@ export function renderHeaderButton(gridView: GridView) { menu.showAtMouseEvent(event); } }); - + // 添加右鍵選單支援 forwardButton.addEventListener('contextmenu', (event) => { // 只有在有歷史記錄或未來記錄時才顯示右鍵選單 @@ -250,7 +250,7 @@ export function renderHeaderButton(gridView: GridView) { event.preventDefault(); const menu = new Menu(); - + // 添加未來記錄 gridView.futureSources.forEach((sourceInfoStr, index) => { try { @@ -381,7 +381,7 @@ export function renderHeaderButton(gridView: GridView) { // 添加新增筆記按鈕 const newNoteButton = headerButtonsDiv.createEl('button', { attr: { 'aria-label': t('new_note') } }); setIcon(newNoteButton, 'square-pen'); - newNoteButton.addEventListener('click', (event) => { + newNoteButton.addEventListener('click', (event) => { event.preventDefault(); const menu = new Menu(); // 新增筆記 @@ -396,54 +396,54 @@ export function renderHeaderButton(gridView: GridView) { // 新增資料夾 menu.addItem((item) => { item.setTitle(t('new_folder')) - .setIcon('folder') - .onClick(async () => { - await createNewFolder(gridView.app, gridView.sourcePath, () => { - requestAnimationFrame(() => { - gridView.render(); + .setIcon('folder') + .onClick(async () => { + await createNewFolder(gridView.app, gridView.sourcePath, () => { + requestAnimationFrame(() => { + gridView.render(); + }); }); }); - }); }); // 新增畫布 menu.addItem((item) => { item.setTitle(t('new_canvas')) - .setIcon('layout-dashboard') - .onClick(async () => { - await createNewCanvas(gridView.app, gridView.sourcePath); - }); + .setIcon('layout-dashboard') + .onClick(async () => { + await createNewCanvas(gridView.app, gridView.sourcePath); + }); }); // 新增 base menu.addItem((item) => { item.setTitle(t('new_base')) - .setIcon('layout-dashboard') - .onClick(async () => { - await createNewBase(gridView.app, gridView.sourcePath); - }); + .setIcon('layout-dashboard') + .onClick(async () => { + await createNewBase(gridView.app, gridView.sourcePath); + }); }); // 新增捷徑 menu.addItem((item) => { item.setTitle(t('new_shortcut')) - .setIcon('shuffle') - .onClick(async () => { - const modal = new ShortcutSelectionModal(gridView.app, gridView.plugin, async (option) => { - await createShortcutUtil(gridView.app, gridView.sourcePath, option); + .setIcon('shuffle') + .onClick(async () => { + const modal = new ShortcutSelectionModal(gridView.app, gridView.plugin, async (option) => { + await createShortcutUtil(gridView.app, gridView.sourcePath, option); + }); + modal.open(); }); - modal.open(); - }); }); menu.showAtMouseEvent(event); }); // 添加重新選擇資料夾按鈕 - const reselectButton = headerButtonsDiv.createEl('button', { attr: { 'aria-label': t('reselect') } }); + const reselectButton = headerButtonsDiv.createEl('button', { attr: { 'aria-label': t('reselect') } }); reselectButton.addEventListener('click', () => { showFolderSelectionModal(gridView.app, gridView.plugin, gridView, reselectButton); }); setIcon(reselectButton, "grid"); // 添加重新整理按鈕 - const refreshButton = headerButtonsDiv.createEl('button', { attr: { 'aria-label': t('refresh') } }); + const refreshButton = headerButtonsDiv.createEl('button', { attr: { 'aria-label': t('refresh') } }); refreshButton.addEventListener('click', () => { if (gridView.sortType === 'random') { gridView.clearSelection(); @@ -462,51 +462,10 @@ export function renderHeaderButton(gridView: GridView) { searchButton.addEventListener('click', () => { showSearchModal(gridView.app, gridView, gridView.searchQuery, searchButton); }); - - - // 如果有搜尋關鍵字,顯示搜尋文字和取消按鈕 - // if (gridView.searchQuery) { - // searchButton.style.display = 'none'; - // const searchTextContainer = searchButtonContainer.createDiv('ge-search-text-container'); - // searchTextContainer.setAttribute('aria-label', gridView.searchQuery); - - // // 創建搜尋文字 - // const searchText = searchTextContainer.createEl('span', { cls: 'ge-search-text', text: gridView.searchQuery }); - // // 讓搜尋文字可點選 - // searchText.style.cursor = 'pointer'; - // searchText.addEventListener('click', () => { - // showSearchModal(gridView.app, gridView, gridView.searchQuery, searchText); - // }); - - // // 先保存開啟 Modal 時的原始狀態 - // const originalSearchQuery = gridView.searchQuery; - // const originalsearchCurrentLocationOnly = gridView.searchCurrentLocationOnly; - // const originalSearchFilesNameOnly = gridView.searchFilesNameOnly; - // const originalSearchMediaFiles = gridView.searchMediaFiles; - - // // 創建取消按鈕 - // const clearButton = searchTextContainer.createDiv('ge-clear-button'); - // setIcon(clearButton, 'x'); - // clearButton.addEventListener('click', (e) => { - // e.stopPropagation(); // 防止觸發搜尋文字的點擊事件 - // gridView.pushHistory( - // gridView.sourceMode, - // gridView.sourcePath, - // originalSearchQuery, - // originalsearchCurrentLocationOnly, - // originalSearchFilesNameOnly, - // originalSearchMediaFiles, - // ); - // gridView.searchQuery = ''; - // gridView.clearSelection(); - // gridView.app.workspace.requestSaveLayout(); - // gridView.render(); - // }); - // } // 更多選項按鈕 - const menu = new Menu(); - menu.addItem((item) => { + const moreMenu = new Menu(); + moreMenu.addItem((item) => { item .setTitle(t('open_new_grid_view')) .setIcon('grid') @@ -539,10 +498,10 @@ export function renderHeaderButton(gridView: GridView) { workspace.revealLeaf(leaf); }); }); - menu.addSeparator(); + moreMenu.addSeparator(); // 直向卡片切換 - menu.addItem((item) => { + moreMenu.addItem((item) => { item.setTitle(t('vertical_card')) .setIcon('layout') .setChecked(gridView.baseCardLayout === 'vertical') @@ -554,7 +513,7 @@ export function renderHeaderButton(gridView: GridView) { }); }); // 最小化模式選項 - menu.addItem((item) => { + moreMenu.addItem((item) => { item .setTitle(t('min_mode')) .setIcon('minimize-2') @@ -567,7 +526,7 @@ export function renderHeaderButton(gridView: GridView) { }); // 顯示日期分隔器 if (gridView.plugin.settings.dateDividerMode !== 'none') { - menu.addItem((item) => { + moreMenu.addItem((item) => { item .setTitle(t('show_date_dividers')) .setIcon('calendar') @@ -580,7 +539,7 @@ export function renderHeaderButton(gridView: GridView) { }); } // 顯示筆記標籤 - menu.addItem((item) => { + moreMenu.addItem((item) => { item .setTitle(t('show_note_tags')) .setIcon('tag') @@ -592,7 +551,7 @@ export function renderHeaderButton(gridView: GridView) { }); }); // 顯示忽略的資料夾及檔案選項 - menu.addItem((item) => { + moreMenu.addItem((item) => { item .setTitle(t('show_ignored_items')) .setIcon('folder-open-dot') @@ -603,11 +562,11 @@ export function renderHeaderButton(gridView: GridView) { gridView.render(); }); }); - - menu.addSeparator(); + + moreMenu.addSeparator(); // 開啟瀏覽器 - menu.addItem((item) => { + moreMenu.addItem((item) => { item .setTitle(t('open_explorer')) .setIcon('folder-tree') @@ -626,7 +585,7 @@ export function renderHeaderButton(gridView: GridView) { }); // 顯示設定選項 - menu.addItem((item) => { + moreMenu.addItem((item) => { item .setTitle(t('open_settings')) .setIcon('settings') @@ -636,17 +595,17 @@ export function renderHeaderButton(gridView: GridView) { (gridView.app as any).setting.openTabById(gridView.plugin.manifest.id); }); }); - + const moreOptionsButton = headerButtonsDiv.createEl('button', { attr: { 'aria-label': t('more_options') } }); setIcon(moreOptionsButton, 'ellipsis-vertical'); moreOptionsButton.addEventListener('click', (event) => { - menu.showAtMouseEvent(event); + moreMenu.showAtMouseEvent(event); }); headerButtonsDiv.addEventListener('contextmenu', (event) => { if (event.target === headerButtonsDiv) { event.preventDefault(); - menu.showAtMouseEvent(event); + moreMenu.showAtMouseEvent(event); } }); } diff --git a/src/renderModePath.ts b/src/renderModePath.ts index 50ffeac..de37dd8 100644 --- a/src/renderModePath.ts +++ b/src/renderModePath.ts @@ -13,20 +13,20 @@ export function renderModePath(gridView: GridView) { // 創建模式名稱和排序按鈕的容器 const modeHeaderContainer = gridView.containerEl.createDiv('ge-mode-header-container'); - + // 左側:模式名稱 const modenameContainer = modeHeaderContainer.createDiv('ge-modename-content'); - + // 右側:排序按鈕 const rightActions = modeHeaderContainer.createDiv('ge-right-actions'); - + // 添加排序按鈕 - if (gridView.sourceMode !== 'bookmarks' && - gridView.sourceMode !== 'recent-files' && + if (gridView.sourceMode !== 'bookmarks' && + gridView.sourceMode !== 'recent-files' && gridView.sourceMode !== 'random-note') { - const sortButton = rightActions.createEl('a', { + const sortButton = rightActions.createEl('a', { cls: 'ge-sort-button', - attr: { + attr: { 'aria-label': t('sorting'), 'href': '#' } @@ -648,6 +648,53 @@ export function renderModePath(gridView: GridView) { } switch (gridView.sourceMode) { + case 'bookmarks': + const bookmarkGroupName = gridView.bookmarkGroupId === 'all' ? t('all_bookmarks') : gridView.bookmarkGroupId; + const bookmarkGroupSpan = modenameContainer.createEl('a', { text: bookmarkGroupName, cls: 'ge-sub-option' }); + bookmarkGroupSpan.addEventListener('click', (evt) => { + const menu = new Menu(); + + // "全部" 選項 + menu.addItem((item) => { + item.setTitle(t('all_bookmarks')) + .setIcon('layers') + .setChecked(gridView.bookmarkGroupId === 'all') + .onClick(() => { + gridView.setSource('bookmarks', '', false, undefined, undefined, undefined, undefined, 'all'); + }); + }); + + menu.addSeparator(); + + // 取得所有書籤群組 + const bookmarksPlugin = (gridView.app as any).internalPlugins.plugins.bookmarks; + if (bookmarksPlugin?.enabled) { + const bookmarks = bookmarksPlugin.instance.items; + const groups: string[] = []; + const findGroups = (items: any[]) => { + items.forEach(item => { + if (item.type === 'group') { + groups.push(item.title); + if (item.items) findGroups(item.items); + } + }); + }; + findGroups(bookmarks); + + groups.forEach(groupTitle => { + menu.addItem((item) => { + item.setTitle(groupTitle) + .setIcon('folder') + .setChecked(gridView.bookmarkGroupId === groupTitle) + .onClick(() => { + gridView.setSource('bookmarks', '', false, undefined, undefined, undefined, undefined, groupTitle); + }); + }); + }); + } + menu.showAtMouseEvent(evt); + }); + break; case 'random-note': case 'recent-files': case 'all-files': diff --git a/src/translations/en.ts b/src/translations/en.ts index a622ccd..bbd9462 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -178,6 +178,7 @@ export default { 'all': 'All', 'default': 'Default', 'hidden': 'Hidden', + 'all_bookmarks': 'All Bookmarks', // Hide header elements setting 'hide_header_elements': 'Hide header elements', diff --git a/src/translations/ja.ts b/src/translations/ja.ts index eef6751..54a9e7a 100644 --- a/src/translations/ja.ts +++ b/src/translations/ja.ts @@ -177,6 +177,7 @@ export default { 'all': 'すべて', 'default': 'デフォルト', 'hidden': '隠す', + 'bookmark_groups': 'ブックマーク群組', // 隠すヘッダー要素 'hide_header_elements': 'ヘッダー要素を隠す', @@ -301,7 +302,7 @@ export default { 'use_quick_access_mode': 'クイックアクセスモードを使用する', 'open_quick_access_folder': 'クイックアクセス フォルダーを開く', 'open_quick_access_mode': 'クイックアクセスモードを開く', - + // Explorer Stash 'stash': '一時保存', 'drop_files_here': 'ここにドラッグして一時保存', diff --git a/src/translations/ko.ts b/src/translations/ko.ts index 427ae42..2ab20a6 100644 --- a/src/translations/ko.ts +++ b/src/translations/ko.ts @@ -178,6 +178,7 @@ export default { 'all': '전체', 'default': '기본값', 'hidden': '숨김', + 'bookmark_groups': '북마크群組', // Hide header elements setting 'hide_header_elements': '헤더 요소 숨기기', diff --git a/src/translations/ru.ts b/src/translations/ru.ts index 5884382..8f93faa 100644 --- a/src/translations/ru.ts +++ b/src/translations/ru.ts @@ -177,6 +177,7 @@ export default { 'all': 'Все', 'default': 'По умолчанию', 'hidden': 'Скрытые', + 'bookmark_groups': 'Букмарк группы', // Hide header elements setting 'hide_header_elements': 'Скрыть элементы заголовка', @@ -301,7 +302,7 @@ export default { 'use_quick_access_mode': 'Использовать режим быстрого доступа', 'open_quick_access_folder': 'Открыть папку быстрого доступа', 'open_quick_access_mode': 'Открыть быстрый доступ по режиму', - + // Explorer Stash 'stash': 'Хранилище', 'drop_files_here': 'Перетащите файлы сюда, чтобы сохранить', diff --git a/src/translations/uk.ts b/src/translations/uk.ts index 55bbff3..b11d625 100644 --- a/src/translations/uk.ts +++ b/src/translations/uk.ts @@ -177,6 +177,7 @@ export default { 'all': 'Усі', 'default': 'За замовчуванням', 'hidden': 'Приховані', + 'bookmark_groups': 'Групи закладок', // Hide header elements setting 'hide_header_elements': 'Приховати елементи заголовка', diff --git a/src/translations/zh-TW.ts b/src/translations/zh-TW.ts index 49b53e3..698d8a1 100644 --- a/src/translations/zh-TW.ts +++ b/src/translations/zh-TW.ts @@ -178,6 +178,7 @@ export default { 'all': '全部', 'default': '預設', 'hidden': '隱藏', + 'all_bookmarks': '全部書籤', // 隱藏頂部元素 'hide_header_elements': '隱藏頂部元素', diff --git a/src/translations/zh.ts b/src/translations/zh.ts index b42284f..6579b40 100644 --- a/src/translations/zh.ts +++ b/src/translations/zh.ts @@ -177,6 +177,7 @@ export default { 'all': '全部', 'default': '默认', 'hidden': '隐藏', + 'all_bookmarks': '全部书签', // 隐藏頂部元素 'hide_header_elements': '隱藏頂部元素', diff --git a/src/utils/fileUtils.ts b/src/utils/fileUtils.ts index 4795a4c..815751d 100644 --- a/src/utils/fileUtils.ts +++ b/src/utils/fileUtils.ts @@ -73,7 +73,7 @@ export function sortFiles(files: TFile[], gridView: GridView, overrideSortType?: const hasModifiedField = !!settings.modifiedDateField; const hasCreatedField = !!settings.createdDateField; const hasAnyDateField = hasModifiedField || hasCreatedField; - + // 符合以下任一條件就使用簡單排序: // 1. 非日期排序類型 (name-asc, name-desc, random) // 2. 沒有設定任何日期欄位 @@ -106,7 +106,7 @@ export function sortFiles(files: TFile[], gridView: GridView, overrideSortType?: // 只對 .md 檔案讀取 metadata const shouldReadMetadata = file.extension === 'md'; const metadata = shouldReadMetadata ? app.metadataCache.getFileCache(file) : null; - + return { file, mDate: (() => { @@ -167,12 +167,12 @@ export function isFolderIgnored(folder: TFolder, ignoredFolders: string[], ignor if (showIgnoredFolders) return false; // 檢查資料夾是否在忽略清單中 - const isInIgnoredFolders = ignoredFolders.some(ignored => + const isInIgnoredFolders = ignoredFolders.some(ignored => folder.path === ignored || folder.path.startsWith(ignored + '/') ); - + if (isInIgnoredFolders) return true; - + // 檢查資料夾是否符合忽略的模式 if (ignoredFolderPatterns && ignoredFolderPatterns.length > 0) { const matchesIgnoredPattern = ignoredFolderPatterns.some(pattern => { @@ -180,7 +180,7 @@ export function isFolderIgnored(folder: TFolder, ignoredFolders: string[], ignor // 嘗試將模式作為正則表達式處理 // 如果模式包含特殊字符,使用正則表達式處理 if (/[\^\$\*\+\?\(\)\[\]\{\}\|\\]/.test(pattern)) { - const regex = new RegExp(pattern); + const regex = new RegExp(pattern); return regex.test(folder.path); } else { // 檢查資料夾名稱是否包含模式字串(不區分大小寫) @@ -191,7 +191,7 @@ export function isFolderIgnored(folder: TFolder, ignoredFolders: string[], ignor return folder.name.toLowerCase().includes(pattern.toLowerCase()); } }); - + if (matchesIgnoredPattern) return true; } @@ -201,15 +201,15 @@ export function isFolderIgnored(folder: TFolder, ignoredFolders: string[], ignor // 檢查檔案是否應該被忽略 export function ignoredFiles(files: TFile[], gridView: GridView): TFile[] { const settings = gridView.plugin.settings; - + // 如果開啟顯示忽略項目模式,則顯示所有檔案 if (gridView.showIgnoredItems) { return files; } - + // 建立一個快取來存儲已經檢查過的資料夾路徑 - const folderCache: {[path: string]: boolean} = {}; - + const folderCache: { [path: string]: boolean } = {}; + return files.filter(file => { // 檢查檔案的 metadata 是否有 hidden: true if (file.extension === 'md') { @@ -222,13 +222,13 @@ export function ignoredFiles(files: TFile[], gridView: GridView): TFile[] { // 獲取檔案所在的資料夾路徑 const folderPath = file.parent?.path || '/'; - - + + // 如果快取中沒有這個資料夾的檢查結果,則進行檢查 if (folderCache[folderPath] === undefined) { // 獲取資料夾對象 const folder = gridView.app.vault.getAbstractFileByPath(folderPath); - + if (folder instanceof TFolder) { // 使用 isFolderIgnored 檢查資料夾是否應該被忽略 folderCache[folderPath] = isFolderIgnored( @@ -242,7 +242,7 @@ export function ignoredFiles(files: TFile[], gridView: GridView): TFile[] { folderCache[folderPath] = false; } } - + // 如果資料夾被忽略,則過濾掉該檔案 return !folderCache[folderPath]; }); @@ -262,11 +262,11 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): // 只取得當前資料夾中的支援檔案,不包含子資料夾 const files = folder.children.filter((file): file is TFile => { if (!(file instanceof TFile)) return false; - + // 當存在搜尋關鍵字時,是否包含媒體檔案取決於 includeMediaFiles // 不存在搜尋關鍵字時,是否包含媒體檔案取決於 settings.showMediaFiles const allowMedia = settings.showMediaFiles && (!gridView.searchQuery || includeMediaFiles); - + // 如果是文件檔案,直接包含;若為媒體檔案則依 allowMedia 判斷 if (isDocumentFile(file) || (allowMedia && isMediaFile(file))) { return true; @@ -285,13 +285,13 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): const resultDomLookup = searchLeaf.view.dom.resultDomLookup; if (resultDomLookup) { const files = Array.from(resultDomLookup.keys()) - .filter((file): file is TFile => file instanceof TFile); + .filter((file): file is TFile => file instanceof TFile); return sortFiles(files, gridView); } } } return []; - } else if (sourceMode === 'backlinks') { + } else if (sourceMode === 'backlinks') { // 反向連結模式:找出所有引用當前筆記的檔案 const activeFile = app.workspace.getActiveFile(); if (!activeFile) { @@ -305,10 +305,10 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): if (Object.keys(links).includes(activeFile.path)) { const sourceFile = app.vault.getAbstractFileByPath(sourcePath) as TFile; if (sourceFile) { - backlinks.add(sourceFile); - } + backlinks.add(sourceFile); } } + } return sortFiles(Array.from(backlinks) as TFile[], gridView); } else if (sourceMode === 'outgoinglinks') { @@ -322,11 +322,11 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): // 使用 resolvedLinks 來找出外部連結 const resolvedLinks = app.metadataCache.resolvedLinks; const fileLinks = resolvedLinks[activeFile.path]; - + if (fileLinks) { for (const targetPath of Object.keys(fileLinks)) { const targetFile = app.vault.getAbstractFileByPath(targetPath) as TFile; - if (targetFile && (isDocumentFile(targetFile) || + if (targetFile && (isDocumentFile(targetFile) || (settings.showMediaFiles && isMediaFile(targetFile)))) { outgoingLinks.add(targetFile); } @@ -362,7 +362,7 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): } return sortFiles(Array.from(outgoingLinks), gridView); - } else if(sourceMode === 'bookmarks') { + } else if (sourceMode === 'bookmarks') { // 書籤模式 const bookmarksPlugin = (app as any).internalPlugins.plugins.bookmarks; if (!bookmarksPlugin?.enabled) { @@ -371,7 +371,7 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): const bookmarks = bookmarksPlugin.instance.items; const bookmarkedFiles = new Set(); - + const processBookmarkItem = (item: any) => { if (item.type === 'file') { const file = app.vault.getAbstractFileByPath(item.path); @@ -386,8 +386,29 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): item.items.forEach(processBookmarkItem); } }; - - bookmarks.forEach(processBookmarkItem); + + const bookmarkGroupId = gridView.bookmarkGroupId; + if (!bookmarkGroupId || bookmarkGroupId === 'all') { + bookmarks.forEach(processBookmarkItem); + } else { + // 遞迴尋找指定的群組 + const findAndProcessGroup = (items: any[]) => { + for (const item of items) { + if (item.type === 'group') { + // 使用 title 作為 ID + if (item.title === bookmarkGroupId) { + item.items.forEach(processBookmarkItem); + return true; + } + if (item.items && findAndProcessGroup(item.items)) { + return true; + } + } + } + return false; + }; + findAndProcessGroup(bookmarks); + } return Array.from(bookmarkedFiles) as TFile[]; } else if (sourceMode === 'tasks') { // 任務模式 @@ -404,7 +425,7 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): for (const task of allTasks) { const file = app.vault.getAbstractFileByPath(task.path); if (!(file instanceof TFile)) continue; - + // 根據過濾條件檢查任務 if (gridView.taskFilter === 'uncompleted' && !task.isDone) { filesWithTasks.add(file); @@ -423,7 +444,7 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): try { const tasks = dv.pages().file.tasks; let filteredTasks; - + if (gridView.taskFilter === 'uncompleted') { filteredTasks = tasks.where((t: { completed: boolean }) => !t.completed); } else if (gridView.taskFilter === 'completed') { @@ -431,7 +452,7 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): } else { // 'all' filteredTasks = tasks; } - + for (const task of filteredTasks.array()) { const file = app.vault.getAbstractFileByPath(task.path); if (file instanceof TFile) { @@ -442,7 +463,7 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): console.error('Error getting tasks from Dataview:', error); return []; } - + } else { // 使用原生方法 const markdownFiles = app.vault.getMarkdownFiles(); @@ -465,7 +486,7 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): const hasCompleted = /^[\s]*[-*]\s*\[x\](?![^\[]*\[\s*[^\s\]]+\]).*$/m.test(content); shouldAdd = hasIncomplete || hasCompleted; } - + if (shouldAdd) { filesWithTasks.add(file); } @@ -531,7 +552,7 @@ export async function getFiles(gridView: GridView, includeMediaFiles: boolean): dvCode = mode.options[idx].dataviewCode; } } - + try { const activeFile = app.workspace.getActiveFile(); if (activeFile) { diff --git a/versions.json b/versions.json index c508a2e..4e83549 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "3.0.9": "1.1.0" + "3.1.0": "1.1.0" } \ No newline at end of file