diff --git a/.gitignore b/.gitignore index 47cc379..9618c53 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,7 @@ AGENTS.md Changelog.md # Exclude snippets directory -/snippets \ No newline at end of file +/snippets + +# Exclude docs directory +/docs \ No newline at end of file diff --git a/manifest.json b/manifest.json index 28c3566..50494da 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "gridexplorer", "name": "GridExplorer", - "version": "3.4.8", + "version": "3.4.9", "minAppVersion": "1.8.7", "description": "Browse note files in a grid view.", "author": "Devon22", diff --git a/package-lock.json b/package-lock.json index a09c26c..1d9faa0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gridexplorer", - "version": "3.4.8", + "version": "3.4.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gridexplorer", - "version": "3.4.8", + "version": "3.4.9", "license": "MIT", "dependencies": { "jszip": "^3.10.1" diff --git a/package.json b/package.json index e5598d0..260bffe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gridexplorer", - "version": "3.4.8", + "version": "3.4.9", "description": "Browse note files in a grid view.", "main": "main.js", "scripts": { diff --git a/src/ExplorerView.ts b/src/ExplorerView.ts index db64be9..9aa305e 100644 --- a/src/ExplorerView.ts +++ b/src/ExplorerView.ts @@ -227,17 +227,6 @@ export class ExplorerView extends ItemView { if (newMode !== this.lastMode || newPath !== this.lastPath) { this.lastMode = newMode; this.lastPath = newPath; - - // 當模式切換時,清理搜尋狀態以避免資料夾被強制展開 - if (this.searchQuery.trim()) { - this.searchQuery = ''; - if (this.searchInputEl) { - this.searchInputEl.value = ''; - } - // 同步搜尋輸入框狀態 - this.syncSearchInput(); - } - schedule(); } }); @@ -1031,7 +1020,7 @@ export class ExplorerView extends ItemView { const name = header.createSpan({ cls: 'ge-explorer-folder-name' }); name.textContent = folder.name || '/'; - this.setupFolderIcon(folder, name, toggle, expanded); + this.setupFolderIcon(folder, name, toggle, expanded, icon); this.highlightActiveFolder(folder, header); this.attachDropTarget(header, folder.path); @@ -1044,7 +1033,7 @@ export class ExplorerView extends ItemView { } // 設置資料夾圖示 - private setupFolderIcon(folder: TFolder, name: HTMLElement, toggle: HTMLElement, expanded: boolean) { + private setupFolderIcon(folder: TFolder, name: HTMLElement, toggle: HTMLElement, expanded: boolean, iconEl: HTMLElement) { // 根據同名筆記設置背景色與圖示 const iconFromFrontmatter = this.getFolderIconFromFrontmatter(folder, name); @@ -1053,7 +1042,16 @@ export class ExplorerView extends ItemView { const customFolderIcon = this.plugin.settings?.customFolderIcon ?? ''; if (customFolderIcon && name) { name.textContent = `${customFolderIcon} ${folder.name || '/'}`.trim(); + if (customFolderIcon.trim()) { + iconEl.setCssProps({ display: 'none' }); + } else { + iconEl.setCssProps({ display: '' }); + } + } else { + iconEl.setCssProps({ display: '' }); } + } else { + iconEl.setCssProps({ display: 'none' }); } // 設置切換圖示 diff --git a/src/FileWatcher.ts b/src/FileWatcher.ts index 6b3f6ad..a119ba2 100644 --- a/src/FileWatcher.ts +++ b/src/FileWatcher.ts @@ -77,6 +77,18 @@ export class FileWatcher { if (file instanceof TFile) { // 如果有 GridView 實例 if (this.gridView) { + // 如果刪除的檔案剛好是目前正在預覽的檔案,應隱藏預覽 + if (this.gridView.isShowingNote || this.gridView.isShowingZip) { + const currentFile = this.gridView.previewManager.getCurrentFile(); + if (currentFile && file.path === currentFile.path) { + if (this.gridView.isShowingNote) { + this.gridView.hideNoteInGrid(); + } else { + this.gridView.hideZipInGrid(); + } + } + } + // 找到當前檔案在 GridView 中的索引 const gridItemIndex = this.gridView.gridItems.findIndex((item: HTMLElement) => item.dataset.filePath === file.path @@ -98,6 +110,28 @@ export class FileWatcher { this.plugin.registerEvent( this.app.vault.on('rename', (file, oldPath) => { if (file instanceof TFile) { + // 如果重命名的檔案是目前正在預覽的檔案,需要更新預覽標題與內容 + if (this.gridView.isShowingNote || this.gridView.isShowingZip) { + const currentFile = this.gridView.previewManager.getCurrentFile(); + if (currentFile && (file === currentFile || currentFile.path === oldPath || currentFile.path === file.path)) { + if (this.gridView.isShowingNote) { + // 更新頂部標題文字 + const titleEl = this.gridView.noteViewContainer?.querySelector('.ge-note-title'); + if (titleEl) { + titleEl.textContent = file.basename; + } + // 更新預覽內容(這會重新讀取並渲染,順便更新內部的第一行標題與 backlinks) + void this.gridView.previewManager.updateCurrentPreview(); + } else if (this.gridView.isShowingZip) { + // ZIP 預覽:更新頂部標題文字 + const titleEl = this.gridView.zipViewContainer?.querySelector('.ge-zip-title'); + if (titleEl) { + titleEl.textContent = file.basename; + } + } + } + } + const fileDirPath = file.path.split('/').slice(0, -1).join('/') || '/'; const oldDirPath = oldPath.split('/').slice(0, -1).join('/') || '/'; // 來源與目標路徑不同,表示移動檔案 diff --git a/src/GridPreviewManager.ts b/src/GridPreviewManager.ts index 07e8daa..54688d4 100644 --- a/src/GridPreviewManager.ts +++ b/src/GridPreviewManager.ts @@ -25,20 +25,52 @@ interface AppWithInternalPlugins { export class GridPreviewManager { private view: GridView; + private previewHistory: TFile[] = []; + private previewHistoryIndex: number = -1; constructor(view: GridView) { this.view = view; + + // 監聽檔案修改事件以即時更新預覽 + this.view.registerEvent( + this.view.app.vault.on('modify', (file) => { + if (file instanceof TFile && this.view.isShowingNote) { + const currentFile = this.getCurrentFile(); + if (currentFile && file.path === currentFile.path) { + void this.updateCurrentPreview(); + } + } + }) + ); } + + // 在網格視圖中直接顯示筆記 - async showNoteInGrid(file: TFile) { + async showNoteInGrid(file: TFile, isHistoryNavigation = false) { + if (file.extension === 'zip') { + return this.showZipInGrid(file, isHistoryNavigation); + } + + // 更新歷史紀錄 + if (!this.view.isShowingNote) { + // 從外部全新開啟 + this.previewHistory = [file]; + this.previewHistoryIndex = 0; + } else if (!isHistoryNavigation) { + // 在內部點擊連結跳轉 + this.previewHistory = this.previewHistory.slice(0, this.previewHistoryIndex + 1); + this.previewHistory.push(file); + this.previewHistoryIndex = this.previewHistory.length - 1; + } + // 關閉之前的筆記顯示 if (this.view.isShowingNote) { - this.hideNoteInGrid(); + this.hideNoteInGrid(false); // 傳入 false 避免清空我們剛更新的歷史 } // 關閉之前的 ZIP 顯示 if (this.view.isShowingZip) { - this.hideZipInGrid(); + this.hideZipInGrid(false); } const gridContainer = this.view.containerEl.querySelector('.ge-grid-container'); @@ -51,28 +83,55 @@ export class GridPreviewManager { // 頂部列 (左右區塊) const topBar = noteViewContainer.createDiv('ge-note-top-bar'); const leftBar = topBar.createDiv('ge-note-top-left'); - const rightBar = topBar.createDiv('ge-note-top-right'); - // 筆記標題 - const noteTitle = leftBar.createDiv('ge-note-title'); + // 返回與前進按鈕 + const backButton = leftBar.createEl('button', { cls: 'clickable-icon ge-note-nav-button ge-note-back-button' }); + setIcon(backButton, 'arrow-left'); + backButton.disabled = this.previewHistoryIndex <= 0; + backButton.addEventListener('click', () => { + if (this.previewHistoryIndex > 0) { + this.previewHistoryIndex--; + const prevFile = this.previewHistory[this.previewHistoryIndex]; + void this.showNoteInGrid(prevFile, true); + } + }); + + const forwardButton = leftBar.createEl('button', { cls: 'clickable-icon ge-note-nav-button ge-note-forward-button' }); + setIcon(forwardButton, 'arrow-right'); + forwardButton.disabled = this.previewHistoryIndex >= this.previewHistory.length - 1; + forwardButton.addEventListener('click', () => { + if (this.previewHistoryIndex < this.previewHistory.length - 1) { + this.previewHistoryIndex++; + const nextFile = this.previewHistory[this.previewHistoryIndex]; + void this.showNoteInGrid(nextFile, true); + } + }); + + // 筆記標題 (置中) + const noteTitle = topBar.createDiv('ge-note-title'); noteTitle.textContent = file.basename; if (Platform.isDesktop) { setTooltip(noteTitle, file.basename); } + + + const rightBar = topBar.createDiv('ge-note-top-right'); + // 編輯按鈕 - const editButton = rightBar.createEl('button', { cls: 'ge-note-edit-button' }); + const editButton = rightBar.createEl('button', { cls: 'clickable-icon ge-note-edit-button' }); setIcon(editButton, 'pencil'); editButton.addEventListener('click', () => { void this.view.getLeafByMode(file).openFile(file); }); + // Metadata 切換按鈕 - const infoButton = rightBar.createEl('button', { cls: 'ge-note-info-button' }); + const infoButton = rightBar.createEl('button', { cls: 'clickable-icon ge-note-info-button' }); setIcon(infoButton, 'info'); // 關閉按鈕 - const closeButton = rightBar.createEl('button', { cls: 'ge-note-close-button' }); + const closeButton = rightBar.createEl('button', { cls: 'clickable-icon ge-note-close-button' }); setIcon(closeButton, 'x'); closeButton.addEventListener('click', () => { this.hideNoteInGrid(); @@ -213,7 +272,11 @@ export class GridPreviewManager { e.preventDefault(); const linkedFile = this.view.app.metadataCache.getFirstLinkpathDest(linkPath, file.path); if (linkedFile) { - void this.view.getLeafByMode(linkedFile).openFile(linkedFile); + if (e.ctrlKey || e.metaKey) { + void this.view.getLeafByMode(linkedFile).openFile(linkedFile); + } else { + void this.showNoteInGrid(linkedFile); + } } }); lastIndex = wikilinkRegex.lastIndex; @@ -291,6 +354,18 @@ export class GridPreviewManager { // 創建筆記內容區域 const noteContentArea = noteContent.createDiv('ge-note-content'); + // 建立筆記內第一行標題 + const vaultWithConfig = this.view.app.vault as unknown as { config?: { showInlineTitle?: boolean } }; + const showInlineTitle = vaultWithConfig.config?.showInlineTitle ?? true; + if (showInlineTitle) { + noteContentArea.createEl('h1', { + cls: 'ge-note-internal-title', + text: file.basename + }); + } + + + try { // 讀取筆記內容 const content = await this.view.app.vault.read(file); @@ -322,7 +397,11 @@ export class GridPreviewManager { const linkText = link.getAttribute('data-href') || href; const linkedFile = this.view.app.metadataCache.getFirstLinkpathDest(linkText, file.path); if (linkedFile) { - void this.view.getLeafByMode(linkedFile).openFile(linkedFile); + if (e.ctrlKey || e.metaKey) { + void this.view.getLeafByMode(linkedFile).openFile(linkedFile); + } else { + void this.showNoteInGrid(linkedFile); + } } } } @@ -335,6 +414,10 @@ export class GridPreviewManager { console.error('Error loading note content:', error); } + // 渲染反向連結與出站連結區塊 + await this.renderLinksSection(file, noteContent); + + // 行動裝置下拉或上拉關閉筆記 if (Platform.isMobile && noteViewContainer) { let startY = 0; @@ -456,23 +539,10 @@ export class GridPreviewManager { // 設定狀態 this.view.isShowingNote = true; - - // 註冊鍵盤事件監聽器 - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - this.hideNoteInGrid(); - e.preventDefault(); - } - }; - - activeDocument.addEventListener('keydown', handleKeyDown); - - // 儲存事件監聽器以便後續移除 - (noteViewContainer as NoteViewContainerWithKeydownHandler).keydownHandler = handleKeyDown; } // 隱藏筆記顯示 - hideNoteInGrid() { + hideNoteInGrid(clearHistory = true) { if (!this.view.isShowingNote) return; // 顯示移動端導航欄 (僅在行動裝置上) @@ -487,28 +557,40 @@ export class GridPreviewManager { } if (this.view.noteViewContainer) { - // 移除鍵盤事件監聽器 - const keydownHandler = (this.view.noteViewContainer as NoteViewContainerWithKeydownHandler).keydownHandler; - if (keydownHandler) { - activeDocument.removeEventListener('keydown', keydownHandler); - } - this.view.noteViewContainer.remove(); this.view.noteViewContainer = null; } this.view.isShowingNote = false; + + if (clearHistory) { + this.previewHistory = []; + this.previewHistoryIndex = -1; + } } + // 在網格視圖中直接顯示 ZIP 圖片網格 - async showZipInGrid(file: TFile) { + async showZipInGrid(file: TFile, isHistoryNavigation = false) { + // 更新歷史紀錄 + if (!this.view.isShowingNote && !this.view.isShowingZip) { + // 從外部全新開啟 + this.previewHistory = [file]; + this.previewHistoryIndex = 0; + } else if (!isHistoryNavigation) { + // 在內部點擊連結跳轉 + this.previewHistory = this.previewHistory.slice(0, this.previewHistoryIndex + 1); + this.previewHistory.push(file); + this.previewHistoryIndex = this.previewHistory.length - 1; + } + // 關閉之前的 ZIP 顯示 if (this.view.isShowingZip) { - this.hideZipInGrid(); + this.hideZipInGrid(false); } // 關閉之前的筆記顯示 if (this.view.isShowingNote) { - this.hideNoteInGrid(); + this.hideNoteInGrid(false); } const gridContainer = this.view.containerEl.querySelector('.ge-grid-container'); @@ -521,17 +603,41 @@ export class GridPreviewManager { // 頂部列 (左右區塊) const topBar = zipViewContainer.createDiv('ge-zip-top-bar'); const leftBar = topBar.createDiv('ge-zip-top-left'); - const rightBar = topBar.createDiv('ge-zip-top-right'); + + // 返回與前進按鈕 + const backButton = leftBar.createEl('button', { cls: 'clickable-icon ge-note-nav-button ge-note-back-button' }); + setIcon(backButton, 'arrow-left'); + backButton.disabled = this.previewHistoryIndex <= 0; + backButton.addEventListener('click', () => { + if (this.previewHistoryIndex > 0) { + this.previewHistoryIndex--; + const prevFile = this.previewHistory[this.previewHistoryIndex]; + void this.showNoteInGrid(prevFile, true); + } + }); + + const forwardButton = leftBar.createEl('button', { cls: 'clickable-icon ge-note-nav-button ge-note-forward-button' }); + setIcon(forwardButton, 'arrow-right'); + forwardButton.disabled = this.previewHistoryIndex >= this.previewHistory.length - 1; + forwardButton.addEventListener('click', () => { + if (this.previewHistoryIndex < this.previewHistory.length - 1) { + this.previewHistoryIndex++; + const nextFile = this.previewHistory[this.previewHistoryIndex]; + void this.showNoteInGrid(nextFile, true); + } + }); // ZIP 標題 - const zipTitle = leftBar.createDiv('ge-zip-title'); + const zipTitle = topBar.createDiv('ge-zip-title'); zipTitle.textContent = file.basename; if (Platform.isDesktop) { setTooltip(zipTitle, file.basename); } + const rightBar = topBar.createDiv('ge-zip-top-right'); + // 開啟按鈕 (在分頁中開啟 ZIP 檔案) - const openButton = rightBar.createEl('button', { cls: 'ge-zip-open-button' }); + const openButton = rightBar.createEl('button', { cls: 'clickable-icon ge-zip-open-button' }); setIcon(openButton, 'folder-archive'); openButton.setAttribute('aria-label', t('zip_open_file')); if (Platform.isDesktop) { @@ -542,7 +648,7 @@ export class GridPreviewManager { }); // 關閉按鈕 - const closeButton = rightBar.createEl('button', { cls: 'ge-zip-close-button' }); + const closeButton = rightBar.createEl('button', { cls: 'clickable-icon ge-zip-close-button' }); setIcon(closeButton, 'x'); closeButton.setAttribute('aria-label', t('zip_close_view')); if (Platform.isDesktop) { @@ -1036,7 +1142,7 @@ export class GridPreviewManager { } // 隱藏 ZIP 顯示 - hideZipInGrid() { + hideZipInGrid(clearHistory = true) { if (!this.view.isShowingZip) return; // 顯示移動端導航欄 (僅在行動裝置上) @@ -1074,6 +1180,11 @@ export class GridPreviewManager { } this.view.isShowingZip = false; + + if (clearHistory) { + this.previewHistory = []; + this.previewHistoryIndex = -1; + } } selectZipItem(idx: number, gridEl: HTMLElement) { @@ -1089,4 +1200,106 @@ export class GridPreviewManager { } }); } + + private async renderLinksSection(file: TFile, parentEl: HTMLElement) { + const app = this.view.app; + const resolvedLinks = app.metadataCache.resolvedLinks; + + // 1. 獲取反向連結 (Backlinks) + const backlinks = new Set(); + for (const [sourcePath, links] of Object.entries(resolvedLinks)) { + if (Object.keys(links).includes(file.path)) { + const sourceFile = app.vault.getAbstractFileByPath(sourcePath); + if (sourceFile instanceof TFile) { + backlinks.add(sourceFile); + } + } + } + + // 如果沒有反向連結,就不需要渲染此區塊 + if (backlinks.size === 0) { + return; + } + + const linksContainer = parentEl.createDiv('ge-note-links-section'); + + // 渲染反向連結 + if (backlinks.size > 0) { + const backlinkHeader = linksContainer.createDiv('ge-note-links-header'); + backlinkHeader.createEl('span', { text: `🔗 ${t('backlinks')} (${backlinks.size})` }); + const backlinkList = linksContainer.createDiv('ge-note-links-list'); + + Array.from(backlinks).forEach((linkFile) => { + const item = backlinkList.createEl('a', { + cls: 'ge-note-link-item', + text: linkFile.basename, + }); + item.addEventListener('click', (e) => { + e.preventDefault(); + void this.showNoteInGrid(linkFile); + }); + }); + } + } + + public getCurrentFile(): TFile | null { + if (this.previewHistoryIndex >= 0 && this.previewHistoryIndex < this.previewHistory.length) { + return this.previewHistory[this.previewHistoryIndex]; + } + return null; + } + + public async updateCurrentPreview() { + const file = this.getCurrentFile(); + if (!file || !this.view.noteViewContainer) return; + + const scrollContainer = this.view.noteViewContainer.querySelector('.ge-note-scroll-container'); + const noteContent = this.view.noteViewContainer.querySelector('.ge-note-content-container'); + const noteContentArea = this.view.noteViewContainer.querySelector('.ge-note-content'); + + if (!scrollContainer || !noteContent || !noteContentArea) return; + + const savedScrollTop = scrollContainer.scrollTop; + + try { + // 1. 清空舊的內容區域 + noteContentArea.empty(); + + // 2. 重新建立筆記內第一行標題 + noteContentArea.createEl('h1', { + cls: 'ge-note-internal-title', + text: file.basename + }); + + // 3. 讀取與渲染 + const content = await this.view.app.vault.read(file); + await MarkdownRenderer.render( + this.view.app, + content, + noteContentArea as HTMLElement, + file.path, + this.view + ); + + // 4. 加上圖片的 data-source-path + noteContentArea + .querySelectorAll('img') + .forEach((img) => (img.dataset.sourcePath = file.path)); + + // 5. 重新渲染 backlinks + const oldLinks = noteContent.querySelector('.ge-note-links-section'); + if (oldLinks) { + oldLinks.remove(); + } + await this.renderLinksSection(file, noteContent as HTMLElement); + + // 6. 還原滾動位置 + scrollContainer.scrollTop = savedScrollTop; + } catch (error) { + console.error('Error updating current preview note:', error); + } + } } + + + diff --git a/src/GridView.ts b/src/GridView.ts index 9bc8705..aaaa665 100644 --- a/src/GridView.ts +++ b/src/GridView.ts @@ -96,12 +96,14 @@ interface GridViewStateData { searchMediaFiles?: boolean; fileNameFilterQuery?: string; includeMedia?: boolean | 'media-only'; + baseMinMode?: boolean; minMode?: boolean; showIgnoredItems?: boolean; baseCardLayout?: 'horizontal' | 'vertical'; cardLayout?: 'horizontal' | 'vertical'; hideHeaderElements?: boolean; showFileNameFilter?: boolean; + baseShowDateDividers?: boolean; showDateDividers?: boolean; showNoteTags?: boolean; recentSources?: string[]; @@ -163,9 +165,11 @@ export class GridView extends ItemView { fileWatcher: FileWatcher | null = null; // 檔案監聽器 recentSources: string[] = []; // 歷史記錄 futureSources: string[] = []; // 未來紀錄(前進堆疊) - minMode: boolean = false; // 最小模式 + baseMinMode: boolean = false; // 基礎最小模式(不受資料夾臨時覆蓋影響) + minMode: boolean = false; // 目前實際使用的最小模式(可能被資料夾 metadata 臨時覆蓋) showIgnoredItems: boolean = false; // 顯示忽略的資料夾及檔案 - showDateDividers: boolean = false; // 顯示日期分隔器 + baseShowDateDividers: boolean = false; // 基礎顯示日期分隔器狀態(不受資料夾臨時覆蓋影響) + showDateDividers: boolean = false; // 目前實際使用的顯示日期分隔器狀態(可能被資料夾 metadata 臨時覆蓋) showNoteTags: boolean = false; // 顯示筆記標籤 showFileNameFilter: boolean = true; // 顯示檔名篩選輸入框 pinnedList: string[] = []; // 置頂清單 @@ -198,7 +202,10 @@ export class GridView extends ItemView { this.sortType = this.baseSortType; this.baseCardLayout = this.plugin.settings.cardLayout; this.cardLayout = this.baseCardLayout; - this.showDateDividers = this.plugin.settings.dateDividerMode !== 'none'; + this.baseMinMode = false; + this.minMode = this.baseMinMode; + this.baseShowDateDividers = this.plugin.settings.dateDividerMode !== 'none'; + this.showDateDividers = this.baseShowDateDividers; this.showNoteTags = this.plugin.settings.showNoteTags; this.showFileNameFilter = this.plugin.settings.showFileNameFilter; this.searchCurrentLocationOnly = this.plugin.settings.searchCurrentLocationOnly; @@ -401,6 +408,14 @@ export class GridView extends ItemView { return; } + // 當切換來源路徑或模式時,應先隱藏當前筆記/ZIP預覽,並清空預覽歷史記錄 + if (this.isShowingNote) { + this.hideNoteInGrid(); + } + if (this.isShowingZip) { + this.hideZipInGrid(); + } + // 記錄之前的狀態到歷史記錄中(如果有) if (this.sourceMode && recordHistory) { this.pushHistory( @@ -433,14 +448,30 @@ export class GridView extends ItemView { const mdFile = this.app.vault.getAbstractFileByPath(mdFilePath); let tempLayout: 'horizontal' | 'vertical' = this.baseCardLayout; let folderSort: string | undefined; + let tempMinMode: boolean = this.baseMinMode; + let tempShowDateDividers: boolean = this.baseShowDateDividers; + if (mdFile instanceof TFile) { const metadata = this.app.metadataCache.getFileCache(mdFile)?.frontmatter; folderSort = typeof metadata?.sort === 'string' ? metadata.sort : undefined; if (metadata?.cardLayout === 'horizontal' || metadata?.cardLayout === 'vertical') { tempLayout = metadata.cardLayout as 'horizontal' | 'vertical'; } + if (metadata?.minMode === true || metadata?.minMode === 'true') { + tempMinMode = true; + } else if (metadata?.minMode === false || metadata?.minMode === 'false') { + tempMinMode = false; + } + if (metadata?.showDateDividers === true || metadata?.showDateDividers === 'true') { + tempShowDateDividers = true; + } else if (metadata?.showDateDividers === false || metadata?.showDateDividers === 'false') { + tempShowDateDividers = false; + } } this.cardLayout = tempLayout; + this.minMode = tempMinMode; + this.showDateDividers = tempShowDateDividers; + // 根據資料夾 frontmatter 的 sort 覆蓋實際排序,否則使用 baseSortType this.sortType = folderSort && typeof folderSort === 'string' && folderSort.trim() !== '' ? folderSort @@ -448,6 +479,8 @@ export class GridView extends ItemView { } else { // 非資料夾模式時 this.cardLayout = this.baseCardLayout; // 回復基礎卡片排列 + this.minMode = this.baseMinMode; // 回復基礎最小化模式 + this.showDateDividers = this.baseShowDateDividers; // 回復基礎日期分隔器模式 this.sourcePath = '/'; // 強制設定路徑為根目錄 (創建筆記用) // 切換到自訂模式時:重設選項索引,並將排序設為 'none' @@ -1136,7 +1169,34 @@ export class GridView extends ItemView { } else { // 其他檔案顯示副檔名 if (!this.minMode) { - contentArea.createEl('p', { text: file.extension.toUpperCase() }); + const extension = file.extension.toLowerCase(); + const pEl = contentArea.createEl('p', { cls: 'ge-non-doc-preview' }); + const iconSpan = pEl.createSpan({ cls: 'ge-non-doc-icon' }); + let iconName = 'file'; + if (isImageFile(file)) { + iconName = 'image'; + iconSpan.addClass('ge-img'); + } else if (isVideoFile(file)) { + iconName = 'play-circle'; + iconSpan.addClass('ge-video'); + } else if (isAudioFile(file)) { + iconName = 'music'; + iconSpan.addClass('ge-audio'); + } else if (extension === 'pdf') { + iconName = 'paperclip'; + iconSpan.addClass('ge-pdf'); + } else if (extension === 'canvas') { + iconName = 'layout-dashboard'; + iconSpan.addClass('ge-canvas'); + } else if (extension === 'base') { + iconName = 'layout-list'; + iconSpan.addClass('ge-base'); + } else if (extension === 'zip') { + iconName = 'folder-archive'; + iconSpan.addClass('ge-zip'); + } + setIcon(iconSpan, iconName); + pEl.createSpan({ text: file.extension.toUpperCase() }); } setTooltip(fileEl, `${file.name}`, { delay: 2000 }) @@ -1565,36 +1625,6 @@ export class GridView extends ItemView { fileEl.addClass('ge-media-card'); } - // 添加檔案類型圖示 - if (isImageFile(file)) { - const iconContainer = titleContainer.createDiv('ge-icon-container ge-img'); - setIcon(iconContainer, 'image'); - } else if (isVideoFile(file)) { - const iconContainer = titleContainer.createDiv('ge-icon-container ge-video'); - setIcon(iconContainer, 'play-circle'); - } else if (isAudioFile(file)) { - const iconContainer = titleContainer.createDiv('ge-icon-container ge-audio'); - setIcon(iconContainer, 'music'); - } else if (extension === 'pdf') { - const iconContainer = titleContainer.createDiv('ge-icon-container ge-pdf'); - setIcon(iconContainer, 'paperclip'); - } else if (extension === 'canvas') { - const iconContainer = titleContainer.createDiv('ge-icon-container ge-canvas'); - setIcon(iconContainer, 'layout-dashboard'); - } else if (extension === 'base') { - const iconContainer = titleContainer.createDiv('ge-icon-container ge-base'); - setIcon(iconContainer, 'layout-list'); - } else if (extension === 'zip') { - const iconContainer = titleContainer.createDiv('ge-icon-container ge-zip'); - setIcon(iconContainer, 'folder-archive'); - } else if (extension === 'md' || extension === 'txt') { - const iconContainer = titleContainer.createDiv('ge-icon-container'); - setIcon(iconContainer, 'file-text'); - } else { - const iconContainer = titleContainer.createDiv('ge-icon-container'); - setIcon(iconContainer, 'file'); - } - // 創建標題(立即載入) const shouldShowExtension = this.minMode && extension !== 'md'; const displayText = shouldShowExtension ? `${file.basename}.${file.extension}` : file.basename; @@ -2006,6 +2036,41 @@ export class GridView extends ItemView { }); } + // 複製選項 + menu.addItem((item) => { + item + .setTitle(t("make_a_copy")) + .setIcon("copy") + .setSection?.("action") + .onClick(async () => { + for (const f of selectedFiles) { + const parentPath = f.parent ? f.parent.path : ""; + let copyPath = ""; + if (parentPath && parentPath !== "/") { + copyPath = `${parentPath}/${f.basename} 1.${f.extension}`; + } else { + copyPath = `${f.basename} 1.${f.extension}`; + } + + let counter = 1; + while (this.app.vault.getAbstractFileByPath(copyPath)) { + counter++; + if (parentPath && parentPath !== "/") { + copyPath = `${parentPath}/${f.basename} ${counter}.${f.extension}`; + } else { + copyPath = `${f.basename} ${counter}.${f.extension}`; + } + } + + try { + await this.app.vault.copy(f, copyPath); + } catch (err) { + console.error("Failed to copy file:", err); + } + } + }); + }); + // 刪除選項 menu.addItem((item) => { (item as MenuItemWithWarning).setWarning(true); @@ -2390,12 +2455,14 @@ export class GridView extends ItemView { searchMediaFiles: this.searchMediaFiles, fileNameFilterQuery: this.fileNameFilterQuery, includeMedia: this.includeMedia, + baseMinMode: this.baseMinMode, minMode: this.minMode, showIgnoredItems: this.showIgnoredItems, baseCardLayout: this.baseCardLayout, cardLayout: this.cardLayout, hideHeaderElements: this.hideHeaderElements, showFileNameFilter: this.showFileNameFilter, + baseShowDateDividers: this.baseShowDateDividers, showDateDividers: this.showDateDividers, showNoteTags: this.showNoteTags, recentSources: this.recentSources, @@ -2418,13 +2485,15 @@ export class GridView extends ItemView { this.searchMediaFiles = state.state.searchMediaFiles ?? false; this.fileNameFilterQuery = state.state.fileNameFilterQuery ?? ''; this.includeMedia = state.state.includeMedia ?? false; - this.minMode = state.state.minMode ?? false; + this.baseMinMode = state.state.baseMinMode ?? state.state.minMode ?? false; + this.minMode = state.state.minMode ?? this.baseMinMode; this.showIgnoredItems = state.state.showIgnoredItems ?? false; this.baseCardLayout = state.state.baseCardLayout ?? 'horizontal'; this.cardLayout = state.state.cardLayout ?? this.baseCardLayout; this.hideHeaderElements = state.state.hideHeaderElements ?? false; this.showFileNameFilter = state.state.showFileNameFilter ?? this.plugin.settings.showFileNameFilter; - this.showDateDividers = state.state.showDateDividers ?? this.plugin.settings.dateDividerMode !== 'none'; + this.baseShowDateDividers = state.state.baseShowDateDividers ?? state.state.showDateDividers ?? (this.plugin.settings.dateDividerMode !== 'none'); + this.showDateDividers = state.state.showDateDividers ?? this.baseShowDateDividers; this.showNoteTags = state.state.showNoteTags ?? this.plugin.settings.showNoteTags; this.recentSources = state.state.recentSources ?? []; this.futureSources = state.state.futureSources ?? []; diff --git a/src/main.ts b/src/main.ts index 1d655da..572ee45 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,6 +12,16 @@ interface MenuItemWithSubmenu extends MenuItem { setSubmenu(): Menu; } +interface CustomWorkspace { + on(name: 'grid-explorer:preview-file', callback: (fileOrPath: string | TFile) => void): EventRef; +} + +declare global { + interface Window { + GridExplorer?: GridExplorerPlugin; + } +} + type WorkspaceEventHandler = (name: string, callback: (...data: unknown[]) => unknown) => EventRef; interface CanvasPosition { @@ -548,8 +558,18 @@ export default class GridExplorerPlugin extends Plugin { // console.log('GridExplorer: Starting cache warming process.'); // this.startWarmingCache(); }); + + // 註冊全域事件與掛載 window API 供其他外掛呼叫 + this.registerEvent( + (this.app.workspace as unknown as CustomWorkspace).on('grid-explorer:preview-file', (fileOrPath: string | TFile) => { + void this.previewFile(fileOrPath); + }) + ); + window.GridExplorer = this; } + + // startWarmingCache() { // // 1. 取得所有 Markdown 檔案的列表 // const filesToWarm = this.app.vault.getMarkdownFiles(); @@ -844,8 +864,41 @@ export default class GridExplorerPlugin extends Plugin { }); } } + + async previewFile(fileOrPath: TFile | string) { + let file: TFile | null = null; + if (typeof fileOrPath === 'string') { + const abstractFile = this.app.vault.getAbstractFileByPath(fileOrPath); + if (abstractFile instanceof TFile) { + file = abstractFile; + } + } else if (fileOrPath instanceof TFile) { + file = fileOrPath; + } + + if (!file) { + console.warn('Grid Explorer: Invalid file or path provided for preview.', fileOrPath); + return; + } + + // 取得或啟用 GridView + const activeView = this.app.workspace.getActiveViewOfType(GridView); + const view = activeView ?? await this.activateView(); + if (view instanceof GridView) { + await view.showNoteInGrid(file); + } + } + + onunload() { + if (window.GridExplorer === this) { + delete window.GridExplorer; + } + super.onunload(); + } + } + function migrateDataviewCodeToQuery(settings: GallerySettings): void { if (!settings.customModes) return; for (const mode of settings.customModes) { diff --git a/src/modal/folderNoteSettingsModal.ts b/src/modal/folderNoteSettingsModal.ts index f4c0d71..4ecaed7 100644 --- a/src/modal/folderNoteSettingsModal.ts +++ b/src/modal/folderNoteSettingsModal.ts @@ -9,6 +9,8 @@ export interface FolderNoteSettings { icon: string; isPinned: boolean; cardLayout: '' | 'horizontal' | 'vertical'; + minMode: '' | 'true' | 'false'; + showDateDividers: '' | 'true' | 'false'; } interface FolderNoteFrontmatter { @@ -17,6 +19,8 @@ interface FolderNoteFrontmatter { icon?: unknown; cardLayout?: unknown; pinned?: unknown; + minMode?: unknown; + showDateDividers?: unknown; } function getStringValue(value: unknown, fallback = ''): string { @@ -43,7 +47,9 @@ export class FolderNoteSettingsModal extends Modal { color: '', icon: '📁', isPinned: false, - cardLayout: '' + cardLayout: '', + minMode: '', + showDateDividers: '' }; existingFile: TFile | null = null; @@ -180,6 +186,34 @@ export class FolderNoteSettingsModal extends Modal { }); }); + // 最小化模式選項 + new Setting(contentEl) + .setName(t('min_mode')) + .setDesc(t('display_minimized_desc')) + .addDropdown(drop => { + drop.addOption('', t('default')); + drop.addOption('true', t('enable')); + drop.addOption('false', t('disable')); + drop.setValue(this.settings.minMode); + drop.onChange(async (value) => { + this.settings.minMode = value as '' | 'true' | 'false'; + }); + }); + + // 顯示日期分隔器選項 + new Setting(contentEl) + .setName(t('show_date_dividers')) + .setDesc(t('show_date_dividers_desc')) + .addDropdown(drop => { + drop.addOption('', t('default')); + drop.addOption('true', t('enable')); + drop.addOption('false', t('disable')); + drop.setValue(this.settings.showDateDividers); + drop.onChange(async (value) => { + this.settings.showDateDividers = value as '' | 'true' | 'false'; + }); + }); + // 置頂勾選框 new Setting(contentEl) .setName(t('foldernote_pinned')) @@ -226,6 +260,24 @@ export class FolderNoteSettingsModal extends Modal { this.settings.cardLayout = cardLayout === 'horizontal' || cardLayout === 'vertical' ? cardLayout : ''; + const minMode = frontmatter.minMode; + if (minMode === true || minMode === 'true') { + this.settings.minMode = 'true'; + } else if (minMode === false || minMode === 'false') { + this.settings.minMode = 'false'; + } else { + this.settings.minMode = ''; + } + + const showDateDividers = frontmatter.showDateDividers; + if (showDateDividers === true || showDateDividers === 'true') { + this.settings.showDateDividers = 'true'; + } else if (showDateDividers === false || showDateDividers === 'false') { + this.settings.showDateDividers = 'false'; + } else { + this.settings.showDateDividers = ''; + } + if (Array.isArray(frontmatter.pinned)) { this.settings.isPinned = frontmatter.pinned.some((item: unknown) => { const pinnedName = getPinnedName(item); @@ -284,6 +336,22 @@ export class FolderNoteSettingsModal extends Modal { } else { delete frontmatter.cardLayout; } + + if (this.settings.minMode === 'true') { + frontmatter.minMode = true; + } else if (this.settings.minMode === 'false') { + frontmatter.minMode = false; + } else { + delete frontmatter.minMode; + } + + if (this.settings.showDateDividers === 'true') { + frontmatter.showDateDividers = true; + } else if (this.settings.showDateDividers === 'false') { + frontmatter.showDateDividers = false; + } else { + delete frontmatter.showDateDividers; + } const folderName = `${this.folder.name}.md`; if (this.settings.isPinned) { diff --git a/src/renderHeaderButton.ts b/src/renderHeaderButton.ts index 1bee125..aa556d0 100644 --- a/src/renderHeaderButton.ts +++ b/src/renderHeaderButton.ts @@ -566,7 +566,8 @@ export function renderHeaderButton(gridView: GridView) { .setIcon('minimize-2') .setChecked(gridView.minMode) .onClick(() => { - gridView.minMode = !gridView.minMode; + gridView.baseMinMode = !gridView.minMode; + gridView.minMode = gridView.baseMinMode; gridView.app.workspace.requestSaveLayout(); void gridView.render(); }); @@ -579,7 +580,8 @@ export function renderHeaderButton(gridView: GridView) { .setIcon('calendar') .setChecked(gridView.showDateDividers) .onClick(() => { - gridView.showDateDividers = !gridView.showDateDividers; + gridView.baseShowDateDividers = !gridView.showDateDividers; + gridView.showDateDividers = gridView.baseShowDateDividers; gridView.app.workspace.requestSaveLayout(); void gridView.render(); }); diff --git a/src/settings.ts b/src/settings.ts index 1f17c51..db573d0 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -110,7 +110,7 @@ export const DEFAULT_SETTINGS: GallerySettings = { verticalImageAreaHeight: 180, // 直向卡片 - 圖片區域高度 verticalCardImagePosition: 'top', // 直向卡片圖片位置 titleFontSize: 1.0, // 筆記標題的字型大小,預設 1.0 - multiLineTitle: false, + multiLineTitle: true, // 標題允許兩行顯示 summaryLength: 100, // 筆記摘要的字數,預設 100 enableFileWatcher: true, // 預設啟用檔案監控 folderDisplayStyle: 'show', // 預設直接顯示資料夾 @@ -155,7 +155,7 @@ export const DEFAULT_SETTINGS: GallerySettings = { quickAccessCommandPath: '', // Path used by "Open quick access folder" command useQuickAccessAsNewTabMode: 'default', quickAccessModeType: 'all-files', // Default quick access view type - showNoteInGrid: false, // 預設不在 grid container 中顯示筆記 + showNoteInGrid: Platform.isMobile ? true : false, // 行動裝置: 預設在網格中顯示筆記,非行動裝置預設不在網格中顯示筆記 closeGridViewOnOpenNote: false, // 預設開啟筆記檔案時不關閉目前的網格視圖 openNoteLayout: 'default', // 預設開啟筆記的方式 searchCurrentLocationOnly: false, // 預設搜尋所有筆記 @@ -307,6 +307,9 @@ export class GridExplorerSettingTab extends PluginSettingTab { const infoEl = itemEl.createDiv({ cls: 'ge-custom-mode-info' }); infoEl.createSpan({ text: mode.icon || '🧩', cls: 'ge-custom-mode-icon' }); infoEl.createSpan({ text: mode.displayName, cls: 'ge-custom-mode-name' }); + infoEl.addEventListener('click', () => { + checkbox.click(); + }); // 操作按鈕 const actionsEl = itemEl.createDiv({ cls: 'ge-custom-mode-actions' }); @@ -523,6 +526,9 @@ export class GridExplorerSettingTab extends PluginSettingTab { const infoEl = itemEl.createDiv({ cls: 'ge-display-mode-info' }); infoEl.createSpan({ text: icon, cls: 'ge-display-mode-icon' }); infoEl.createSpan({ text: title, cls: 'ge-display-mode-name' }); + infoEl.addEventListener('click', () => { + checkbox.click(); + }); if (extraRenderer) { const extraEl = itemEl.createDiv({ cls: 'ge-display-mode-extra' }); diff --git a/src/translations/en.ts b/src/translations/en.ts index fcd3a29..a499672 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -6,6 +6,7 @@ export default { 'sorting': 'Sort by', 'refresh': 'Refresh', 'reselect': 'Reselect', + 'backlinks': 'Backlinks', 'no_backlinks': 'No backlinks', 'back': 'Back', 'search': 'Search', @@ -39,6 +40,7 @@ export default { 'save': 'Save', 'option': 'Option', 'add_option': 'Add option', + 'make_a_copy': 'Make a copy', // View Titles 'grid_view_title': 'Grid view', @@ -185,6 +187,8 @@ export default { 'foldernote_display_settings_desc': 'Set the display mode of folder notes', 'all': 'All', 'default': 'Default', + 'enable': 'Enable', + 'disable': 'Disable', 'hidden': 'Hidden', 'all_bookmarks': 'All Bookmarks', 'ungrouped_bookmarks': 'Ungrouped Bookmarks', diff --git a/src/translations/ja.ts b/src/translations/ja.ts index 0fdd760..b4bec08 100644 --- a/src/translations/ja.ts +++ b/src/translations/ja.ts @@ -6,6 +6,7 @@ export default { 'sorting': '並び替え', 'refresh': '更新', 'reselect': '再選択', + 'backlinks': 'バックリンク', 'no_backlinks': 'バックリンクなし', 'back': '戻る', 'search': '検索', @@ -38,6 +39,7 @@ export default { 'save': '保存', 'option': '選択肢', 'add_option': '選択肢を追加', + 'make_a_copy': '複製を作成', // ビュータイトル 'grid_view_title': 'グリッドビュー', @@ -184,6 +186,8 @@ export default { 'foldernote_display_settings_desc': 'フォルダノートの表示方法を設定します', 'all': 'すべて', 'default': 'デフォルト', + 'enable': '有効', + 'disable': '無効', 'hidden': '隠す', 'all_bookmarks': 'すべてのブックマーク', 'ungrouped_bookmarks': 'グループなし', diff --git a/src/translations/ko.ts b/src/translations/ko.ts index 03f34a6..40de01f 100644 --- a/src/translations/ko.ts +++ b/src/translations/ko.ts @@ -6,6 +6,7 @@ export default { 'sorting': '정렬 기준', 'refresh': '새로고침', 'reselect': '다시 선택', + 'backlinks': '백링크', 'no_backlinks': '백링크 없음', 'back': '뒤로', 'search': '검색', @@ -39,6 +40,7 @@ export default { 'save': '저장', 'option': '옵션', 'add_option': '옵션 추가', + 'make_a_copy': '복사본 만들기', // View Titles 'grid_view_title': '그리드 뷰', @@ -185,6 +187,8 @@ export default { 'foldernote_display_settings_desc': '폴더 노트의 표시 모드 설정', 'all': '전체', 'default': '기본값', + 'enable': '활성화', + 'disable': '비활성화', 'hidden': '숨김', 'all_bookmarks': '모든 북마크', 'ungrouped_bookmarks': '그룹 해제됨', diff --git a/src/translations/ru.ts b/src/translations/ru.ts index 054e5fd..eebd6c1 100644 --- a/src/translations/ru.ts +++ b/src/translations/ru.ts @@ -6,6 +6,7 @@ export default { 'sorting': 'Сортировать по', 'refresh': 'Обновить', 'reselect': 'Перевыбрать', + 'backlinks': 'Обратные ссылки', 'no_backlinks': 'Нет обратных ссылок', 'back': 'Назад', 'search': 'Поиск', @@ -38,6 +39,7 @@ export default { 'save': 'Сохранить', 'option': 'Опция', 'add_option': 'Добавить опцию', + 'make_a_copy': 'Создать копию', // View Titles 'grid_view_title': 'Сеточный вид', @@ -184,6 +186,8 @@ export default { 'foldernote_display_settings_desc': 'Установить режим отображения заметок папок', 'all': 'Все', 'default': 'По умолчанию', + 'enable': 'Включить', + 'disable': 'Выключить', 'hidden': 'Скрытые', 'all_bookmarks': 'Все закладки', 'ungrouped_bookmarks': 'Без группы', diff --git a/src/translations/uk.ts b/src/translations/uk.ts index a507a9c..12ffe55 100644 --- a/src/translations/uk.ts +++ b/src/translations/uk.ts @@ -6,6 +6,7 @@ export default { 'sorting': 'Сортувати за', 'refresh': 'Оновити', 'reselect': 'Перевибрати', + 'backlinks': 'Зворотні посилання', 'no_backlinks': 'Немає зворотних посилань', 'back': 'Назад', 'search': 'Пошук', @@ -38,6 +39,7 @@ export default { 'save': 'Зберегти', 'option': 'Опція', 'add_option': 'Додати опцію', + 'make_a_copy': 'Створити копію', // View Titles 'grid_view_title': 'Сітковий вигляд', @@ -184,6 +186,8 @@ export default { 'foldernote_display_settings_desc': 'Встановіть режим відображення нотаток папок', 'all': 'Усі', 'default': 'За замовчуванням', + 'enable': 'Увімкнути', + 'disable': 'Вимкнути', 'hidden': 'Приховані', 'all_bookmarks': 'Усі закладки', 'ungrouped_bookmarks': 'Без групи', diff --git a/src/translations/zh-TW.ts b/src/translations/zh-TW.ts index 526d180..b5b7b3e 100644 --- a/src/translations/zh-TW.ts +++ b/src/translations/zh-TW.ts @@ -6,6 +6,7 @@ export default { 'sorting': '排序方式', 'refresh': '重新整理', 'reselect': '重新選擇位置', + 'backlinks': '反向連結', 'no_backlinks': '沒有反向連結', 'back': '返回', 'search': '搜尋', @@ -39,6 +40,7 @@ export default { 'save': '儲存', 'option': '選項', 'add_option': '新增選項', + 'make_a_copy': "建立副本", // 視圖標題 'grid_view_title': '網格視圖', @@ -185,6 +187,8 @@ export default { 'foldernote_display_settings_desc': '設定資料夾筆記的顯示方式', 'all': '全部', 'default': '預設', + 'enable': '啟用', + 'disable': '停用', 'hidden': '隱藏', 'all_bookmarks': '全部書籤', 'ungrouped_bookmarks': '無群組書籤', diff --git a/src/translations/zh.ts b/src/translations/zh.ts index 17d737b..d795620 100644 --- a/src/translations/zh.ts +++ b/src/translations/zh.ts @@ -6,6 +6,7 @@ export default { 'sorting': '排序方式', 'refresh': '刷新', 'reselect': '重新选择位置', + 'backlinks': '反向链接', 'no_backlinks': '没有反向链接', 'back': '返回', 'search': '搜索', @@ -38,6 +39,7 @@ export default { 'save': '保存', 'option': '选项', 'add_option': '添加选项', + 'make_a_copy': '创建副本', // 视图标题 'grid_view_title': '网格视图', @@ -184,6 +186,8 @@ export default { 'foldernote_display_settings_desc': '设置文件夹笔记的显示方式', 'all': '全部', 'default': '默认', + 'enable': '启用', + 'disable': '禁用', 'hidden': '隐藏', 'all_bookmarks': '全部书签', 'ungrouped_bookmarks': '无群组书签', diff --git a/styles.css b/styles.css index 88b050d..c8b6fe7 100644 --- a/styles.css +++ b/styles.css @@ -129,39 +129,9 @@ color: var(--text-muted); } -/* 圖片圖示 Image Icon */ -.ge-icon-container.ge-img { - color: var(--color-blue); -} - -/* 影片圖示 Video Icon */ -.ge-icon-container.ge-video { - color: var(--color-red); -} - -/* 音樂圖示 Audio Icon */ -.ge-icon-container.ge-audio { - color: var(--color-purple); -} - -/* PDF圖示 PDF Icon */ -.ge-icon-container.ge-pdf { - color: var(--color-orange); -} - -/* Canvas圖示 Canvas Icon */ -.ge-icon-container.ge-canvas { - color: var(--color-green); -} - -/* 基礎圖示 Base Icon */ -.ge-icon-container.ge-base { - color: var(--color-cyan); -} - /* 標題 Title */ .ge-title { - margin: 0 0 0 3px; + margin: 0; font-size: var(--title-font-size); color: var(--text-normal); overflow: hidden; @@ -176,22 +146,11 @@ align-items: flex-start; } -.ge-title-container.has-multiline-title .ge-icon-container { - width: 16px; - height: 16px; - margin-top: 2px; -} - -.ge-title-container.has-multiline-title .ge-icon-container svg { - width: 14px; - height: 14px; -} - .ge-title.ge-multiline-title { white-space: normal; display: -webkit-box; - -webkit-line-clamp: 2; - line-clamp: 2; + -webkit-line-clamp: 3; + line-clamp: 3; -webkit-box-orient: vertical; line-height: 1.3; overflow: hidden; @@ -225,6 +184,53 @@ height: auto; } +/* 非文件檔預覽樣式 */ +.ge-content-area p.ge-non-doc-preview { + display: flex; + align-items: center; + gap: 4px; + height: auto; +} + +.ge-non-doc-icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 14px; + height: 14px; + color: var(--text-muted); +} + +.ge-non-doc-icon.ge-img { + color: var(--color-blue); +} + +.ge-non-doc-icon.ge-video { + color: var(--color-red); +} + +.ge-non-doc-icon.ge-audio { + color: var(--color-purple); +} + +.ge-non-doc-icon.ge-pdf { + color: var(--color-orange); +} + +.ge-non-doc-icon.ge-canvas { + color: var(--color-green); +} + +.ge-non-doc-icon.ge-base { + color: var(--color-cyan); +} + +.ge-non-doc-icon.ge-zip { + color: var(--color-folder); /* obsidian color or fallback */ +} + + /* 圖片區域 Image Area */ .ge-image-area { width: var(--image-area-width); @@ -1058,6 +1064,7 @@ span.ge-mode-title { gap: 8px; flex-grow: 1; min-width: 0; + cursor: pointer; } .ge-custom-mode-icon { @@ -1151,6 +1158,7 @@ span.ge-mode-title { gap: 8px; flex-grow: 1; min-width: 0; + cursor: pointer; } .ge-display-mode-icon { @@ -2296,11 +2304,10 @@ a.ge-current-folder:hover { display: flex; align-items: center; gap: 8px; - padding-left: 20px; - flex: 1; - min-width: 0; + padding-left: 15px; } + .ge-note-top-right { display: flex; align-items: center; @@ -2330,50 +2337,47 @@ a.ge-current-folder:hover { .ge-note-info-button { display: flex; align-items: center; - gap: 4px; - padding: 6px 12px; - background-color: var(--interactive-normal); - border-radius: var(--button-radius); - color: var(--text-normal); - font-size: 14px; + justify-content: center; + padding: 6px; + background-color: transparent; + border-radius: var(--clickable-icon-radius, 4px); + color: var(--icon-color); cursor: pointer; - transition: background-color 0.2s, transform 0.1s; -} - -.is-tablet .ge-note-close-button:not(.clickable-icon), -.is-tablet .ge-note-edit-button:not(.clickable-icon), -.is-tablet .ge-note-info-button:not(.clickable-icon) { - padding: 6px 12px; + transition: background-color 0.2s; + border: none; + box-shadow: none; + outline: none; } .ge-note-close-button:hover, .ge-note-edit-button:hover, .ge-note-info-button:hover { - background-color: var(--interactive-hover); - transform: translateY(-1px); - opacity: 1; + background-color: var(--background-modifier-hover); + color: var(--icon-color-focused); } .ge-note-close-button:active, .ge-note-edit-button:active, .ge-note-info-button:active { - transform: translateY(0); + background-color: var(--background-modifier-active); } .is-mobile .ge-note-info-button:focus, .is-mobile .ge-note-info-button:focus-visible { outline: none; - background-color: var(--interactive-normal); - transform: none; + background-color: transparent; } .ge-note-content-container { - max-width: 800px; margin: 0 auto; - padding: 20px; + padding: var(--file-margins); min-height: 100%; } +.is-mobile .ge-note-content-container { + padding-top: 15px; +} + .ge-note-title { font-size: 1em; font-weight: var(--inline-title-weight); @@ -3295,9 +3299,7 @@ a.ge-current-folder:hover { display: flex; align-items: center; gap: 8px; - padding-left: 20px; - flex: 1; - min-width: 0; + padding-left: 15px; } .ge-zip-top-right { @@ -3308,33 +3310,37 @@ a.ge-current-folder:hover { } .ge-zip-title { - font-size: 1em; - font-weight: bold; - color: var(--text-normal); + flex: 1; + text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + font-weight: 500; + font-size: 0.95em; + color: var(--text-muted); + min-width: 0; } .ge-zip-close-button, .ge-zip-open-button { display: flex; align-items: center; - gap: 4px; - padding: 6px 12px; - background-color: var(--interactive-normal); - border-radius: var(--button-radius); - color: var(--text-normal); - font-size: 14px; + justify-content: center; + padding: 6px; + background-color: transparent; + border-radius: var(--clickable-icon-radius, 4px); + color: var(--icon-color); cursor: pointer; - transition: background-color 0.2s, transform 0.1s; + transition: background-color 0.2s; border: none; + box-shadow: none; + outline: none; } .ge-zip-close-button:hover, .ge-zip-open-button:hover { - background-color: var(--interactive-hover); - transform: translateY(-1px); + background-color: var(--background-modifier-hover); + color: var(--icon-color-focused); } .ge-zip-scroll-container { @@ -3422,7 +3428,7 @@ a.ge-current-folder:hover { @media (hover: hover) { .ge-folder-button:hover { - background-color: var(--background-modifier-hover); + background-color: var(--text-selection); transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } @@ -3502,4 +3508,106 @@ a.ge-current-folder:hover { .ge-folder-button.ge-folder-color-custom { background-color: var(--ge-folder-color-bg); border-color: var(--ge-folder-color-border); +} + +/* 筆記預覽內部關聯連結區塊 */ +.ge-note-links-section { + margin-top: 40px; + padding-top: 20px; + border-top: 1px solid var(--background-modifier-border); + margin-bottom: 40px; +} + +.ge-note-links-header { + font-size: 0.9em; + font-weight: 600; + color: var(--text-muted); + margin-bottom: 12px; + margin-top: 20px; + display: flex; + align-items: center; +} + +.ge-note-links-list { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.ge-note-link-item { + font-size: 0.85em; + padding: 4px 10px; + background-color: var(--background-secondary-alt); + border: 1px solid var(--background-modifier-border); + border-radius: 12px; + color: var(--text-normal); + text-decoration: none; + transition: all 0.2s ease; + cursor: pointer; +} + +.ge-note-link-item:hover { + background-color: var(--interactive-accent); + color: var(--text-on-accent); + border-color: var(--interactive-accent); +} + +/* 歷史導航按鈕 */ +.ge-note-nav-button { + display: flex; + align-items: center; + justify-content: center; + padding: 6px; + background-color: transparent; + border-radius: var(--clickable-icon-radius, 4px); + color: var(--icon-color); + cursor: pointer; + transition: background-color 0.2s; + border: none; + box-shadow: none; + outline: none; +} + +.ge-note-nav-button:hover:not(:disabled) { + background-color: var(--background-modifier-hover); + color: var(--icon-color-focused); +} + +.ge-note-nav-button:disabled { + opacity: 0.3; + cursor: default; +} + +.ge-note-nav-button svg { + width: 16px; + height: 16px; +} + + +/* 頂部列與標題置中 */ +.ge-note-title { + flex: 1; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 500; + font-size: 0.95em; + color: var(--text-muted); + min-width: 0; +} + +/* 筆記內第一行標題 (預設顯示) */ +.ge-note-internal-title { + margin-top: 0px; + margin-bottom: 20px; + border-bottom: 1px solid var(--background-modifier-border); + padding-bottom: 12px; + font-size: var(--inline-title-size); + font-weight: var(--inline-title-weight); + line-height: var(--inline-title-line-height); + font-style: var(--inline-title-style); + font-variant: var(--inline-title-variant); + font-family: var(--inline-title-font); + letter-spacing: -0.015em; } \ No newline at end of file