This commit is contained in:
Devon22 2025-03-10 00:36:27 +08:00
parent 7903d6f1de
commit 178bf42dc9
11 changed files with 206 additions and 25 deletions

View file

@ -1,7 +1,7 @@
{
"id": "gridexplorer",
"name": "GridExplorer",
"version": "1.8.2",
"version": "1.8.4",
"minAppVersion": "1.1.0",
"description": "Browse note files in a grid view.",
"author": "Devon22",

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "gridexplorer",
"version": "1.8.2",
"version": "1.8.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gridexplorer",
"version": "1.8.2",
"version": "1.8.4",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",

View file

@ -1,6 +1,6 @@
{
"name": "gridexplorer",
"version": "1.8.2",
"version": "1.8.4",
"description": "Browse note files in a grid view.",
"main": "main.js",
"scripts": {

View file

@ -20,6 +20,7 @@ export class FileWatcher {
return;
}
//新增檔案
this.plugin.registerEvent(
this.app.vault.on('create', (file) => {
if (file instanceof TFile) {
@ -35,6 +36,7 @@ export class FileWatcher {
})
);
//刪除檔案
this.plugin.registerEvent(
this.app.vault.on('delete', (file) => {
if (file instanceof TFile) {

View file

@ -1,4 +1,4 @@
import { App, WorkspaceLeaf, ItemView, Modal, TFolder, TFile, Menu, Notice, Setting} from 'obsidian';
import { WorkspaceLeaf, ItemView, TFolder, TFile, Menu, Notice } from 'obsidian';
import { setIcon, getFrontMatterInfo } from 'obsidian';
import { showFolderSelectionModal } from './FolderSelectionModal';
import { findFirstImageInNote } from './mediaUtils';
@ -15,6 +15,8 @@ export class GridView extends ItemView {
sourcePath: string;
sortType: string;
searchQuery: string;
searchAllFiles: boolean;
searchType: string;
selectedItemIndex: number = -1; // 當前選中的項目索引
gridItems: HTMLElement[] = []; // 存儲所有網格項目的引用
hasKeyboardFocus: boolean = false; // 是否有鍵盤焦點
@ -29,6 +31,7 @@ export class GridView extends ItemView {
this.sourcePath = ''; // 用於資料夾模式的路徑
this.sortType = this.plugin.settings.defaultSortType; // 使用設定中的預設排序模式
this.searchQuery = ''; // 搜尋關鍵字
this.searchAllFiles = true;
// 根據設定決定是否註冊檔案變更監聽器
if (this.plugin.settings.enableFileWatcher) {
@ -322,7 +325,7 @@ export class GridView extends ItemView {
}
// 添加回上層按鈕(僅在資料夾模式且不在根目錄時顯示)
if (this.sourceMode === 'folder' && this.sourcePath !== '/') {
if (this.sourceMode === 'folder' && this.sourcePath !== '/' && this.searchQuery === '') {
const upButton = headerButtonsDiv.createEl('button', { attr: { 'aria-label': t('go_up') } });
upButton.addEventListener('click', () => {
const parentPath = this.sourcePath.split('/').slice(0, -1).join('/') || '/';
@ -453,6 +456,7 @@ export class GridView extends ItemView {
clearButton.addEventListener('click', (e) => {
e.stopPropagation(); // 防止觸發搜尋文字的點擊事件
this.searchQuery = '';
this.searchAllFiles = true;
this.clearSelection();
this.render();
// 通知 Obsidian 保存視圖狀態
@ -493,9 +497,15 @@ export class GridView extends ItemView {
container.empty();
container.addClass('ge-grid-container');
container.style.setProperty('--grid-item-width', this.plugin.settings.gridItemWidth + 'px');
if (this.plugin.settings.gridItemHeight === 0) {
container.style.setProperty('--grid-item-height', '100%');
} else {
container.style.setProperty('--grid-item-height', this.plugin.settings.gridItemHeight + 'px');
}
container.style.setProperty('--image-area-width', this.plugin.settings.imageAreaWidth + 'px');
container.style.setProperty('--image-area-height', this.plugin.settings.imageAreaHeight + 'px');
container.style.setProperty('--title-font-size', this.plugin.settings.titleFontSize + 'em');
// 重置網格項目數組
this.gridItems = [];
@ -550,7 +560,7 @@ export class GridView extends ItemView {
return false;
}
}
return true;
})
.sort((a, b) => a.name.localeCompare(b.name));
@ -581,8 +591,13 @@ export class GridView extends ItemView {
loadingDiv.setText(t('searching'));
// 取得 vault 中所有支援的檔案
const allFiles = this.app.vault.getFiles();
let allFiles: TFile[] = [];
if (this.searchAllFiles) {
allFiles = this.app.vault.getFiles();
} else {
allFiles = await this.getFiles();
}
// 根據設定過濾檔案
const filteredFiles = allFiles.filter(file => {
// 非媒體檔案Markdown、PDF、Canvas始終包含
@ -648,14 +663,22 @@ export class GridView extends ItemView {
if (!(file instanceof TFile)) return;
// 載入預覽內容
let imageUrl: string | null = '';
const contentArea = fileEl.querySelector('.ge-content-area') as Element;
if (!contentArea.hasAttribute('data-loaded')) {
// 根據檔案類型處理
if (file.extension === 'md') {
let summaryLength = this.plugin.settings.summaryLength;
if (summaryLength < 50) {
summaryLength = 100;
this.plugin.settings.summaryLength = 100;
this.plugin.saveSettings();
}
// Markdown 檔案顯示內容預覽
const content = await this.app.vault.cachedRead(file);
const frontMatterInfo = getFrontMatterInfo(content);
const contentWithoutFrontmatter = content.substring(frontMatterInfo.contentStart).slice(0, 500);
const contentWithoutFrontmatter = content.substring(frontMatterInfo.contentStart).slice(0, summaryLength+summaryLength);
let contentWithoutMediaLinks = contentWithoutFrontmatter.replace(/```[\s\S]*?```|<!--[\s\S]*?-->|!?(?:\[[^\]]*\]\([^)]+\)|\[\[[^\]]+\]\])/g, '').trim();
//把開頭的標題整行刪除
@ -664,10 +687,12 @@ export class GridView extends ItemView {
}
// 只取前100個字符作為預覽
const preview = contentWithoutMediaLinks.slice(0, 100) + (contentWithoutMediaLinks.length > 100 ? '...' : '');
const preview = contentWithoutMediaLinks.slice(0, summaryLength) + (contentWithoutMediaLinks.length > summaryLength ? '...' : '');
// 創建預覽內容
const contentEl = contentArea.createEl('p', { text: preview.trim() });
imageUrl = await findFirstImageInNote(this.app, content);
} else {
// 其他檔案顯示副檔名
const contentEl = contentArea.createEl('p', { text: file.extension.toUpperCase() });
@ -700,7 +725,6 @@ export class GridView extends ItemView {
imageArea.setAttribute('data-loaded', 'true');
} else if (file.extension === 'md') {
// Markdown 檔案尋找內部圖片
const imageUrl = await findFirstImageInNote(this.app, file);
if (imageUrl) {
const img = imageArea.createEl('img');
img.src = imageUrl;
@ -1081,7 +1105,8 @@ export class GridView extends ItemView {
sourceMode: this.sourceMode,
sourcePath: this.sourcePath,
sortType: this.sortType,
searchQuery: this.searchQuery
searchQuery: this.searchQuery,
searchAllFiles: this.searchAllFiles
}
};
}
@ -1093,6 +1118,7 @@ export class GridView extends ItemView {
this.sourcePath = state.state.sourcePath || null;
this.sortType = state.state.sortType || 'mtime-desc';
this.searchQuery = state.state.searchQuery || '';
this.searchAllFiles = state.state.searchAllFiles ?? true;
this.render();
}
}

View file

@ -49,6 +49,31 @@ export class SearchModal extends Modal {
searchInput.focus();
});
// 創建搜尋範圍設定
const searchScopeContainer = contentEl.createDiv('ge-search-scope-container');
const searchScopeCheckbox = searchScopeContainer.createEl('input', {
type: 'checkbox',
cls: 'ge-search-scope-checkbox'
});
searchScopeCheckbox.checked = !this.gridView.searchAllFiles;
const searchScopeLabel = searchScopeContainer.createEl('span', {
text: t('search_current_location_only'),
cls: 'ge-search-scope-label'
});
// 點擊容器時切換勾選框狀態
searchScopeContainer.addEventListener('click', (e) => {
if (e.target !== searchScopeCheckbox) {
searchScopeCheckbox.checked = !searchScopeCheckbox.checked;
this.gridView.searchAllFiles = !searchScopeCheckbox.checked;
}
});
// 勾選框變更時更新搜尋範圍
searchScopeCheckbox.addEventListener('change', () => {
this.gridView.searchAllFiles = !searchScopeCheckbox.checked;
});
// 創建按鈕容器
const buttonContainer = contentEl.createDiv('ge-button-container');
@ -65,6 +90,7 @@ export class SearchModal extends Modal {
// 綁定搜尋事件
const performSearch = () => {
this.gridView.searchQuery = searchInput.value;
this.gridView.searchAllFiles = !searchScopeCheckbox.checked;
this.gridView.clearSelection();
this.gridView.render();
// 通知 Obsidian 保存視圖狀態

View file

@ -1,10 +1,9 @@
import { App, TFile } from 'obsidian';
// 尋找筆記中的第一張圖片
export async function findFirstImageInNote(app: App, file: TFile) {
export async function findFirstImageInNote(app: App, content: string) {
try {
const content = await app.vault.cachedRead(file);
const internalMatch = content.match(/(?:!\[\[(.*?\.(?:jpg|jpeg|png|gif|webp))(?:\|.*?)?\]\]|!\[(.*?)\]\(\s*(\S+?(?:\.(?:jpg|jpeg|png|gif|webp)|format=(?:jpg|jpeg|png|gif|webp))[^\s)]*)\s*(?:\s+["'][^"']*["'])?\s*\))/gi);
const internalMatch = content.match(/(?:!\[\[(.*?\.(?:jpg|jpeg|png|gif|webp))(?:\|.*?)?\]\]|!\[(.*?)\]\(\s*(\S+?(?:\.(?:jpg|jpeg|png|gif|webp)|format=(?:jpg|jpeg|png|gif|webp))[^\s)]*)\s*(?:\s+["'][^"']*["'])?\s*\))/i);
if (internalMatch) {
return processMediaLink(app, internalMatch[0]);
} else {

View file

@ -1,4 +1,4 @@
import { App, PluginSettingTab, Setting } from 'obsidian';
import { App, PluginSettingTab, Setting, Notice } from 'obsidian';
import { t } from './translations';
import GridExplorerPlugin from '../main';
@ -7,8 +7,11 @@ export interface GallerySettings {
ignoredFolderPatterns: string[];
defaultSortType: string;
gridItemWidth: number;
gridItemHeight: number;
imageAreaWidth: number;
imageAreaHeight: number;
titleFontSize: number;
summaryLength: number;
enableFileWatcher: boolean;
showMediaFiles: boolean;
searchMediaFiles: boolean;
@ -21,8 +24,11 @@ export const DEFAULT_SETTINGS: GallerySettings = {
ignoredFolderPatterns: [], // 預設以字串忽略的資料夾模式
defaultSortType: 'mtime-desc', // 預設排序模式:修改時間倒序
gridItemWidth: 300, // 網格項目寬度,預設 300
gridItemHeight: 0, // 網格項目高度,預設 0
imageAreaWidth: 100, // 圖片區域寬度,預設 100
imageAreaHeight: 100, // 圖片區域高度,預設 100
titleFontSize: 1.1, // 筆記標題的字型大小,預設 1.1
summaryLength: 100, // 筆記摘要的字數,預設 100
enableFileWatcher: true, // 預設啟用檔案監控
showMediaFiles: false, // 預設顯示圖片和影片
searchMediaFiles: false, // 預設搜尋時也包含圖片和影片
@ -42,6 +48,19 @@ export class GridExplorerSettingTab extends PluginSettingTab {
const { containerEl } = this;
containerEl.empty();
// 回復預設值按鈕
new Setting(containerEl)
.setName(t('reset_to_default'))
.setDesc(t('reset_to_default_desc'))
.addButton(button => button
.setButtonText(t('reset'))
.onClick(async () => {
this.plugin.settings = { ...DEFAULT_SETTINGS };
await this.plugin.saveSettings();
this.display();
new Notice(t('settings_reset_notice'));
}));
// 媒體檔案設定區域
containerEl.createEl('h3', { text: t('media_files_settings') });
@ -133,7 +152,7 @@ export class GridExplorerSettingTab extends PluginSettingTab {
.setDesc(t('grid_item_width_desc'))
.addSlider(slider => {
slider
.setLimits(200, 600, 50)
.setLimits(200, 600, 10)
.setValue(this.plugin.settings.gridItemWidth)
.setDynamicTooltip()
.onChange(async (value) => {
@ -142,6 +161,21 @@ export class GridExplorerSettingTab extends PluginSettingTab {
});
});
// 網格項目高度設定
new Setting(containerEl)
.setName(t('grid_item_height'))
.setDesc(t('grid_item_height_desc'))
.addSlider(slider => {
slider
.setLimits(0, 600, 10)
.setValue(this.plugin.settings.gridItemHeight)
.setDynamicTooltip()
.onChange(async (value) => {
this.plugin.settings.gridItemHeight = value;
await this.plugin.saveSettings();
});
});
// 圖片區域寬度設定
new Setting(containerEl)
.setName(t('image_area_width'))
@ -172,6 +206,37 @@ export class GridExplorerSettingTab extends PluginSettingTab {
});
});
//筆記標題的字型大小
new Setting(containerEl)
.setName(t('title_font_size'))
.setDesc(t('title_font_size_desc'))
.addSlider(slider => {
slider
.setLimits(1, 1.5, 0.05)
.setValue(this.plugin.settings.titleFontSize)
.setDynamicTooltip()
.onChange(async (value) => {
this.plugin.settings.titleFontSize = value;
await this.plugin.saveSettings();
});
});
// 筆記摘要的字數設定
new Setting(containerEl)
.setName(t('summary_length'))
.setDesc(t('summary_length_desc'))
.addSlider(slider => {
slider
.setLimits(50, 600, 50)
.setValue(this.plugin.settings.summaryLength)
.setDynamicTooltip()
.onChange(async (value) => {
this.plugin.settings.summaryLength = value;
await this.plugin.saveSettings();
});
});
// 忽略的資料夾設定
const ignoredFoldersContainer = containerEl.createDiv('ignored-folders-container');

View file

@ -31,6 +31,7 @@ export const TRANSLATIONS: Translations = {
'no_backlinks': '沒有反向連結',
'search': '搜尋',
'search_placeholder': '搜尋關鍵字',
'search_current_location_only': '僅搜尋目前位置',
'cancel': '取消',
'new_note': '新增筆記',
'untitled': '未命名',
@ -77,12 +78,21 @@ export const TRANSLATIONS: Translations = {
'default_sort_type_desc': '設定開啟網格視圖時的預設排序模式',
'grid_item_width': '網格項目寬度',
'grid_item_width_desc': '設定網格項目的寬度',
'grid_item_height': '網格項目高度',
'grid_item_height_desc': '設定網格項目的高度 (設為0時為自動調整)',
'image_area_width': '圖片區域寬度',
'image_area_width_desc': '設定圖片預覽區域的寬度',
'image_area_height': '圖片區域高度',
'image_area_height_desc': '設定圖片預覽區域的高度',
'title_font_size': '標題字體大小',
'title_font_size_desc': '設定標題字體的大小',
'summary_length': '摘要長度',
'summary_length_desc': '設定摘要的長度',
'enable_file_watcher': '啟用檔案監控',
'enable_file_watcher_desc': '啟用後會自動偵測檔案變更並更新視圖,關閉後需手動點擊重新整理按鈕',
'reset_to_default' : '重置為預設值',
'reset_to_default_desc': '將所有設定重置為預設值',
'settings_reset_notice': '設定值已重置為預設值',
// 選擇資料夾對話框
'select_folders': '選擇資料夾',
@ -107,6 +117,7 @@ export const TRANSLATIONS: Translations = {
'no_backlinks': 'No backlinks',
'search': 'Search',
'search_placeholder': 'Search keyword',
'search_current_location_only': 'Search current location only',
'cancel': 'Cancel',
'new_note': 'New note',
'untitled': 'Untitled',
@ -153,12 +164,22 @@ export const TRANSLATIONS: Translations = {
'default_sort_type_desc': 'Set the default sorting method when opening Grid View',
'grid_item_width': 'Grid item width',
'grid_item_width_desc': 'Set the width of grid items',
'grid_item_height': 'Grid item height',
'grid_item_height_desc': 'Set the height of grid items (set to 0 to automatically adjust)',
'image_area_width': 'Image area width',
'image_area_width_desc': 'Set the width of the image preview area',
'image_area_height': 'Image area height',
'image_area_height_desc': 'Set the height of the image preview area',
'title_font_size': 'Title font size',
'title_font_size_desc': 'Set the size of the title font',
'summary_length': 'Summary length',
'summary_length_desc': 'Set the length of the summary',
'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',
'reset_to_default': 'Reset to default',
'reset_to_default_desc': 'Reset all settings to default values',
'settings_reset_notice': 'Settings have been reset to default values',
// Select Folder Dialog
'select_folders': 'Select folder',
@ -182,7 +203,8 @@ export const TRANSLATIONS: Translations = {
'go_up': '返回上层文件夹',
'no_backlinks': '没有反向链接',
'search': '搜索',
'search_placeholder': '搜索关键词',
'search_placeholder': '搜索关键字',
'search_current_location_only': '仅搜索当前位置',
'cancel': '取消',
'new_note': '新建笔记',
'untitled': '未命名',
@ -229,12 +251,21 @@ export const TRANSLATIONS: Translations = {
'default_sort_type_desc': '设置打开网格视图时的默认排序模式',
'grid_item_width': '网格项目宽度',
'grid_item_width_desc': '设置网格项目的宽度',
'grid_item_height': '网格项目高度',
'grid_item_height_desc': '设置网格项目的高度设置为0时将自动调整',
'image_area_width': '图片区域宽度',
'image_area_width_desc': '设置图片预览区域的宽度',
'image_area_height': '图片区域高度',
'image_area_height_desc': '设置图片预览区域的高度',
'title_font_size': '标题字体大小',
'title_font_size_desc': '设置标题字体的大小',
'summary_length': '摘要长度',
'summary_length_desc': '设置摘要的长度',
'enable_file_watcher': '启用文件监控',
'enable_file_watcher_desc': '启用后会自动检测文件变更并更新视图,关闭后需手动点击刷新按钮',
'reset_to_default': '重置为默认',
'reset_to_default_desc': '重置所有设置为默认值',
'settings_reset_notice': '设置值已重置为默认值',
// 选择文件夹对话框
'select_folders': '选择文件夹',
@ -258,7 +289,8 @@ export const TRANSLATIONS: Translations = {
'go_up': '上のフォルダへ',
'no_backlinks': 'バックリンクなし',
'search': '検索',
'search_placeholder': 'キーワード検索',
'search_placeholder': '検索キーワード',
'search_current_location_only': '現在の場所のみ検索',
'cancel': 'キャンセル',
'new_note': '新規ノート',
'untitled': '無題',
@ -305,12 +337,21 @@ export const TRANSLATIONS: Translations = {
'default_sort_type_desc': 'グリッドビューを開いたときのデフォルトの並び替えを設定',
'grid_item_width': 'グリッドアイテムの幅',
'grid_item_width_desc': 'グリッドアイテムの幅を設定',
'grid_item_height': 'グリッドアイテムの高さ',
'grid_item_height_desc': 'グリッドアイテムの高さを設定0を設定すると自動調整されます',
'image_area_width': '画像エリアの幅',
'image_area_width_desc': '画像プレビューエリアの幅を設定',
'image_area_height': '画像エリアの高さ',
'image_area_height_desc': '画像プレビューエリアの高さを設定',
'title_font_size': 'タイトルフォントサイズ',
'title_font_size_desc': 'タイトルフォントのサイズを設定',
'summary_length': '要約の長さ',
'summary_length_desc': '要約の長さを設定',
'enable_file_watcher': 'ファイル監視を有効にする',
'enable_file_watcher_desc': '有効にすると、ファイルの変更を自動的に検出してビューを更新します。無効にすると、手動で更新ボタンをクリックする必要があります',
'reset_to_default': 'デフォルトに戻す',
'reset_to_default_desc': 'すべての設定をデフォルト値に戻',
'settings_reset_notice': '設定値がデフォルト値にリセットされました',
// フォルダ選択ダイアログ
'select_folders': 'フォルダを選択',

View file

@ -43,7 +43,7 @@
transition: transform 0.2s, box-shadow 0.2s;
display: flex;
gap: 14px;
height: 100%;
height: var(--grid-item-height);
}
.ge-grid-item:hover {
@ -95,7 +95,7 @@
.ge-title {
margin: 0 0 0 3px;
font-size: var(--h5-size);
font-size: var(--title-font-size);
color: var(--text-normal);
overflow: hidden;
text-overflow: ellipsis;
@ -113,6 +113,7 @@
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
height: 100%;
}
.ge-content-area p:empty {
@ -146,6 +147,7 @@
padding: 7px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: all 0.2s ease;
height: 100%;
}
.ge-grid-item.ge-folder-item:hover {
@ -230,7 +232,7 @@
/* 搜尋對話框樣式 */
.ge-search-container {
margin-bottom: 16px;
margin-bottom: 8px;
position: relative;
display: flex;
align-items: center;
@ -257,6 +259,25 @@
color: var(--text-muted);
}
/* 搜尋範圍設定樣式 */
.ge-search-scope-container {
display: flex;
align-items: center;
margin: 4px 0;
cursor: pointer;
padding: 4px;
}
.ge-search-scope-checkbox {
cursor: pointer;
}
.ge-search-scope-label {
cursor: pointer;
user-select: none;
color: var(--text-normal);
}
.ge-button-container {
display: flex;
gap: 8px;
@ -656,3 +677,4 @@
border: 2px dashed var(--interactive-accent);
transform: scale(1.05);
}

View file

@ -1,3 +1,3 @@
{
"1.8.2": "1.1.0"
"1.8.4": "1.1.0"
}