This commit is contained in:
Devon22 2026-06-27 23:47:05 +08:00
parent 87549960d7
commit f01a2cbf83
8 changed files with 175 additions and 109 deletions

View file

@ -1,7 +1,7 @@
{
"id": "gridexplorer",
"name": "GridExplorer",
"version": "3.4.9",
"version": "3.4.10",
"minAppVersion": "1.8.7",
"description": "Browse note files in a grid view.",
"author": "Devon22",

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "gridexplorer",
"version": "3.4.9",
"version": "3.4.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gridexplorer",
"version": "3.4.9",
"version": "3.4.10",
"license": "MIT",
"dependencies": {
"jszip": "^3.10.1"

View file

@ -1,6 +1,6 @@
{
"name": "gridexplorer",
"version": "3.4.9",
"version": "3.4.10",
"description": "Browse note files in a grid view.",
"main": "main.js",
"scripts": {

View file

@ -44,6 +44,30 @@ export class GridPreviewManager {
);
}
// 預覽歷史記錄後退
async navigatePreviewBack() {
if (this.previewHistoryIndex > 0) {
this.previewHistoryIndex--;
const prevFile = this.previewHistory[this.previewHistoryIndex];
await this.showNoteInGrid(prevFile, true);
} else {
if (this.view.isShowingZip) {
this.hideZipInGrid();
} else {
this.hideNoteInGrid();
}
}
}
// 預覽歷史記錄前進
async navigatePreviewForward() {
if (this.previewHistoryIndex < this.previewHistory.length - 1) {
this.previewHistoryIndex++;
const nextFile = this.previewHistory[this.previewHistoryIndex];
await this.showNoteInGrid(nextFile, true);
}
}
// 在網格視圖中直接顯示筆記
@ -87,12 +111,14 @@ export class GridPreviewManager {
// 返回與前進按鈕
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.disabled = false;
backButton.addEventListener('click', () => {
if (this.previewHistoryIndex > 0) {
this.previewHistoryIndex--;
const prevFile = this.previewHistory[this.previewHistoryIndex];
void this.showNoteInGrid(prevFile, true);
} else {
this.hideNoteInGrid();
}
});
@ -607,12 +633,14 @@ export class GridPreviewManager {
// 返回與前進按鈕
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.disabled = false;
backButton.addEventListener('click', () => {
if (this.previewHistoryIndex > 0) {
this.previewHistoryIndex--;
const prevFile = this.previewHistory[this.previewHistoryIndex];
void this.showNoteInGrid(prevFile, true);
} else {
this.hideZipInGrid();
}
});

View file

@ -85,6 +85,15 @@ interface WorkspaceSplitWithChildren {
children?: unknown[];
}
interface SourceInfo {
mode: string;
path?: string;
searchQuery?: string;
searchCurrentLocationOnly?: boolean;
searchFilesNameOnly?: boolean;
searchMediaFiles?: boolean;
}
interface GridViewStateData {
sourceMode?: string;
sourcePath?: string;
@ -218,36 +227,66 @@ export class GridView extends ItemView {
this.fileWatcher.registerFileWatcher();
}
// 在 window 層級以捕獲階段攔截 Alt+ArrowLeft優先阻擋 Obsidian 內建快捷鍵
// 在 window 層級以捕獲階段攔截快捷鍵,優先阻擋 Obsidian 內建快捷鍵並處理歷史前後退與關閉預覽
this.registerDomEvent(window, 'keydown', (event: KeyboardEvent) => {
// 如果焦點在輸入框或可編輯區域,不處理
const target = event.target as HTMLElement | null;
if (target && (
target.isContentEditable ||
target.closest('input, textarea, select, [contenteditable="true"]')
)) {
return;
}
if (event.key === 'ArrowLeft' && event.altKey) {
// 僅在本視圖為活動視圖時才處理
if (this.app.workspace.getActiveViewOfType(GridView) !== this) return;
// 與一般鍵盤邏輯相同的防護:顯示筆記中或有 modal 時不處理
if (this.isShowingNote) return;
// 有 modal 時不處理
if (activeDocument.querySelector('.modal-container')) return;
// 僅在資料夾模式且不是根目錄時才後退
if (this.sourceMode === 'folder' && this.sourcePath && this.sourcePath !== '/') {
// 阻止內建快捷鍵與其他監聽器
event.preventDefault();
// 停止後續所有監聽器(包含 Obsidian 內建 hotkey
event.stopImmediatePropagation();
const parentPath = this.sourcePath.split('/').slice(0, -1).join('/') || '/';
void this.setSource('folder', parentPath);
// 阻止內建快捷鍵與其他監聽器
event.preventDefault();
// 停止後續所有監聽器(包含 Obsidian 內建 hotkey
event.stopImmediatePropagation();
if (this.isShowingNote || this.isShowingZip) {
void this.previewManager.navigatePreviewBack();
} else {
void this.navigateBack();
this.clearSelection();
}
} else if (event.key === 'ArrowRight' && event.altKey) {
// Alt + 右鍵:若有選中項目則模擬點擊(例如開啟/預覽)
// 僅在本視圖為活動視圖時才處理
if (this.app.workspace.getActiveViewOfType(GridView) !== this) return;
// 與一般鍵盤邏輯相同的防護:顯示筆記中或有 modal 時不處理
if (this.isShowingNote) return;
// 有 modal 時不處理
if (activeDocument.querySelector('.modal-container')) return;
if (this.selectedItemIndex >= 0 && this.selectedItemIndex < this.gridItems.length) {
// 阻止可能的其他快捷鍵或後續處理,避免重複觸發
// 阻止內建快捷鍵與其他監聽器
event.preventDefault();
// 停止後續所有監聽器(包含 Obsidian 內建 hotkey
event.stopImmediatePropagation();
if (this.isShowingNote || this.isShowingZip) {
void this.previewManager.navigatePreviewForward();
} else {
void this.navigateForward();
this.clearSelection();
}
} else if ((event.key === 'ArrowUp' && event.altKey) || event.key === 'Backspace') {
// 僅在本視圖為活動視圖時才處理
if (this.app.workspace.getActiveViewOfType(GridView) !== this) return;
// 有 modal 時不處理
if (activeDocument.querySelector('.modal-container')) return;
// 若正在顯示預覽,則關閉預覽
if (this.isShowingNote || this.isShowingZip) {
event.preventDefault();
event.stopImmediatePropagation();
this.gridItems[this.selectedItemIndex].click();
if (this.isShowingZip) {
this.previewManager.hideZipInGrid();
} else {
this.previewManager.hideNoteInGrid();
}
}
}
}, true);
@ -385,6 +424,78 @@ export class GridView extends ItemView {
}
}
private parseSourceInfo(sourceInfoStr: string): SourceInfo | null {
try {
return JSON.parse(sourceInfoStr) as SourceInfo;
} catch {
return null;
}
}
// 歷史記錄後退
async navigateBack() {
if (this.recentSources.length > 0) {
const currentKey = JSON.stringify({
mode: this.sourceMode,
path: this.sourcePath,
searchQuery: this.searchQuery,
searchCurrentLocationOnly: this.searchCurrentLocationOnly,
searchFilesNameOnly: this.searchFilesNameOnly,
searchMediaFiles: this.searchMediaFiles,
});
this.futureSources.unshift(currentKey);
if (this.futureSources.length > 10) {
this.futureSources.length = 10;
}
const lastSource = this.parseSourceInfo(this.recentSources[0]);
if (!lastSource) return;
this.recentSources.shift(); // 從歷史記錄中移除
await this.setSource(
lastSource.mode,
lastSource.path || '',
false, // 不記錄到歷史
lastSource.searchQuery || '',
lastSource.searchCurrentLocationOnly ?? false,
lastSource.searchFilesNameOnly ?? false,
lastSource.searchMediaFiles ?? false
);
}
}
// 歷史記錄前進
async navigateForward() {
if (this.futureSources.length > 0) {
const currentKey = JSON.stringify({
mode: this.sourceMode,
path: this.sourcePath,
searchQuery: this.searchQuery,
searchCurrentLocationOnly: this.searchCurrentLocationOnly,
searchFilesNameOnly: this.searchFilesNameOnly,
searchMediaFiles: this.searchMediaFiles,
});
this.recentSources.unshift(currentKey);
if (this.recentSources.length > 10) {
this.recentSources.length = 10;
}
const nextSource = this.parseSourceInfo(this.futureSources[0]);
if (!nextSource) return;
this.futureSources.shift(); // 從未來紀錄中移除
await this.setSource(
nextSource.mode,
nextSource.path || '',
false, // 不記錄到歷史
nextSource.searchQuery || '',
nextSource.searchCurrentLocationOnly ?? false,
nextSource.searchFilesNameOnly ?? false,
nextSource.searchMediaFiles ?? false
);
}
}
// 設定來源模式
async setSource(
mode: string,

View file

@ -29,10 +29,10 @@ export function handleKeyDown(gridView: GridView, event: KeyboardEvent) {
switch (event.key) {
case 'ArrowRight':
if (event.altKey) {
// 如果有選中的項目,模擬點擊
if (gridView.selectedItemIndex >= 0 && gridView.selectedItemIndex < gridView.gridItems.length) {
gridView.gridItems[gridView.selectedItemIndex].click();
}
void gridView.navigateForward();
gridView.clearSelection();
event.preventDefault();
break;
}
newIndex = Math.min(gridView.gridItems.length - 1, gridView.selectedItemIndex + 1);
gridView.hasKeyboardFocus = true;
@ -40,14 +40,9 @@ export function handleKeyDown(gridView: GridView, event: KeyboardEvent) {
break;
case 'ArrowLeft':
if (event.altKey) {
// 如果按下 Alt + 左鍵,且是資料夾模式且不是根目錄
if (gridView.sourceMode === 'folder' && gridView.sourcePath && gridView.sourcePath !== '/') {
// 獲取上一層資料夾路徑
const parentPath = gridView.sourcePath.split('/').slice(0, -1).join('/') || '/';
void gridView.setSource('folder', parentPath);
gridView.clearSelection();
event.preventDefault();
}
void gridView.navigateBack();
gridView.clearSelection();
event.preventDefault();
break;
}
newIndex = Math.max(0, gridView.selectedItemIndex - 1);

View file

@ -70,44 +70,8 @@ export function renderHeaderButton(gridView: GridView) {
void (async () => {
event.preventDefault();
event.stopPropagation();
// 如果有歷史記錄
if (gridView.recentSources.length > 0) {
// 將當前狀態推入 futureSources 以便前進
const currentKey = JSON.stringify({
mode: gridView.sourceMode,
path: gridView.sourcePath,
searchQuery: gridView.searchQuery,
searchCurrentLocationOnly: gridView.searchCurrentLocationOnly,
searchFilesNameOnly: gridView.searchFilesNameOnly,
searchMediaFiles: gridView.searchMediaFiles,
});
gridView.futureSources.unshift(currentKey);
if (gridView.futureSources.length > 10) {
gridView.futureSources.length = 10;
}
// 取得最近一筆歷史記錄
const lastSource = parseSourceInfo(gridView.recentSources[0]);
if (!lastSource) return;
gridView.recentSources.shift(); // 從歷史記錄中移除
// 設定來源及搜尋狀態(不記錄到歷史)
await gridView.setSource(
lastSource.mode,
lastSource.path || '',
false, // 不記錄到歷史
lastSource.searchQuery || '',
lastSource.searchCurrentLocationOnly ?? false,
lastSource.searchFilesNameOnly ?? false,
lastSource.searchMediaFiles ?? false
);
// 更新按鈕狀態
updateNavButtons();
} else {
// 如果沒有歷史記錄,點擊按鈕無效
}
await gridView.navigateBack();
updateNavButtons();
})();
});
@ -118,42 +82,8 @@ export function renderHeaderButton(gridView: GridView) {
void (async () => {
event.preventDefault();
event.stopPropagation();
// 如果有未來紀錄
if (gridView.futureSources.length > 0) {
// 將當前狀態推入 recentSources 以便返回
const currentKey = JSON.stringify({
mode: gridView.sourceMode,
path: gridView.sourcePath,
searchQuery: gridView.searchQuery,
searchCurrentLocationOnly: gridView.searchCurrentLocationOnly,
searchFilesNameOnly: gridView.searchFilesNameOnly,
searchMediaFiles: gridView.searchMediaFiles,
});
gridView.recentSources.unshift(currentKey);
if (gridView.recentSources.length > 10) {
gridView.recentSources.length = 10;
}
// 取得下一筆未來紀錄
const nextSource = parseSourceInfo(gridView.futureSources[0]);
if (!nextSource) return;
gridView.futureSources.shift(); // 從未來紀錄中移除
// 設定來源及搜尋狀態(不記錄到歷史)
await gridView.setSource(
nextSource.mode,
nextSource.path || '',
false, // 不記錄到歷史
nextSource.searchQuery || '',
nextSource.searchCurrentLocationOnly ?? false,
nextSource.searchFilesNameOnly ?? false,
nextSource.searchMediaFiles ?? false
);
// 更新按鈕狀態
updateNavButtons();
}
await gridView.navigateForward();
updateNavButtons();
})();
});

View file

@ -53,6 +53,7 @@
transform: translateY(-2px);
background-color: var(--text-selection);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-color: var(--interactive-accent) !important;
}
.ge-grid-item:hover p {
@ -3431,6 +3432,7 @@ a.ge-current-folder:hover {
background-color: var(--text-selection);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-color: var(--interactive-accent) !important;
}
}