import { App, Notice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian'; import { ItemView, WorkspaceLeaf } from "obsidian"; import { MarkdownView } from "obsidian"; import { TFolder } from "obsidian"; interface CurrentFolderNotesDisplaySettings { excludeTitlesFilter: string; includeTitleFilter: string; prettyTitleCase: boolean; includeSubfolderNotes: boolean; includeCurrentFileOutline: boolean; includeListFileOutlines: boolean; styleMode: 'minimal' | 'fancy' | 'neobrutalist'; showNavigation: boolean; biggerText: boolean; // Add this line biggerTextMobileOnly: boolean; // Add this new setting maxFilesDisplay: number; // Add new setting for max files to display } const DEFAULT_SETTINGS: Partial = { excludeTitlesFilter: '_index', includeTitleFilter: '', prettyTitleCase: true, includeSubfolderNotes: false, includeCurrentFileOutline: true, includeListFileOutlines: false, styleMode: 'fancy', showNavigation: false, biggerText: false, biggerTextMobileOnly: false, maxFilesDisplay: 100 // Default to showing 100 files } export default class CurrentFolderNotesDisplay extends Plugin { settings: CurrentFolderNotesDisplaySettings; public leaves: WorkspaceLeaf[] = []; fileChangeHandler(file: TFile) { if (file instanceof TFile) { this.load(); } } async onload() { await this.loadSettings(); // 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 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 this.addCommand({ id: 'activate-folder-notes-display', name: 'Open Pane', callback: () => { this.activateView(); } }); // Add debouncing for refreshView let refreshTimeout: NodeJS.Timeout | null = null; const debouncedRefresh = () => { if (refreshTimeout) clearTimeout(refreshTimeout); refreshTimeout = setTimeout(() => { this.refreshView(); }, 300); // Wait 300ms before refreshing }; // Use debounced refresh for all file events this.registerEvent(this.app.workspace.on('file-open', debouncedRefresh)); this.registerEvent(this.app.vault.on('delete', debouncedRefresh)); this.registerEvent(this.app.vault.on('create', debouncedRefresh)); this.registerEvent(this.app.vault.on('rename', debouncedRefresh)); } 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]; } else { // Our view could not be found in the workspace, create a new leaf // in the right sidebar for it leaf = workspace.getRightLeaf(false); if (leaf) { await leaf.setViewState({ type: VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY, active: true }); // Only add to leaves array if it's a new leaf this.leaves.push(leaf); } } if (!leaf) { new Notice('Could not create a new leaf for the view'); return; } workspace.revealLeaf(leaf); } async refreshView() { const { workspace } = this.app; // console.log("[CFN] Refresh view triggered"); const leaves = workspace.getLeavesOfType(VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY); // console.log("[CFN] Found leaves:", leaves.length); if (leaves.length > 1) { // console.log("[CFN] Multiple leaves found, cleaning up extras"); for (let i = 1; i < leaves.length; i++) { leaves[i].detach(); } } if (leaves.length === 1) { // console.log("[CFN] Updating existing view"); const leaf = leaves[0]; const view = leaf.view; // Check if view is an instance of CurrentFolderNotesDisplayView if (view && view instanceof CurrentFolderNotesDisplayView) { await view.displayNotesInCurrentFolder(); } else { // If the view doesn't have the expected method, recreate it console.log("[CFN] View is not a CurrentFolderNotesDisplayView, recreating view"); leaf.detach(); await this.activateView(); } } else if (leaves.length === 0) { // console.log("[CFN] No view found, creating new one"); this.activateView(); } } async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } async saveSettings() { await this.saveData(this.settings); } // Add this method to load styles loadStyles() { // Load styles from the plugin's styles.css 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 const defaultStyles = ` .folder-notes-style { font-weight: 500; } .folder-notes-style.current-file { font-weight: bold; } .hover-style-file { text-decoration: underline; } .hover-style-heading { font-style: italic; } /* Add more default styles as needed */ `; styleEl.textContent = defaultStyles; }); } } 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; } getViewType() { return VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY; } getDisplayText() { return "Current Folder Notes"; } getIcon(): string { return 'folder'; } async onOpen() { await this.displayNotesInCurrentFolder(); } async onClose() { // Remove this leaf from the plugin's leaves array to avoid memory leaks const index = this.plugin.leaves.indexOf(this.leaf); if (index > -1) { this.plugin.leaves.splice(index, 1); } } // main.ts // Function to create clickable headings createClickableHeadings(container: HTMLElement, currentFileContent: string, currentFilePath: string, addExtraHeadingCSS: boolean): void { // Set a maximum limit on the file content to process for safety const MAX_CONTENT_SIZE = 500000; // 500KB max processing size if (currentFileContent.length > MAX_CONTENT_SIZE) { currentFileContent = currentFileContent.substring(0, MAX_CONTENT_SIZE); // Add a note that content was truncated container.createEl('p', { cls: 'content-truncated-note', text: 'Note is very large. Only showing headings from the first portion.' }); } // Use a more efficient regex approach for large files const headingRegex = /^(#+)\s+(.*)$/gm; let match; let headingCount = 0; const MAX_HEADINGS = 100; // Limit the number of headings displayed // Process headings in batches for very large files try { while ((match = headingRegex.exec(currentFileContent)) !== null && headingCount < MAX_HEADINGS) { const headingLevel = match[1].length; let headingText = match[2]; // Use extractAlias to get the alias from the heading text headingText = this.extractAlias(headingText); // Add a right arrow symbol to the heading text let headingLabel = '→ ' + headingText; 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', (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) { selection.removeAllRanges(); } }); // Add hover effect p.onmouseover = () => { p.classList.add('hover-style-heading'); } // Remove hover effect when not hovering p.onmouseout = () => { p.classList.remove('hover-style-heading'); } headingCount++; } // Indicate if there are more headings not shown if (headingCount >= MAX_HEADINGS) { container.createEl('p', { cls: 'more-headings-note', text: '... more headings available (not shown)' }); } } catch (err) { console.error("Error processing headings:", err); container.createEl('p', { cls: 'error-note', text: 'Error processing headings' }); } } // Function to extract alias from heading text extractAlias(headingText: string): string { const matches = headingText.match(/\[\[.*\|(.*?)\]\]/); return matches ? matches[1] : headingText; } private getShortNoteName(basename: string): string { // 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(); // Get display settings // const displayMode = this.plugin.settings.displayMode; // Removed const styleMode = this.plugin.settings.styleMode; const showNavigation = this.plugin.settings.showNavigation; // Create header const headerContainer = container.createDiv({ cls: 'folder-view-header' }); headerContainer.createEl("h6", { text: "Current Folder Notes" }); // 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 : ''; 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; } // Sort files by name/sequence const sequenceWithPrefixOrLongest = (str: string) => { const tMatches = str.match(/T(\d+)/); 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) || []; return Math.max(...matches.map(numStr => parseInt(numStr)), 0); }; files.sort((a, b) => sequenceWithPrefixOrLongest(a.basename) - sequenceWithPrefixOrLongest(b.basename)); const currentFileIndex = files.findIndex(file => file.path === currentFilePath); // 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]; const prevLink = navHeader.createDiv({ cls: 'nav-link prev-note' }); const prevIcon = prevLink.createSpan({ cls: 'nav-icon' }); prevIcon.innerHTML = ``; prevLink.createSpan({ cls: 'nav-direction', text: 'Previous' }); const prevTitle = prevLink.createSpan({ cls: 'nav-title', text: this.getShortNoteName(prevNote.basename) }); prevLink.setAttribute('aria-label', `Previous: ${prevNote.basename}`); prevLink.addEventListener('click', () => { // Use the full path to handle duplicate file names this.app.workspace.openLinkText(prevNote.path, ''); }); } // Next note if (currentFileIndex < files.length - 1) { const nextNote = files[currentFileIndex + 1]; const nextLink = navHeader.createDiv({ cls: 'nav-link next-note' }); nextLink.createSpan({ cls: 'nav-direction', text: 'Next' }); const nextIcon = nextLink.createSpan({ cls: 'nav-icon' }); nextIcon.innerHTML = ``; const nextTitle = nextLink.createSpan({ cls: 'nav-title', text: this.getShortNoteName(nextNote.basename) }); nextLink.setAttribute('aria-label', `Next: ${nextNote.basename}`); nextLink.addEventListener('click', () => { // Use the full path to handle duplicate file names this.app.workspace.openLinkText(nextNote.path, ''); }); } // Current note outline - LAZY LOAD if (this.plugin.settings.includeCurrentFileOutline) { const currentFile = files[currentFileIndex]; // Create the outline section first const outlineSection = navigationSection.createDiv({ cls: 'outline-section' }); // 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' }); // Add a loading indicator const loadingEl = outlineSection.createEl('div', { cls: 'loading-indicator', text: 'Loading outline...' }); // Load content asynchronously setTimeout(async () => { const fileContent = await this.app.vault.read(currentFile); if (fileContent) { // Remove loading indicator loadingEl.remove(); // Create the headings this.createClickableHeadings(outlineSection, fileContent, currentFile.path, true); } else { loadingEl.setText('No content found'); } }, 10); } // Add separator mainContent.createDiv({ cls: 'section-separator' }); } // Always show flat list view const listContainer = mainContent.createDiv({ cls: 'notes-flat-list' }); listContainer.createEl('div', { cls: 'folder-section-header', text: 'FOLDER NOTES' }); // OPTIMIZE: Limit the number of files displayed at once to prevent memory issues const MAX_FILES_DISPLAY = this.plugin.settings.maxFilesDisplay; // Use the setting value const displayedFiles = files.slice(0, MAX_FILES_DISPLAY); const hasMoreFiles = files.length > MAX_FILES_DISPLAY; // Process files in batches to prevent UI freezing const BATCH_SIZE = 20; const processBatch = async (startIdx: number) => { const endIdx = Math.min(startIdx + BATCH_SIZE, displayedFiles.length); for (let i = startIdx; i < endIdx; i++) { const file = displayedFiles[i]; const isCurrentFile = file.path === currentFilePath; const fileContainer = listContainer.createDiv({ cls: isCurrentFile ? 'file-container current' : 'file-container' }); this.createFileLink(fileContainer, file, currentFilePath, parentFolderPath); // Only load outlines if explicitly enabled if (this.plugin.settings.includeListFileOutlines) { // Add a placeholder initially const outlinePlaceholder = fileContainer.createEl('div', { cls: 'outline-placeholder', text: 'Loading outline...' }); // Load outline in the next animation frame requestAnimationFrame(async () => { try { const fileContent = await this.app.vault.read(file); outlinePlaceholder.remove(); if (fileContent) { this.createClickableHeadings(fileContainer, fileContent, file.path, isCurrentFile); } } catch (err) { console.error("Error loading outline:", err); outlinePlaceholder.setText("Failed to load outline"); } }); } } // Process next batch if needed if (endIdx < displayedFiles.length) { setTimeout(() => processBatch(endIdx), 50); } // Show message if we're not displaying all files if (hasMoreFiles && endIdx === displayedFiles.length) { listContainer.createEl('div', { cls: 'more-files-message', text: `Showing ${MAX_FILES_DISPLAY} of ${files.length} notes. Use filters to narrow results.` }); } }; // Start processing the first batch processBatch(0); // Apply display classes container.classList.add('compact-mode'); // Always use compact mode // container.classList.toggle('expanded-mode', displayMode === 'expanded'); // Removed 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 && (!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', () => { // Use the full path instead of just basename to handle duplicate file names this.app.workspace.openLinkText(file.path, ''); }); } // Helper method to show empty state 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', { 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', { text: `🔍 No notes match the current filter "${this.plugin.settings.includeTitleFilter}".`, cls: 'empty-state-highlight' }); } else if (this.plugin.settings.excludeTitlesFilter) { emptyStateDiv.createEl('p', { text: `All notes are currently filtered out by the exclude pattern "${this.plugin.settings.excludeTitlesFilter}"`, }); } else { emptyStateDiv.createEl('p', { text: 'This folder is empty', }); } if (currentFilePath && activeFile) { emptyStateDiv.createEl('p', { text: 'Showing outline of current note:', cls: 'empty-state-subtext' }); // Add a loading indicator const loadingEl = emptyStateDiv.createEl('div', { cls: 'loading-indicator', text: 'Loading outline...' }); // Load content asynchronously with timeout to prevent blocking UI setTimeout(async () => { try { const fileContent = await this.app.vault.read(activeFile); if (fileContent) { // Remove loading indicator loadingEl.remove(); // Create the headings with a limited subset of content if it's large const contentSize = fileContent.length; if (contentSize > 100000) { // If content is very large (>100KB) const truncatedContent = fileContent.substring(0, 100000); this.createClickableHeadings(container as HTMLElement, truncatedContent, currentFilePath, true); container.createEl('p', { text: 'Note is very large. Only showing first portion of headings.', cls: 'empty-state-subtext' }); } else { this.createClickableHeadings(container as HTMLElement, fileContent, currentFilePath, true); } } else { loadingEl.setText('No content found'); } } catch (err) { console.error("Error loading outline:", err); loadingEl.setText("Failed to load outline"); } }, 10); } } private applyFilters(files: TFile[], parentFolderPath: string): TFile[] { // Create a single-pass filter function to avoid multiple array iterations return files.filter(file => { // Skip files in subfolders if that setting is disabled if (!this.plugin.settings.includeSubfolderNotes && file.path.substring(parentFolderPath.length + 1).includes('/')) { return false; } const lowerBasename = file.basename.toLowerCase(); // Check exclude filter const excludeFilter = this.plugin.settings.excludeTitlesFilter; if (excludeFilter && excludeFilter.length > 0) { const excludeWords = excludeFilter.split(/[,\s]+/).filter(word => word.trim().length > 0) .map(word => word.trim().toLowerCase()); if (excludeWords.length > 0 && excludeWords.some(word => lowerBasename.includes(word))) { return false; } } // Check include filter const includesFilter = this.plugin.settings.includeTitleFilter; if (includesFilter && includesFilter.length > 0) { const includeWords = includesFilter.split(/[,\s]+/).filter(word => word.trim().length > 0) .map(word => word.trim().toLowerCase()); if (includeWords.length > 0 && !includeWords.some(word => lowerBasename.includes(word))) { return false; } } return true; }); } } class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab { plugin: CurrentFolderNotesDisplay; constructor(app: App, plugin: CurrentFolderNotesDisplay) { super(app, plugin); this.plugin = plugin; } display(): void { const {containerEl} = this; containerEl.empty(); // Add some CSS for styling the settings containerEl.createEl('style', { text: ` .settings-section { margin-bottom: 24px; border-bottom: 1px solid var(--background-modifier-border); padding-bottom: 16px; } .settings-section-header { margin-bottom: 12px; font-size: 16px; font-weight: 600; color: var(--text-normal); } .settings-section-description { margin-bottom: 12px; font-size: 13px; color: var(--text-muted); } .setting-item { border-top: none !important; padding-top: 12px !important; } .setting-item-description { font-size: 12px !important; } ` }); // Title section with plugin description const titleEl = containerEl.createDiv({ cls: 'settings-section' }); titleEl.createEl('h2', { text: 'Current Folder Notes Settings' }); titleEl.createEl('p', { cls: 'settings-section-description', text: 'Configure how notes from the current folder are displayed in the panel.' }); // Filter settings section const filtersEl = containerEl.createDiv({ cls: 'settings-section' }); filtersEl.createEl('h3', { cls: 'settings-section-header', text: '📂 Filter Settings' }); 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.') .addText(text => text .setPlaceholder('_index, draft, template') .setValue(this.plugin.settings.excludeTitlesFilter) .onChange(async (value) => { 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.') .addText(text => text .setPlaceholder('chapter, section, lesson') .setValue(this.plugin.settings.includeTitleFilter) .onChange(async (value) => { this.plugin.settings.includeTitleFilter = value; await this.plugin.saveSettings(); })); // Display options section const displayEl = containerEl.createDiv({ cls: 'settings-section' }); displayEl.createEl('h3', { cls: 'settings-section-header', text: 'đŸ–Ĩī¸ Display Options' }); displayEl.createEl('p', { cls: 'settings-section-description', text: 'Control how notes and their contents are displayed in the panel.' }); new Setting(displayEl) .setName('Show navigation') .setDesc('Show navigation links for previous and next notes.') .addToggle(toggle => toggle .setValue(this.plugin.settings.showNavigation) .onChange(async (value) => { this.plugin.settings.showNavigation = value; await this.plugin.saveSettings(); this.plugin.refreshView(); })); new Setting(displayEl) .setName('Pretty title case') .setDesc('Convert note titles to Title Case for better readability.') .addToggle(toggle => toggle .setValue(this.plugin.settings.prettyTitleCase) .onChange(async (value) => { this.plugin.settings.prettyTitleCase = value; await this.plugin.saveSettings(); })); new Setting(displayEl) .setName('Style mode') .setDesc('Choose between minimal, fancy, and neobrutalist styles.') .addDropdown(dropdown => dropdown .addOption('minimal', 'Minimal') .addOption('fancy', 'Fancy') .addOption('neobrutalist', 'Neobrutalist') .setValue(this.plugin.settings.styleMode) .onChange(async (value) => { this.plugin.settings.styleMode = value as 'minimal' | 'fancy' | 'neobrutalist'; await this.plugin.saveSettings(); this.plugin.refreshView(); })); new Setting(displayEl) .setName('Bigger text') .setDesc('Make all the text bigger for easier clicking.') .addToggle(toggle => toggle .setValue(this.plugin.settings.biggerText) .onChange(async (value) => { this.plugin.settings.biggerText = value; await this.plugin.saveSettings(); this.plugin.refreshView(); })); // Add this after the existing biggerText setting new Setting(displayEl) .setName('Mobile-only bigger text') .setDesc('Only apply bigger text when using Obsidian on mobile devices.') .addToggle(toggle => toggle .setValue(this.plugin.settings.biggerTextMobileOnly) .onChange(async (value) => { this.plugin.settings.biggerTextMobileOnly = value; await this.plugin.saveSettings(); this.plugin.refreshView(); })); // Content options section const contentEl = containerEl.createDiv({ cls: 'settings-section' }); contentEl.createEl('h3', { cls: 'settings-section-header', text: '📝 Content Options' }); 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.') .addToggle(toggle => toggle .setValue(this.plugin.settings.includeSubfolderNotes) .onChange(async (value) => { 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.') .addToggle(toggle => toggle .setValue(this.plugin.settings.includeCurrentFileOutline) .onChange(async (value) => { 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.') .addToggle(toggle => toggle .setValue(this.plugin.settings.includeListFileOutlines) .onChange(async (value) => { this.plugin.settings.includeListFileOutlines = value; await this.plugin.saveSettings(); })); new Setting(contentEl) .setName('Maximum files to display') .setDesc('Limit the number of files shown in the folder notes list. Use a lower value for better performance in large folders.') .addSlider(slider => slider .setLimits(10, 500, 10) .setValue(this.plugin.settings.maxFilesDisplay) .setDynamicTooltip() .onChange(async (value) => { this.plugin.settings.maxFilesDisplay = value; await this.plugin.saveSettings(); this.plugin.refreshView(); })); // About section with help text const aboutEl = containerEl.createDiv({ cls: 'settings-section' }); aboutEl.createEl('h3', { cls: 'settings-section-header', text: 'â„šī¸ About' }); 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') .setDesc('Manually refresh the Current Folder Notes panel to apply changes.') .addButton(button => button .setButtonText('Refresh Now') .setCta() .onClick(() => { this.plugin.refreshView(); new Notice('Current Folder Notes panel refreshed'); })); } }