mirror of
https://github.com/devon22/obsidian-gridexplorer.git
synced 2026-07-22 05:38:16 +00:00
1.8.2
This commit is contained in:
parent
933848439f
commit
7903d6f1de
14 changed files with 896 additions and 245 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -19,4 +19,7 @@ main.js
|
|||
data.json
|
||||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
*.bat
|
||||
Changelog.md
|
||||
5
main.ts
5
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
80
src/FileWatcher.ts
Normal file
80
src/FileWatcher.ts
Normal file
|
|
@ -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();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
289
src/GridView.ts
289
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;
|
||||
|
|
|
|||
311
src/MediaModal.ts
Normal file
311
src/MediaModal.ts
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
src/SearchModal.ts
Normal file
105
src/SearchModal.ts
Normal file
|
|
@ -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();
|
||||
}
|
||||
148
src/settings.ts
148
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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': 'フォルダをフィルタリング...',
|
||||
},
|
||||
};
|
||||
10
styles.css
10
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%;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"1.8.1": "1.1.0"
|
||||
"1.8.2": "1.1.0"
|
||||
}
|
||||
Loading…
Reference in a new issue