diff --git a/src/FolderSelectionModal.ts b/src/FolderSelectionModal.ts index 853accf..9ccbef1 100644 --- a/src/FolderSelectionModal.ts +++ b/src/FolderSelectionModal.ts @@ -11,6 +11,11 @@ export function showFolderSelectionModal(app: App, plugin: GridExplorerPlugin, a export class FolderSelectionModal extends Modal { plugin: GridExplorerPlugin; activeView: GridView | undefined; + folderOptionsContainer: HTMLElement; + folderOptions: HTMLElement[] = []; + selectedIndex: number = -1; // 當前選中的選項索引 + searchInput: HTMLInputElement; + constructor(app: App, plugin: GridExplorerPlugin, activeView?: GridView) { super(app); this.plugin = plugin; @@ -22,11 +27,33 @@ export class FolderSelectionModal extends Modal { contentEl.empty(); new Setting(contentEl).setName(t('select_folders')).setHeading(); + // 添加搜尋輸入框 + const searchContainer = contentEl.createEl('div', { cls: 'ge-folder-search-container' }); + this.searchInput = searchContainer.createEl('input', { + cls: 'ge-folder-search-input', + attr: { + type: 'text', + placeholder: t('filter_folders') + } + }); + + // 創建一個容器來存放所有資料夾選項 + this.folderOptionsContainer = contentEl.createEl('div', { cls: 'ge-folder-options-container' }); + + // 搜尋輸入事件處理 + this.searchInput.addEventListener('input', () => { + const searchTerm = this.searchInput.value.toLowerCase(); + this.filterFolderOptions(searchTerm); + }); + + // 鍵盤事件處理 + this.searchInput.addEventListener('keydown', this.handleKeyDown.bind(this)); + // 建立書籤選項 const bookmarksPlugin = (this.app as any).internalPlugins.plugins.bookmarks; if (bookmarksPlugin?.enabled) { - const bookmarkOption = contentEl.createEl('div', { - cls: 'ge-grid-view-folder-option', + const bookmarkOption = this.folderOptionsContainer.createEl('div', { + cls: 'ge-grid-view-folder-option ge-special-option', text: `📑 ${t('bookmarks_mode')}` }); @@ -38,18 +65,19 @@ export class FolderSelectionModal extends Modal { } this.close(); }); + this.folderOptions.push(bookmarkOption); } // 建立搜尋結果選項 const searchLeaf = (this.app as any).workspace.getLeavesOfType('search')[0]; if (searchLeaf) { const searchView = searchLeaf.view; - const searchInput = searchView.searchComponent ? searchView.searchComponent.inputEl : null; - if(searchInput) { - if (searchInput.value.trim().length > 0) { - const searchOption = contentEl.createEl('div', { - cls: 'ge-grid-view-folder-option', - text: `🔍 ${t('search_results')}: ${searchInput.value}` + const searchInputEl = searchView.searchComponent ? searchView.searchComponent.inputEl : null; + if(searchInputEl) { + if (searchInputEl.value.trim().length > 0) { + const searchOption = this.folderOptionsContainer.createEl('div', { + cls: 'ge-grid-view-folder-option ge-special-option', + text: `🔍 ${t('search_results')}: ${searchInputEl.value}` }); searchOption.addEventListener('click', () => { @@ -60,6 +88,7 @@ export class FolderSelectionModal extends Modal { } this.close(); }); + this.folderOptions.push(searchOption); } } } @@ -68,8 +97,8 @@ export class FolderSelectionModal extends Modal { const activeFile = this.app.workspace.getActiveFile(); if (activeFile) { const activeFileName = activeFile ? `: ${activeFile.basename}` : ''; - const backlinksOption = contentEl.createEl('div', { - cls: 'ge-grid-view-folder-option', + const backlinksOption = this.folderOptionsContainer.createEl('div', { + cls: 'ge-grid-view-folder-option ge-special-option', text: `🔗 ${t('backlinks_mode')}${activeFileName}` }); @@ -81,11 +110,12 @@ export class FolderSelectionModal extends Modal { } this.close(); }); + this.folderOptions.push(backlinksOption); } // 建立所有筆記選項 - const allNotesOption = contentEl.createEl('div', { - cls: 'ge-grid-view-folder-option', + const allNotesOption = this.folderOptionsContainer.createEl('div', { + cls: 'ge-grid-view-folder-option ge-special-option', text: `📔 ${t('all_notes_mode')}` }); @@ -97,9 +127,10 @@ export class FolderSelectionModal extends Modal { } this.close(); }); + this.folderOptions.push(allNotesOption); // 建立根目錄選項 - const rootFolderOption = contentEl.createEl('div', { + const rootFolderOption = this.folderOptionsContainer.createEl('div', { cls: 'ge-grid-view-folder-option', text: `📁 /` }); @@ -112,6 +143,7 @@ export class FolderSelectionModal extends Modal { } this.close(); }); + this.folderOptions.push(rootFolderOption); // 取得所有資料夾(排除被忽略的資料夾) const folders = this.app.vault.getAllFolders() @@ -125,7 +157,7 @@ export class FolderSelectionModal extends Modal { // 建立資料夾選項 folders.forEach(folder => { - const folderOption = contentEl.createEl('div', { + const folderOption = this.folderOptionsContainer.createEl('div', { cls: 'ge-grid-view-folder-option', text: `📁 ${folder.path || '/'}` }); @@ -138,7 +170,127 @@ export class FolderSelectionModal extends Modal { } this.close(); }); + this.folderOptions.push(folderOption); }); + + // 為每個選項添加滑鼠事件 + this.folderOptions.forEach((option, index) => { + option.addEventListener('mouseenter', () => { + this.updateSelection(index); + }); + }); + + // 設置初始焦點到搜尋輸入框 + this.searchInput.focus(); + } + + // 處理鍵盤事件 + handleKeyDown(event: KeyboardEvent) { + const visibleOptions = this.getVisibleOptions(); + + if (visibleOptions.length === 0) return; + + switch (event.key) { + case 'ArrowDown': + event.preventDefault(); + this.moveSelection(1, visibleOptions); + break; + case 'ArrowUp': + event.preventDefault(); + this.moveSelection(-1, visibleOptions); + break; + case 'Enter': + event.preventDefault(); + if (this.selectedIndex >= 0) { + const selectedOption = this.folderOptions[this.selectedIndex]; + if (selectedOption && selectedOption.style.display !== 'none') { + selectedOption.click(); + } + } + break; + case 'Escape': + this.close(); + break; + } + } + + // 移動選擇 + moveSelection(direction: number, visibleOptions: HTMLElement[]) { + // 如果沒有選中項或當前選中項不可見,則從頭開始 + let currentVisibleIndex = -1; + + if (this.selectedIndex >= 0) { + const selectedOption = this.folderOptions[this.selectedIndex]; + currentVisibleIndex = visibleOptions.indexOf(selectedOption); + } + + // 計算新的可見索引 + let newVisibleIndex = currentVisibleIndex + direction; + + // 循環選擇 + if (newVisibleIndex < 0) { + newVisibleIndex = visibleOptions.length - 1; + } else if (newVisibleIndex >= visibleOptions.length) { + newVisibleIndex = 0; + } + + // 轉換為實際的選項索引 + if (newVisibleIndex >= 0 && newVisibleIndex < visibleOptions.length) { + const newSelectedOption = visibleOptions[newVisibleIndex]; + const newIndex = this.folderOptions.indexOf(newSelectedOption); + this.updateSelection(newIndex); + + // 確保選中項在視圖中可見 + newSelectedOption.scrollIntoView({ block: 'nearest' }); + } + } + + // 更新選擇 + updateSelection(index: number) { + // 清除之前的選擇 + if (this.selectedIndex >= 0 && this.selectedIndex < this.folderOptions.length) { + this.folderOptions[this.selectedIndex].removeClass('ge-selected-option'); + } + + this.selectedIndex = index; + + // 設置新的選擇 + if (this.selectedIndex >= 0 && this.selectedIndex < this.folderOptions.length) { + this.folderOptions[this.selectedIndex].addClass('ge-selected-option'); + } + } + + // 獲取當前可見的選項 + getVisibleOptions(): HTMLElement[] { + return this.folderOptions.filter(option => + option.style.display !== 'none' + ); + } + + // 篩選資料夾選項 + filterFolderOptions(searchTerm: string) { + let hasVisibleOptions = false; + + this.folderOptions.forEach(option => { + const text = option.textContent?.toLowerCase() || ''; + if (searchTerm === '' || text.includes(searchTerm)) { + option.style.display = 'block'; + hasVisibleOptions = true; + } else { + option.style.display = 'none'; + } + }); + + // 重置選擇,並選中第一個可見選項(如果有) + this.updateSelection(-1); + + if (hasVisibleOptions) { + const visibleOptions = this.getVisibleOptions(); + if (visibleOptions.length > 0) { + const firstVisibleIndex = this.folderOptions.indexOf(visibleOptions[0]); + this.updateSelection(firstVisibleIndex); + } + } } onClose() { diff --git a/src/settings.ts b/src/settings.ts index f2e1229..4420fc7 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -32,22 +32,6 @@ export class GridExplorerSettingTab extends PluginSettingTab { const { containerEl } = this; containerEl.empty(); - // 忽略的資料夾設定 - new Setting(containerEl) - .setName(t('ignored_folders')) - .setDesc(t('ignored_folders_desc')) - .addTextArea(text => text - .setPlaceholder(t('ignored_folders_placeholder')) - .setValue(this.plugin.settings.ignoredFolders.join('\n')) - .onChange(async (value) => { - // 將文字區域的內容轉換為陣列,並過濾掉空行 - this.plugin.settings.ignoredFolders = value - .split('\n') - .map(folder => folder.trim()) - .filter(folder => folder.length > 0); - await this.plugin.saveSettings(); - }).inputEl.rows = 8); - // 預設排序模式設定 new Setting(containerEl) .setName(t('default_sort_type')) @@ -112,5 +96,89 @@ export class GridExplorerSettingTab extends PluginSettingTab { await this.plugin.saveSettings(); }); }); + + // 忽略的資料夾設定 + const ignoredFoldersContainer = containerEl.createDiv('ignored-folders-container'); + + new Setting(containerEl) + .setName(t('ignored_folders')) + .setDesc(t('ignored_folders_desc')) + .setHeading(); + + // 新增資料夾選擇器 + new Setting(ignoredFoldersContainer) + .setName(t('add_ignored_folder')) + .addDropdown(dropdown => { + // 獲取所有資料夾 + const folders = this.app.vault.getAllFolders() + .filter(folder => folder.path !== '/') // 排除根目錄 + .sort((a, b) => a.path.localeCompare(b.path)); + + // 新增空選項作為預設值 + dropdown.addOption('', t('select_folders')); + + // 新增所有資料夾作為選項 + folders.forEach(folder => { + // 只顯示尚未被忽略的資料夾 + if (!this.plugin.settings.ignoredFolders.includes(folder.path)) { + dropdown.addOption(folder.path, folder.path); + } + }); + + dropdown.onChange(async (value) => { + if (value) { + // 新增到忽略列表 + this.plugin.settings.ignoredFolders.push(value); + await this.plugin.saveSettings(); + + // 重新渲染列表 + this.renderIgnoredFoldersList(ignoredFoldersList); + + // 重設下拉選單 + dropdown.setValue(''); + this.display(); + } + }); + }); + + // 顯示目前已忽略的資料夾列表 + const ignoredFoldersList = ignoredFoldersContainer.createDiv('ge-ignored-folders-list'); + this.renderIgnoredFoldersList(ignoredFoldersList); + + containerEl.appendChild(ignoredFoldersContainer); + } + + // 渲染已忽略的資料夾列表 + renderIgnoredFoldersList(containerEl: HTMLElement) { + containerEl.empty(); + + if (this.plugin.settings.ignoredFolders.length === 0) { + containerEl.createEl('p', { text: t('no_ignored_folders') }); + return; + } + + const list = containerEl.createEl('ul', { cls: 'ge-ignored-folders-list' }); + + this.plugin.settings.ignoredFolders.forEach(folder => { + const item = list.createEl('li', { cls: 'ge-ignored-folder-item' }); + + item.createSpan({ text: folder, cls: 'ge-ignored-folder-path' }); + + const removeButton = item.createEl('button', { + cls: 'ge-ignored-folder-remove', + text: t('remove') + }); + + removeButton.addEventListener('click', async () => { + // 從忽略列表中移除 + this.plugin.settings.ignoredFolders = this.plugin.settings.ignoredFolders + .filter(f => f !== folder); + await this.plugin.saveSettings(); + + // 重新渲染列表 + this.renderIgnoredFoldersList(containerEl); + this.display(); + }); + }); } } \ No newline at end of file diff --git a/src/translations.ts b/src/translations.ts index 6f91d31..f71d30d 100644 --- a/src/translations.ts +++ b/src/translations.ts @@ -11,7 +11,8 @@ type LanguageKey = keyof Translations; // 全域翻譯函式 export function t(key: string): string { - const lang: LanguageKey = getLanguage() as LanguageKey; + const lang = window.localStorage.getItem('language') as LanguageKey; + //const lang: LanguageKey = getLanguage() as LanguageKey; const translations = TRANSLATIONS[lang] || TRANSLATIONS['en']; return translations[key] || key; } @@ -76,6 +77,7 @@ export const TRANSLATIONS: Translations = { 'open_in_new_tab': '在新分頁開啟', 'searching': '搜尋中...', 'no_files': '沒有找到任何筆記', + 'filter_folders': '篩選資料夾...', }, 'en': { // Notifications @@ -135,6 +137,7 @@ export const TRANSLATIONS: Translations = { 'open_in_new_tab': 'Open in new tab', 'searching': 'Searching...', 'no_files': 'No notes found', + 'filter_folders': 'Filter folders...', }, 'zh': { // 通知信息 @@ -191,9 +194,10 @@ export const TRANSLATIONS: Translations = { 'open_grid_view': '开启网格视图', 'open_in_grid_view': '在网格视图中开启', 'delete_note': '删除笔记', - 'open_in_new_tab': '在新分頁開啟', + 'open_in_new_tab': '在新标签页打开', 'searching': '搜索中...', 'no_files': '没有找到任何笔记', + 'filter_folders': '筛选文件夹...', }, 'ja': { // 通知メッジ @@ -253,5 +257,6 @@ export const TRANSLATIONS: Translations = { 'open_in_new_tab': '新しいタブで開く', 'searching': '検索中...', 'no_files': 'ノートが見つかりません', + 'filter_folders': 'フォルダをフィルタリング...', }, }; \ No newline at end of file