From 7903d6f1de0e28fd2a19d2a316139ef54422db49 Mon Sep 17 00:00:00 2001 From: Devon22 Date: Fri, 7 Mar 2025 00:45:12 +0800 Subject: [PATCH] 1.8.2 --- .gitignore | 5 +- main.ts | 5 - manifest.json | 2 +- package-lock.json | 4 +- package.json | 2 +- src/FileWatcher.ts | 80 ++++++++++ src/FolderSelectionModal.ts | 2 +- src/GridView.ts | 289 ++++++++++++++------------------- src/MediaModal.ts | 311 ++++++++++++++++++++++++++++++++++++ src/SearchModal.ts | 105 ++++++++++++ src/settings.ts | 148 ++++++++++++++++- src/translations.ts | 176 +++++++++++++------- styles.css | 10 ++ versions.json | 2 +- 14 files changed, 896 insertions(+), 245 deletions(-) create mode 100644 src/FileWatcher.ts create mode 100644 src/MediaModal.ts create mode 100644 src/SearchModal.ts diff --git a/.gitignore b/.gitignore index 61ccc0f..5d57384 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,7 @@ main.js data.json # Exclude macOS Finder (System Explorer) View States -.DS_Store \ No newline at end of file +.DS_Store + +*.bat +Changelog.md \ No newline at end of file diff --git a/main.ts b/main.ts index 5edf4ea..0f26fc2 100644 --- a/main.ts +++ b/main.ts @@ -90,11 +90,6 @@ export default class GridExplorerPlugin extends Plugin { const leaves = this.app.workspace.getLeavesOfType('grid-view'); leaves.forEach(leaf => { if (leaf.view instanceof GridView) { - // 如果檔案監控設定已變更,需要重新設定檔案監控 - if (this.settings.enableFileWatcher) { - leaf.view.registerFileWatcher(); - } - // 重新渲染視圖以套用新設定 leaf.view.render(); } diff --git a/manifest.json b/manifest.json index 1572ec1..f4f2893 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "gridexplorer", "name": "GridExplorer", - "version": "1.8.1", + "version": "1.8.2", "minAppVersion": "1.1.0", "description": "Browse note files in a grid view.", "author": "Devon22", diff --git a/package-lock.json b/package-lock.json index a86fee1..fb5d40b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gridexplorer", - "version": "1.8.1", + "version": "1.8.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gridexplorer", - "version": "1.8.1", + "version": "1.8.2", "license": "MIT", "devDependencies": { "@types/node": "^16.11.6", diff --git a/package.json b/package.json index fc03c00..553a51b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gridexplorer", - "version": "1.8.1", + "version": "1.8.2", "description": "Browse note files in a grid view.", "main": "main.js", "scripts": { diff --git a/src/FileWatcher.ts b/src/FileWatcher.ts new file mode 100644 index 0000000..334789e --- /dev/null +++ b/src/FileWatcher.ts @@ -0,0 +1,80 @@ +import { TFile, App } from 'obsidian'; +import GridExplorerPlugin from '../main'; +import { GridView } from './GridView'; + +//檔案監聽器 +export class FileWatcher { + private plugin: GridExplorerPlugin; + private gridView: GridView; + private app: App; + + constructor(plugin: GridExplorerPlugin, gridView: GridView) { + this.plugin = plugin; + this.gridView = gridView; + this.app = plugin.app; + } + + registerFileWatcher() { + // 只有在設定啟用時才註冊檔案監聽器 + if (!this.plugin.settings.enableFileWatcher) { + return; + } + + this.plugin.registerEvent( + this.app.vault.on('create', (file) => { + if (file instanceof TFile) { + if (this.gridView.sourceMode === 'folder' && this.gridView.sourcePath && this.gridView.searchQuery === '') { + const fileDirPath = file.path.split('/').slice(0, -1).join('/') || '/'; + if (fileDirPath === this.gridView.sourcePath) { + this.gridView.render(); + } + } else { + this.gridView.render(); + } + } + }) + ); + + this.plugin.registerEvent( + this.app.vault.on('delete', (file) => { + if (file instanceof TFile) { + if (this.gridView.sourceMode === 'folder' && this.gridView.sourcePath && this.gridView.searchQuery === '') { + const fileDirPath = file.path.split('/').slice(0, -1).join('/') || '/'; + if (fileDirPath === this.gridView.sourcePath) { + this.gridView.render(); + } + } else { + this.gridView.render(); + } + } + }) + ); + + //更名及檔案移動 + this.plugin.registerEvent( + this.app.vault.on('rename', (file, oldPath) => { + if (file instanceof TFile) { + if (this.gridView.sourceMode === 'folder' && this.gridView.sourcePath && this.gridView.searchQuery === '') { + const fileDirPath = file.path.split('/').slice(0, -1).join('/') || '/'; + const oldDirPath = oldPath.split('/').slice(0, -1).join('/') || '/'; + if (fileDirPath === this.gridView.sourcePath || oldDirPath === this.gridView.sourcePath) { + this.gridView.render(); + } + } else { + this.gridView.render(); + } + } + }) + ); + + // 處理書籤變更 + this.plugin.registerEvent( + (this.app as any).internalPlugins.plugins.bookmarks.instance.on('changed', () => { + if (this.gridView.sourceMode === 'bookmarks') { + this.gridView.render(); + } + }) + ); + } + +} diff --git a/src/FolderSelectionModal.ts b/src/FolderSelectionModal.ts index 24ae5cd..4806dff 100644 --- a/src/FolderSelectionModal.ts +++ b/src/FolderSelectionModal.ts @@ -1,7 +1,7 @@ import { App, Modal, Setting, Platform } from 'obsidian'; -import GridExplorerPlugin from '../main'; import { GridView } from './GridView'; import { t } from './translations'; +import GridExplorerPlugin from '../main'; // 顯示資料夾選擇 modal export function showFolderSelectionModal(app: App, plugin: GridExplorerPlugin, activeView?: GridView) { diff --git a/src/GridView.ts b/src/GridView.ts index bc80130..6b69d89 100644 --- a/src/GridView.ts +++ b/src/GridView.ts @@ -1,10 +1,12 @@ import { App, WorkspaceLeaf, ItemView, Modal, TFolder, TFile, Menu, Notice, Setting} from 'obsidian'; import { setIcon, getFrontMatterInfo } from 'obsidian'; -import GridExplorerPlugin from '../main'; import { showFolderSelectionModal } from './FolderSelectionModal'; import { findFirstImageInNote } from './mediaUtils'; import { MediaModal } from './MediaModal'; +import { showSearchModal } from './SearchModal'; +import { FileWatcher } from './FileWatcher'; import { t } from './translations'; +import GridExplorerPlugin from '../main'; // 定義網格視圖 export class GridView extends ItemView { @@ -17,6 +19,7 @@ export class GridView extends ItemView { gridItems: HTMLElement[] = []; // 存儲所有網格項目的引用 hasKeyboardFocus: boolean = false; // 是否有鍵盤焦點 keyboardNavigationEnabled: boolean = true; // 是否啟用鍵盤導航 + fileWatcher: FileWatcher; constructor(leaf: WorkspaceLeaf, plugin: GridExplorerPlugin) { super(leaf); @@ -29,7 +32,8 @@ export class GridView extends ItemView { // 根據設定決定是否註冊檔案變更監聽器 if (this.plugin.settings.enableFileWatcher) { - this.registerFileWatcher(); + this.fileWatcher = new FileWatcher(plugin, this); + this.fileWatcher.registerFileWatcher(); } // 註冊鍵盤事件處理 @@ -207,11 +211,41 @@ export class GridView extends ItemView { //忽略檔案 ignoredFiles(files: TFile[]) { - return files.filter(file => - !this.plugin.settings.ignoredFolders.some((folder => + return files.filter(file => { + // 檢查是否在忽略的資料夾中 + const isInIgnoredFolder = this.plugin.settings.ignoredFolders.some(folder => file.path.startsWith(`${folder}/`) - ) - )); + ); + + if (isInIgnoredFolder) { + return false; + } + + // 檢查是否符合忽略的資料夾模式 + if (this.plugin.settings.ignoredFolderPatterns && this.plugin.settings.ignoredFolderPatterns.length > 0) { + const matchesIgnoredPattern = this.plugin.settings.ignoredFolderPatterns.some(pattern => { + try { + // 嘗試將模式作為正則表達式處理 + // 如果模式包含特殊字符,使用正則表達式處理 + if (/[\^\$\*\+\?\(\)\[\]\{\}\|\\]/.test(pattern)) { + const regex = new RegExp(pattern); + // 檢查檔案路徑是否符合正則表達式 + return regex.test(file.path); + } else { + // 如果模式不包含特殊字符,直接檢查檔案路徑 + return file.path.toLowerCase().includes(pattern.toLowerCase()) + } + } catch (error) { + // 如果正則表達式無效,直接檢查檔案路徑 + return file.path.toLowerCase().includes(pattern.toLowerCase()) + } + }); + // 如果符合任何忽略模式,則忽略此檔案 + return !matchesIgnoredPattern; + } + + return true; + }); } // 判斷是否為媒體檔案 @@ -238,6 +272,23 @@ export class GridView extends ItemView { // 創建頂部按鈕區域 const headerButtonsDiv = this.containerEl.createDiv('ge-header-buttons'); + // 為頂部按鈕區域添加右鍵選單事件 + headerButtonsDiv.addEventListener('contextmenu', (event: MouseEvent) => { + event.preventDefault(); + const menu = new Menu(); + menu.addItem((item) => { + item + .setTitle(t('open_settings')) + .setIcon('settings') + .onClick(() => { + // 打開插件設定頁面 + (this.app as any).setting.open(); + (this.app as any).setting.openTabById(this.plugin.manifest.id); + }); + }); + menu.showAtMouseEvent(event); + }); + // 添加新增筆記按鈕 if (this.sourceMode === 'folder' && this.searchQuery === '') { const newNoteButton = headerButtonsDiv.createEl('button', { attr: { 'aria-label': t('new_note') } }); @@ -408,7 +459,7 @@ export class GridView extends ItemView { this.app.workspace.requestSaveLayout(); }); - const searchText = searchTextContainer.createEl('span', { text: this.searchQuery, cls: 'ge-search-text' }); + const searchText = searchTextContainer.createEl('span', { cls: 'ge-search-text', text: this.searchQuery }); // 讓搜尋文字可點選 searchText.style.cursor = 'pointer'; searchText.addEventListener('click', () => { @@ -464,17 +515,45 @@ export class GridView extends ItemView { if (this.sourceMode === 'folder' && this.searchQuery === '') { const currentFolder = this.app.vault.getAbstractFileByPath(this.sourcePath || '/'); if (currentFolder instanceof TFolder) { - // 只取得當前資料夾中的 Markdown 檔案,不包含子資料夾 const subfolders = currentFolder.children .filter(child => { if (!(child instanceof TFolder)) return false; + // 檢查資料夾是否在忽略清單中 - return !this.plugin.settings.ignoredFolders.some( + const isInIgnoredFolders = this.plugin.settings.ignoredFolders.some( ignoredPath => child.path === ignoredPath || child.path.startsWith(ignoredPath + '/') ); + + if (isInIgnoredFolders) { + return false; + } + + // 檢查資料夾是否符合忽略的模式 + if (this.plugin.settings.ignoredFolderPatterns && this.plugin.settings.ignoredFolderPatterns.length > 0) { + const matchesIgnoredPattern = this.plugin.settings.ignoredFolderPatterns.some(pattern => { + try { + // 嘗試將模式作為正則表達式處理 + if (/[\^\$\*\+\?\(\)\[\]\{\}\|\\]/.test(pattern)) { + const regex = new RegExp(pattern); + return regex.test(child.path); + } else { + // 檢查資料夾名稱是否包含模式字串(不區分大小寫) + return child.name.toLowerCase().includes(pattern.toLowerCase()); + } + } catch (error) { + // 如果正則表達式無效,直接檢查資料夾名稱 + return child.name.toLowerCase().includes(pattern.toLowerCase()); + } + }); + + if (matchesIgnoredPattern) { + return false; + } + } + + return true; }) .sort((a, b) => a.name.localeCompare(b.name)); - for (const folder of subfolders) { const folderEl = container.createDiv('ge-grid-item ge-folder-item'); this.gridItems.push(folderEl); // 添加到網格項目數組 @@ -851,11 +930,28 @@ export class GridView extends ItemView { switch (event.key) { case 'ArrowRight': + if (event.altKey) { + // 如果有選中的項目,模擬點擊 + if (this.selectedItemIndex >= 0 && this.selectedItemIndex < this.gridItems.length) { + this.gridItems[this.selectedItemIndex].click(); + } + } newIndex = Math.min(this.gridItems.length - 1, this.selectedItemIndex + 1); this.hasKeyboardFocus = true; event.preventDefault(); break; case 'ArrowLeft': + if (event.altKey) { + // 如果按下 Alt + 左鍵,且是資料夾模式且不是根目錄 + if (this.sourceMode === 'folder' && this.sourcePath && this.sourcePath !== '/') { + // 獲取上一層資料夾路徑 + const parentPath = this.sourcePath.split('/').slice(0, -1).join('/') || '/'; + this.setSource('folder', parentPath); + this.clearSelection(); + event.preventDefault(); + } + break; + } newIndex = Math.max(0, this.selectedItemIndex - 1); this.hasKeyboardFocus = true; event.preventDefault(); @@ -866,6 +962,17 @@ export class GridView extends ItemView { event.preventDefault(); break; case 'ArrowUp': + if (event.altKey) { + // 如果按下 Alt + 左鍵,且是資料夾模式且不是根目錄 + if (this.sourceMode === 'folder' && this.sourcePath && this.sourcePath !== '/') { + // 獲取上一層資料夾路徑 + const parentPath = this.sourcePath.split('/').slice(0, -1).join('/') || '/'; + this.setSource('folder', parentPath); + this.clearSelection(); + event.preventDefault(); + } + break; + } newIndex = Math.max(0, this.selectedItemIndex - itemsPerRow); this.hasKeyboardFocus = true; event.preventDefault(); @@ -963,103 +1070,7 @@ export class GridView extends ItemView { // 顯示搜尋 modal showSearchModal(defaultQuery = '') { - class SearchModal extends Modal { - gridView: GridView; - defaultQuery: string; - constructor(app: App, gridView: GridView, defaultQuery: string) { - super(app); - this.gridView = gridView; - this.defaultQuery = defaultQuery; - } - - onOpen() { - const { contentEl } = this; - contentEl.empty(); - new Setting(contentEl).setName(t('search')).setHeading(); - - // 如果有 GridView 實例,禁用其鍵盤導航 - if (this.gridView) { - this.gridView.disableKeyboardNavigation(); - } - - // 創建搜尋輸入框容器 - const searchContainer = contentEl.createDiv('ge-search-container'); - - // 創建搜尋輸入框 - const searchInput = searchContainer.createEl('input', { - type: 'text', - value: this.defaultQuery, - placeholder: t('search_placeholder') - }); - - // 創建清空按鈕 - const clearButton = searchContainer.createDiv('ge-search-clear-button'); //這裡不是用 ge-clear-button - clearButton.style.display = this.defaultQuery ? 'flex' : 'none'; - setIcon(clearButton, 'x'); - - // 監聽輸入框變化來控制清空按鈕的顯示 - searchInput.addEventListener('input', () => { - clearButton.style.display = searchInput.value ? 'flex' : 'none'; - }); - - // 清空按鈕點擊事件 - clearButton.addEventListener('click', () => { - searchInput.value = ''; - clearButton.style.display = 'none'; - searchInput.focus(); - }); - - // 創建按鈕容器 - const buttonContainer = contentEl.createDiv('ge-button-container'); - - // 創建搜尋按鈕 - const searchButton = buttonContainer.createEl('button', { - text: t('search') - }); - - // 創建取消按鈕 - const cancelButton = buttonContainer.createEl('button', { - text: t('cancel') - }); - - // 綁定搜尋事件 - const performSearch = () => { - this.gridView.searchQuery = searchInput.value; - this.gridView.clearSelection(); - this.gridView.render(); - // 通知 Obsidian 保存視圖狀態 - this.gridView.app.workspace.requestSaveLayout(); - this.close(); - }; - - searchButton.addEventListener('click', performSearch); - searchInput.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - performSearch(); - } - }); - - cancelButton.addEventListener('click', () => { - this.close(); - }); - - // 自動聚焦到搜尋輸入框,並將游標移到最後 - searchInput.focus(); - searchInput.setSelectionRange(searchInput.value.length, searchInput.value.length); - } - - onClose() { - const { contentEl } = this; - contentEl.empty(); - - // 如果有 GridView 實例,重新啟用其鍵盤導航 - if (this.gridView) { - this.gridView.enableKeyboardNavigation(); - } - } - } - - new SearchModal(this.app, this, defaultQuery).open(); + showSearchModal(this.app,this, defaultQuery); } // 保存視圖狀態 @@ -1086,70 +1097,6 @@ export class GridView extends ItemView { } } - // 註冊檔案監聽器 - registerFileWatcher() { - // 只有在設定啟用時才註冊檔案監聽器 - if (!this.plugin.settings.enableFileWatcher) { - return; - } - - this.registerEvent( - this.app.vault.on('create', (file) => { - if (file instanceof TFile) { - if (this.sourceMode === 'folder' && this.sourcePath && this.searchQuery === '') { - const fileDirPath = file.path.split('/').slice(0, -1).join('/') || '/'; - if (fileDirPath === this.sourcePath) { - this.render(); - } - } else { - this.render(); - } - } - }) - ); - - this.registerEvent( - this.app.vault.on('delete', (file) => { - if (file instanceof TFile) { - if (this.sourceMode === 'folder' && this.sourcePath && this.searchQuery === '') { - const fileDirPath = file.path.split('/').slice(0, -1).join('/') || '/'; - if (fileDirPath === this.sourcePath) { - this.render(); - } - } else { - this.render(); - } - } - }) - ); - - //更名及檔案移動 - this.registerEvent( - this.app.vault.on('rename', (file, oldPath) => { - if (file instanceof TFile) { - if (this.sourceMode === 'folder' && this.sourcePath && this.searchQuery === '') { - const fileDirPath = file.path.split('/').slice(0, -1).join('/') || '/'; - const oldDirPath = oldPath.split('/').slice(0, -1).join('/') || '/'; - if (fileDirPath === this.sourcePath || oldDirPath === this.sourcePath) { - this.render(); - } - } else { - this.render(); - } - } - }) - ); - - // 處理書籤變更 - this.registerEvent( - (this.app as any).internalPlugins.plugins.bookmarks.instance.on('changed', () => { - if (this.sourceMode === 'bookmarks') { - this.render(); - } - }) - ); - } - // 禁用鍵盤導航 disableKeyboardNavigation() { this.keyboardNavigationEnabled = false; diff --git a/src/MediaModal.ts b/src/MediaModal.ts new file mode 100644 index 0000000..86c828b --- /dev/null +++ b/src/MediaModal.ts @@ -0,0 +1,311 @@ +import { App, Modal, TFile, setIcon } from 'obsidian'; + +export class MediaModal extends Modal { + private file: TFile; + private mediaFiles: TFile[]; + private currentIndex: number; + private currentMediaElement: HTMLElement | null = null; + private isZoomed = false; + private handleWheel: ((event: WheelEvent) => void) | null = null; + private gridView: any; // 儲存 GridView 實例的引用 + + constructor(app: App, file: TFile, mediaFiles: TFile[], gridView?: any) { + super(app); + this.file = file; + this.mediaFiles = mediaFiles; + this.currentIndex = this.mediaFiles.findIndex(f => f.path === file.path); + this.gridView = gridView; // 保存 GridView 實例 + + // 設置 modal 樣式 + this.modalEl.addClass('ge-media-modal'); + } + + onOpen() { + const { contentEl } = this; + + // 如果有 GridView 實例,禁用其鍵盤導航 + if (this.gridView) { + this.gridView.disableKeyboardNavigation(); + } + + // 設置 modal 樣式為全螢幕 + contentEl.empty(); + contentEl.style.width = '100%'; + contentEl.style.height = '100%'; + contentEl.addClass('ge-media-modal-content'); + + // 創建媒體顯示區域 + const mediaView = contentEl.createDiv('ge-media-view'); + + // 創建關閉按鈕 + const closeButton = contentEl.createDiv('ge-media-close-button'); + setIcon(closeButton, 'x'); + closeButton.addEventListener('click', (e) => { + e.stopPropagation(); + this.close(); + }); + + // 創建左右切換按鈕區域 + const prevArea = contentEl.createDiv('ge-media-prev-area'); + const nextArea = contentEl.createDiv('ge-media-next-area'); + + // 創建媒體元素容器 + const mediaContainer = mediaView.createDiv('ge-media-container'); + + // 點擊背景關閉媒體檢視器 + mediaContainer.addEventListener('click', (e) => { + // 確保點擊的是背景,而不是媒體內容或其他控制元素 + if (e.target === mediaContainer) { + this.close(); + } + }); + + // 註冊左右區域點擊事件 + prevArea.addEventListener('click', (e) => { + e.stopPropagation(); + this.showPrevMedia(); + }); + + nextArea.addEventListener('click', (e) => { + e.stopPropagation(); + this.showNextMedia(); + }); + + // 註冊滑鼠滾輪事件 + contentEl.addEventListener('wheel', (e) => { + // 只有在非縮放狀態下才使用滾輪切換圖片 + if (!this.isZoomed) { + e.preventDefault(); + if (e.deltaY > 0) { + this.showNextMedia(); + } else { + this.showPrevMedia(); + } + } + }); + + // 註冊鍵盤快捷鍵 + this.scope.register(null, 'ArrowLeft', () => { + this.showPrevMedia(); + return false; + }); + + this.scope.register(null, 'ArrowRight', () => { + this.showNextMedia(); + return false; + }); + + // 顯示當前媒體檔案 + this.showMediaAtIndex(this.currentIndex); + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + + // 如果存在之前的滾輪事件處理程序,先移除它 + if (this.handleWheel) { + const mediaView = contentEl.querySelector('.ge-media-view'); + if (mediaView) { + mediaView.removeEventListener('wheel', this.handleWheel); + } + this.handleWheel = null; + } + + // 如果有 GridView 實例,重新啟用其鍵盤導航並跳轉到當前選中的項目 + if (this.gridView) { + this.gridView.enableKeyboardNavigation(); + + // 找到當前媒體檔案在 GridView 中的索引 + const currentFile = this.mediaFiles[this.currentIndex]; + const gridItemIndex = this.gridView.gridItems.findIndex((item: HTMLElement) => + item.dataset.filePath === currentFile.path + ); + + // 如果找到了對應的項目,選中它並設置鍵盤焦點 + if (gridItemIndex >= 0) { + this.gridView.hasKeyboardFocus = true; + this.gridView.selectItem(gridItemIndex); + } + } + } + + // 顯示指定索引的媒體檔案 + showMediaAtIndex(index: number) { + if (index < 0 || index >= this.mediaFiles.length) return; + + const { contentEl } = this; + const mediaContainer = contentEl.querySelector('.ge-media-container'); + if (!mediaContainer) return; + + // 更新當前顯示的索引 + this.currentIndex = index; + + // 移除當前顯示的媒體元素 + if (this.currentMediaElement) { + this.currentMediaElement.remove(); + this.currentMediaElement = null; + } + + // 如果存在之前的滾輪事件處理程序,先移除它 + if (this.handleWheel) { + const mediaView = contentEl.querySelector('.ge-media-view'); + if (mediaView) { + mediaView.removeEventListener('wheel', this.handleWheel); + } + this.handleWheel = null; + } + + this.isZoomed = false; + + const mediaFile = this.mediaFiles[index]; + const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; + const videoExtensions = ['mp4', 'webm', 'mov', 'avi', 'mkv']; + + if (imageExtensions.includes(mediaFile.extension.toLowerCase())) { + // 創建圖片元素 + const img = document.createElement('img'); + img.className = 'ge-fullscreen-image'; + img.src = this.app.vault.getResourcePath(mediaFile); + mediaContainer.appendChild(img); + this.currentMediaElement = img; + + // 設置圖片樣式,預設滿屏顯示 + this.resetImageStyles(img); + + // 圖片點擊事件(放大/縮小) + img.addEventListener('click', (event) => { + event.stopPropagation(); + this.toggleImageZoom(img); + }); + + } else if (videoExtensions.includes(mediaFile.extension.toLowerCase())) { + // 創建影片元素 + const video = document.createElement('video'); + video.className = 'ge-fullscreen-video'; + video.controls = true; + video.autoplay = true; + + const source = document.createElement('source'); + source.src = this.app.vault.getResourcePath(mediaFile); + source.type = `video/${mediaFile.extension === 'mov' ? 'quicktime' : mediaFile.extension}`; + + video.appendChild(source); + mediaContainer.appendChild(video); + this.currentMediaElement = video; + } + } + + // 顯示下一個媒體檔案 + showNextMedia() { + const nextIndex = (this.currentIndex + 1) % this.mediaFiles.length; + this.showMediaAtIndex(nextIndex); + } + + // 顯示上一個媒體檔案 + showPrevMedia() { + const prevIndex = (this.currentIndex - 1 + this.mediaFiles.length) % this.mediaFiles.length; + this.showMediaAtIndex(prevIndex); + } + + // 重設圖片樣式 + resetImageStyles(img: HTMLImageElement) { + const mediaView = this.contentEl.querySelector('.ge-media-view'); + if (!mediaView) return; + + img.style.width = 'auto'; + img.style.height = 'auto'; + img.style.maxWidth = '100vw'; + img.style.maxHeight = '100vh'; + img.style.position = 'absolute'; + img.style.left = '50%'; + img.style.top = '50%'; + img.style.transform = 'translate(-50%, -50%)'; + img.style.cursor = 'zoom-in'; + + (mediaView as HTMLElement).style.overflowX = 'hidden'; + (mediaView as HTMLElement).style.overflowY = 'hidden'; + + // 等待圖片載入完成後調整大小 + img.onload = () => { + if (mediaView.clientWidth > mediaView.clientHeight) { + if (img.naturalHeight < mediaView.clientHeight) { + img.style.height = '100%'; + } + } else { + if (img.naturalWidth < mediaView.clientWidth) { + img.style.width = '100%'; + } + } + }; + + // 如果圖片已經載入,立即調整大小 + if (img.complete) { + if (mediaView.clientWidth > mediaView.clientHeight) { + if (img.naturalHeight < mediaView.clientHeight) { + img.style.height = '100%'; + } + } else { + if (img.naturalWidth < mediaView.clientWidth) { + img.style.width = '100%'; + } + } + } + } + + // 切換圖片縮放 + toggleImageZoom(img: HTMLImageElement) { + const mediaView = this.contentEl.querySelector('.ge-media-view'); + if (!mediaView) return; + + if (!this.isZoomed) { // 放大 + if (mediaView.clientWidth > mediaView.clientHeight) { + if (img.naturalHeight < mediaView.clientHeight) { + img.style.maxWidth = 'none'; + } + } else { + if (img.naturalWidth < mediaView.clientWidth) { + img.style.maxHeight = 'none'; + } + } + + if (img.offsetWidth < mediaView.clientWidth) { + img.style.width = '100vw'; + img.style.height = 'auto'; + (mediaView as HTMLElement).style.overflowX = 'hidden'; + (mediaView as HTMLElement).style.overflowY = 'scroll'; + } else { + img.style.width = 'auto'; + img.style.height = '100vh'; + (mediaView as HTMLElement).style.overflowX = 'scroll'; + (mediaView as HTMLElement).style.overflowY = 'hidden'; + + // 將事件處理程序存儲在變數中 + this.handleWheel = (event) => { + event.preventDefault(); + (mediaView as HTMLElement).scrollLeft += event.deltaY; + }; + mediaView.addEventListener('wheel', this.handleWheel); + } + + img.style.maxWidth = 'none'; + img.style.maxHeight = 'none'; + img.style.position = 'relative'; + img.style.left = '0'; + img.style.top = '0'; + img.style.margin = 'auto'; + img.style.transform = 'none'; + img.style.cursor = 'zoom-out'; + this.isZoomed = true; + } else { // 縮小 + // 如果存在之前的滾輪事件處理程序,先移除它 + if (this.handleWheel) { + mediaView.removeEventListener('wheel', this.handleWheel); + this.handleWheel = null; + } + + this.resetImageStyles(img); + this.isZoomed = false; + } + } +} diff --git a/src/SearchModal.ts b/src/SearchModal.ts new file mode 100644 index 0000000..3f21ae3 --- /dev/null +++ b/src/SearchModal.ts @@ -0,0 +1,105 @@ +import { App, Modal, Setting } from 'obsidian'; +import { setIcon } from 'obsidian'; +import { t } from './translations'; +import { GridView } from './GridView'; + +export class SearchModal extends Modal { + gridView: GridView; + defaultQuery: string; + constructor(app: App, gridView: GridView, defaultQuery: string) { + super(app); + this.gridView = gridView; + this.defaultQuery = defaultQuery; + } + + onOpen() { + const { contentEl } = this; + contentEl.empty(); + new Setting(contentEl).setName(t('search')).setHeading(); + + // 如果有 GridView 實例,禁用其鍵盤導航 + if (this.gridView) { + this.gridView.disableKeyboardNavigation(); + } + + // 創建搜尋輸入框容器 + const searchContainer = contentEl.createDiv('ge-search-container'); + + // 創建搜尋輸入框 + const searchInput = searchContainer.createEl('input', { + type: 'text', + value: this.defaultQuery, + placeholder: t('search_placeholder') + }); + + // 創建清空按鈕 + const clearButton = searchContainer.createDiv('ge-search-clear-button'); //這裡不是用 ge-clear-button + clearButton.style.display = this.defaultQuery ? 'flex' : 'none'; + setIcon(clearButton, 'x'); + + // 監聽輸入框變化來控制清空按鈕的顯示 + searchInput.addEventListener('input', () => { + clearButton.style.display = searchInput.value ? 'flex' : 'none'; + }); + + // 清空按鈕點擊事件 + clearButton.addEventListener('click', () => { + searchInput.value = ''; + clearButton.style.display = 'none'; + searchInput.focus(); + }); + + // 創建按鈕容器 + const buttonContainer = contentEl.createDiv('ge-button-container'); + + // 創建搜尋按鈕 + const searchButton = buttonContainer.createEl('button', { + text: t('search') + }); + + // 創建取消按鈕 + const cancelButton = buttonContainer.createEl('button', { + text: t('cancel') + }); + + // 綁定搜尋事件 + const performSearch = () => { + this.gridView.searchQuery = searchInput.value; + this.gridView.clearSelection(); + this.gridView.render(); + // 通知 Obsidian 保存視圖狀態 + this.gridView.app.workspace.requestSaveLayout(); + this.close(); + }; + + searchButton.addEventListener('click', performSearch); + searchInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + performSearch(); + } + }); + + cancelButton.addEventListener('click', () => { + this.close(); + }); + + // 自動聚焦到搜尋輸入框,並將游標移到最後 + searchInput.focus(); + searchInput.setSelectionRange(searchInput.value.length, searchInput.value.length); + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + + // 如果有 GridView 實例,重新啟用其鍵盤導航 + if (this.gridView) { + this.gridView.enableKeyboardNavigation(); + } + } +} + +// 顯示搜尋 modal +export function showSearchModal(app:App, gridView: GridView, defaultQuery = '') { + new SearchModal(app, gridView, defaultQuery).open(); +} diff --git a/src/settings.ts b/src/settings.ts index 809b7ff..824c12e 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,24 +1,32 @@ import { App, PluginSettingTab, Setting } from 'obsidian'; -import GridExplorerPlugin from '../main'; import { t } from './translations'; +import GridExplorerPlugin from '../main'; export interface GallerySettings { ignoredFolders: string[]; + ignoredFolderPatterns: string[]; defaultSortType: string; gridItemWidth: number; imageAreaWidth: number; imageAreaHeight: number; enableFileWatcher: boolean; + showMediaFiles: boolean; + searchMediaFiles: boolean; + showVideoThumbnails: boolean; } // 預設設定 export const DEFAULT_SETTINGS: GallerySettings = { ignoredFolders: [], + ignoredFolderPatterns: [], // 預設以字串忽略的資料夾模式 defaultSortType: 'mtime-desc', // 預設排序模式:修改時間倒序 gridItemWidth: 300, // 網格項目寬度,預設 300 imageAreaWidth: 100, // 圖片區域寬度,預設 100 imageAreaHeight: 100, // 圖片區域高度,預設 100 - enableFileWatcher: true // 預設啟用檔案監控 + enableFileWatcher: true, // 預設啟用檔案監控 + showMediaFiles: false, // 預設顯示圖片和影片 + searchMediaFiles: false, // 預設搜尋時也包含圖片和影片 + showVideoThumbnails: false // 預設不顯示影片縮圖 }; // 設定頁面類別 @@ -34,6 +42,58 @@ export class GridExplorerSettingTab extends PluginSettingTab { const { containerEl } = this; containerEl.empty(); + // 媒體檔案設定區域 + containerEl.createEl('h3', { text: t('media_files_settings') }); + + // 顯示圖片和影片設定 + new Setting(containerEl) + .setName(t('show_media_files')) + .setDesc(t('show_media_files_desc')) + .addToggle(toggle => { + toggle + .setValue(this.plugin.settings.showMediaFiles) + .onChange(async (value) => { + this.plugin.settings.showMediaFiles = value; + + // 如果關閉了顯示媒體檔案,也自動關閉搜尋媒體檔案 + if (!value) { + this.plugin.settings.searchMediaFiles = false; + // 重新載入設定頁面以更新 UI + this.display(); + } + + await this.plugin.saveSettings(); + }); + }); + + // 搜尋圖片和影片設定 + new Setting(containerEl) + .setName(t('search_media_files')) + .setDesc(t('search_media_files_desc')) + .addToggle(toggle => { + toggle + .setValue(this.plugin.settings.searchMediaFiles) + .onChange(async (value) => { + this.plugin.settings.searchMediaFiles = value; + await this.plugin.saveSettings(); + }); + }); + + // 顯示影片縮圖設定 + new Setting(containerEl) + .setName(t('show_video_thumbnails')) + .setDesc(t('show_video_thumbnails_desc')) + .addToggle(toggle => { + toggle + .setValue(this.plugin.settings.showVideoThumbnails) + .onChange(async (value) => { + this.plugin.settings.showVideoThumbnails = value; + await this.plugin.saveSettings(); + }); + }); + + containerEl.createEl('h3', { text: t('grid_view_settings') }); + // 預設排序模式設定 new Setting(containerEl) .setName(t('default_sort_type')) @@ -161,6 +221,57 @@ export class GridExplorerSettingTab extends PluginSettingTab { this.renderIgnoredFoldersList(ignoredFoldersList); containerEl.appendChild(ignoredFoldersContainer); + + // 以字串忽略資料夾(可用正則表達式)設定 + const ignoredFolderPatternsContainer = containerEl.createDiv('ignored-folder-patterns-container'); + + new Setting(containerEl) + .setName(t('ignored_folder_patterns')) + .setDesc(t('ignored_folder_patterns_desc')) + .setHeading(); + + // 新增字串模式輸入框 + const patternSetting = new Setting(ignoredFolderPatternsContainer) + .setName(t('add_ignored_folder_pattern')) + .addText(text => { + text.setPlaceholder(t('ignored_folder_pattern_placeholder')) + .onChange(() => { + // 僅用於更新輸入值,不進行保存 + }); + + // 儲存文字輸入元素的引用以便後續使用 + return text; + }); + + // 添加按鈕 + patternSetting.addButton(button => { + button + .setButtonText(t('add')) + .setCta() + .onClick(async () => { + // 獲取輸入值 + const inputEl = patternSetting.controlEl.querySelector('input') as HTMLInputElement; + const pattern = inputEl.value.trim(); + + if (pattern && !this.plugin.settings.ignoredFolderPatterns.includes(pattern)) { + // 新增到忽略模式列表 + this.plugin.settings.ignoredFolderPatterns.push(pattern); + await this.plugin.saveSettings(); + + // 重新渲染列表 + this.renderIgnoredFolderPatternsList(ignoredFolderPatternsList); + + // 清空輸入框 + inputEl.value = ''; + } + }); + }); + + // 顯示目前已忽略的資料夾模式列表 + const ignoredFolderPatternsList = ignoredFolderPatternsContainer.createDiv('ge-ignored-folder-patterns-list'); + this.renderIgnoredFolderPatternsList(ignoredFolderPatternsList); + + containerEl.appendChild(ignoredFolderPatternsContainer); } // 渲染已忽略的資料夾列表 @@ -196,4 +307,37 @@ export class GridExplorerSettingTab extends PluginSettingTab { }); }); } + + // 渲染已忽略的資料夾模式列表 + renderIgnoredFolderPatternsList(containerEl: HTMLElement) { + containerEl.empty(); + + if (this.plugin.settings.ignoredFolderPatterns.length === 0) { + containerEl.createEl('p', { text: t('no_ignored_folder_patterns') }); + return; + } + + const list = containerEl.createEl('ul', { cls: 'ge-ignored-folders-list' }); + + this.plugin.settings.ignoredFolderPatterns.forEach(pattern => { + const item = list.createEl('li', { cls: 'ge-ignored-folder-item' }); + + item.createSpan({ text: pattern, 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.ignoredFolderPatterns = this.plugin.settings.ignoredFolderPatterns + .filter(p => p !== pattern); + await this.plugin.saveSettings(); + + // 重新渲染列表 + this.renderIgnoredFolderPatternsList(containerEl); + }); + }); + } } \ No newline at end of file diff --git a/src/translations.ts b/src/translations.ts index 9cdf3b3..eaa1abe 100644 --- a/src/translations.ts +++ b/src/translations.ts @@ -34,7 +34,8 @@ export const TRANSLATIONS: Translations = { 'cancel': '取消', 'new_note': '新增筆記', 'untitled': '未命名', - 'notes': '個筆記', + 'files': '個檔案', + 'add': '新增', // 視圖標題 'grid_view_title': '網格視圖', @@ -42,7 +43,7 @@ export const TRANSLATIONS: Translations = { 'folder_mode': '資料夾', 'search_results': '搜尋結果', 'backlinks_mode': '反向連結', - 'all_notes_mode': '所有筆記', + 'all_notes_mode': '所有檔案', // 排序選項 'sort_name_asc': '名稱 (A → Z)', @@ -55,10 +56,22 @@ export const TRANSLATIONS: Translations = { // 設定 'grid_view_settings': '網格視圖設定', + 'media_files_settings': '媒體檔案設定', + 'show_media_files': '顯示圖片和影片', + 'show_media_files_desc': '在網格視圖中顯示圖片和影片檔案', + 'search_media_files': '搜尋圖片和影片', + 'search_media_files_desc': '在搜尋結果中包含圖片和影片檔案(僅在啟用顯示圖片和影片時有效)', + 'show_video_thumbnails': '顯示影片縮圖', + 'show_video_thumbnails_desc': '在網格視圖中顯示影片的縮圖,關閉時將顯示播放圖示', 'ignored_folders': '忽略的資料夾', 'ignored_folders_desc': '在這裡設定要忽略的資料夾', 'add_ignored_folder': '新增忽略資料夾', 'no_ignored_folders': '沒有忽略的資料夾。', + 'ignored_folder_patterns': '以字串忽略資料夾和檔案', + 'ignored_folder_patterns_desc': '使用字串模式忽略資料夾和檔案(支援正則表達式)', + 'add_ignored_folder_pattern': '新增忽略資料夾模式', + 'ignored_folder_pattern_placeholder': '輸入資料夾名稱或正則表達式', + 'no_ignored_folder_patterns': '沒有忽略的資料夾模式。', 'remove': '移除', 'default_sort_type': '預設排序模式', 'default_sort_type_desc': '設定開啟網格視圖時的預設排序模式', @@ -75,10 +88,11 @@ export const TRANSLATIONS: Translations = { 'select_folders': '選擇資料夾', 'open_grid_view': '開啟網格視圖', 'open_in_grid_view': '在網格視圖中開啟', - 'delete_note': '刪除筆記', + 'open_settings': '開啟設定', + 'delete_note': '刪除檔案', 'open_in_new_tab': '在新分頁開啟', 'searching': '搜尋中...', - 'no_files': '沒有找到任何筆記', + 'no_files': '沒有找到任何檔案', 'filter_folders': '篩選資料夾...', }, 'en': { @@ -96,7 +110,8 @@ export const TRANSLATIONS: Translations = { 'cancel': 'Cancel', 'new_note': 'New note', 'untitled': 'Untitled', - 'notes': 'Notes', + 'files': 'files', + 'add': 'Add', // View Titles 'grid_view_title': 'Grid view', @@ -104,23 +119,35 @@ export const TRANSLATIONS: Translations = { 'folder_mode': 'Folder', 'search_results': 'Search results', 'backlinks_mode': 'Backlinks', - 'all_notes_mode': 'All notes', + 'all_notes_mode': 'All files', // Sort Options 'sort_name_asc': 'Name (A → Z)', 'sort_name_desc': 'Name (Z → A)', - 'sort_mtime_desc': 'Modified time (New → Old)', - 'sort_mtime_asc': 'Modified time (Old → New)', - 'sort_ctime_desc': 'Created time (New → Old)', - 'sort_ctime_asc': 'Created time (Old → New)', + 'sort_mtime_desc': 'Modified (New → Old)', + 'sort_mtime_asc': 'Modified (Old → New)', + 'sort_ctime_desc': 'Created (New → Old)', + 'sort_ctime_asc': 'Created (Old → New)', 'sort_random': 'Random', // Settings 'grid_view_settings': 'Grid view settings', + 'media_files_settings': 'Media files settings', + 'show_media_files': 'Show images and videos', + 'show_media_files_desc': 'Display image and video files in the grid view', + 'search_media_files': 'Search images and videos', + 'search_media_files_desc': 'Include image and video files in search results (only effective when show images and videos is enabled)', + 'show_video_thumbnails': 'Show video thumbnails', + 'show_video_thumbnails_desc': 'Display thumbnails for videos in the grid view, shows a play icon when disabled', 'ignored_folders': 'Ignored folders', - 'ignored_folders_desc': 'Set folders to be ignored.', + 'ignored_folders_desc': 'Set folders to ignore here', 'add_ignored_folder': 'Add ignored folder', 'no_ignored_folders': 'No ignored folders.', + 'ignored_folder_patterns': 'Ignore folders and files by pattern', + 'ignored_folder_patterns_desc': 'Use string patterns to ignore folders and files (supports regular expressions)', + 'add_ignored_folder_pattern': 'Add folder pattern', + 'ignored_folder_pattern_placeholder': 'Enter folder name or regex pattern', + 'no_ignored_folder_patterns': 'No ignored folder patterns.', 'remove': 'Remove', 'default_sort_type': 'Default sort type', 'default_sort_type_desc': 'Set the default sorting method when opening Grid View', @@ -133,14 +160,15 @@ export const TRANSLATIONS: Translations = { 'enable_file_watcher': 'Enable file watcher', 'enable_file_watcher_desc': 'When enabled, the view will automatically update when files change. If disabled, you need to click the refresh button manually', - // Folder Selection Dialog + // Select Folder Dialog 'select_folders': 'Select folder', 'open_grid_view': 'Open grid view', 'open_in_grid_view': 'Open in grid view', - 'delete_note': 'Delete note', + 'open_settings': 'Open settings', + 'delete_note': 'Delete file', 'open_in_new_tab': 'Open in new tab', 'searching': 'Searching...', - 'no_files': 'No notes found', + 'no_files': 'No files found', 'filter_folders': 'Filter folders...', }, 'zh': { @@ -151,14 +179,15 @@ export const TRANSLATIONS: Translations = { 'sorting': '排序方式', 'refresh': '刷新', 'reselect_folder': '重新选择位置', - 'go_up': '回上层文件夹', + 'go_up': '返回上层文件夹', 'no_backlinks': '没有反向链接', 'search': '搜索', - 'search_placeholder': '搜索关键字', + 'search_placeholder': '搜索关键词', 'cancel': '取消', - 'new_note': '新增笔记', + 'new_note': '新建笔记', 'untitled': '未命名', - 'notes': '個笔记', + 'files': '个文件', + 'add': '添加', // 视图标题 'grid_view_title': '网格视图', @@ -166,43 +195,56 @@ export const TRANSLATIONS: Translations = { 'folder_mode': '文件夹', 'search_results': '搜索结果', 'backlinks_mode': '反向链接', - 'all_notes_mode': '所有笔记', + 'all_notes_mode': '所有文件', // 排序选项 'sort_name_asc': '名称 (A → Z)', 'sort_name_desc': '名称 (Z → A)', 'sort_mtime_desc': '修改时间 (新 → 旧)', 'sort_mtime_asc': '修改时间 (旧 → 新)', - 'sort_ctime_desc': '建立时间 (新 → 旧)', - 'sort_ctime_asc': '建立时间 (旧 → 新)', + 'sort_ctime_desc': '创建时间 (新 → 旧)', + 'sort_ctime_asc': '创建时间 (旧 → 新)', 'sort_random': '随机排序', // 设置 'grid_view_settings': '网格视图设置', + 'media_files_settings': '媒体文件设置', + 'show_media_files': '显示图片和视频', + 'show_media_files_desc': '在网格视图中显示图片和视频文件', + 'search_media_files': '搜索图片和视频', + 'search_media_files_desc': '在搜索结果中包含图片和视频文件(仅在启用显示图片和视频时有效)', + 'show_video_thumbnails': '显示视频缩图', + 'show_video_thumbnails_desc': '在网格视图中显示视频的缩图,关闭时将显示播放图标', 'ignored_folders': '忽略的文件夹', 'ignored_folders_desc': '在这里设置要忽略的文件夹', - 'add_ignored_folder': '新增忽略資料夾', - 'no_ignored_folders': '沒有忽略的資料夾。', + 'add_ignored_folder': '添加忽略文件夹', + 'no_ignored_folders': '没有忽略的文件夹。', + 'ignored_folder_patterns': '以字符串忽略文件夹和文件', + 'ignored_folder_patterns_desc': '使用字符串模式忽略文件夹和文件(支持正则表达式)', + 'add_ignored_folder_pattern': '添加忽略文件夹模式', + 'ignored_folder_pattern_placeholder': '输入文件夹名称或正则表达式', + 'no_ignored_folder_patterns': '没有忽略的文件夹模式。', 'remove': '移除', - 'default_sort_type': '預設排序模式', - 'default_sort_type_desc': '设置开启网格视图时的預设排序模式', + 'default_sort_type': '默认排序模式', + 'default_sort_type_desc': '设置打开网格视图时的默认排序模式', 'grid_item_width': '网格项目宽度', 'grid_item_width_desc': '设置网格项目的宽度', - 'image_area_width': '圖片區域寬度', - 'image_area_width_desc': '设置圖片預覽區域的寬度', - 'image_area_height': '圖片區域高度', - 'image_area_height_desc': '设置圖片預覽區域的高度', + 'image_area_width': '图片区域宽度', + 'image_area_width_desc': '设置图片预览区域的宽度', + 'image_area_height': '图片区域高度', + 'image_area_height_desc': '设置图片预览区域的高度', 'enable_file_watcher': '启用文件监控', 'enable_file_watcher_desc': '启用后会自动检测文件变更并更新视图,关闭后需手动点击刷新按钮', - // 选择资料夹对话框 + // 选择文件夹对话框 'select_folders': '选择文件夹', 'open_grid_view': '开启网格视图', 'open_in_grid_view': '在网格视图中开启', - 'delete_note': '删除笔记', + 'open_settings': '开启设置', + 'delete_note': '删除文件', 'open_in_new_tab': '在新标签页打开', 'searching': '搜索中...', - 'no_files': '没有找到任何笔记', + 'no_files': '没有找到任何文件', 'filter_folders': '筛选文件夹...', }, 'ja': { @@ -210,17 +252,18 @@ export const TRANSLATIONS: Translations = { 'bookmarks_plugin_disabled': 'ブックマークプラグインを有効にしてください', // ボタンとラベル - 'sorting': 'ソート', - 'refresh': 'リフレッシュ', - 'reselect_folder': 'フォルダを再選択', - 'go_up': '上へ', - 'no_backlinks': 'バックリンクはありません', + 'sorting': '並び替え', + 'refresh': '更新', + 'reselect_folder': '場所を再選択', + 'go_up': '上のフォルダへ', + 'no_backlinks': 'バックリンクなし', 'search': '検索', - 'search_placeholder': '検索キーワード', + 'search_placeholder': 'キーワード検索', 'cancel': 'キャンセル', 'new_note': '新規ノート', - 'untitled': '無題のファイル', - 'notes': 'ファイル', + 'untitled': '無題', + 'files': 'ファイル', + 'add': '追加', // ビュータイトル 'grid_view_title': 'グリッドビュー', @@ -228,43 +271,56 @@ export const TRANSLATIONS: Translations = { 'folder_mode': 'フォルダ', 'search_results': '検索結果', 'backlinks_mode': 'バックリンク', - 'all_notes_mode': 'すべてのノート', + 'all_notes_mode': 'すべてのファイル', - // ソートオプション + // 並べ替えオプション 'sort_name_asc': '名前 (A → Z)', 'sort_name_desc': '名前 (Z → A)', - 'sort_mtime_desc': '変更時間 (新 → 旧)', - 'sort_mtime_asc': '変更時間 (旧 → 新)', - 'sort_ctime_desc': '作成時間 (新 → 旧)', - 'sort_ctime_asc': '作成時間 (旧 → 新)', + 'sort_mtime_desc': '更新日時 (新 → 旧)', + 'sort_mtime_asc': '更新日時 (旧 → 新)', + 'sort_ctime_desc': '作成日時 (新 → 旧)', + 'sort_ctime_asc': '作成日時 (旧 → 新)', 'sort_random': 'ランダム', // 設定 - 'grid_view_settings': 'グリッドビューセッティング', + 'grid_view_settings': 'グリッドビュー設定', + 'media_files_settings': 'メディアファイル設定', + 'show_media_files': '画像と動画を表示', + 'show_media_files_desc': 'グリッドビューに画像と動画ファイルを表示する', + 'search_media_files': '画像と動画を検索', + 'search_media_files_desc': '検索結果に画像と動画ファイルを含める(画像と動画を表示が有効な場合のみ有効)', + 'show_video_thumbnails': '動画サムネイルを表示', + 'show_video_thumbnails_desc': 'グリッドビューに動画のサムネイルを表示する、無効にすると再生アイコンが表示される', 'ignored_folders': '無視するフォルダ', - 'ignored_folders_desc': '無視するフォルダを設定します', + 'ignored_folders_desc': '無視するフォルダをここで設定します', 'add_ignored_folder': '無視するフォルダを追加', - 'no_ignored_folders': '無視するフォルダはありません', + 'no_ignored_folders': '無視するフォルダはありません。', + 'ignored_folder_patterns': 'パターンでフォルダとファイルを無視', + 'ignored_folder_patterns_desc': '文字列パターンを使用してフォルダとファイルを無視します(正規表現をサポート)', + 'add_ignored_folder_pattern': 'フォルダパターンを追加', + 'ignored_folder_pattern_placeholder': 'フォルダ名または正規表現を入力', + 'no_ignored_folder_patterns': '無視するフォルダパターンはありません。', 'remove': '削除', - 'default_sort_type': 'デフォルトのソートタイプ', - 'default_sort_type_desc': 'グリッドビューを開くときのデフォルトのソート方法を設定します', + 'default_sort_type': 'デフォルトの並び替え', + 'default_sort_type_desc': 'グリッドビューを開いたときのデフォルトの並び替えを設定', 'grid_item_width': 'グリッドアイテムの幅', - 'grid_item_width_desc': 'グリッドアイテムの幅を設定します', - 'image_area_width': '画像エリア幅', - 'image_area_width_desc': '画像プレビュー領域の幅を設定します', - 'image_area_height': '画像エリア高さ', - 'image_area_height_desc': '画像プレビュー領域の高さを設定します', - 'enable_file_watcher': 'ファイルウォッチャーを有効にする', - 'enable_file_watcher_desc': '有効にすると、ファイルの変更を自動的に検知してビューを更新します。無効にすると、手動でリフレッシュボタンをクリックする必要があります', + 'grid_item_width_desc': 'グリッドアイテムの幅を設定', + 'image_area_width': '画像エリアの幅', + 'image_area_width_desc': '画像プレビューエリアの幅を設定', + 'image_area_height': '画像エリアの高さ', + 'image_area_height_desc': '画像プレビューエリアの高さを設定', + 'enable_file_watcher': 'ファイル監視を有効にする', + 'enable_file_watcher_desc': '有効にすると、ファイルの変更を自動的に検出してビューを更新します。無効にすると、手動で更新ボタンをクリックする必要があります', // フォルダ選択ダイアログ 'select_folders': 'フォルダを選択', 'open_grid_view': 'グリッドビューを開く', 'open_in_grid_view': 'グリッドビューで開く', - 'delete_note': 'ノートを削除', + 'open_settings': '設定を開く', + 'delete_note': 'ファイルを削除', 'open_in_new_tab': '新しいタブで開く', 'searching': '検索中...', - 'no_files': 'ノートが見つかりません', + 'no_files': 'ファイルが見つかりません', 'filter_folders': 'フォルダをフィルタリング...', }, }; \ No newline at end of file diff --git a/styles.css b/styles.css index 2acb135..90a351b 100644 --- a/styles.css +++ b/styles.css @@ -479,6 +479,16 @@ background-color: var(--interactive-hover); } +/* 忽略資料夾樣式 */ +.ignored-folder-patterns-container { + margin-bottom: 16px; +} + +.ge-ignored-folder-patterns-list { + list-style: none; + padding: 0; +} + /* 影片縮圖樣式 */ .ge-video-thumbnail { width: 100%; diff --git a/versions.json b/versions.json index b051299..8976fbb 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "1.8.1": "1.1.0" + "1.8.2": "1.1.0" } \ No newline at end of file