This commit is contained in:
Devon22 2026-06-25 20:33:43 +08:00
parent ec9f6e731d
commit 87549960d7
20 changed files with 764 additions and 182 deletions

3
.gitignore vendored
View file

@ -33,3 +33,6 @@ Changelog.md
# Exclude snippets directory
/snippets
# Exclude docs directory
/docs

View file

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

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "gridexplorer",
"version": "3.4.8",
"version": "3.4.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gridexplorer",
"version": "3.4.8",
"version": "3.4.9",
"license": "MIT",
"dependencies": {
"jszip": "^3.10.1"

View file

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

View file

@ -227,17 +227,6 @@ export class ExplorerView extends ItemView {
if (newMode !== this.lastMode || newPath !== this.lastPath) {
this.lastMode = newMode;
this.lastPath = newPath;
// 當模式切換時,清理搜尋狀態以避免資料夾被強制展開
if (this.searchQuery.trim()) {
this.searchQuery = '';
if (this.searchInputEl) {
this.searchInputEl.value = '';
}
// 同步搜尋輸入框狀態
this.syncSearchInput();
}
schedule();
}
});
@ -1031,7 +1020,7 @@ export class ExplorerView extends ItemView {
const name = header.createSpan({ cls: 'ge-explorer-folder-name' });
name.textContent = folder.name || '/';
this.setupFolderIcon(folder, name, toggle, expanded);
this.setupFolderIcon(folder, name, toggle, expanded, icon);
this.highlightActiveFolder(folder, header);
this.attachDropTarget(header, folder.path);
@ -1044,7 +1033,7 @@ export class ExplorerView extends ItemView {
}
// 設置資料夾圖示
private setupFolderIcon(folder: TFolder, name: HTMLElement, toggle: HTMLElement, expanded: boolean) {
private setupFolderIcon(folder: TFolder, name: HTMLElement, toggle: HTMLElement, expanded: boolean, iconEl: HTMLElement) {
// 根據同名筆記設置背景色與圖示
const iconFromFrontmatter = this.getFolderIconFromFrontmatter(folder, name);
@ -1053,7 +1042,16 @@ export class ExplorerView extends ItemView {
const customFolderIcon = this.plugin.settings?.customFolderIcon ?? '';
if (customFolderIcon && name) {
name.textContent = `${customFolderIcon} ${folder.name || '/'}`.trim();
if (customFolderIcon.trim()) {
iconEl.setCssProps({ display: 'none' });
} else {
iconEl.setCssProps({ display: '' });
}
} else {
iconEl.setCssProps({ display: '' });
}
} else {
iconEl.setCssProps({ display: 'none' });
}
// 設置切換圖示

View file

@ -77,6 +77,18 @@ export class FileWatcher {
if (file instanceof TFile) {
// 如果有 GridView 實例
if (this.gridView) {
// 如果刪除的檔案剛好是目前正在預覽的檔案,應隱藏預覽
if (this.gridView.isShowingNote || this.gridView.isShowingZip) {
const currentFile = this.gridView.previewManager.getCurrentFile();
if (currentFile && file.path === currentFile.path) {
if (this.gridView.isShowingNote) {
this.gridView.hideNoteInGrid();
} else {
this.gridView.hideZipInGrid();
}
}
}
// 找到當前檔案在 GridView 中的索引
const gridItemIndex = this.gridView.gridItems.findIndex((item: HTMLElement) =>
item.dataset.filePath === file.path
@ -98,6 +110,28 @@ export class FileWatcher {
this.plugin.registerEvent(
this.app.vault.on('rename', (file, oldPath) => {
if (file instanceof TFile) {
// 如果重命名的檔案是目前正在預覽的檔案,需要更新預覽標題與內容
if (this.gridView.isShowingNote || this.gridView.isShowingZip) {
const currentFile = this.gridView.previewManager.getCurrentFile();
if (currentFile && (file === currentFile || currentFile.path === oldPath || currentFile.path === file.path)) {
if (this.gridView.isShowingNote) {
// 更新頂部標題文字
const titleEl = this.gridView.noteViewContainer?.querySelector('.ge-note-title');
if (titleEl) {
titleEl.textContent = file.basename;
}
// 更新預覽內容(這會重新讀取並渲染,順便更新內部的第一行標題與 backlinks
void this.gridView.previewManager.updateCurrentPreview();
} else if (this.gridView.isShowingZip) {
// ZIP 預覽:更新頂部標題文字
const titleEl = this.gridView.zipViewContainer?.querySelector('.ge-zip-title');
if (titleEl) {
titleEl.textContent = file.basename;
}
}
}
}
const fileDirPath = file.path.split('/').slice(0, -1).join('/') || '/';
const oldDirPath = oldPath.split('/').slice(0, -1).join('/') || '/';
// 來源與目標路徑不同,表示移動檔案

View file

@ -25,20 +25,52 @@ interface AppWithInternalPlugins {
export class GridPreviewManager {
private view: GridView;
private previewHistory: TFile[] = [];
private previewHistoryIndex: number = -1;
constructor(view: GridView) {
this.view = view;
// 監聽檔案修改事件以即時更新預覽
this.view.registerEvent(
this.view.app.vault.on('modify', (file) => {
if (file instanceof TFile && this.view.isShowingNote) {
const currentFile = this.getCurrentFile();
if (currentFile && file.path === currentFile.path) {
void this.updateCurrentPreview();
}
}
})
);
}
// 在網格視圖中直接顯示筆記
async showNoteInGrid(file: TFile) {
async showNoteInGrid(file: TFile, isHistoryNavigation = false) {
if (file.extension === 'zip') {
return this.showZipInGrid(file, isHistoryNavigation);
}
// 更新歷史紀錄
if (!this.view.isShowingNote) {
// 從外部全新開啟
this.previewHistory = [file];
this.previewHistoryIndex = 0;
} else if (!isHistoryNavigation) {
// 在內部點擊連結跳轉
this.previewHistory = this.previewHistory.slice(0, this.previewHistoryIndex + 1);
this.previewHistory.push(file);
this.previewHistoryIndex = this.previewHistory.length - 1;
}
// 關閉之前的筆記顯示
if (this.view.isShowingNote) {
this.hideNoteInGrid();
this.hideNoteInGrid(false); // 傳入 false 避免清空我們剛更新的歷史
}
// 關閉之前的 ZIP 顯示
if (this.view.isShowingZip) {
this.hideZipInGrid();
this.hideZipInGrid(false);
}
const gridContainer = this.view.containerEl.querySelector('.ge-grid-container');
@ -51,28 +83,55 @@ export class GridPreviewManager {
// 頂部列 (左右區塊)
const topBar = noteViewContainer.createDiv('ge-note-top-bar');
const leftBar = topBar.createDiv('ge-note-top-left');
const rightBar = topBar.createDiv('ge-note-top-right');
// 筆記標題
const noteTitle = leftBar.createDiv('ge-note-title');
// 返回與前進按鈕
const backButton = leftBar.createEl('button', { cls: 'clickable-icon ge-note-nav-button ge-note-back-button' });
setIcon(backButton, 'arrow-left');
backButton.disabled = this.previewHistoryIndex <= 0;
backButton.addEventListener('click', () => {
if (this.previewHistoryIndex > 0) {
this.previewHistoryIndex--;
const prevFile = this.previewHistory[this.previewHistoryIndex];
void this.showNoteInGrid(prevFile, true);
}
});
const forwardButton = leftBar.createEl('button', { cls: 'clickable-icon ge-note-nav-button ge-note-forward-button' });
setIcon(forwardButton, 'arrow-right');
forwardButton.disabled = this.previewHistoryIndex >= this.previewHistory.length - 1;
forwardButton.addEventListener('click', () => {
if (this.previewHistoryIndex < this.previewHistory.length - 1) {
this.previewHistoryIndex++;
const nextFile = this.previewHistory[this.previewHistoryIndex];
void this.showNoteInGrid(nextFile, true);
}
});
// 筆記標題 (置中)
const noteTitle = topBar.createDiv('ge-note-title');
noteTitle.textContent = file.basename;
if (Platform.isDesktop) {
setTooltip(noteTitle, file.basename);
}
const rightBar = topBar.createDiv('ge-note-top-right');
// 編輯按鈕
const editButton = rightBar.createEl('button', { cls: 'ge-note-edit-button' });
const editButton = rightBar.createEl('button', { cls: 'clickable-icon ge-note-edit-button' });
setIcon(editButton, 'pencil');
editButton.addEventListener('click', () => {
void this.view.getLeafByMode(file).openFile(file);
});
// Metadata 切換按鈕
const infoButton = rightBar.createEl('button', { cls: 'ge-note-info-button' });
const infoButton = rightBar.createEl('button', { cls: 'clickable-icon ge-note-info-button' });
setIcon(infoButton, 'info');
// 關閉按鈕
const closeButton = rightBar.createEl('button', { cls: 'ge-note-close-button' });
const closeButton = rightBar.createEl('button', { cls: 'clickable-icon ge-note-close-button' });
setIcon(closeButton, 'x');
closeButton.addEventListener('click', () => {
this.hideNoteInGrid();
@ -213,7 +272,11 @@ export class GridPreviewManager {
e.preventDefault();
const linkedFile = this.view.app.metadataCache.getFirstLinkpathDest(linkPath, file.path);
if (linkedFile) {
if (e.ctrlKey || e.metaKey) {
void this.view.getLeafByMode(linkedFile).openFile(linkedFile);
} else {
void this.showNoteInGrid(linkedFile);
}
}
});
lastIndex = wikilinkRegex.lastIndex;
@ -291,6 +354,18 @@ export class GridPreviewManager {
// 創建筆記內容區域
const noteContentArea = noteContent.createDiv('ge-note-content');
// 建立筆記內第一行標題
const vaultWithConfig = this.view.app.vault as unknown as { config?: { showInlineTitle?: boolean } };
const showInlineTitle = vaultWithConfig.config?.showInlineTitle ?? true;
if (showInlineTitle) {
noteContentArea.createEl('h1', {
cls: 'ge-note-internal-title',
text: file.basename
});
}
try {
// 讀取筆記內容
const content = await this.view.app.vault.read(file);
@ -322,7 +397,11 @@ export class GridPreviewManager {
const linkText = link.getAttribute('data-href') || href;
const linkedFile = this.view.app.metadataCache.getFirstLinkpathDest(linkText, file.path);
if (linkedFile) {
if (e.ctrlKey || e.metaKey) {
void this.view.getLeafByMode(linkedFile).openFile(linkedFile);
} else {
void this.showNoteInGrid(linkedFile);
}
}
}
}
@ -335,6 +414,10 @@ export class GridPreviewManager {
console.error('Error loading note content:', error);
}
// 渲染反向連結與出站連結區塊
await this.renderLinksSection(file, noteContent);
// 行動裝置下拉或上拉關閉筆記
if (Platform.isMobile && noteViewContainer) {
let startY = 0;
@ -456,23 +539,10 @@ export class GridPreviewManager {
// 設定狀態
this.view.isShowingNote = true;
// 註冊鍵盤事件監聽器
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
this.hideNoteInGrid();
e.preventDefault();
}
};
activeDocument.addEventListener('keydown', handleKeyDown);
// 儲存事件監聽器以便後續移除
(noteViewContainer as NoteViewContainerWithKeydownHandler).keydownHandler = handleKeyDown;
}
// 隱藏筆記顯示
hideNoteInGrid() {
hideNoteInGrid(clearHistory = true) {
if (!this.view.isShowingNote) return;
// 顯示移動端導航欄 (僅在行動裝置上)
@ -487,28 +557,40 @@ export class GridPreviewManager {
}
if (this.view.noteViewContainer) {
// 移除鍵盤事件監聽器
const keydownHandler = (this.view.noteViewContainer as NoteViewContainerWithKeydownHandler).keydownHandler;
if (keydownHandler) {
activeDocument.removeEventListener('keydown', keydownHandler);
}
this.view.noteViewContainer.remove();
this.view.noteViewContainer = null;
}
this.view.isShowingNote = false;
if (clearHistory) {
this.previewHistory = [];
this.previewHistoryIndex = -1;
}
}
// 在網格視圖中直接顯示 ZIP 圖片網格
async showZipInGrid(file: TFile) {
async showZipInGrid(file: TFile, isHistoryNavigation = false) {
// 更新歷史紀錄
if (!this.view.isShowingNote && !this.view.isShowingZip) {
// 從外部全新開啟
this.previewHistory = [file];
this.previewHistoryIndex = 0;
} else if (!isHistoryNavigation) {
// 在內部點擊連結跳轉
this.previewHistory = this.previewHistory.slice(0, this.previewHistoryIndex + 1);
this.previewHistory.push(file);
this.previewHistoryIndex = this.previewHistory.length - 1;
}
// 關閉之前的 ZIP 顯示
if (this.view.isShowingZip) {
this.hideZipInGrid();
this.hideZipInGrid(false);
}
// 關閉之前的筆記顯示
if (this.view.isShowingNote) {
this.hideNoteInGrid();
this.hideNoteInGrid(false);
}
const gridContainer = this.view.containerEl.querySelector('.ge-grid-container');
@ -521,17 +603,41 @@ export class GridPreviewManager {
// 頂部列 (左右區塊)
const topBar = zipViewContainer.createDiv('ge-zip-top-bar');
const leftBar = topBar.createDiv('ge-zip-top-left');
const rightBar = topBar.createDiv('ge-zip-top-right');
// 返回與前進按鈕
const backButton = leftBar.createEl('button', { cls: 'clickable-icon ge-note-nav-button ge-note-back-button' });
setIcon(backButton, 'arrow-left');
backButton.disabled = this.previewHistoryIndex <= 0;
backButton.addEventListener('click', () => {
if (this.previewHistoryIndex > 0) {
this.previewHistoryIndex--;
const prevFile = this.previewHistory[this.previewHistoryIndex];
void this.showNoteInGrid(prevFile, true);
}
});
const forwardButton = leftBar.createEl('button', { cls: 'clickable-icon ge-note-nav-button ge-note-forward-button' });
setIcon(forwardButton, 'arrow-right');
forwardButton.disabled = this.previewHistoryIndex >= this.previewHistory.length - 1;
forwardButton.addEventListener('click', () => {
if (this.previewHistoryIndex < this.previewHistory.length - 1) {
this.previewHistoryIndex++;
const nextFile = this.previewHistory[this.previewHistoryIndex];
void this.showNoteInGrid(nextFile, true);
}
});
// ZIP 標題
const zipTitle = leftBar.createDiv('ge-zip-title');
const zipTitle = topBar.createDiv('ge-zip-title');
zipTitle.textContent = file.basename;
if (Platform.isDesktop) {
setTooltip(zipTitle, file.basename);
}
const rightBar = topBar.createDiv('ge-zip-top-right');
// 開啟按鈕 (在分頁中開啟 ZIP 檔案)
const openButton = rightBar.createEl('button', { cls: 'ge-zip-open-button' });
const openButton = rightBar.createEl('button', { cls: 'clickable-icon ge-zip-open-button' });
setIcon(openButton, 'folder-archive');
openButton.setAttribute('aria-label', t('zip_open_file'));
if (Platform.isDesktop) {
@ -542,7 +648,7 @@ export class GridPreviewManager {
});
// 關閉按鈕
const closeButton = rightBar.createEl('button', { cls: 'ge-zip-close-button' });
const closeButton = rightBar.createEl('button', { cls: 'clickable-icon ge-zip-close-button' });
setIcon(closeButton, 'x');
closeButton.setAttribute('aria-label', t('zip_close_view'));
if (Platform.isDesktop) {
@ -1036,7 +1142,7 @@ export class GridPreviewManager {
}
// 隱藏 ZIP 顯示
hideZipInGrid() {
hideZipInGrid(clearHistory = true) {
if (!this.view.isShowingZip) return;
// 顯示移動端導航欄 (僅在行動裝置上)
@ -1074,6 +1180,11 @@ export class GridPreviewManager {
}
this.view.isShowingZip = false;
if (clearHistory) {
this.previewHistory = [];
this.previewHistoryIndex = -1;
}
}
selectZipItem(idx: number, gridEl: HTMLElement) {
@ -1089,4 +1200,106 @@ export class GridPreviewManager {
}
});
}
private async renderLinksSection(file: TFile, parentEl: HTMLElement) {
const app = this.view.app;
const resolvedLinks = app.metadataCache.resolvedLinks;
// 1. 獲取反向連結 (Backlinks)
const backlinks = new Set<TFile>();
for (const [sourcePath, links] of Object.entries(resolvedLinks)) {
if (Object.keys(links).includes(file.path)) {
const sourceFile = app.vault.getAbstractFileByPath(sourcePath);
if (sourceFile instanceof TFile) {
backlinks.add(sourceFile);
}
}
}
// 如果沒有反向連結,就不需要渲染此區塊
if (backlinks.size === 0) {
return;
}
const linksContainer = parentEl.createDiv('ge-note-links-section');
// 渲染反向連結
if (backlinks.size > 0) {
const backlinkHeader = linksContainer.createDiv('ge-note-links-header');
backlinkHeader.createEl('span', { text: `🔗 ${t('backlinks')} (${backlinks.size})` });
const backlinkList = linksContainer.createDiv('ge-note-links-list');
Array.from(backlinks).forEach((linkFile) => {
const item = backlinkList.createEl('a', {
cls: 'ge-note-link-item',
text: linkFile.basename,
});
item.addEventListener('click', (e) => {
e.preventDefault();
void this.showNoteInGrid(linkFile);
});
});
}
}
public getCurrentFile(): TFile | null {
if (this.previewHistoryIndex >= 0 && this.previewHistoryIndex < this.previewHistory.length) {
return this.previewHistory[this.previewHistoryIndex];
}
return null;
}
public async updateCurrentPreview() {
const file = this.getCurrentFile();
if (!file || !this.view.noteViewContainer) return;
const scrollContainer = this.view.noteViewContainer.querySelector('.ge-note-scroll-container');
const noteContent = this.view.noteViewContainer.querySelector('.ge-note-content-container');
const noteContentArea = this.view.noteViewContainer.querySelector('.ge-note-content');
if (!scrollContainer || !noteContent || !noteContentArea) return;
const savedScrollTop = scrollContainer.scrollTop;
try {
// 1. 清空舊的內容區域
noteContentArea.empty();
// 2. 重新建立筆記內第一行標題
noteContentArea.createEl('h1', {
cls: 'ge-note-internal-title',
text: file.basename
});
// 3. 讀取與渲染
const content = await this.view.app.vault.read(file);
await MarkdownRenderer.render(
this.view.app,
content,
noteContentArea as HTMLElement,
file.path,
this.view
);
// 4. 加上圖片的 data-source-path
noteContentArea
.querySelectorAll<HTMLImageElement>('img')
.forEach((img) => (img.dataset.sourcePath = file.path));
// 5. 重新渲染 backlinks
const oldLinks = noteContent.querySelector('.ge-note-links-section');
if (oldLinks) {
oldLinks.remove();
}
await this.renderLinksSection(file, noteContent as HTMLElement);
// 6. 還原滾動位置
scrollContainer.scrollTop = savedScrollTop;
} catch (error) {
console.error('Error updating current preview note:', error);
}
}
}

View file

@ -96,12 +96,14 @@ interface GridViewStateData {
searchMediaFiles?: boolean;
fileNameFilterQuery?: string;
includeMedia?: boolean | 'media-only';
baseMinMode?: boolean;
minMode?: boolean;
showIgnoredItems?: boolean;
baseCardLayout?: 'horizontal' | 'vertical';
cardLayout?: 'horizontal' | 'vertical';
hideHeaderElements?: boolean;
showFileNameFilter?: boolean;
baseShowDateDividers?: boolean;
showDateDividers?: boolean;
showNoteTags?: boolean;
recentSources?: string[];
@ -163,9 +165,11 @@ export class GridView extends ItemView {
fileWatcher: FileWatcher | null = null; // 檔案監聽器
recentSources: string[] = []; // 歷史記錄
futureSources: string[] = []; // 未來紀錄(前進堆疊)
minMode: boolean = false; // 最小模式
baseMinMode: boolean = false; // 基礎最小模式(不受資料夾臨時覆蓋影響)
minMode: boolean = false; // 目前實際使用的最小模式(可能被資料夾 metadata 臨時覆蓋)
showIgnoredItems: boolean = false; // 顯示忽略的資料夾及檔案
showDateDividers: boolean = false; // 顯示日期分隔器
baseShowDateDividers: boolean = false; // 基礎顯示日期分隔器狀態(不受資料夾臨時覆蓋影響)
showDateDividers: boolean = false; // 目前實際使用的顯示日期分隔器狀態(可能被資料夾 metadata 臨時覆蓋)
showNoteTags: boolean = false; // 顯示筆記標籤
showFileNameFilter: boolean = true; // 顯示檔名篩選輸入框
pinnedList: string[] = []; // 置頂清單
@ -198,7 +202,10 @@ export class GridView extends ItemView {
this.sortType = this.baseSortType;
this.baseCardLayout = this.plugin.settings.cardLayout;
this.cardLayout = this.baseCardLayout;
this.showDateDividers = this.plugin.settings.dateDividerMode !== 'none';
this.baseMinMode = false;
this.minMode = this.baseMinMode;
this.baseShowDateDividers = this.plugin.settings.dateDividerMode !== 'none';
this.showDateDividers = this.baseShowDateDividers;
this.showNoteTags = this.plugin.settings.showNoteTags;
this.showFileNameFilter = this.plugin.settings.showFileNameFilter;
this.searchCurrentLocationOnly = this.plugin.settings.searchCurrentLocationOnly;
@ -401,6 +408,14 @@ export class GridView extends ItemView {
return;
}
// 當切換來源路徑或模式時,應先隱藏當前筆記/ZIP預覽並清空預覽歷史記錄
if (this.isShowingNote) {
this.hideNoteInGrid();
}
if (this.isShowingZip) {
this.hideZipInGrid();
}
// 記錄之前的狀態到歷史記錄中(如果有)
if (this.sourceMode && recordHistory) {
this.pushHistory(
@ -433,14 +448,30 @@ export class GridView extends ItemView {
const mdFile = this.app.vault.getAbstractFileByPath(mdFilePath);
let tempLayout: 'horizontal' | 'vertical' = this.baseCardLayout;
let folderSort: string | undefined;
let tempMinMode: boolean = this.baseMinMode;
let tempShowDateDividers: boolean = this.baseShowDateDividers;
if (mdFile instanceof TFile) {
const metadata = this.app.metadataCache.getFileCache(mdFile)?.frontmatter;
folderSort = typeof metadata?.sort === 'string' ? metadata.sort : undefined;
if (metadata?.cardLayout === 'horizontal' || metadata?.cardLayout === 'vertical') {
tempLayout = metadata.cardLayout as 'horizontal' | 'vertical';
}
if (metadata?.minMode === true || metadata?.minMode === 'true') {
tempMinMode = true;
} else if (metadata?.minMode === false || metadata?.minMode === 'false') {
tempMinMode = false;
}
if (metadata?.showDateDividers === true || metadata?.showDateDividers === 'true') {
tempShowDateDividers = true;
} else if (metadata?.showDateDividers === false || metadata?.showDateDividers === 'false') {
tempShowDateDividers = false;
}
}
this.cardLayout = tempLayout;
this.minMode = tempMinMode;
this.showDateDividers = tempShowDateDividers;
// 根據資料夾 frontmatter 的 sort 覆蓋實際排序,否則使用 baseSortType
this.sortType = folderSort && typeof folderSort === 'string' && folderSort.trim() !== ''
? folderSort
@ -448,6 +479,8 @@ export class GridView extends ItemView {
} else {
// 非資料夾模式時
this.cardLayout = this.baseCardLayout; // 回復基礎卡片排列
this.minMode = this.baseMinMode; // 回復基礎最小化模式
this.showDateDividers = this.baseShowDateDividers; // 回復基礎日期分隔器模式
this.sourcePath = '/'; // 強制設定路徑為根目錄 (創建筆記用)
// 切換到自訂模式時:重設選項索引,並將排序設為 'none'
@ -1136,7 +1169,34 @@ export class GridView extends ItemView {
} else {
// 其他檔案顯示副檔名
if (!this.minMode) {
contentArea.createEl('p', { text: file.extension.toUpperCase() });
const extension = file.extension.toLowerCase();
const pEl = contentArea.createEl('p', { cls: 'ge-non-doc-preview' });
const iconSpan = pEl.createSpan({ cls: 'ge-non-doc-icon' });
let iconName = 'file';
if (isImageFile(file)) {
iconName = 'image';
iconSpan.addClass('ge-img');
} else if (isVideoFile(file)) {
iconName = 'play-circle';
iconSpan.addClass('ge-video');
} else if (isAudioFile(file)) {
iconName = 'music';
iconSpan.addClass('ge-audio');
} else if (extension === 'pdf') {
iconName = 'paperclip';
iconSpan.addClass('ge-pdf');
} else if (extension === 'canvas') {
iconName = 'layout-dashboard';
iconSpan.addClass('ge-canvas');
} else if (extension === 'base') {
iconName = 'layout-list';
iconSpan.addClass('ge-base');
} else if (extension === 'zip') {
iconName = 'folder-archive';
iconSpan.addClass('ge-zip');
}
setIcon(iconSpan, iconName);
pEl.createSpan({ text: file.extension.toUpperCase() });
}
setTooltip(fileEl, `${file.name}`, { delay: 2000 })
@ -1565,36 +1625,6 @@ export class GridView extends ItemView {
fileEl.addClass('ge-media-card');
}
// 添加檔案類型圖示
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 === 'base') {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-base');
setIcon(iconContainer, 'layout-list');
} else if (extension === 'zip') {
const iconContainer = titleContainer.createDiv('ge-icon-container ge-zip');
setIcon(iconContainer, 'folder-archive');
} 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 shouldShowExtension = this.minMode && extension !== 'md';
const displayText = shouldShowExtension ? `${file.basename}.${file.extension}` : file.basename;
@ -2006,6 +2036,41 @@ export class GridView extends ItemView {
});
}
// 複製選項
menu.addItem((item) => {
item
.setTitle(t("make_a_copy"))
.setIcon("copy")
.setSection?.("action")
.onClick(async () => {
for (const f of selectedFiles) {
const parentPath = f.parent ? f.parent.path : "";
let copyPath = "";
if (parentPath && parentPath !== "/") {
copyPath = `${parentPath}/${f.basename} 1.${f.extension}`;
} else {
copyPath = `${f.basename} 1.${f.extension}`;
}
let counter = 1;
while (this.app.vault.getAbstractFileByPath(copyPath)) {
counter++;
if (parentPath && parentPath !== "/") {
copyPath = `${parentPath}/${f.basename} ${counter}.${f.extension}`;
} else {
copyPath = `${f.basename} ${counter}.${f.extension}`;
}
}
try {
await this.app.vault.copy(f, copyPath);
} catch (err) {
console.error("Failed to copy file:", err);
}
}
});
});
// 刪除選項
menu.addItem((item) => {
(item as MenuItemWithWarning).setWarning(true);
@ -2390,12 +2455,14 @@ export class GridView extends ItemView {
searchMediaFiles: this.searchMediaFiles,
fileNameFilterQuery: this.fileNameFilterQuery,
includeMedia: this.includeMedia,
baseMinMode: this.baseMinMode,
minMode: this.minMode,
showIgnoredItems: this.showIgnoredItems,
baseCardLayout: this.baseCardLayout,
cardLayout: this.cardLayout,
hideHeaderElements: this.hideHeaderElements,
showFileNameFilter: this.showFileNameFilter,
baseShowDateDividers: this.baseShowDateDividers,
showDateDividers: this.showDateDividers,
showNoteTags: this.showNoteTags,
recentSources: this.recentSources,
@ -2418,13 +2485,15 @@ export class GridView extends ItemView {
this.searchMediaFiles = state.state.searchMediaFiles ?? false;
this.fileNameFilterQuery = state.state.fileNameFilterQuery ?? '';
this.includeMedia = state.state.includeMedia ?? false;
this.minMode = state.state.minMode ?? false;
this.baseMinMode = state.state.baseMinMode ?? state.state.minMode ?? false;
this.minMode = state.state.minMode ?? this.baseMinMode;
this.showIgnoredItems = state.state.showIgnoredItems ?? false;
this.baseCardLayout = state.state.baseCardLayout ?? 'horizontal';
this.cardLayout = state.state.cardLayout ?? this.baseCardLayout;
this.hideHeaderElements = state.state.hideHeaderElements ?? false;
this.showFileNameFilter = state.state.showFileNameFilter ?? this.plugin.settings.showFileNameFilter;
this.showDateDividers = state.state.showDateDividers ?? this.plugin.settings.dateDividerMode !== 'none';
this.baseShowDateDividers = state.state.baseShowDateDividers ?? state.state.showDateDividers ?? (this.plugin.settings.dateDividerMode !== 'none');
this.showDateDividers = state.state.showDateDividers ?? this.baseShowDateDividers;
this.showNoteTags = state.state.showNoteTags ?? this.plugin.settings.showNoteTags;
this.recentSources = state.state.recentSources ?? [];
this.futureSources = state.state.futureSources ?? [];

View file

@ -12,6 +12,16 @@ interface MenuItemWithSubmenu extends MenuItem {
setSubmenu(): Menu;
}
interface CustomWorkspace {
on(name: 'grid-explorer:preview-file', callback: (fileOrPath: string | TFile) => void): EventRef;
}
declare global {
interface Window {
GridExplorer?: GridExplorerPlugin;
}
}
type WorkspaceEventHandler = (name: string, callback: (...data: unknown[]) => unknown) => EventRef;
interface CanvasPosition {
@ -548,8 +558,18 @@ export default class GridExplorerPlugin extends Plugin {
// console.log('GridExplorer: Starting cache warming process.');
// this.startWarmingCache();
});
// 註冊全域事件與掛載 window API 供其他外掛呼叫
this.registerEvent(
(this.app.workspace as unknown as CustomWorkspace).on('grid-explorer:preview-file', (fileOrPath: string | TFile) => {
void this.previewFile(fileOrPath);
})
);
window.GridExplorer = this;
}
// startWarmingCache() {
// // 1. 取得所有 Markdown 檔案的列表
// const filesToWarm = this.app.vault.getMarkdownFiles();
@ -844,8 +864,41 @@ export default class GridExplorerPlugin extends Plugin {
});
}
}
async previewFile(fileOrPath: TFile | string) {
let file: TFile | null = null;
if (typeof fileOrPath === 'string') {
const abstractFile = this.app.vault.getAbstractFileByPath(fileOrPath);
if (abstractFile instanceof TFile) {
file = abstractFile;
}
} else if (fileOrPath instanceof TFile) {
file = fileOrPath;
}
if (!file) {
console.warn('Grid Explorer: Invalid file or path provided for preview.', fileOrPath);
return;
}
// 取得或啟用 GridView
const activeView = this.app.workspace.getActiveViewOfType(GridView);
const view = activeView ?? await this.activateView();
if (view instanceof GridView) {
await view.showNoteInGrid(file);
}
}
onunload() {
if (window.GridExplorer === this) {
delete window.GridExplorer;
}
super.onunload();
}
}
function migrateDataviewCodeToQuery(settings: GallerySettings): void {
if (!settings.customModes) return;
for (const mode of settings.customModes) {

View file

@ -9,6 +9,8 @@ export interface FolderNoteSettings {
icon: string;
isPinned: boolean;
cardLayout: '' | 'horizontal' | 'vertical';
minMode: '' | 'true' | 'false';
showDateDividers: '' | 'true' | 'false';
}
interface FolderNoteFrontmatter {
@ -17,6 +19,8 @@ interface FolderNoteFrontmatter {
icon?: unknown;
cardLayout?: unknown;
pinned?: unknown;
minMode?: unknown;
showDateDividers?: unknown;
}
function getStringValue(value: unknown, fallback = ''): string {
@ -43,7 +47,9 @@ export class FolderNoteSettingsModal extends Modal {
color: '',
icon: '📁',
isPinned: false,
cardLayout: ''
cardLayout: '',
minMode: '',
showDateDividers: ''
};
existingFile: TFile | null = null;
@ -180,6 +186,34 @@ export class FolderNoteSettingsModal extends Modal {
});
});
// 最小化模式選項
new Setting(contentEl)
.setName(t('min_mode'))
.setDesc(t('display_minimized_desc'))
.addDropdown(drop => {
drop.addOption('', t('default'));
drop.addOption('true', t('enable'));
drop.addOption('false', t('disable'));
drop.setValue(this.settings.minMode);
drop.onChange(async (value) => {
this.settings.minMode = value as '' | 'true' | 'false';
});
});
// 顯示日期分隔器選項
new Setting(contentEl)
.setName(t('show_date_dividers'))
.setDesc(t('show_date_dividers_desc'))
.addDropdown(drop => {
drop.addOption('', t('default'));
drop.addOption('true', t('enable'));
drop.addOption('false', t('disable'));
drop.setValue(this.settings.showDateDividers);
drop.onChange(async (value) => {
this.settings.showDateDividers = value as '' | 'true' | 'false';
});
});
// 置頂勾選框
new Setting(contentEl)
.setName(t('foldernote_pinned'))
@ -226,6 +260,24 @@ export class FolderNoteSettingsModal extends Modal {
this.settings.cardLayout =
cardLayout === 'horizontal' || cardLayout === 'vertical' ? cardLayout : '';
const minMode = frontmatter.minMode;
if (minMode === true || minMode === 'true') {
this.settings.minMode = 'true';
} else if (minMode === false || minMode === 'false') {
this.settings.minMode = 'false';
} else {
this.settings.minMode = '';
}
const showDateDividers = frontmatter.showDateDividers;
if (showDateDividers === true || showDateDividers === 'true') {
this.settings.showDateDividers = 'true';
} else if (showDateDividers === false || showDateDividers === 'false') {
this.settings.showDateDividers = 'false';
} else {
this.settings.showDateDividers = '';
}
if (Array.isArray(frontmatter.pinned)) {
this.settings.isPinned = frontmatter.pinned.some((item: unknown) => {
const pinnedName = getPinnedName(item);
@ -285,6 +337,22 @@ export class FolderNoteSettingsModal extends Modal {
delete frontmatter.cardLayout;
}
if (this.settings.minMode === 'true') {
frontmatter.minMode = true;
} else if (this.settings.minMode === 'false') {
frontmatter.minMode = false;
} else {
delete frontmatter.minMode;
}
if (this.settings.showDateDividers === 'true') {
frontmatter.showDateDividers = true;
} else if (this.settings.showDateDividers === 'false') {
frontmatter.showDateDividers = false;
} else {
delete frontmatter.showDateDividers;
}
const folderName = `${this.folder.name}.md`;
if (this.settings.isPinned) {
// 如果原本就有 pinned 陣列,則添加或更新

View file

@ -566,7 +566,8 @@ export function renderHeaderButton(gridView: GridView) {
.setIcon('minimize-2')
.setChecked(gridView.minMode)
.onClick(() => {
gridView.minMode = !gridView.minMode;
gridView.baseMinMode = !gridView.minMode;
gridView.minMode = gridView.baseMinMode;
gridView.app.workspace.requestSaveLayout();
void gridView.render();
});
@ -579,7 +580,8 @@ export function renderHeaderButton(gridView: GridView) {
.setIcon('calendar')
.setChecked(gridView.showDateDividers)
.onClick(() => {
gridView.showDateDividers = !gridView.showDateDividers;
gridView.baseShowDateDividers = !gridView.showDateDividers;
gridView.showDateDividers = gridView.baseShowDateDividers;
gridView.app.workspace.requestSaveLayout();
void gridView.render();
});

View file

@ -110,7 +110,7 @@ export const DEFAULT_SETTINGS: GallerySettings = {
verticalImageAreaHeight: 180, // 直向卡片 - 圖片區域高度
verticalCardImagePosition: 'top', // 直向卡片圖片位置
titleFontSize: 1.0, // 筆記標題的字型大小,預設 1.0
multiLineTitle: false,
multiLineTitle: true, // 標題允許兩行顯示
summaryLength: 100, // 筆記摘要的字數,預設 100
enableFileWatcher: true, // 預設啟用檔案監控
folderDisplayStyle: 'show', // 預設直接顯示資料夾
@ -155,7 +155,7 @@ export const DEFAULT_SETTINGS: GallerySettings = {
quickAccessCommandPath: '', // Path used by "Open quick access folder" command
useQuickAccessAsNewTabMode: 'default',
quickAccessModeType: 'all-files', // Default quick access view type
showNoteInGrid: false, // 預設不在 grid container 中顯示筆記
showNoteInGrid: Platform.isMobile ? true : false, // 行動裝置: 預設在網格中顯示筆記,非行動裝置預設不在網格中顯示筆記
closeGridViewOnOpenNote: false, // 預設開啟筆記檔案時不關閉目前的網格視圖
openNoteLayout: 'default', // 預設開啟筆記的方式
searchCurrentLocationOnly: false, // 預設搜尋所有筆記
@ -307,6 +307,9 @@ export class GridExplorerSettingTab extends PluginSettingTab {
const infoEl = itemEl.createDiv({ cls: 'ge-custom-mode-info' });
infoEl.createSpan({ text: mode.icon || '🧩', cls: 'ge-custom-mode-icon' });
infoEl.createSpan({ text: mode.displayName, cls: 'ge-custom-mode-name' });
infoEl.addEventListener('click', () => {
checkbox.click();
});
// 操作按鈕
const actionsEl = itemEl.createDiv({ cls: 'ge-custom-mode-actions' });
@ -523,6 +526,9 @@ export class GridExplorerSettingTab extends PluginSettingTab {
const infoEl = itemEl.createDiv({ cls: 'ge-display-mode-info' });
infoEl.createSpan({ text: icon, cls: 'ge-display-mode-icon' });
infoEl.createSpan({ text: title, cls: 'ge-display-mode-name' });
infoEl.addEventListener('click', () => {
checkbox.click();
});
if (extraRenderer) {
const extraEl = itemEl.createDiv({ cls: 'ge-display-mode-extra' });

View file

@ -6,6 +6,7 @@ export default {
'sorting': 'Sort by',
'refresh': 'Refresh',
'reselect': 'Reselect',
'backlinks': 'Backlinks',
'no_backlinks': 'No backlinks',
'back': 'Back',
'search': 'Search',
@ -39,6 +40,7 @@ export default {
'save': 'Save',
'option': 'Option',
'add_option': 'Add option',
'make_a_copy': 'Make a copy',
// View Titles
'grid_view_title': 'Grid view',
@ -185,6 +187,8 @@ export default {
'foldernote_display_settings_desc': 'Set the display mode of folder notes',
'all': 'All',
'default': 'Default',
'enable': 'Enable',
'disable': 'Disable',
'hidden': 'Hidden',
'all_bookmarks': 'All Bookmarks',
'ungrouped_bookmarks': 'Ungrouped Bookmarks',

View file

@ -6,6 +6,7 @@ export default {
'sorting': '並び替え',
'refresh': '更新',
'reselect': '再選択',
'backlinks': 'バックリンク',
'no_backlinks': 'バックリンクなし',
'back': '戻る',
'search': '検索',
@ -38,6 +39,7 @@ export default {
'save': '保存',
'option': '選択肢',
'add_option': '選択肢を追加',
'make_a_copy': '複製を作成',
// ビュータイトル
'grid_view_title': 'グリッドビュー',
@ -184,6 +186,8 @@ export default {
'foldernote_display_settings_desc': 'フォルダノートの表示方法を設定します',
'all': 'すべて',
'default': 'デフォルト',
'enable': '有効',
'disable': '無効',
'hidden': '隠す',
'all_bookmarks': 'すべてのブックマーク',
'ungrouped_bookmarks': 'グループなし',

View file

@ -6,6 +6,7 @@ export default {
'sorting': '정렬 기준',
'refresh': '새로고침',
'reselect': '다시 선택',
'backlinks': '백링크',
'no_backlinks': '백링크 없음',
'back': '뒤로',
'search': '검색',
@ -39,6 +40,7 @@ export default {
'save': '저장',
'option': '옵션',
'add_option': '옵션 추가',
'make_a_copy': '복사본 만들기',
// View Titles
'grid_view_title': '그리드 뷰',
@ -185,6 +187,8 @@ export default {
'foldernote_display_settings_desc': '폴더 노트의 표시 모드 설정',
'all': '전체',
'default': '기본값',
'enable': '활성화',
'disable': '비활성화',
'hidden': '숨김',
'all_bookmarks': '모든 북마크',
'ungrouped_bookmarks': '그룹 해제됨',

View file

@ -6,6 +6,7 @@ export default {
'sorting': 'Сортировать по',
'refresh': 'Обновить',
'reselect': 'Перевыбрать',
'backlinks': 'Обратные ссылки',
'no_backlinks': 'Нет обратных ссылок',
'back': 'Назад',
'search': 'Поиск',
@ -38,6 +39,7 @@ export default {
'save': 'Сохранить',
'option': 'Опция',
'add_option': 'Добавить опцию',
'make_a_copy': 'Создать копию',
// View Titles
'grid_view_title': 'Сеточный вид',
@ -184,6 +186,8 @@ export default {
'foldernote_display_settings_desc': 'Установить режим отображения заметок папок',
'all': 'Все',
'default': 'По умолчанию',
'enable': 'Включить',
'disable': 'Выключить',
'hidden': 'Скрытые',
'all_bookmarks': 'Все закладки',
'ungrouped_bookmarks': 'Без группы',

View file

@ -6,6 +6,7 @@ export default {
'sorting': 'Сортувати за',
'refresh': 'Оновити',
'reselect': 'Перевибрати',
'backlinks': 'Зворотні посилання',
'no_backlinks': 'Немає зворотних посилань',
'back': 'Назад',
'search': 'Пошук',
@ -38,6 +39,7 @@ export default {
'save': 'Зберегти',
'option': 'Опція',
'add_option': 'Додати опцію',
'make_a_copy': 'Створити копію',
// View Titles
'grid_view_title': 'Сітковий вигляд',
@ -184,6 +186,8 @@ export default {
'foldernote_display_settings_desc': 'Встановіть режим відображення нотаток папок',
'all': 'Усі',
'default': 'За замовчуванням',
'enable': 'Увімкнути',
'disable': 'Вимкнути',
'hidden': 'Приховані',
'all_bookmarks': 'Усі закладки',
'ungrouped_bookmarks': 'Без групи',

View file

@ -6,6 +6,7 @@ export default {
'sorting': '排序方式',
'refresh': '重新整理',
'reselect': '重新選擇位置',
'backlinks': '反向連結',
'no_backlinks': '沒有反向連結',
'back': '返回',
'search': '搜尋',
@ -39,6 +40,7 @@ export default {
'save': '儲存',
'option': '選項',
'add_option': '新增選項',
'make_a_copy': "建立副本",
// 視圖標題
'grid_view_title': '網格視圖',
@ -185,6 +187,8 @@ export default {
'foldernote_display_settings_desc': '設定資料夾筆記的顯示方式',
'all': '全部',
'default': '預設',
'enable': '啟用',
'disable': '停用',
'hidden': '隱藏',
'all_bookmarks': '全部書籤',
'ungrouped_bookmarks': '無群組書籤',

View file

@ -6,6 +6,7 @@ export default {
'sorting': '排序方式',
'refresh': '刷新',
'reselect': '重新选择位置',
'backlinks': '反向链接',
'no_backlinks': '没有反向链接',
'back': '返回',
'search': '搜索',
@ -38,6 +39,7 @@ export default {
'save': '保存',
'option': '选项',
'add_option': '添加选项',
'make_a_copy': '创建副本',
// 视图标题
'grid_view_title': '网格视图',
@ -184,6 +186,8 @@ export default {
'foldernote_display_settings_desc': '设置文件夹笔记的显示方式',
'all': '全部',
'default': '默认',
'enable': '启用',
'disable': '禁用',
'hidden': '隐藏',
'all_bookmarks': '全部书签',
'ungrouped_bookmarks': '无群组书签',

View file

@ -129,39 +129,9 @@
color: var(--text-muted);
}
/* 圖片圖示 Image Icon */
.ge-icon-container.ge-img {
color: var(--color-blue);
}
/* 影片圖示 Video Icon */
.ge-icon-container.ge-video {
color: var(--color-red);
}
/* 音樂圖示 Audio Icon */
.ge-icon-container.ge-audio {
color: var(--color-purple);
}
/* PDF圖示 PDF Icon */
.ge-icon-container.ge-pdf {
color: var(--color-orange);
}
/* Canvas圖示 Canvas Icon */
.ge-icon-container.ge-canvas {
color: var(--color-green);
}
/* 基礎圖示 Base Icon */
.ge-icon-container.ge-base {
color: var(--color-cyan);
}
/* 標題 Title */
.ge-title {
margin: 0 0 0 3px;
margin: 0;
font-size: var(--title-font-size);
color: var(--text-normal);
overflow: hidden;
@ -176,22 +146,11 @@
align-items: flex-start;
}
.ge-title-container.has-multiline-title .ge-icon-container {
width: 16px;
height: 16px;
margin-top: 2px;
}
.ge-title-container.has-multiline-title .ge-icon-container svg {
width: 14px;
height: 14px;
}
.ge-title.ge-multiline-title {
white-space: normal;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-line-clamp: 3;
line-clamp: 3;
-webkit-box-orient: vertical;
line-height: 1.3;
overflow: hidden;
@ -225,6 +184,53 @@
height: auto;
}
/* 非文件檔預覽樣式 */
.ge-content-area p.ge-non-doc-preview {
display: flex;
align-items: center;
gap: 4px;
height: auto;
}
.ge-non-doc-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 14px;
height: 14px;
color: var(--text-muted);
}
.ge-non-doc-icon.ge-img {
color: var(--color-blue);
}
.ge-non-doc-icon.ge-video {
color: var(--color-red);
}
.ge-non-doc-icon.ge-audio {
color: var(--color-purple);
}
.ge-non-doc-icon.ge-pdf {
color: var(--color-orange);
}
.ge-non-doc-icon.ge-canvas {
color: var(--color-green);
}
.ge-non-doc-icon.ge-base {
color: var(--color-cyan);
}
.ge-non-doc-icon.ge-zip {
color: var(--color-folder); /* obsidian color or fallback */
}
/* 圖片區域 Image Area */
.ge-image-area {
width: var(--image-area-width);
@ -1058,6 +1064,7 @@ span.ge-mode-title {
gap: 8px;
flex-grow: 1;
min-width: 0;
cursor: pointer;
}
.ge-custom-mode-icon {
@ -1151,6 +1158,7 @@ span.ge-mode-title {
gap: 8px;
flex-grow: 1;
min-width: 0;
cursor: pointer;
}
.ge-display-mode-icon {
@ -2296,11 +2304,10 @@ a.ge-current-folder:hover {
display: flex;
align-items: center;
gap: 8px;
padding-left: 20px;
flex: 1;
min-width: 0;
padding-left: 15px;
}
.ge-note-top-right {
display: flex;
align-items: center;
@ -2330,50 +2337,47 @@ a.ge-current-folder:hover {
.ge-note-info-button {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
background-color: var(--interactive-normal);
border-radius: var(--button-radius);
color: var(--text-normal);
font-size: 14px;
justify-content: center;
padding: 6px;
background-color: transparent;
border-radius: var(--clickable-icon-radius, 4px);
color: var(--icon-color);
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
}
.is-tablet .ge-note-close-button:not(.clickable-icon),
.is-tablet .ge-note-edit-button:not(.clickable-icon),
.is-tablet .ge-note-info-button:not(.clickable-icon) {
padding: 6px 12px;
transition: background-color 0.2s;
border: none;
box-shadow: none;
outline: none;
}
.ge-note-close-button:hover,
.ge-note-edit-button:hover,
.ge-note-info-button:hover {
background-color: var(--interactive-hover);
transform: translateY(-1px);
opacity: 1;
background-color: var(--background-modifier-hover);
color: var(--icon-color-focused);
}
.ge-note-close-button:active,
.ge-note-edit-button:active,
.ge-note-info-button:active {
transform: translateY(0);
background-color: var(--background-modifier-active);
}
.is-mobile .ge-note-info-button:focus,
.is-mobile .ge-note-info-button:focus-visible {
outline: none;
background-color: var(--interactive-normal);
transform: none;
background-color: transparent;
}
.ge-note-content-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
padding: var(--file-margins);
min-height: 100%;
}
.is-mobile .ge-note-content-container {
padding-top: 15px;
}
.ge-note-title {
font-size: 1em;
font-weight: var(--inline-title-weight);
@ -3295,9 +3299,7 @@ a.ge-current-folder:hover {
display: flex;
align-items: center;
gap: 8px;
padding-left: 20px;
flex: 1;
min-width: 0;
padding-left: 15px;
}
.ge-zip-top-right {
@ -3308,33 +3310,37 @@ a.ge-current-folder:hover {
}
.ge-zip-title {
font-size: 1em;
font-weight: bold;
color: var(--text-normal);
flex: 1;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 500;
font-size: 0.95em;
color: var(--text-muted);
min-width: 0;
}
.ge-zip-close-button,
.ge-zip-open-button {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
background-color: var(--interactive-normal);
border-radius: var(--button-radius);
color: var(--text-normal);
font-size: 14px;
justify-content: center;
padding: 6px;
background-color: transparent;
border-radius: var(--clickable-icon-radius, 4px);
color: var(--icon-color);
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
transition: background-color 0.2s;
border: none;
box-shadow: none;
outline: none;
}
.ge-zip-close-button:hover,
.ge-zip-open-button:hover {
background-color: var(--interactive-hover);
transform: translateY(-1px);
background-color: var(--background-modifier-hover);
color: var(--icon-color-focused);
}
.ge-zip-scroll-container {
@ -3422,7 +3428,7 @@ a.ge-current-folder:hover {
@media (hover: hover) {
.ge-folder-button:hover {
background-color: var(--background-modifier-hover);
background-color: var(--text-selection);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
@ -3503,3 +3509,105 @@ a.ge-current-folder:hover {
background-color: var(--ge-folder-color-bg);
border-color: var(--ge-folder-color-border);
}
/* 筆記預覽內部關聯連結區塊 */
.ge-note-links-section {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid var(--background-modifier-border);
margin-bottom: 40px;
}
.ge-note-links-header {
font-size: 0.9em;
font-weight: 600;
color: var(--text-muted);
margin-bottom: 12px;
margin-top: 20px;
display: flex;
align-items: center;
}
.ge-note-links-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.ge-note-link-item {
font-size: 0.85em;
padding: 4px 10px;
background-color: var(--background-secondary-alt);
border: 1px solid var(--background-modifier-border);
border-radius: 12px;
color: var(--text-normal);
text-decoration: none;
transition: all 0.2s ease;
cursor: pointer;
}
.ge-note-link-item:hover {
background-color: var(--interactive-accent);
color: var(--text-on-accent);
border-color: var(--interactive-accent);
}
/* 歷史導航按鈕 */
.ge-note-nav-button {
display: flex;
align-items: center;
justify-content: center;
padding: 6px;
background-color: transparent;
border-radius: var(--clickable-icon-radius, 4px);
color: var(--icon-color);
cursor: pointer;
transition: background-color 0.2s;
border: none;
box-shadow: none;
outline: none;
}
.ge-note-nav-button:hover:not(:disabled) {
background-color: var(--background-modifier-hover);
color: var(--icon-color-focused);
}
.ge-note-nav-button:disabled {
opacity: 0.3;
cursor: default;
}
.ge-note-nav-button svg {
width: 16px;
height: 16px;
}
/* 頂部列與標題置中 */
.ge-note-title {
flex: 1;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 500;
font-size: 0.95em;
color: var(--text-muted);
min-width: 0;
}
/* 筆記內第一行標題 (預設顯示) */
.ge-note-internal-title {
margin-top: 0px;
margin-bottom: 20px;
border-bottom: 1px solid var(--background-modifier-border);
padding-bottom: 12px;
font-size: var(--inline-title-size);
font-weight: var(--inline-title-weight);
line-height: var(--inline-title-line-height);
font-style: var(--inline-title-style);
font-variant: var(--inline-title-variant);
font-family: var(--inline-title-font);
letter-spacing: -0.015em;
}