This commit is contained in:
Devon22 2026-04-30 23:51:53 +08:00
parent 8da5b10e71
commit 15a0f8e929
20 changed files with 343 additions and 46 deletions

10
.gitignore vendored
View file

@ -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
# Changelog
Changelog.md

View file

@ -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
}
}

6
package-lock.json generated
View file

@ -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 @@
}
}
}
}
}

View file

@ -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"
}
}
}

View file

@ -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<number> = 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();
}
}
}
}

View file

@ -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;

View file

@ -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) => {

View file

@ -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;

View file

@ -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) => {
// 只有當點擊的是頂部按鈕區域本身(而不是其中的按鈕)時才觸發捲動

View file

@ -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 {
});
});
}
}
}

View file

@ -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',

View file

@ -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': '無視するフォルダを追加',

View file

@ -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': '무시된 폴더 추가',

View file

@ -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': 'Добавить игнорируемую папку',

View file

@ -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': 'Додати ігноровану папку',

View file

@ -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': '新增忽略資料夾',

View file

@ -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': '添加忽略文件夹',

View file

@ -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;
}
}

View file

@ -6,7 +6,7 @@
"target": "ES2020",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "bundler",
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,

View file

@ -1,3 +1,3 @@
{
"3.1.12": "1.1.0"
}
"3.2.0": "1.1.0"
}