This commit is contained in:
Devon22 2025-04-13 16:08:46 +08:00
parent 0d78177e79
commit 9941dad5fc
10 changed files with 614 additions and 306 deletions

View file

@ -1,7 +1,7 @@
{
"id": "gridexplorer",
"name": "GridExplorer",
"version": "1.9.11",
"version": "1.9.12",
"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.9.11",
"version": "1.9.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gridexplorer",
"version": "1.9.11",
"version": "1.9.12",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",

View file

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

View file

@ -532,43 +532,41 @@ export class GridView extends ItemView {
setIcon(sortButton, 'arrow-up-narrow-wide');
}
if (this.sourceMode !== 'random-note') {
// 添加搜尋按鈕
const searchButtonContainer = headerButtonsDiv.createDiv('ge-search-button-container');
const searchButton = searchButtonContainer.createEl('button', {
cls: 'search-button',
attr: { 'aria-label': t('search') }
});
setIcon(searchButton, 'search');
searchButton.addEventListener('click', () => {
showSearchModal(this.app, this, '');
// 添加搜尋按鈕
const searchButtonContainer = headerButtonsDiv.createDiv('ge-search-button-container');
const searchButton = searchButtonContainer.createEl('button', {
cls: 'search-button',
attr: { 'aria-label': t('search') }
});
setIcon(searchButton, 'search');
searchButton.addEventListener('click', () => {
showSearchModal(this.app, this, '');
});
// 如果有搜尋關鍵字,顯示搜尋文字和取消按鈕
if (this.searchQuery) {
searchButton.style.display = 'none';
const searchTextContainer = searchButtonContainer.createDiv('ge-search-text-container');
// 創建搜尋文字
const searchText = searchTextContainer.createEl('span', { cls: 'ge-search-text', text: this.searchQuery });
// 讓搜尋文字可點選
searchText.style.cursor = 'pointer';
searchText.addEventListener('click', () => {
showSearchModal(this.app, this, this.searchQuery);
});
// 如果有搜尋關鍵字,顯示搜尋文字和取消按鈕
if (this.searchQuery) {
searchButton.style.display = 'none';
const searchTextContainer = searchButtonContainer.createDiv('ge-search-text-container');
// 創建搜尋文字
const searchText = searchTextContainer.createEl('span', { cls: 'ge-search-text', text: this.searchQuery });
// 讓搜尋文字可點選
searchText.style.cursor = 'pointer';
searchText.addEventListener('click', () => {
showSearchModal(this.app, this, this.searchQuery);
});
// 創建取消按鈕
const clearButton = searchTextContainer.createDiv('ge-clear-button');
setIcon(clearButton, 'x');
clearButton.addEventListener('click', (e) => {
e.stopPropagation(); // 防止觸發搜尋文字的點擊事件
this.searchQuery = '';
this.clearSelection();
this.render();
// 通知 Obsidian 保存視圖狀態
this.app.workspace.requestSaveLayout();
});
}
// 創建取消按鈕
const clearButton = searchTextContainer.createDiv('ge-clear-button');
setIcon(clearButton, 'x');
clearButton.addEventListener('click', (e) => {
e.stopPropagation(); // 防止觸發搜尋文字的點擊事件
this.searchQuery = '';
this.clearSelection();
this.render();
// 通知 Obsidian 保存視圖狀態
this.app.workspace.requestSaveLayout();
});
}
if ((this.sourceMode === 'all-files' || this.sourceMode === 'recent-files' || this.sourceMode === 'random-note') &&
@ -901,7 +899,9 @@ export class GridView extends ItemView {
}
let files: TFile[] = [];
if (this.searchQuery && this.sourceMode !== 'random-note') {
// 使用 Map 來記錄原始順序
let fileIndexMap = new Map<TFile, number>();
if (this.searchQuery) {
// 顯示搜尋中的提示
const loadingDiv = container.createDiv('ge-loading-indicator');
loadingDiv.setText(t('searching'));
@ -909,63 +909,101 @@ export class GridView extends ItemView {
// 取得 vault 中所有支援的檔案
let allFiles: TFile[] = [];
if (this.searchAllFiles) {
allFiles = this.app.vault.getFiles();
// 全部檔案
allFiles = this.app.vault.getFiles().filter(file =>
isDocumentFile(file) || (isMediaFile(file) && this.searchMediaFiles)
);
} else {
// 當前位置檔案
const randomNoteIncludeMedia = this.randomNoteIncludeMedia;
this.randomNoteIncludeMedia = this.searchMediaFiles;
allFiles = await getFiles(this);
this.randomNoteIncludeMedia = randomNoteIncludeMedia;
if (this.sourceMode === 'recent-files') {
// 搜尋"最近檔案"的當前位置時先作忽略檔案和只取前n筆
allFiles = ignoredFiles(allFiles, this).slice(0, this.plugin.settings.recentFilesCount);
} else if (this.sourceMode === 'bookmarks') {
allFiles = allFiles.filter(file =>
isDocumentFile(file) || (isMediaFile(file) && this.searchMediaFiles)
);
// 使用 Map 來記錄原始順序
allFiles.forEach((file, index) => {
fileIndexMap.set(file, index);
});
} else if (this.sourceMode === 'search') {
allFiles = allFiles.filter(file =>
isDocumentFile(file) || (isMediaFile(file) && this.searchMediaFiles)
);
}
}
// 根據設定過濾檔案
const filteredFiles = allFiles.filter(file => {
// 文件檔案始終包含
if (isDocumentFile(file)) {
return true;
}
// 媒體檔案根據 searchMediaFiles 設定決定是否包含
if (isMediaFile(file)) {
return this.searchMediaFiles;
}
return false;
});
// 根據搜尋關鍵字進行過濾(不分大小寫)
const lowerCaseSearchQuery = this.searchQuery.toLowerCase();
const searchTerms = this.searchQuery.toLowerCase().split(/\s+/).filter(term => term.trim() !== '');
// 使用 Promise.all 來非同步地讀取所有檔案內容
await Promise.all(
filteredFiles.map(async file => {
allFiles.map(async file => {
const fileName = file.name.toLowerCase();
if (fileName.includes(lowerCaseSearchQuery)) {
// 檢查檔案名稱是否包含所有搜尋字串
const matchesFileName = searchTerms.every(term => fileName.includes(term));
if (matchesFileName) {
files.push(file);
} else if (file.extension === 'md') {
// 只對 Markdown 檔案進行內容搜尋
const content = (await this.app.vault.cachedRead(file)).toLowerCase();
if (content.includes(lowerCaseSearchQuery)) {
// 檢查檔案內容是否包含所有搜尋字串
const matchesContent = searchTerms.every(term => content.includes(term));
if (matchesContent) {
files.push(file);
}
}
})
);
// 根據設定的排序方式排序檔案
files = sortFiles(files, this);
// 排序檔案
if (this.sourceMode === 'recent-files') {
// 臨時的排序類型
const sortType = this.sortType;
this.sortType = 'mtime-desc';
files = sortFiles(files, this);
this.sortType = sortType;
} else if (this.sourceMode === 'bookmarks') {
// 保持原始順序
files.sort((a, b) => {
const indexA = fileIndexMap.get(a) ?? Number.MAX_SAFE_INTEGER;
const indexB = fileIndexMap.get(b) ?? Number.MAX_SAFE_INTEGER;
return indexA - indexB;
});
} else if (this.sourceMode === 'random-note') {
const sortType = this.sortType;
this.sortType = 'random';
files = sortFiles(files, this);
this.sortType = sortType;
} else {
files = sortFiles(files, this);
}
// 忽略檔案
files = ignoredFiles(files, this);
// 移除搜尋中的提示
loadingDiv.remove();
} else {
// 獲取檔案列表並根據搜尋關鍵字過濾
// 無搜尋關鍵字的情況
files = await getFiles(this);
}
//忽略檔案
files = ignoredFiles(files, this)
// 忽略檔案
files = ignoredFiles(files, this)
//最近檔案模式只取前30筆
if (this.sourceMode === 'recent-files') {
files = files.slice(0, this.plugin.settings.recentFilesCount);
}
// 最近檔案模式只取前n
if (this.sourceMode === 'recent-files') {
files = files.slice(0, this.plugin.settings.recentFilesCount);
}
// 隨機筆記模式只取前10筆
if (this.sourceMode === 'random-note') {
files = files.slice(0, this.plugin.settings.randomNoteCount);
// 隨機筆記模式只取前n筆
if (this.sourceMode === 'random-note') {
files = files.slice(0, this.plugin.settings.randomNoteCount);
}
}
// 如果沒有檔案,顯示提示訊息
@ -1048,6 +1086,78 @@ export class GridView extends ItemView {
// 其他檔案顯示副檔名
contentArea.createEl('p', { text: file.extension.toUpperCase() });
}
// 顯示標籤(僅限 Markdown 檔案)
if (file.extension === 'md' && this.plugin.settings.showNoteTags) {
const fileCache = this.app.metadataCache.getFileCache(file);
const allTags = new Set<string>();
// 從 frontmatter 獲取標籤
let frontmatterTags = fileCache?.frontmatter?.tags || [];
// 處理不同的標籤格式
if (typeof frontmatterTags === 'string') {
// 如果是字符串,按逗號或空格分割
frontmatterTags.split(/[,\s]+/).filter(tag => tag.trim() !== '')
.forEach(tag => allTags.add(tag));
} else if (Array.isArray(frontmatterTags)) {
frontmatterTags.forEach(tag => {
// 處理陣列中的每個標籤,可能是字符串或包含空格的字符串
if (typeof tag === 'string') {
// 檢查標籤是否包含空格(可能是未被正確分割的多個標籤)
if (tag.includes(' ')) {
// 按空格分割並添加每個子標籤
tag.split(/\s+/).filter(subTag => subTag.trim() !== '')
.forEach(subTag => allTags.add(subTag));
} else {
allTags.add(tag);
}
}
});
}
// 從檔案 cache 中獲取內文標籤
const cacheTags = fileCache?.tags || [];
cacheTags.forEach(tagObj => {
const tag = tagObj.tag.startsWith('#') ? tagObj.tag.substring(1) : tagObj.tag;
allTags.add(tag);
});
if (allTags.size > 0) {
// 創建標籤容器
const tagsContainer = contentArea.createDiv('ge-tags-container');
// 根據區塊寬度動態計算可顯示的標籤數量
const containerWidth = tagsContainer.getBoundingClientRect().width;
const tagWidth = 70;
const maxTags = Math.floor(containerWidth / tagWidth);
// 取得要顯示的標籤
const displayTags = Array.from(allTags).slice(0, maxTags);
displayTags.forEach(tag => {
const tagEl = tagsContainer.createEl('span', {
cls: 'ge-tag',
text: tag.startsWith('#') ? tag : `#${tag}`
});
// 添加點擊事件,點擊後設置搜尋關鍵字並重新渲染
tagEl.addEventListener('click', (e) => {
e.stopPropagation(); // 防止事件冒泡到卡片
const tagText = tag.startsWith('#') ? tag.substring(1) : tag;
if (this.searchQuery === tagText) {
return;
}
this.searchQuery = tagText;
this.searchAllFiles = true;
this.searchMediaFiles = false;
this.render(true);
return false;
});
});
}
}
contentArea.setAttribute('data-loaded', 'true');
}
@ -1099,66 +1209,167 @@ export class GridView extends ItemView {
});
// 顯示檔案
for (const file of files) {
const fileEl = container.createDiv('ge-grid-item');
this.gridItems.push(fileEl); // 添加到網格項目數組
fileEl.dataset.filePath = file.path;
// 創建左側內容區,包含圖示和標題
const contentArea = fileEl.createDiv('ge-content-area');
// 創建標題容器
const titleContainer = contentArea.createDiv('ge-title-container');
const extension = file.extension.toLowerCase();
if (files.length > 0) {
// 檢查是否應該顯示日期分隔器
const dateDividerMode = this.plugin.settings.dateDividerMode || 'none';
const sortType = this.folderSortType ? this.folderSortType : this.sortType;
const shouldShowDateDividers = dateDividerMode !== 'none' &&
(sortType.startsWith('mtime-') || sortType.startsWith('ctime-')) &&
this.sourceMode !== 'random-note' &&
this.sourceMode !== 'bookmarks';
// 添加檔案類型圖示
if (isImageFile(file)) {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-img');
setIcon(iconContainer, 'image');
} else if (isVideoFile(file)) {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-video');
setIcon(iconContainer, 'play-circle');
} else if (isAudioFile(file)) {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-audio');
setIcon(iconContainer, 'music');
} else if (extension === 'pdf') {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-pdf');
setIcon(iconContainer, 'paperclip');
} else if (extension === 'canvas') {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-canvas');
setIcon(iconContainer, 'layout-dashboard');
} else if (extension === 'md' || extension === 'txt') {
const iconContainer = titleContainer.createDiv('ge-icon-container');
setIcon(iconContainer, 'file-text');
} else {
const iconContainer = titleContainer.createDiv('ge-icon-container');
setIcon(iconContainer, 'file');
}
let lastDateString = '';
// 創建標題(立即載入)
const titleEl = titleContainer.createEl('span', { cls: 'ge-title', text: file.basename });
titleEl.setAttribute('title', file.basename);
// 創建圖片區域,但先不載入圖片
fileEl.createDiv('ge-image-area');
// 開始觀察這個元素
observer.observe(fileEl);
// 點擊時開啟檔案
fileEl.addEventListener('click', (event) => {
// 獲取項目索引
const index = this.gridItems.indexOf(fileEl);
if (index < 0) return;
// 處理多選邏輯
if (event.ctrlKey || event.metaKey) {
if (this.selectedItemIndex !== -1) {
// 如果已有選中狀態,則繼續選中
this.selectItem(index, true);
this.hasKeyboardFocus = true;
for (const file of files) {
// 如果啟用日期分隔器,檢查是否需要添加新的日期分隔器
if (shouldShowDateDividers) {
let timestamp = 0;
// 根據排序類型獲取日期時間戳
if (sortType.startsWith('mtime-') || sortType.startsWith('ctime-')) {
const isModifiedTime = sortType.startsWith('mtime-');
// 檢查是否是 Markdown 文件,且有設定對應的 frontmatter 字段
let frontMatterDate = null;
if (file.extension === 'md') {
const metadata = this.app.metadataCache.getFileCache(file);
if (metadata?.frontmatter) {
const fieldName = isModifiedTime
? this.plugin.settings.modifiedDateField
: this.plugin.settings.createdDateField;
if (fieldName && metadata.frontmatter[fieldName]) {
const dateStr = metadata.frontmatter[fieldName];
const date = new Date(dateStr);
if (!isNaN(date.getTime())) {
frontMatterDate = date;
}
}
}
}
// 使用 frontmatter 中的日期或檔案的狀態日期
if (frontMatterDate) {
timestamp = frontMatterDate.getTime();
} else {
timestamp = isModifiedTime ? file.stat.mtime : file.stat.ctime;
}
}
// 創建日期物件並格式化
const fileDate = new Date(timestamp);
// 根據日期分隔器模式設定格式化
let currentDateString = '';
if (dateDividerMode === 'year') {
// 年分隔器:只顯示年份
currentDateString = fileDate.getFullYear().toString();
} else if (dateDividerMode === 'month') {
// 月分隔器:顯示年-月
const year = fileDate.getFullYear();
const month = fileDate.getMonth() + 1; // getMonth() 回傳 0-11
currentDateString = `${year}-${month.toString().padStart(2, '0')}`;
} else {
// 沒有選中狀態則開啟新分頁
// 日分隔器:顯示完整日期(預設行為)
currentDateString = fileDate.toLocaleDateString();
}
// 如果日期不同於上一個檔案的日期,添加分隔器
if (currentDateString !== lastDateString) {
lastDateString = currentDateString;
// 創建日期分隔器
const dateDivider = container.createDiv('ge-date-divider');
dateDivider.textContent = currentDateString;
}
}
const fileEl = container.createDiv('ge-grid-item');
this.gridItems.push(fileEl); // 添加到網格項目數組
fileEl.dataset.filePath = file.path;
// 創建左側內容區,包含圖示和標題
const contentArea = fileEl.createDiv('ge-content-area');
// 創建標題容器
const titleContainer = contentArea.createDiv('ge-title-container');
const extension = file.extension.toLowerCase();
// 添加檔案類型圖示
if (isImageFile(file)) {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-img');
setIcon(iconContainer, 'image');
} else if (isVideoFile(file)) {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-video');
setIcon(iconContainer, 'play-circle');
} else if (isAudioFile(file)) {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-audio');
setIcon(iconContainer, 'music');
} else if (extension === 'pdf') {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-pdf');
setIcon(iconContainer, 'paperclip');
} else if (extension === 'canvas') {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-canvas');
setIcon(iconContainer, 'layout-dashboard');
} else if (extension === 'md' || extension === 'txt') {
const iconContainer = titleContainer.createDiv('ge-icon-container');
setIcon(iconContainer, 'file-text');
} else {
const iconContainer = titleContainer.createDiv('ge-icon-container');
setIcon(iconContainer, 'file');
}
// 創建標題(立即載入)
const titleEl = titleContainer.createEl('span', { cls: 'ge-title', text: file.basename });
titleEl.setAttribute('title', file.basename);
// 創建圖片區域,但先不載入圖片
fileEl.createDiv('ge-image-area');
// 開始觀察這個元素
observer.observe(fileEl);
// 點擊時開啟檔案
fileEl.addEventListener('click', (event) => {
// 獲取項目索引
const index = this.gridItems.indexOf(fileEl);
if (index < 0) return;
// 處理多選邏輯
if (event.ctrlKey || event.metaKey) {
if (this.selectedItemIndex !== -1) {
// 如果已有選中狀態,則繼續選中
this.selectItem(index, true);
this.hasKeyboardFocus = true;
} else {
// 沒有選中狀態則開啟新分頁
if (isMediaFile(file)) {
// 開啟媒體檔案
if (isAudioFile(file)) {
FloatingAudioPlayer.open(this.app, file);
} else {
this.openMediaFile(file, files);
}
} else {
// 開啟文件檔案
this.app.workspace.getLeaf().openFile(file);
}
}
event.preventDefault();
return;
} else if (event.shiftKey) {
// Shift 鍵:範圍選擇
this.handleRangeSelection(index);
this.hasKeyboardFocus = true;
event.preventDefault();
return;
} else {
// 一般點擊:選中單個項目並開啟
this.selectItem(index);
this.hasKeyboardFocus = true;
// 根據檔案類型處理點擊事件
if (isMediaFile(file)) {
// 開啟媒體檔案
if (isAudioFile(file)) {
@ -1168,57 +1379,87 @@ export class GridView extends ItemView {
}
} else {
// 開啟文件檔案
this.app.workspace.getLeaf().openFile(file);
}
}
});
// 處理滑鼠中鍵點擊
fileEl.addEventListener('mousedown', (event) => {
if (event.button === 1) {
event.preventDefault();
}
});
fileEl.addEventListener('mouseup', (event) => {
if (event.button === 1) {
event.preventDefault();
if (!isMediaFile(file)) {
this.app.workspace.getLeaf(true).openFile(file);
}
}
event.preventDefault();
return;
} else if (event.shiftKey) {
// Shift 鍵:範圍選擇
this.handleRangeSelection(index);
this.hasKeyboardFocus = true;
event.preventDefault();
return;
} else {
// 一般點擊:選中單個項目並開啟
this.selectItem(index);
this.hasKeyboardFocus = true;
});
// 根據檔案類型處理點擊事件
if (isMediaFile(file)) {
// 開啟媒體檔案
if (isAudioFile(file)) {
FloatingAudioPlayer.open(this.app, file);
} else {
this.openMediaFile(file, files);
if(Platform.isDesktop) {
// 添加拖曳功能
fileEl.setAttribute('draggable', 'true');
fileEl.addEventListener('dragstart', (event) => {
// 獲取項目索引
const index = this.gridItems.indexOf(fileEl);
if (index >= 0) {
// 如果項目未被選中,則選中它
if (!this.selectedItems.has(index)) {
this.selectItem(index);
}
}
} else {
// 開啟文件檔案
this.app.workspace.getLeaf().openFile(file);
}
}
});
// 處理滑鼠中鍵點擊
fileEl.addEventListener('mousedown', (event) => {
if (event.button === 1) {
event.preventDefault();
}
});
fileEl.addEventListener('mouseup', (event) => {
if (event.button === 1) {
event.preventDefault();
if (!isMediaFile(file)) {
this.app.workspace.getLeaf(true).openFile(file);
}
}
});
// 獲取選中的檔案
const selectedFiles = this.getSelectedFiles();
// 添加拖曳資料
if (selectedFiles.length > 1) {
// 如果多個檔案被選中,使用 files-menu
const fileList = selectedFiles.map(f => {
const isMedia = isMediaFile(f);
return isMedia ? `![[${f.path}]]` : `[[${f.path}]]`;
}).join('\n');
event.dataTransfer?.setData('text/plain', fileList);
// 添加檔案路徑列表
event.dataTransfer?.setData('application/obsidian-grid-explorer-files',
JSON.stringify(selectedFiles.map(f => f.path)));
} else {
// 如果只有單個檔案被選中,使用檔案路徑
const isMedia = isMediaFile(file);
const mdLink = isMedia
? `![[${file.path}]]` // 媒體檔案使用 ![[]] 格式
: `[[${file.path}]]`; // 一般檔案使用 [[]] 格式
if(Platform.isDesktop) {
// 添加拖曳功能
fileEl.setAttribute('draggable', 'true');
fileEl.addEventListener('dragstart', (event) => {
// 添加拖曳資料
event.dataTransfer?.setData('text/plain', mdLink);
// 添加檔案路徑列表
event.dataTransfer?.setData('application/obsidian-grid-explorer-files',
JSON.stringify([file.path]));
}
// 設定拖曳效果
event.dataTransfer!.effectAllowed = 'all';
// 添加拖曳中的視覺效果
fileEl.addClass('ge-dragging');
});
fileEl.addEventListener('dragend', () => {
// 移除拖曳中的視覺效果
fileEl.removeClass('ge-dragging');
});
}
// 添加右鍵選單
fileEl.addEventListener('contextmenu', (event) => {
event.preventDefault();
const menu = new Menu();
// 獲取項目索引
const index = this.gridItems.indexOf(fileEl);
if (index >= 0) {
@ -1231,122 +1472,67 @@ export class GridView extends ItemView {
// 獲取選中的檔案
const selectedFiles = this.getSelectedFiles();
// 添加拖曳資料
if (selectedFiles.length > 1) {
// 如果多個檔案被選中,使用 files-menu
const fileList = selectedFiles.map(f => {
const isMedia = isMediaFile(f);
return isMedia ? `![[${f.path}]]` : `[[${f.path}]]`;
}).join('\n');
event.dataTransfer?.setData('text/plain', fileList);
// 多個檔案被選中,使用 files-menu
this.app.workspace.trigger('files-menu', menu, selectedFiles);
// 添加檔案路徑列表
event.dataTransfer?.setData('application/obsidian-grid-explorer-files',
JSON.stringify(selectedFiles.map(f => f.path)));
// 檢查是否所有選中的檔案都是 md 檔案
const allMdFiles = selectedFiles.every(file => file.extension === 'md');
if (allMdFiles) {
menu.addItem((item) => {
item
.setTitle(t('set_note_color'))
.setIcon('palette')
.onClick(() => {
showNoteColorSettingsModal(this.app, this.plugin, selectedFiles);
});
});
}
} else {
// 如果只有單個檔案被選中,使用檔案路徑
const isMedia = isMediaFile(file);
const mdLink = isMedia
? `![[${file.path}]]` // 媒體檔案使用 ![[]] 格式
: `[[${file.path}]]`; // 一般檔案使用 [[]] 格式
// 添加拖曳資料
event.dataTransfer?.setData('text/plain', mdLink);
// 添加檔案路徑列表
event.dataTransfer?.setData('application/obsidian-grid-explorer-files',
JSON.stringify([file.path]));
this.app.workspace.trigger('file-menu', menu, file);
}
// 設定拖曳效果
event.dataTransfer!.effectAllowed = 'all';
// 添加拖曳中的視覺效果
fileEl.addClass('ge-dragging');
});
fileEl.addEventListener('dragend', () => {
// 移除拖曳中的視覺效果
fileEl.removeClass('ge-dragging');
// 新增在新分頁開啟選項
menu.addItem((item) => {
item
.setTitle(t('open_in_new_tab'))
.setIcon('external-link')
.setSection?.("open")
.onClick(() => {
if (selectedFiles.length > 1) {
// 如果多個檔案被選中,開啟所有文件檔案
const documentFiles = selectedFiles.filter(f => isDocumentFile(f));
for (const docFile of documentFiles) {
this.app.workspace.getLeaf(true).openFile(docFile);
}
} else {
this.app.workspace.getLeaf(true).openFile(file);
}
});
});
// 刪除選項
menu.addItem((item) => {
(item as any).setWarning(true);
item
.setTitle(t('delete_note'))
.setIcon('trash')
.onClick(async () => {
if (selectedFiles.length > 1) {
// 刪除多個檔案
for (const f of selectedFiles) {
await this.app.fileManager.trashFile(f);
}
// 清除選中狀態
this.clearSelection();
} else {
// 刪除單個檔案
await this.app.fileManager.trashFile(file);
}
});
});
menu.showAtMouseEvent(event);
});
}
// 添加右鍵選單
fileEl.addEventListener('contextmenu', (event) => {
event.preventDefault();
const menu = new Menu();
// 獲取項目索引
const index = this.gridItems.indexOf(fileEl);
if (index >= 0) {
// 如果項目未被選中,則選中它
if (!this.selectedItems.has(index)) {
this.selectItem(index);
}
}
// 獲取選中的檔案
const selectedFiles = this.getSelectedFiles();
if (selectedFiles.length > 1) {
// 多個檔案被選中,使用 files-menu
this.app.workspace.trigger('files-menu', menu, selectedFiles);
// 檢查是否所有選中的檔案都是 md 檔案
const allMdFiles = selectedFiles.every(file => file.extension === 'md');
if (allMdFiles) {
menu.addItem((item) => {
item
.setTitle(t('set_note_color'))
.setIcon('palette')
.onClick(() => {
showNoteColorSettingsModal(this.app, this.plugin, selectedFiles);
});
});
}
} else {
this.app.workspace.trigger('file-menu', menu, file);
}
// 新增在新分頁開啟選項
menu.addItem((item) => {
item
.setTitle(t('open_in_new_tab'))
.setIcon('external-link')
.setSection?.("open")
.onClick(() => {
if (selectedFiles.length > 1) {
// 如果多個檔案被選中,開啟所有文件檔案
const documentFiles = selectedFiles.filter(f => isDocumentFile(f));
for (const docFile of documentFiles) {
this.app.workspace.getLeaf(true).openFile(docFile);
}
} else {
this.app.workspace.getLeaf(true).openFile(file);
}
});
});
// 刪除選項
menu.addItem((item) => {
(item as any).setWarning(true);
item
.setTitle(t('delete_note'))
.setIcon('trash')
.onClick(async () => {
if (selectedFiles.length > 1) {
// 刪除多個檔案
for (const f of selectedFiles) {
await this.app.fileManager.trashFile(f);
}
// 清除選中狀態
this.clearSelection();
} else {
// 刪除單個檔案
await this.app.fileManager.trashFile(file);
}
});
});
menu.showAtMouseEvent(event);
});
}
if(Platform.isDesktop) {

View file

@ -62,6 +62,11 @@ export class SearchModal extends Modal {
text: t('search_current_location_only'),
cls: 'ge-search-scope-label'
});
// 隨機筆記模式下,搜尋範圍設定不顯示
if (this.gridView.sourceMode === 'random-note') {
searchScopeContainer.style.display = 'none';
searchScopeCheckbox.checked = false;
}
// 創建搜尋媒體檔案設定
const searchMediaFilesContainer = searchOptionsContainer.createDiv('ge-search-media-files-container');
@ -74,6 +79,12 @@ export class SearchModal extends Modal {
text: t('search_media_files'),
cls: 'ge-search-media-files-label'
});
// 如果設定中的顯示媒體檔案為false或在反向連結模式下則隱藏搜尋媒體檔案設定
if (!this.gridView.plugin.settings.showMediaFiles || this.gridView.sourceMode === 'backlinks') {
searchMediaFilesContainer.style.display = 'none';
searchMediaFilesCheckbox.checked = false;
this.gridView.searchMediaFiles = false;
}
// 點擊容器時切換勾選框狀態
searchScopeContainer.addEventListener('click', (e) => {

View file

@ -184,7 +184,6 @@ export async function getFiles(gridView: GridView): Promise<TFile[]> {
const settings = gridView.plugin.settings;
const sourceMode = gridView.sourceMode;
const sourcePath = gridView.sourcePath;
const searchQuery = gridView.searchQuery;
const randomNoteIncludeMedia = gridView.randomNoteIncludeMedia;
if (sourceMode === 'folder' && sourcePath) {
@ -200,10 +199,8 @@ export async function getFiles(gridView: GridView): Promise<TFile[]> {
(settings.showMediaFiles && isMediaFile(file))) {
return true;
}
return false;
});
return sortFiles(files, gridView);
}
return [];
@ -222,11 +219,7 @@ export async function getFiles(gridView: GridView): Promise<TFile[]> {
}
}
return [];
} else if (sourceMode === 'backlinks') {
if(searchQuery !== '') {
return [];
}
} else if (sourceMode === 'backlinks') {
// 反向連結模式:找出所有引用當前筆記的檔案
const activeFile = app.workspace.getActiveFile();
if (!activeFile) {

View file

@ -30,6 +30,8 @@ export interface GallerySettings {
createdDateField: string; // 建立時間的欄位名稱
recentFilesCount: number; // 最近筆記模式顯示的筆數
randomNoteCount: number; // 隨機筆記模式顯示的筆數
showNoteTags: boolean; // 是否顯示筆記標籤
dateDividerMode: string; // 日期分隔器模式none, year, month, day
}
// 預設設定
@ -60,7 +62,9 @@ export const DEFAULT_SETTINGS: GallerySettings = {
customDocumentExtensions: '', // 自訂文件副檔名(用逗號分隔)
recentSources: [], // 預設最近的瀏覽記錄
modifiedDateField: '',
createdDateField: ''
createdDateField: '',
showNoteTags: false, // 預設不顯示筆記標籤
dateDividerMode: 'none', // 預設不使用日期分隔器
};
// 設定頁面類別
@ -292,6 +296,23 @@ export class GridExplorerSettingTab extends PluginSettingTab {
await this.plugin.saveSettings(false);
}));
// 日期分隔器模式設定
new Setting(containerEl)
.setName(t('date_divider_mode'))
.setDesc(t('date_divider_mode_desc'))
.addDropdown(dropdown => {
dropdown
.addOption('none', t('date_divider_mode_none'))
.addOption('year', t('date_divider_mode_year'))
.addOption('month', t('date_divider_mode_month'))
.addOption('day', t('date_divider_mode_day'))
.setValue(this.plugin.settings.dateDividerMode)
.onChange(async (value) => {
this.plugin.settings.dateDividerMode = value;
await this.plugin.saveSettings();
});
});
// 檔案監控功能設定
new Setting(containerEl)
.setName(t('enable_file_watcher'))
@ -319,7 +340,7 @@ export class GridExplorerSettingTab extends PluginSettingTab {
});
});
// 顯示"返回上级文件夹"選項設定
// 顯示"回上層資料夾"選項設定
new Setting(containerEl)
.setName(t('show_parent_folder_item'))
.setDesc(t('show_parent_folder_item_desc'))
@ -332,6 +353,19 @@ export class GridExplorerSettingTab extends PluginSettingTab {
});
});
// 顯示筆記標籤設定
new Setting(containerEl)
.setName(t('show_note_tags'))
.setDesc(t('show_note_tags_desc'))
.addToggle(toggle => {
toggle
.setValue(this.plugin.settings.showNoteTags)
.onChange(async (value) => {
this.plugin.settings.showNoteTags = value;
await this.plugin.saveSettings();
});
});
// 網格項目寬度設定
new Setting(containerEl)
.setName(t('grid_item_width'))

View file

@ -63,10 +63,12 @@ export const TRANSLATIONS: Translations = {
// 設定
'grid_view_settings': '網格視圖設定',
'media_files_settings': '媒體檔案設定',
'show_media_files': '顯示圖片和影片',
'show_media_files_desc': '在網格視圖中顯示圖片和影片檔案',
'show_media_files': '顯示媒體檔案',
'show_media_files_desc': '在網格視圖中顯示媒體檔案',
'show_video_thumbnails': '顯示影片縮圖',
'show_video_thumbnails_desc': '在網格視圖中顯示影片的縮圖,關閉時將顯示播放圖示',
'show_note_tags': '顯示筆記標籤',
'show_note_tags_desc': '在網格視圖中顯示筆記的標籤',
'ignored_folders': '忽略的資料夾',
'ignored_folders_desc': '在這裡設定要忽略的資料夾',
'add_ignored_folder': '新增忽略資料夾',
@ -111,7 +113,7 @@ export const TRANSLATIONS: Translations = {
'show_random_note_mode': '顯示隨機筆記模式',
'random_note_count': '隨機筆記模式顯示筆數',
'random_note_notes_only': '僅筆記',
'random_note_include_media_files': '包含圖片影片',
'random_note_include_media_files': '包含媒體檔案',
// 顯示"返回上層資料夾"選項設定
'show_parent_folder_item': '顯示「返回上層資料夾」',
@ -174,6 +176,15 @@ export const TRANSLATIONS: Translations = {
'rename_folder': '重新命名資料夾',
'enter_new_folder_name': '輸入新資料夾名稱',
'search_selection_in_grid_view': '在網格視圖中搜尋...',
'show_date_dividers': '顯示日期分隔器',
'show_date_dividers_desc': '在日期相關排序時,在不同天的第一筆之前顯示日期分隔器',
'date_divider_format': '日期分隔器格式',
'date_divider_mode': '日期分隔器',
'date_divider_mode_desc': '選擇日期分隔器的顯示模式',
'date_divider_mode_none': '不使用',
'date_divider_mode_year': '年',
'date_divider_mode_month': '月',
'date_divider_mode_day': '日',
},
'en': {
// Notifications
@ -219,10 +230,12 @@ export const TRANSLATIONS: Translations = {
// 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',
'show_media_files': 'Show media files',
'show_media_files_desc': 'Display media files in the grid view',
'show_video_thumbnails': 'Show video thumbnails',
'show_video_thumbnails_desc': 'Display thumbnails for videos in the grid view, shows a play icon when disabled',
'show_note_tags': 'Show note tags',
'show_note_tags_desc': 'Display tags for notes in the grid view',
'ignored_folders': 'Ignored folders',
'ignored_folders_desc': 'Set folders to ignore here',
'add_ignored_folder': 'Add ignored folder',
@ -330,6 +343,15 @@ export const TRANSLATIONS: Translations = {
'rename_folder': 'Rename folder',
'enter_new_folder_name': 'Enter new folder name',
'search_selection_in_grid_view': 'Search ... in grid view',
'show_date_dividers': 'Show date dividers',
'show_date_dividers_desc': 'Show date dividers before the first item of each different day when using date-related sorting',
'date_divider_format': 'Date divider format',
'date_divider_mode': 'Date divider',
'date_divider_mode_desc': 'Select the display mode for date dividers',
'date_divider_mode_none': 'None',
'date_divider_mode_year': 'Year',
'date_divider_mode_month': 'Month',
'date_divider_mode_day': 'Day',
},
'zh': {
// 通知信息
@ -375,10 +397,12 @@ export const TRANSLATIONS: Translations = {
// 设置
'grid_view_settings': '网格视图设置',
'media_files_settings': '媒体文件设置',
'show_media_files': '显示图片和视频',
'show_media_files_desc': '在网格视图中显示图片和视频文件',
'show_media_files': '显示媒体文件',
'show_media_files_desc': '在网格视图中显示媒体文件',
'show_video_thumbnails': '显示视频缩略图',
'show_video_thumbnails_desc': '在网格视图中显示视频的缩略图,关闭时将显示播放图标',
'show_note_tags': '显示笔记标签',
'show_note_tags_desc': '在网格视图中显示笔记的标签',
'ignored_folders': '忽略的文件夹',
'ignored_folders_desc': '在这里设置要忽略的文件夹',
'add_ignored_folder': '添加忽略文件夹',
@ -423,7 +447,7 @@ export const TRANSLATIONS: Translations = {
'show_random_note_mode': '显示随机笔记模式',
'random_note_count': '随机笔记模式显示笔数',
'random_note_notes_only': '仅笔记',
'random_note_include_media_files': '包含图片视频',
'random_note_include_media_files': '包含媒体文件',
// 显示"返回上级文件夹"选项设置
'show_parent_folder_item': '显示「返回上级文件夹」',
@ -486,6 +510,15 @@ export const TRANSLATIONS: Translations = {
'rename_folder': '重新命名文件夹',
'enter_new_folder_name': '输入新文件夹名称',
'search_selection_in_grid_view': '在网格视图中搜寻...',
'show_date_dividers': '显示日期分隔器',
'show_date_dividers_desc': '在日期相关排序时,在不同天的第一条之前显示日期分隔器',
'date_divider_format': '日期分隔器格式',
'date_divider_mode': '日期分隔器',
'date_divider_mode_desc': '选择日期分隔器的显示模式',
'date_divider_mode_none': '不使用',
'date_divider_mode_year': '年',
'date_divider_mode_month': '月',
'date_divider_mode_day': '日',
},
'ja': {
// 通知メッジ
@ -531,10 +564,12 @@ export const TRANSLATIONS: Translations = {
// 設定
'grid_view_settings': 'グリッドビュー設定',
'media_files_settings': 'メディアファイル設定',
'show_media_files': '画像と動画を表示',
'show_media_files_desc': 'グリッドビューで画像と動画ファイルを表示する',
'show_media_files': 'メディアファイルを表示',
'show_media_files_desc': 'グリッドビューでメディアファイルを表示する',
'show_video_thumbnails': '動画のサムネイルを表示',
'show_video_thumbnails_desc': 'グリッドビューで動画のサムネイルを表示する、無効の場合は再生アイコンを表示',
'show_note_tags': 'ノートのタグを表示',
'show_note_tags_desc': 'グリッドビューでノートのタグを表示する',
'ignored_folders': '無視するフォルダ',
'ignored_folders_desc': '無視するフォルダを設定する',
'add_ignored_folder': '無視するフォルダを追加',
@ -579,9 +614,9 @@ export const TRANSLATIONS: Translations = {
'show_random_note_mode': 'ランダムノートモードを表示',
'random_note_count': 'ランダムノートモード表示筆数',
'random_note_notes_only': 'ノートのみ',
'random_note_include_media_files': 'メディアを含む',
'random_note_include_media_files': 'メディアファイルを含む',
// 「親フォルダ」オプション設定を表示
// "親フォルダ"オプション設定を表示
'show_parent_folder_item': '「親フォルダ」項目を表示',
'show_parent_folder_item_desc': 'グリッドの最初の項目として「親フォルダ」項目を表示します',
'parent_folder': '親フォルダ',
@ -642,5 +677,14 @@ export const TRANSLATIONS: Translations = {
'rename_folder': 'フォルダを再命名',
'enter_new_folder_name': '新しいフォルダ名を入力',
'search_selection_in_grid_view': '... をグリッドビューで検索',
'show_date_dividers': '日付区切りを表示',
'show_date_dividers_desc': '日付関連の並び替え時、各日の最初のアイテムの前に日付区切りを表示する',
'date_divider_format': '日付区切りフォーマット',
'date_divider_mode': '日付区切り',
'date_divider_mode_desc': '日付区切りの表示モードを選択',
'date_divider_mode_none': '使用しない',
'date_divider_mode_year': '年',
'date_divider_mode_month': '月',
'date_divider_mode_day': '日',
}
}

View file

@ -143,6 +143,10 @@
height: 100%;
}
.ge-grid-item:hover p {
color: var(--text-muted);
}
.ge-content-area p:empty {
margin: 0;
}
@ -167,6 +171,30 @@
border-radius: 4px;
}
/* 標籤樣式 */
.ge-tags-container {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 8px;
margin-bottom: 2px;
}
.ge-tag {
display: inline-flex;
align-items: center;
padding: 0 6px;
font-size: 0.8em;
border: 1px solid var(--background-modifier-border);
border-radius: 5px;
background-color: var(--background-secondary-alt);
color: var(--text-accent);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
/* 資料夾項目的特殊樣式 */
.ge-grid-item.ge-folder-item {
background-color: var(--background-primary-alt);
@ -210,6 +238,18 @@
align-items: center;
}
/* 日期分隔器樣式 */
.ge-date-divider {
width: 100%;
text-align: left;
color: var(--text-normal);
margin-left: 8px;
grid-column: 1 / -1;
pointer-events: none;
user-select: none;
background-color: var(--background-secondary);
}
/* 頂部按鈕區域樣式 */
.ge-header-buttons {
display: flex;

View file

@ -1,3 +1,3 @@
{
"1.9.11": "1.1.0"
"1.9.12": "1.1.0"
}