From 15a0f8e929da21b90727270881a18b395435ea71 Mon Sep 17 00:00:00 2001 From: Devon22 Date: Thu, 30 Apr 2026 23:51:53 +0800 Subject: [PATCH] 3.2.0 --- .gitignore | 10 +- manifest.json | 4 +- package-lock.json | 6 +- package.json | 4 +- src/GridView.ts | 193 +++++++++++++++++++++++++++++++++----- src/handleKeyDown.ts | 8 ++ src/modal/mediaModal.ts | 18 +++- src/modal/searchModal.ts | 6 +- src/renderModePath.ts | 17 ++++ src/settings.ts | 17 +++- src/translations/en.ts | 4 + src/translations/ja.ts | 4 + src/translations/ko.ts | 4 + src/translations/ru.ts | 4 + src/translations/uk.ts | 4 + src/translations/zh-TW.ts | 4 + src/translations/zh.ts | 4 + styles.css | 72 ++++++++++++-- tsconfig.json | 2 +- versions.json | 4 +- 20 files changed, 343 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index 1c953fe..93bec09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # vscode -.vscode +.vscode # Intellij *.iml @@ -21,6 +21,12 @@ data.json # Exclude macOS Finder (System Explorer) View States .DS_Store +# AI development tools +AGENTS.md + +# Batch files *.bat *.sh -Changelog.md \ No newline at end of file + +# Changelog +Changelog.md diff --git a/manifest.json b/manifest.json index 35a1fb9..600ccf4 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { "id": "gridexplorer", "name": "GridExplorer", - "version": "3.1.12", + "version": "3.2.0", "minAppVersion": "1.1.0", "description": "Browse note files in a grid view.", "author": "Devon22", "authorUrl": "https://github.com/Devon22", "isDesktopOnly": false -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 7011af9..e76f289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gridexplorer", - "version": "3.1.12", + "version": "3.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gridexplorer", - "version": "3.1.12", + "version": "3.2.0", "license": "MIT", "dependencies": { "@esbuild/linux-x64": "0.17.3", @@ -2315,4 +2315,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index a0117f0..66d545c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gridexplorer", - "version": "3.1.11", + "version": "3.2.0", "description": "Browse note files in a grid view.", "main": "main.js", "scripts": { @@ -25,4 +25,4 @@ "@esbuild/linux-x64": "^0.17.3", "@esbuild/win32-x64": "^0.17.3" } -} \ No newline at end of file +} diff --git a/src/GridView.ts b/src/GridView.ts index 154634c..a2c4fcc 100644 --- a/src/GridView.ts +++ b/src/GridView.ts @@ -12,6 +12,7 @@ import { isHexColor, hexToRgba } from './utils/colorUtils'; import { showFolderSelectionModal } from './modal/folderSelectionModal'; import { MediaModal } from './modal/mediaModal'; import { showNoteSettingsModal } from './modal/noteSettingsModal'; +import { showSearchModal } from './modal/searchModal'; import { FloatingAudioPlayer } from './FloatingAudioPlayer'; import { ExplorerView, EXPLORER_VIEW_TYPE } from './ExplorerView'; import { t } from './translations'; @@ -41,6 +42,7 @@ export class GridView extends ItemView { searchCurrentLocationOnly: boolean = false; // 是否只搜尋當前位置 searchFilesNameOnly: boolean = false; // 是否只搜尋筆記名稱 searchMediaFiles: boolean = false; // 是否搜尋媒體檔案 + fileNameFilterQuery: string = ''; // 目前列表的檔名篩選關鍵字 includeMedia: boolean = false; // 是否包含媒體檔案 selectedItemIndex: number = -1; // 當前選中的項目索引 selectedItems: Set = new Set(); // 存儲多選的項目索引 @@ -53,11 +55,12 @@ export class GridView extends ItemView { showIgnoredItems: boolean = false; // 顯示忽略的資料夾及檔案 showDateDividers: boolean = false; // 顯示日期分隔器 showNoteTags: boolean = false; // 顯示筆記標籤 + showFileNameFilter: boolean = true; // 顯示檔名篩選輸入框 pinnedList: string[] = []; // 置頂清單 taskFilter: string = 'uncompleted'; // 任務分類 hideHeaderElements: boolean = false; // 是否隱藏標題列元素(模式名稱和按鈕) bookmarkGroupId: string = 'all'; // 書籤群組 ID - customOptionIndex: number = -1; // 自訂模式選項索引 + customOptionIndex: number = -1; // 自訂模式選項索引 baseCardLayout: 'horizontal' | 'vertical' = 'horizontal'; // 使用者在設定或 UI 中選擇的基礎卡片樣式(不受資料夾臨時覆蓋影響) cardLayout: 'horizontal' | 'vertical' = 'horizontal'; // 目前實際使用的卡片樣式(可能被資料夾 metadata 臨時覆蓋) renderToken: number = 0; // 用於取消尚未完成之批次排程的遞增令牌 @@ -76,6 +79,7 @@ export class GridView extends ItemView { this.cardLayout = this.baseCardLayout; this.showDateDividers = this.plugin.settings.dateDividerMode !== 'none'; this.showNoteTags = this.plugin.settings.showNoteTags; + this.showFileNameFilter = this.plugin.settings.showFileNameFilter; this.searchCurrentLocationOnly = this.plugin.settings.searchCurrentLocationOnly; this.searchFilesNameOnly = this.plugin.settings.searchFilesNameOnly; this.searchMediaFiles = this.plugin.settings.searchMediaFiles; @@ -402,6 +406,11 @@ export class GridView extends ItemView { // 顯示路徑 / 模式名稱 renderModePath(this); + if (this.showFileNameFilter) { + // 顯示目前列表的檔名篩選輸入框 + this.renderFileNameFilter(); + } + // 創建內容區域 const contentEl = this.containerEl.createDiv('view-content'); @@ -450,18 +459,141 @@ export class GridView extends ItemView { // new Notice('GridExplorer: ' + this.sourceMode + ' ' + this.sourcePath); } + // 渲染檔名篩選框 + renderFileNameFilter() { + const filterContainer = this.containerEl.createDiv('ge-file-filter-container'); + const contentEl = this.containerEl.querySelector('.view-content'); + if (contentEl) { + this.containerEl.insertBefore(filterContainer, contentEl); + } + const inputWrapper = filterContainer.createDiv('ge-file-filter-input-wrapper'); + const filterInput = inputWrapper.createEl('input', { + type: 'text', + cls: 'ge-file-filter-input', + attr: { + placeholder: t('file_name_filter_placeholder') + } + }) as HTMLInputElement; + const searchButton = inputWrapper.createEl('button', { + cls: 'ge-file-filter-search clickable-icon', + attr: { + 'aria-label': t('search') + } + }); + setIcon(searchButton, 'arrow-right'); + const clearButton = inputWrapper.createEl('button', { + cls: 'ge-file-filter-clear clickable-icon', + attr: { + 'aria-label': t('clear_file_name_filter') + } + }); + setIcon(clearButton, 'x'); + + const updateClearButton = () => { + const hasContent = filterInput.value.length > 0; + searchButton.toggleClass('is-visible', hasContent); + clearButton.toggleClass('is-visible', hasContent); + }; + let filterRenderTimer: number | null = null; + const renderFilteredFiles = async () => { + filterRenderTimer = null; + this.clearSelection(); + await this.grid_render(); + }; + const scheduleFilteredRender = () => { + if (filterRenderTimer !== null) { + window.clearTimeout(filterRenderTimer); + } + filterRenderTimer = window.setTimeout(renderFilteredFiles, 250); + }; + let isComposingFilterText = false; + + filterInput.value = this.fileNameFilterQuery; + updateClearButton(); + filterInput.addEventListener('compositionstart', () => { + isComposingFilterText = true; + if (filterRenderTimer !== null) { + window.clearTimeout(filterRenderTimer); + filterRenderTimer = null; + } + }); + filterInput.addEventListener('compositionend', () => { + isComposingFilterText = false; + this.fileNameFilterQuery = filterInput.value; + updateClearButton(); + scheduleFilteredRender(); + }); + filterInput.addEventListener('input', (event) => { + if (isComposingFilterText || (event as InputEvent).isComposing) return; + this.fileNameFilterQuery = filterInput.value; + updateClearButton(); + scheduleFilteredRender(); + }); + searchButton.addEventListener('click', (event) => { + event.preventDefault(); + event.stopPropagation(); + const query = filterInput.value.trim(); + if (!query) return; + if (filterRenderTimer !== null) { + window.clearTimeout(filterRenderTimer); + filterRenderTimer = null; + } + // filterInput.value = ''; + // this.fileNameFilterQuery = ''; + updateClearButton(); + showSearchModal(this.app, this, query, searchButton); + }); + clearButton.addEventListener('click', async (event) => { + event.preventDefault(); + event.stopPropagation(); + if (!filterInput.value) return; + if (filterRenderTimer !== null) { + window.clearTimeout(filterRenderTimer); + filterRenderTimer = null; + } + filterInput.value = ''; + this.fileNameFilterQuery = ''; + updateClearButton(); + this.clearSelection(); + await this.grid_render(); + filterInput.focus(); + }); + } + // 渲染網格內容 + setFileNameFilterVisibility(show: boolean, filterButton?: HTMLElement) { + this.showFileNameFilter = show; + filterButton?.toggleClass('is-active', show); + + let fileNameFilterContainer = this.containerEl.querySelector('.ge-file-filter-container') as HTMLElement; + if (!fileNameFilterContainer && show) { + this.renderFileNameFilter(); + fileNameFilterContainer = this.containerEl.querySelector('.ge-file-filter-container') as HTMLElement; + } + + if (fileNameFilterContainer) { + fileNameFilterContainer.style.display = this.hideHeaderElements || !show ? 'none' : 'block'; + if (show && !this.hideHeaderElements) { + const filterInput = fileNameFilterContainer.querySelector('.ge-file-filter-input') as HTMLInputElement | null; + filterInput?.focus(); + } + } + } + async grid_render() { const container = this.containerEl.querySelector('.view-content') as HTMLElement; container.empty(); + this.renderToken++; container.addClass('ge-grid-container'); // 隱藏頂部元素 const displayValue = this.hideHeaderElements ? 'none' : 'flex'; const headerButtons = this.containerEl.querySelector('.ge-header-buttons') as HTMLElement; + const fileNameFilterContainer = this.containerEl.querySelector('.ge-file-filter-container') as HTMLElement; const modeHeaderContainer = this.containerEl.querySelector('.ge-mode-header-container') as HTMLElement; if (headerButtons) headerButtons.style.display = displayValue; + if (fileNameFilterContainer) fileNameFilterContainer.style.display = this.hideHeaderElements || !this.showFileNameFilter ? 'none' : 'block'; if (modeHeaderContainer) modeHeaderContainer.style.display = displayValue; // 根據設定決定是否啟用卡片模式 @@ -559,6 +691,19 @@ export class GridView extends ItemView { // 顯示檔案 let files = await renderFiles(this, container); + // 根據輸入框內容篩選目前列表中的檔案名稱 + const fileNameFilterKeywords = this.fileNameFilterQuery + .trim() + .toLowerCase() + .split(/\s+/) + .filter(Boolean); + if (this.showFileNameFilter && fileNameFilterKeywords.length > 0) { + files = files.filter(file => { + const fileName = file.name.toLowerCase(); + return fileNameFilterKeywords.every(keyword => fileName.includes(keyword)); + }); + } + // 如果沒有檔案,顯示提示訊息 if (files.length === 0) { const noFilesDiv = container.createDiv('ge-no-files'); @@ -1063,28 +1208,28 @@ export class GridView extends ItemView { const selfRef = this; const batchSize = 50; let currentIndex = 0; - + // 建立哨兵元素,用於觸發後續載入 const sentinel = container.createDiv('ge-load-more-sentinel'); sentinel.style.height = '1px'; sentinel.style.width = '100%'; sentinel.style.flexShrink = '0'; - + let isLoading = false; - + const loadMore = (entries?: IntersectionObserverEntry[]) => { if (currentToken !== selfRef.renderToken) return; - + if (entries && entries[0] && !entries[0].isIntersecting) { return; } - + if (isLoading) return; isLoading = true; - + // 暫時移除哨兵 sentinel.remove(); - + let targetMaxEnd = currentIndex + batchSize; if (selfRef.targetFocusPath) { const tIndex = files.findIndex(f => f.path === selfRef.targetFocusPath); @@ -1094,16 +1239,16 @@ export class GridView extends ItemView { } const end = Math.min(targetMaxEnd, files.length); const TIME_BUDGET_MS = Platform.isIosApp ? 6 : 16; - + const renderChunk = () => { if (currentToken !== selfRef.renderToken) return; - + const startTime = performance.now(); while (currentIndex < end && (performance.now() - startTime) < TIME_BUDGET_MS) { selfRef.processFile(files[currentIndex], paramsBase); currentIndex++; } - + if (currentIndex < end) { requestAnimationFrame(renderChunk); } else { @@ -1113,7 +1258,7 @@ export class GridView extends ItemView { const gridItem = Array.from(container.querySelectorAll('.ge-grid-item')).find( item => (item as HTMLElement).dataset.filePath === selfRef.targetFocusPath ) as HTMLElement; - + if (gridItem) { gridItem.scrollIntoView({ block: 'nearest' }); const itemIndex = selfRef.gridItems.indexOf(gridItem); @@ -1127,7 +1272,7 @@ export class GridView extends ItemView { if (currentIndex < files.length) { // 還有剩餘檔案,將哨兵加回底部 container.appendChild(sentinel); - + // 主動判斷是否仍在可視範圍內(解決捲動跳轉後 IntersectionObserver 未觸發的問題) setTimeout(() => { if (currentToken !== selfRef.renderToken) return; @@ -1139,22 +1284,22 @@ export class GridView extends ItemView { } }, 50); } - + isLoading = false; } }; - + renderChunk(); }; - + const sentinelObserver = new IntersectionObserver(loadMore, { root: container, rootMargin: '400px', // 提早 400px 載入 threshold: 0 }); - + sentinelObserver.observe(sentinel); - + // 初始載入 loadMore(); } @@ -2375,7 +2520,7 @@ export class GridView extends ItemView { isPulling = false; return; } - + // 根據位置判斷是否允許滑動方向 let canPullDown = isAtTop && deltaY > 5; let canPullUp = isAtBottom && deltaY < -5; @@ -2383,7 +2528,7 @@ export class GridView extends ItemView { if (canPullDown || canPullUp) { isDragging = true; // 當同時符合(例如內容很短)且往上拉時,視為上拉 - isPullingUp = canPullUp && deltaY < 0; + isPullingUp = canPullUp && deltaY < 0; if (this.noteViewContainer) { this.noteViewContainer.style.transition = 'none'; } @@ -2409,7 +2554,7 @@ export class GridView extends ItemView { const handleTouchEnd = () => { if (!isPulling || !this.noteViewContainer) return; isPulling = false; - + // 如果沒有真正拖曳,就當作一般點擊,不執行後續動畫 if (!isDragging) return; isDragging = false; @@ -2504,12 +2649,14 @@ export class GridView extends ItemView { searchCurrentLocationOnly: this.searchCurrentLocationOnly, searchFilesNameOnly: this.searchFilesNameOnly, searchMediaFiles: this.searchMediaFiles, + fileNameFilterQuery: this.fileNameFilterQuery, includeMedia: this.includeMedia, minMode: this.minMode, showIgnoredItems: this.showIgnoredItems, baseCardLayout: this.baseCardLayout, cardLayout: this.cardLayout, hideHeaderElements: this.hideHeaderElements, + showFileNameFilter: this.showFileNameFilter, showDateDividers: this.showDateDividers, showNoteTags: this.showNoteTags, recentSources: this.recentSources, @@ -2530,12 +2677,14 @@ export class GridView extends ItemView { this.searchCurrentLocationOnly = state.state.searchCurrentLocationOnly ?? false; this.searchFilesNameOnly = state.state.searchFilesNameOnly ?? false; this.searchMediaFiles = state.state.searchMediaFiles ?? false; + this.fileNameFilterQuery = state.state.fileNameFilterQuery ?? ''; this.includeMedia = state.state.includeMedia ?? false; this.minMode = state.state.minMode ?? false; 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.showNoteTags = state.state.showNoteTags ?? this.plugin.settings.showNoteTags; this.recentSources = state.state.recentSources ?? []; @@ -2544,4 +2693,4 @@ export class GridView extends ItemView { await this.render(); } } -} \ No newline at end of file +} diff --git a/src/handleKeyDown.ts b/src/handleKeyDown.ts index 6c03cf0..a1256fb 100644 --- a/src/handleKeyDown.ts +++ b/src/handleKeyDown.ts @@ -1,6 +1,14 @@ import type { GridView } from "./GridView"; export function handleKeyDown(gridView: GridView, event: KeyboardEvent) { + const target = event.target as HTMLElement | null; + if (target && ( + target.isContentEditable || + target.closest('input, textarea, select, [contenteditable="true"]') + )) { + return; + } + // 如果沒有項目或正在檢視筆記,直接返回 if (gridView.gridItems.length === 0 || gridView.isShowingNote) return; diff --git a/src/modal/mediaModal.ts b/src/modal/mediaModal.ts index 16505b8..702a733 100644 --- a/src/modal/mediaModal.ts +++ b/src/modal/mediaModal.ts @@ -1,4 +1,4 @@ -import { App, Modal, TFile, setIcon } from 'obsidian'; +import { App, Modal, TFile, Menu, setIcon } from 'obsidian'; import { isImageFile, isVideoFile, isAudioFile } from '../utils/fileUtils'; export class MediaModal extends Modal { @@ -102,6 +102,14 @@ export class MediaModal extends Modal { // 註冊觸控事件(行動裝置拖曳翻頁) this.registerTouchEvents(this.contentEl); + // 註冊右鍵選單事件 + this.contentEl.addEventListener('contextmenu', (e) => { + const currentFile = this.mediaFiles[this.currentIndex]; + if (currentFile) { + this.onMediaContextMenu(e, currentFile); + } + }); + // 顯示當前媒體檔案 this.showMediaAtIndex(this.currentIndex); } @@ -354,6 +362,14 @@ export class MediaModal extends Modal { } } + // 處理媒體右鍵選單 + private onMediaContextMenu(event: MouseEvent, file: TFile) { + event.preventDefault(); + const menu = new Menu(); + this.app.workspace.trigger('file-menu', menu, file, 'media-viewer'); + menu.showAtMouseEvent(event); + } + // 註冊觸控事件處理器(行動裝置拖曳翻頁) private registerTouchEvents(element: HTMLElement) { element.addEventListener('touchstart', (e) => { diff --git a/src/modal/searchModal.ts b/src/modal/searchModal.ts index 6100114..fd5d668 100644 --- a/src/modal/searchModal.ts +++ b/src/modal/searchModal.ts @@ -1,6 +1,6 @@ -import { App, Modal, Setting, setIcon } from 'obsidian'; -import { GridView } from '../GridView'; -import { t } from '../translations'; +import { App, Modal, Setting, setIcon } from 'obsidian'; +import type { GridView } from '../GridView'; +import { t } from '../translations'; export class SearchModal extends Modal { gridView: GridView; diff --git a/src/renderModePath.ts b/src/renderModePath.ts index ce5bc7c..6416f49 100644 --- a/src/renderModePath.ts +++ b/src/renderModePath.ts @@ -66,6 +66,23 @@ export function renderModePath(gridView: GridView) { }); } + const filterButton = rightActions.createEl('a', { + cls: 'ge-filter-button', + attr: { + 'aria-label': t('show_file_name_filter'), + 'href': '#' + } + }); + setIcon(filterButton, 'filter'); + filterButton.toggleClass('is-active', gridView.showFileNameFilter); + setTooltip(filterButton, t('show_file_name_filter')); + filterButton.addEventListener('click', (evt) => { + evt.preventDefault(); + evt.stopPropagation(); + gridView.setFileNameFilterVisibility(!gridView.showFileNameFilter, filterButton); + gridView.app.workspace.requestSaveLayout(); + }); + // 為區域添加點擊事件,點擊後網格容器捲動到最頂部 modenameContainer.addEventListener('click', (event: MouseEvent) => { // 只有當點擊的是頂部按鈕區域本身(而不是其中的按鈕)時才觸發捲動 diff --git a/src/settings.ts b/src/settings.ts index 8457913..98bf720 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -58,6 +58,7 @@ export interface GallerySettings { createdDateField: string; // 建立時間的欄位名稱 recentFilesCount: number; // 最近筆記模式顯示的筆數 randomNoteCount: number; // 隨機筆記模式顯示的筆數 + showFileNameFilter: boolean; // 預設是否顯示檔名篩選輸入框 showNoteTags: boolean; // 是否顯示筆記標籤 cardLayout: 'horizontal' | 'vertical'; // 卡片排列方向 dateDividerMode: string; // 日期分隔器模式:none, year, month, day @@ -109,6 +110,7 @@ export const DEFAULT_SETTINGS: GallerySettings = { showTasksMode: false, // 預設不顯示任務模式 recentFilesCount: 30, // 預設最近筆記模式顯示的筆數 randomNoteCount: 10, // 預設隨機筆記模式顯示的筆數 + showFileNameFilter: false, // 預設不顯示檔名篩選輸入框 customFolderIcon: '📁', // 自訂資料夾圖示 customDocumentExtensions: '', // 自訂文件副檔名(用逗號分隔) recentSources: [], // 預設最近的瀏覽記錄 @@ -680,6 +682,19 @@ export class GridExplorerSettingTab extends PluginSettingTab { }); }); + // 預設是否顯示檔名篩選輸入框 + new Setting(sectionEl) + .setName(t('show_file_name_filter')) + .setDesc(t('show_file_name_filter_desc')) + .addToggle(toggle => { + toggle + .setValue(this.plugin.settings.showFileNameFilter) + .onChange(async (value) => { + this.plugin.settings.showFileNameFilter = value; + await this.plugin.saveSettings(); + }); + }); + // 資料夾顯示樣式 new Setting(sectionEl) .setName(t('folder_display_style')) @@ -1432,4 +1447,4 @@ export class GridExplorerSettingTab extends PluginSettingTab { }); }); } -} \ No newline at end of file +} diff --git a/src/translations/en.ts b/src/translations/en.ts index 9d3fbc3..30111ec 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -84,6 +84,10 @@ export default { 'open_note_layout_newWindow': 'New window', 'show_note_tags': 'Show note tags', 'show_note_tags_desc': 'Display tags for notes in the grid view.', + 'show_file_name_filter': 'Show file name filter', + 'show_file_name_filter_desc': 'Disabled by default. Turn on to show the file name filter input when opening a grid view.', + 'file_name_filter_placeholder': 'Filter file names', + 'clear_file_name_filter': 'Clear file name filter', 'ignored_folders': 'Ignored folders', 'ignored_folders_desc': 'Set folders to ignore here', 'add_ignored_folder': 'Add ignored folder', diff --git a/src/translations/ja.ts b/src/translations/ja.ts index 890f551..8773d45 100644 --- a/src/translations/ja.ts +++ b/src/translations/ja.ts @@ -83,6 +83,10 @@ export default { 'open_note_layout_newWindow': '新ウィンドウ', 'show_note_tags': 'ノートのタグを表示', 'show_note_tags_desc': 'グリッドビューでノートのタグを表示する', + 'show_file_name_filter': 'ファイル名フィルターを表示', + 'show_file_name_filter_desc': 'デフォルトでは非表示。オンにするとグリッドビューを開くときにファイル名フィルター入力を表示する', + 'file_name_filter_placeholder': 'ファイル名を絞り込み', + 'clear_file_name_filter': 'ファイル名フィルターをクリア', 'ignored_folders': '無視するフォルダ', 'ignored_folders_desc': '無視するフォルダを設定する', 'add_ignored_folder': '無視するフォルダを追加', diff --git a/src/translations/ko.ts b/src/translations/ko.ts index cc221fa..d08e0f1 100644 --- a/src/translations/ko.ts +++ b/src/translations/ko.ts @@ -84,6 +84,10 @@ export default { 'open_note_layout_newWindow': '새 창', 'show_note_tags': '노트 태그 표시', 'show_note_tags_desc': '그리드 뷰에서 노트의 태그 표시', + 'show_file_name_filter': '파일 이름 필터 표시', + 'show_file_name_filter_desc': '기본값은 숨김입니다. 켜면 그리드 뷰를 열 때 파일 이름 필터 입력창을 표시합니다', + 'file_name_filter_placeholder': '파일 이름 필터', + 'clear_file_name_filter': '파일 이름 필터 지우기', 'ignored_folders': '무시된 폴더', 'ignored_folders_desc': '여기에서 무시할 폴더 설정', 'add_ignored_folder': '무시된 폴더 추가', diff --git a/src/translations/ru.ts b/src/translations/ru.ts index 07f0bd1..f7668a7 100644 --- a/src/translations/ru.ts +++ b/src/translations/ru.ts @@ -83,6 +83,10 @@ export default { 'open_note_layout_newWindow': 'Новое окно', 'show_note_tags': 'Показывать теги заметок', 'show_note_tags_desc': 'Отображать теги для заметок в сеточном виде', + 'show_file_name_filter': 'Показывать фильтр имени файла', + 'show_file_name_filter_desc': 'По умолчанию скрыто. Включите, чтобы показывать поле фильтра имени файла при открытии сеточного вида', + 'file_name_filter_placeholder': 'Фильтр имен файлов', + 'clear_file_name_filter': 'Очистить фильтр имени файла', 'ignored_folders': 'Игнорируемые папки', 'ignored_folders_desc': 'Укажите папки для игнорирования здесь', 'add_ignored_folder': 'Добавить игнорируемую папку', diff --git a/src/translations/uk.ts b/src/translations/uk.ts index 23b23d8..f2bdd19 100644 --- a/src/translations/uk.ts +++ b/src/translations/uk.ts @@ -83,6 +83,10 @@ export default { 'open_note_layout_newWindow': 'Нове вікно', 'show_note_tags': 'Показувати теги нотаток', 'show_note_tags_desc': 'Відображати теги для нотаток в сітковому вигляді', + 'show_file_name_filter': 'Показувати фільтр імені файла', + 'show_file_name_filter_desc': 'За замовчуванням приховано. Увімкніть, щоб показувати поле фільтра імені файла під час відкриття сіткового вигляду', + 'file_name_filter_placeholder': 'Фільтр імен файлів', + 'clear_file_name_filter': 'Очистити фільтр імені файла', 'ignored_folders': 'Ігноровані папки', 'ignored_folders_desc': 'Вкажіть папки для ігнорування тут', 'add_ignored_folder': 'Додати ігноровану папку', diff --git a/src/translations/zh-TW.ts b/src/translations/zh-TW.ts index e05047f..a3c50ea 100644 --- a/src/translations/zh-TW.ts +++ b/src/translations/zh-TW.ts @@ -84,6 +84,10 @@ export default { 'open_note_layout_newWindow': '新視窗', 'show_note_tags': '顯示筆記標籤', 'show_note_tags_desc': '在網格視圖中顯示筆記的標籤', + 'show_file_name_filter': '顯示檔名篩選輸入框', + 'show_file_name_filter_desc': '預設不顯示;開啟後,開啟網格視圖時會顯示檔名篩選輸入框', + 'file_name_filter_placeholder': '篩選檔名', + 'clear_file_name_filter': '清除檔名篩選', 'ignored_folders': '忽略的資料夾', 'ignored_folders_desc': '在這裡設定要忽略的資料夾', 'add_ignored_folder': '新增忽略資料夾', diff --git a/src/translations/zh.ts b/src/translations/zh.ts index 04626fc..614a5c3 100644 --- a/src/translations/zh.ts +++ b/src/translations/zh.ts @@ -83,6 +83,10 @@ export default { 'open_note_layout_newWindow': '新窗口', 'show_note_tags': '显示笔记标签', 'show_note_tags_desc': '在网格视图中显示笔记的标签', + 'show_file_name_filter': '显示文件名筛选输入框', + 'show_file_name_filter_desc': '默认不显示;开启后,打开网格视图时会显示文件名筛选输入框', + 'file_name_filter_placeholder': '筛选文件名', + 'clear_file_name_filter': '清除文件名筛选', 'ignored_folders': '忽略的文件夹', 'ignored_folders_desc': '在这里设置要忽略的文件夹', 'add_ignored_folder': '添加忽略文件夹', diff --git a/styles.css b/styles.css index 7b1551d..f77e2cf 100644 --- a/styles.css +++ b/styles.css @@ -599,7 +599,7 @@ margin-bottom: -2px; } -/* 日期分隔器圖示 +/* 日期分隔器圖示 .ge-date-divider::before { content: "📆"; margin-right: 6px; @@ -700,6 +700,56 @@ align-items: center; } +.ge-file-filter-container { + display: block; + padding: 8px 10px; + background: var(--background-secondary); + border-bottom: 1px solid var(--background-modifier-border); + flex-shrink: 0; +} + +.ge-file-filter-input-wrapper { + position: relative; + display: flex; + align-items: center; +} + +.ge-file-filter-input { + width: 100%; + min-height: 30px; + padding-right: 60px; +} + +.ge-file-filter-search, +.ge-file-filter-clear { + position: absolute; + width: 24px; + height: 24px; + padding: 4px; + opacity: 0; + pointer-events: none; + color: var(--text-muted); +} + +.ge-file-filter-search { + right: 30px; +} + +.ge-file-filter-clear { + right: 4px; +} + +.ge-file-filter-search.is-visible, +.ge-file-filter-clear.is-visible { + opacity: 1; + pointer-events: auto; +} + +.ge-file-filter-search:hover, +.ge-file-filter-clear:hover { + color: var(--text-normal); +} + /* 針對非常窄的側邊欄(寬度小於 350px)進一步優化 */ @container (max-width: 350px) { .ge-header-buttons { @@ -744,8 +794,9 @@ padding-right: 8px; } -/* 排序按鈕樣式 */ -.ge-sort-button { +/* 模式列操作按鈕樣式 */ +.ge-sort-button, +.ge-filter-button { background: none; border: none; color: var(--text-muted); @@ -758,13 +809,20 @@ transition: opacity 0.2s ease, background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease; } -.ge-sort-button:hover { +.ge-sort-button:hover, +.ge-filter-button:hover, +.ge-filter-button.is-active { opacity: 1; background-color: var(--background-modifier-hover); box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05) inset; } -.ge-sort-button svg { +.ge-filter-button.is-active { + color: var(--text-accent); +} + +.ge-sort-button svg, +.ge-filter-button svg { width: 16px; height: 16px; } @@ -802,7 +860,7 @@ a.ge-mode-title:hover { /* Root span mode title should look plain: no chip background/border */ span.ge-mode-title { background: transparent !important; - border: none !important; + border: 1px solid transparent !important; box-shadow: none !important; } @@ -2838,4 +2896,4 @@ a.ge-current-folder:hover { .is-tablet .ge-note-content { padding-bottom: var(--safe-area-inset-bottom) !important; -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index c40c97d..3af0cc8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "target": "ES2020", "allowJs": true, "noImplicitAny": true, - "moduleResolution": "bundler", + "moduleResolution": "node", "importHelpers": true, "isolatedModules": true, "strictNullChecks": true, diff --git a/versions.json b/versions.json index e616b06..c84c940 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "3.1.12": "1.1.0" -} \ No newline at end of file + "3.2.0": "1.1.0" +}