From e2e1a3690eb9d8a7417007d5be21aaabca0e32ab Mon Sep 17 00:00:00 2001 From: caffae Date: Mon, 10 Mar 2025 22:36:34 +0800 Subject: [PATCH] added a title to the outline and faster animations --- main.ts | 233 +++++++++++++++++++++++++++++++---------------------- styles.css | 45 ++++++++++- 2 files changed, 179 insertions(+), 99 deletions(-) diff --git a/main.ts b/main.ts index be45c44..939ba92 100644 --- a/main.ts +++ b/main.ts @@ -46,20 +46,19 @@ export default class CurrentFolderNotesDisplay extends Plugin { // Load custom styles this.loadStyles(); - + // This adds a settings tab so the user can configure various aspects of the plugin this.addSettingTab(new CurrentFolderNotesDisplaySettingTab(this.app, this)); - - // add a panel to the right sidebar - view + // add a panel to the right sidebar - view this.registerView(VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY, (leaf) => new CurrentFolderNotesDisplayView(leaf, this)); - + // Add a ribbon icon this.addRibbonIcon('folder', 'Activate folder notes display', () => { this.activateView(); }); - // Add a command to open the view + // Add a command to open the view this.addCommand({ id: 'activate-folder-notes-display', name: 'Open Pane', @@ -91,32 +90,32 @@ export default class CurrentFolderNotesDisplay extends Plugin { async onunload() { console.log('unloading plugin'); - + // Clean up by detaching any leaves created by this plugin const leavesToDetach = this.app.workspace.getLeavesOfType(VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY); leavesToDetach.forEach(leaf => { leaf.detach(); }); - + // Clear the leaves array this.leaves = []; } async activateView() { const { workspace } = this.app; - + // Clean up existing leaves first to prevent duplicates const existingLeaves = workspace.getLeavesOfType(VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY); - + // Keep only the first leaf if multiple exist if (existingLeaves.length > 1) { for (let i = 1; i < existingLeaves.length; i++) { existingLeaves[i].detach(); } } - + let leaf: WorkspaceLeaf | null = null; - + if (existingLeaves.length) { // A leaf with our view already exists, use that leaf = existingLeaves[0]; @@ -140,22 +139,23 @@ export default class CurrentFolderNotesDisplay extends Plugin { async refreshView() { const { workspace } = this.app; + console.log("[CFN] Refresh view triggered"); const leaves = workspace.getLeavesOfType(VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY); - - // Make sure we only have one leaf of our type + console.log("[CFN] Found leaves:", leaves.length); + if (leaves.length > 1) { - // Keep the first one, detach others + console.log("[CFN] Multiple leaves found, cleaning up extras"); for (let i = 1; i < leaves.length; i++) { leaves[i].detach(); } } - + if (leaves.length === 1) { - // A leaf with our view exists, update it + console.log("[CFN] Updating existing view"); const view = leaves[0].view as CurrentFolderNotesDisplayView; await view.displayNotesInCurrentFolder(); - } else if (leaves.length === 0) { - // No leaf exists, create one + } else if (leaves.length === 0) { + console.log("[CFN] No view found, creating new one"); this.activateView(); } } @@ -174,10 +174,10 @@ export default class CurrentFolderNotesDisplay extends Plugin { const styleEl = document.createElement('style'); styleEl.id = 'current-folder-notes-styles'; document.head.appendChild(styleEl); - + // Register the stylesheet to be removed when the plugin unloads this.register(() => styleEl.remove()); - + // Load the CSS file from the plugin directory this.loadData().then(data => { // You can add default styles here if needed @@ -193,13 +193,11 @@ export default class CurrentFolderNotesDisplay extends Plugin { } } - export const VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY = "current-folder-notes-view"; export class CurrentFolderNotesDisplayView extends ItemView { plugin: CurrentFolderNotesDisplay; - constructor(leaf: WorkspaceLeaf, plugin: CurrentFolderNotesDisplay) { super(leaf); this.plugin = plugin; @@ -215,7 +213,7 @@ export class CurrentFolderNotesDisplayView extends ItemView { getIcon(): string { return 'folder'; - + } async onOpen() { @@ -232,7 +230,6 @@ export class CurrentFolderNotesDisplayView extends ItemView { } // main.ts - // Function to create clickable headings createClickableHeadings(container: HTMLElement, currentFileContent: string, currentFilePath: string, addExtraHeadingCSS: boolean): void { @@ -252,18 +249,18 @@ export class CurrentFolderNotesDisplayView extends ItemView { const p: HTMLElement = container.createEl('p', { text: headingLabel }); p.classList.add('basic-heading'); p.classList.add(`heading-level-${headingLevel}`); - + if (addExtraHeadingCSS) { p.classList.add('extra-heading-style'); } - + p.addEventListener('click', async (event) => { // Prevent default behavior event.preventDefault(); - + // Use the openLinkText method to navigate to the heading this.app.workspace.openLinkText('#' + headingText, currentFilePath); - + // Clear any text selection const selection = window.getSelection(); if (selection) { @@ -272,7 +269,7 @@ export class CurrentFolderNotesDisplayView extends ItemView { }); // Add hover effect - p.onmouseover = () => { + p.onmouseover = () => { p.classList.add('hover-style-heading'); } // Remove hover effect when not hovering @@ -294,24 +291,25 @@ export class CurrentFolderNotesDisplayView extends ItemView { // Try to find T numbers first (e.g., T1, T23) const tMatch = basename.match(/T(\d+)/); if (tMatch) return `T${tMatch[1]}`; - + // Try to find Y numbers next (e.g., Y1, Y2023) const yMatch = basename.match(/Y(\d+)/); if (yMatch) return `Y${yMatch[1]}`; - + // Try to find any numbers const numberMatch = basename.match(/\d+/); if (numberMatch) return numberMatch[0]; - + // Try to find text after dash const dashMatch = basename.match(/-\s*(.+)$/); if (dashMatch) return dashMatch[1]; - + // If nothing else matches, return the basename return basename; } async displayNotesInCurrentFolder(): Promise { + console.log("[CFN] Starting displayNotesInCurrentFolder"); const container = this.containerEl.children[1] as HTMLElement; container.empty(); @@ -323,33 +321,66 @@ export class CurrentFolderNotesDisplayView extends ItemView { // Create header const headerContainer = container.createDiv({ cls: 'folder-view-header' }); headerContainer.createEl("h6", { text: "Current Folder Notes" }); - - // Get current file info + + // Get current file info and folder path const activeFile = this.app.workspace.getActiveFile(); + console.log("[CFN] Active file:", activeFile?.path); + const currentFilePath = activeFile ? activeFile.path : ''; - const parentFolderPath = currentFilePath.substring(0, currentFilePath.lastIndexOf('/')); - - // Show current folder path - if (parentFolderPath) { - const pathDisplay = headerContainer.createEl('div', { - cls: 'folder-path-display', - text: parentFolderPath - }); - pathDisplay.style.fontSize = 'var(--font-smaller)'; - pathDisplay.style.color = 'var(--text-muted)'; - pathDisplay.style.marginBottom = '10px'; - pathDisplay.style.wordBreak = 'break-word'; + let parentFolderPath = ''; // Don't default to root + + if (currentFilePath) { + const lastSlashIndex = currentFilePath.lastIndexOf('/'); + parentFolderPath = lastSlashIndex > 0 ? currentFilePath.substring(0, lastSlashIndex) : ''; + console.log("[CFN] Parent folder path from current file:", parentFolderPath); + } else if (this.app.workspace.getActiveViewOfType(MarkdownView)) { + const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); + console.log("[CFN] Found active view:", activeView?.file?.path); + if (activeView?.file) { + const viewFilePath = activeView.file.path; + const lastSlashIndex = viewFilePath.lastIndexOf('/'); + parentFolderPath = lastSlashIndex > 0 ? viewFilePath.substring(0, lastSlashIndex) : ''; + console.log("[CFN] Parent folder path from view:", parentFolderPath); + } } + // Show warning if no valid folder path found + if (!parentFolderPath) { + const warningDiv = container.createDiv({ cls: 'empty-state-message' }); + warningDiv.createEl('p', { + text: 'Please open a note in a folder to view related notes.', + cls: 'empty-state-highlight' + }); + warningDiv.createEl('p', { + text: 'The root folder cannot be shown to prevent performance issues with large vaults.', + cls: 'empty-state-subtext' + }); + return; + } + + // Show current folder path + const pathDisplay = headerContainer.createEl('div', { + cls: 'folder-path-display', + text: parentFolderPath + }); + pathDisplay.style.fontSize = 'var(--font-smaller)'; + pathDisplay.style.color = 'var(--text-muted)'; + pathDisplay.style.marginBottom = '10px'; + pathDisplay.style.wordBreak = 'break-word'; + // Get and filter files let folder = this.app.vault.getAbstractFileByPath(parentFolderPath); + console.log("[CFN] Found folder:", folder?.path); let files: TFile[] = []; if (folder instanceof TFolder) { files = folder.children.filter((file: any) => file instanceof TFile) as TFile[]; + console.log("[CFN] Initial files count:", files.length); } files = this.applyFilters(files, parentFolderPath); - + console.log("[CFN] Files after filtering:", files.length); + console.log("[CFN] Current filters - Include:", this.plugin.settings.includeTitleFilter, "Exclude:", this.plugin.settings.excludeTitlesFilter); + if (files.length === 0) { this.showEmptyState(container, activeFile, currentFilePath, files); return; @@ -358,7 +389,7 @@ export class CurrentFolderNotesDisplayView extends ItemView { // Sort files by name/sequence const sequenceWithPrefixOrLongest = (str: string) => { const tMatches = str.match(/T(\d+)/); - if (tMatches) return parseInt(tMatches[1]); + if (tMatches) return parseInt(tMatches[1]); const yMatches = str.match(/Y(\d+)/); if (yMatches) return parseInt(yMatches[1]) + 1000; const matches = str.match(/\d+/g) || []; @@ -370,14 +401,14 @@ export class CurrentFolderNotesDisplayView extends ItemView { // Create main content container const mainContent = container.createDiv({ cls: 'main-content' }); - + // Show navigation section if enabled and we have a current file if (showNavigation && currentFileIndex !== -1) { const navigationSection = mainContent.createDiv({ cls: 'navigation-section' }); - + // Navigation header with prev/next const navHeader = navigationSection.createDiv({ cls: 'navigation-header' }); - + // Previous note if (currentFileIndex > 0) { const prevNote = files[currentFileIndex - 1]; @@ -385,8 +416,8 @@ export class CurrentFolderNotesDisplayView extends ItemView { const prevIcon = prevLink.createSpan({ cls: 'nav-icon' }); prevIcon.innerHTML = ``; prevLink.createSpan({ cls: 'nav-direction', text: 'Previous' }); - const prevTitle = prevLink.createSpan({ - cls: 'nav-title', + const prevTitle = prevLink.createSpan({ + cls: 'nav-title', text: this.getShortNoteName(prevNote.basename) }); prevLink.setAttribute('aria-label', `Previous: ${prevNote.basename}`); @@ -402,8 +433,8 @@ export class CurrentFolderNotesDisplayView extends ItemView { nextLink.createSpan({ cls: 'nav-direction', text: 'Next' }); const nextIcon = nextLink.createSpan({ cls: 'nav-icon' }); nextIcon.innerHTML = ``; - const nextTitle = nextLink.createSpan({ - cls: 'nav-title', + const nextTitle = nextLink.createSpan({ + cls: 'nav-title', text: this.getShortNoteName(nextNote.basename) }); nextLink.setAttribute('aria-label', `Next: ${nextNote.basename}`); @@ -418,7 +449,14 @@ export class CurrentFolderNotesDisplayView extends ItemView { const fileContent = await this.app.vault.read(currentFile); if (fileContent) { const outlineSection = navigationSection.createDiv({ cls: 'outline-section' }); - outlineSection.createEl('div', { + + // Add current note title + outlineSection.createEl('div', { + cls: 'current-note-title', + text: currentFile.basename + }); + + outlineSection.createEl('div', { cls: 'folder-section-header', text: 'CURRENT NOTE OUTLINE' }); @@ -432,19 +470,19 @@ export class CurrentFolderNotesDisplayView extends ItemView { // Always show flat list view const listContainer = mainContent.createDiv({ cls: 'notes-flat-list' }); - listContainer.createEl('div', { + listContainer.createEl('div', { cls: 'folder-section-header', text: 'FOLDER NOTES' }); - + for (const file of files) { const isCurrentFile = file.path === currentFilePath; - const fileContainer = listContainer.createDiv({ - cls: isCurrentFile ? 'file-container current' : 'file-container' + const fileContainer = listContainer.createDiv({ + cls: isCurrentFile ? 'file-container current' : 'file-container' }); this.createFileLink(fileContainer, file, currentFilePath, parentFolderPath); - + if (this.plugin.settings.includeListFileOutlines) { const fileContent = await this.app.vault.read(file); if (fileContent) { @@ -459,38 +497,36 @@ export class CurrentFolderNotesDisplayView extends ItemView { container.classList.toggle('minimal-style', styleMode === 'minimal'); container.classList.toggle('fancy-style', styleMode === 'fancy'); container.classList.toggle('neobrutalist-style', styleMode === 'neobrutalist'); - const shouldApplyBiggerText = this.plugin.settings.biggerText && + const shouldApplyBiggerText = this.plugin.settings.biggerText && (!this.plugin.settings.biggerTextMobileOnly || (this.app as any).isMobile); container.classList.toggle('bigger-text', shouldApplyBiggerText); - - } // Helper function to create file links with consistent styling createFileLink(container: HTMLElement, file: any, currentFilePath: string, parentFolderPath: string): void { const p = container.createEl('p'); const a = p.createEl('a', { text: file.basename }); - + if (this.plugin.settings.prettyTitleCase) { a.innerText = a.innerText.replace(/\w\S*/g, function(txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); } - + // Apply styling a.className = 'folder-notes-style'; - + // If current file, add special styling if (file.path === currentFilePath) { a.className += ' current-file'; a.innerText = '\u2605 ' + a.innerText; } - + // Add hover effects a.onmouseover = () => a.classList.add('hover-style-file'); a.onmouseout = () => a.classList.remove('hover-style-file'); - + // Add click handler to open the file a.addEventListener('click', () => { this.app.workspace.openLinkText(file.basename, parentFolderPath); @@ -501,32 +537,32 @@ export class CurrentFolderNotesDisplayView extends ItemView { private showEmptyState(container: HTMLElement, activeFile: TFile | null, currentFilePath: string, files: TFile[]): void { const emptyStateDiv = container.createDiv({ cls: 'empty-state-message' }); if (files.length === 0) { - emptyStateDiv.createEl('p', { + emptyStateDiv.createEl('p', { text: 'No notes found. If you have recently added or moved notes, Obsidian might still be indexing them. Please wait a moment and try again.', cls: 'empty-state-highlight' }); } if (this.plugin.settings.includeTitleFilter) { - emptyStateDiv.createEl('p', { + emptyStateDiv.createEl('p', { text: `🔍 No notes match the current filter "${this.plugin.settings.includeTitleFilter}".`, cls: 'empty-state-highlight' }); } else if (this.plugin.settings.excludeTitlesFilter) { - emptyStateDiv.createEl('p', { + emptyStateDiv.createEl('p', { text: `All notes are currently filtered out by the exclude pattern "${this.plugin.settings.excludeTitlesFilter}"`, }); } else { - emptyStateDiv.createEl('p', { + emptyStateDiv.createEl('p', { text: 'This folder is empty', }); } - + if (currentFilePath && activeFile) { emptyStateDiv.createEl('p', { text: 'Showing outline of current note:', cls: 'empty-state-subtext' }); - + this.app.vault.read(activeFile).then(fileContent => { if (fileContent) { this.createClickableHeadings(container as HTMLElement, fileContent, currentFilePath, true); @@ -537,34 +573,41 @@ export class CurrentFolderNotesDisplayView extends ItemView { private applyFilters(files: TFile[], parentFolderPath: string): TFile[] { let filteredFiles = files; - + console.log("[CFN] Starting filter with files:", files.length); + // Apply subfolder filter if (!this.plugin.settings.includeSubfolderNotes) { + const beforeCount = filteredFiles.length; filteredFiles = filteredFiles.filter(file => !file.path.substring(parentFolderPath.length + 1).includes('/')); + console.log("[CFN] After subfolder filter:", filteredFiles.length, "removed:", beforeCount - filteredFiles.length); } - + // Apply include filter const includesFilter = this.plugin.settings.includeTitleFilter; if (includesFilter && includesFilter.length > 0) { + const beforeCount = filteredFiles.length; const includeWords = includesFilter.split(/[,\s]+/).map(word => word.trim().toLowerCase()); if (includeWords.length > 0) { - filteredFiles = filteredFiles.filter(file => + filteredFiles = filteredFiles.filter(file => includeWords.some(word => file.basename.toLowerCase().includes(word)) ); + console.log("[CFN] After include filter:", filteredFiles.length, "removed:", beforeCount - filteredFiles.length); } } - + // Apply exclude filter const excludeFilter = this.plugin.settings.excludeTitlesFilter; if (excludeFilter && excludeFilter.length > 0) { + const beforeCount = filteredFiles.length; const excludeWords = excludeFilter.split(/[,\s]+/).map(word => word.trim().toLowerCase()); if (excludeWords.length > 0) { - filteredFiles = filteredFiles.filter(file => + filteredFiles = filteredFiles.filter(file => !excludeWords.some(word => file.basename.toLowerCase().includes(word)) ); + console.log("[CFN] After exclude filter:", filteredFiles.length, "removed:", beforeCount - filteredFiles.length); } } - + return filteredFiles; } } @@ -581,7 +624,7 @@ class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab { const {containerEl} = this; containerEl.empty(); - + // Add some CSS for styling the settings containerEl.createEl('style', { text: ` @@ -614,7 +657,7 @@ class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab { // Title section with plugin description const titleEl = containerEl.createDiv({ cls: 'settings-section' }); titleEl.createEl('h2', { text: 'Current Folder Notes Settings' }); - titleEl.createEl('p', { + titleEl.createEl('p', { cls: 'settings-section-description', text: 'Configure how notes from the current folder are displayed in the panel.' }); @@ -622,11 +665,11 @@ class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab { // Filter settings section const filtersEl = containerEl.createDiv({ cls: 'settings-section' }); filtersEl.createEl('h3', { cls: 'settings-section-header', text: '📂 Filter Settings' }); - filtersEl.createEl('p', { + filtersEl.createEl('p', { cls: 'settings-section-description', text: 'Control which notes appear in the folder notes panel.' }); - + new Setting(filtersEl) .setName('Exclude titles filter') .setDesc('Notes containing these words will be hidden. Separate multiple words with commas.') @@ -637,7 +680,7 @@ class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab { this.plugin.settings.excludeTitlesFilter = value; await this.plugin.saveSettings(); })); - + new Setting(filtersEl) .setName('Include titles filter') .setDesc('Only notes containing these words will be shown. Leave empty to show all notes. Separate multiple words with commas.') @@ -652,7 +695,7 @@ class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab { // Display options section const displayEl = containerEl.createDiv({ cls: 'settings-section' }); displayEl.createEl('h3', { cls: 'settings-section-header', text: 'đŸ–Ĩī¸ Display Options' }); - displayEl.createEl('p', { + displayEl.createEl('p', { cls: 'settings-section-description', text: 'Control how notes and their contents are displayed in the panel.' }); @@ -667,7 +710,7 @@ class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab { await this.plugin.saveSettings(); this.plugin.refreshView(); })); - + new Setting(displayEl) .setName('Pretty title case') .setDesc('Convert note titles to Title Case for better readability.') @@ -730,11 +773,11 @@ class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab { // Content options section const contentEl = containerEl.createDiv({ cls: 'settings-section' }); contentEl.createEl('h3', { cls: 'settings-section-header', text: '📝 Content Options' }); - contentEl.createEl('p', { + contentEl.createEl('p', { cls: 'settings-section-description', text: 'Configure what content is included in the folder notes panel.' }); - + new Setting(contentEl) .setName('Include subfolder notes') .setDesc('Show notes from subfolders within the current folder.') @@ -744,7 +787,7 @@ class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab { this.plugin.settings.includeSubfolderNotes = value; await this.plugin.saveSettings(); })); - + new Setting(contentEl) .setName('Show outline in current file section') .setDesc('Display headings from the currently active file at the top of the panel.') @@ -754,7 +797,7 @@ class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab { this.plugin.settings.includeCurrentFileOutline = value; await this.plugin.saveSettings(); })); - + new Setting(contentEl) .setName('Show outline in files list') .setDesc('Display headings under each file in the folder notes list.') @@ -768,11 +811,11 @@ class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab { // About section with help text const aboutEl = containerEl.createDiv({ cls: 'settings-section' }); aboutEl.createEl('h3', { cls: 'settings-section-header', text: 'â„šī¸ About' }); - aboutEl.createEl('p', { + aboutEl.createEl('p', { cls: 'settings-section-description', text: 'Current Folder Notes displays notes and outlines from the current folder for quick navigation. Changes to settings will apply immediately.' }); - + // Add a refresh button to explicitly refresh the view new Setting(aboutEl) .setName('Refresh panel') diff --git a/styles.css b/styles.css index ebc666c..0d98354 100644 --- a/styles.css +++ b/styles.css @@ -16,7 +16,7 @@ color: var(--text-muted); text-decoration: none; font-size: var(--font-ui-small); - transition: all 0.15s ease; + transition: color 0.05s ease, border-left-color 0.1s ease; position: relative; border-left: 2px solid transparent; } @@ -35,7 +35,7 @@ font-size: var(--font-ui-smaller); color: var(--text-muted); user-select: none; - transition: all 0.15s ease; + transition: color 0.05s ease; border-left: 1px solid var(--background-modifier-border); display: flex; align-items: center; @@ -162,6 +162,7 @@ padding: 4px 0; cursor: pointer; width: auto; + transition: color 0.05s ease; } .prev-note { @@ -181,6 +182,7 @@ height: 16px; flex: 0 0 auto; margin: 0; /* Remove margin from arrow */ + transition: opacity 0.05s ease; } .nav-direction { @@ -500,7 +502,7 @@ .fancy-style .basic-heading { background-color: var(--background-modifier-hover); border: 1px solid var(--background-modifier-border); - transition: all 0.3s ease; + transition: color 0.05s ease; color: var(--text-accent); font-size: var(--font-ui-small); padding: 4px 8px; @@ -526,7 +528,7 @@ .fancy-style .nav-link { background: var(--background-secondary); - transition: all 0.3s ease; + transition: background-color 0.05s ease, color 0.05s ease; color: var(--text-accent); padding: 4px 8px; border-radius: 4px; @@ -647,3 +649,38 @@ .bigger-text .nav-title { font-size: 1.3em; } + +/* Add current note title styling */ +.current-note-title { + font-size: var(--font-ui-large); + color: var(--text-normal); + font-weight: var(--font-bold); + margin: 4px 0 12px 0; + padding: 8px; + text-align: center; + border-bottom: 1px solid var(--background-modifier-border); +} + +.fancy-style .current-note-title { + color: var(--text-accent); + background: var(--background-primary); + border-radius: 4px 4px 0 0; +} + +.minimal-style .current-note-title { + border-bottom: none; + padding: 4px; +} + +.neobrutalist-style .current-note-title { + background: var(--background-primary); + border: 3px solid #000000; + margin: 0 0 8px 0; + padding: 12px; +} + +/* Bigger text adjustments */ +.bigger-text .current-note-title { + font-size: 1.4em; +} +