mirror of
https://github.com/devon22/obsidian-gridexplorer.git
synced 2026-07-22 05:38:16 +00:00
3.2.0
This commit is contained in:
parent
8da5b10e71
commit
15a0f8e929
20 changed files with 343 additions and 46 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
6
package-lock.json
generated
|
|
@ -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 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
193
src/GridView.ts
193
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<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
// 只有當點擊的是頂部按鈕區域本身(而不是其中的按鈕)時才觸發捲動
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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': '無視するフォルダを追加',
|
||||
|
|
|
|||
|
|
@ -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': '무시된 폴더 추가',
|
||||
|
|
|
|||
|
|
@ -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': 'Добавить игнорируемую папку',
|
||||
|
|
|
|||
|
|
@ -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': 'Додати ігноровану папку',
|
||||
|
|
|
|||
|
|
@ -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': '新增忽略資料夾',
|
||||
|
|
|
|||
|
|
@ -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': '添加忽略文件夹',
|
||||
|
|
|
|||
72
styles.css
72
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"target": "ES2020",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "bundler",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"3.1.12": "1.1.0"
|
||||
}
|
||||
"3.2.0": "1.1.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue