2024-03-26 10:57:13 +00:00
|
|
|
|
import { App, Notice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
|
2024-03-23 02:57:56 +00:00
|
|
|
|
import { ItemView, WorkspaceLeaf } from "obsidian";
|
2024-03-26 10:57:13 +00:00
|
|
|
|
import { MarkdownView } from "obsidian";
|
2024-05-17 04:34:41 +00:00
|
|
|
|
import { TFolder } from "obsidian";
|
2022-04-15 18:13:31 +00:00
|
|
|
|
|
2024-03-23 02:57:56 +00:00
|
|
|
|
interface CurrentFolderNotesDisplaySettings {
|
2024-03-23 04:22:55 +00:00
|
|
|
|
excludeTitlesFilter: string;
|
|
|
|
|
|
includeTitleFilter: string;
|
|
|
|
|
|
prettyTitleCase: boolean;
|
|
|
|
|
|
includeSubfolderNotes: boolean;
|
2025-03-08 17:41:17 +00:00
|
|
|
|
includeCurrentFileOutline: boolean;
|
2025-03-08 13:42:12 +00:00
|
|
|
|
includeListFileOutlines: boolean;
|
|
|
|
|
|
styleMode: 'minimal' | 'fancy' | 'neobrutalist';
|
2025-03-08 12:54:03 +00:00
|
|
|
|
showNavigation: boolean;
|
2025-03-08 18:21:42 +00:00
|
|
|
|
biggerText: boolean; // Add this line
|
|
|
|
|
|
biggerTextMobileOnly: boolean; // Add this new setting
|
2025-05-17 11:18:12 +00:00
|
|
|
|
maxFilesDisplay: number; // Add new setting for max files to display
|
2022-04-15 18:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-08 17:41:17 +00:00
|
|
|
|
const DEFAULT_SETTINGS: Partial<CurrentFolderNotesDisplaySettings> = {
|
2024-03-23 04:22:55 +00:00
|
|
|
|
excludeTitlesFilter: '_index',
|
|
|
|
|
|
includeTitleFilter: '',
|
|
|
|
|
|
prettyTitleCase: true,
|
|
|
|
|
|
includeSubfolderNotes: false,
|
2025-03-08 17:41:17 +00:00
|
|
|
|
includeCurrentFileOutline: true,
|
2025-03-08 13:42:12 +00:00
|
|
|
|
includeListFileOutlines: false,
|
2025-03-08 12:54:03 +00:00
|
|
|
|
styleMode: 'fancy',
|
2025-03-08 18:21:42 +00:00
|
|
|
|
showNavigation: false,
|
|
|
|
|
|
biggerText: false,
|
2025-05-17 11:18:12 +00:00
|
|
|
|
biggerTextMobileOnly: false,
|
|
|
|
|
|
maxFilesDisplay: 100 // Default to showing 100 files
|
2022-04-15 18:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-23 02:57:56 +00:00
|
|
|
|
export default class CurrentFolderNotesDisplay extends Plugin {
|
|
|
|
|
|
settings: CurrentFolderNotesDisplaySettings;
|
2025-03-08 12:54:03 +00:00
|
|
|
|
public leaves: WorkspaceLeaf[] = [];
|
2022-04-15 18:13:31 +00:00
|
|
|
|
|
2024-03-23 04:22:55 +00:00
|
|
|
|
fileChangeHandler(file: TFile) {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
if (file instanceof TFile) {
|
|
|
|
|
|
this.load();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-03-23 04:22:55 +00:00
|
|
|
|
|
2022-04-15 18:13:31 +00:00
|
|
|
|
async onload() {
|
|
|
|
|
|
await this.loadSettings();
|
2025-03-08 17:41:17 +00:00
|
|
|
|
|
2025-03-08 18:21:42 +00:00
|
|
|
|
// Load custom styles
|
|
|
|
|
|
this.loadStyles();
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 17:41:17 +00:00
|
|
|
|
// This adds a settings tab so the user can configure various aspects of the plugin
|
2024-03-23 04:22:55 +00:00
|
|
|
|
this.addSettingTab(new CurrentFolderNotesDisplaySettingTab(this.app, this));
|
2025-03-08 17:41:17 +00:00
|
|
|
|
|
2025-03-10 14:36:34 +00:00
|
|
|
|
// add a panel to the right sidebar - view
|
2025-03-08 17:41:17 +00:00
|
|
|
|
this.registerView(VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY, (leaf) => new CurrentFolderNotesDisplayView(leaf, this));
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 17:41:17 +00:00
|
|
|
|
// Add a ribbon icon
|
2024-03-27 07:49:26 +00:00
|
|
|
|
this.addRibbonIcon('folder', 'Activate folder notes display', () => {
|
2024-03-23 02:57:56 +00:00
|
|
|
|
this.activateView();
|
2022-04-15 18:13:31 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-03-10 14:36:34 +00:00
|
|
|
|
// Add a command to open the view
|
2024-03-23 05:49:16 +00:00
|
|
|
|
this.addCommand({
|
|
|
|
|
|
id: 'activate-folder-notes-display',
|
2024-03-26 10:25:21 +00:00
|
|
|
|
name: 'Open Pane',
|
2024-03-23 05:49:16 +00:00
|
|
|
|
callback: () => {
|
|
|
|
|
|
this.activateView();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// 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));
|
2022-04-15 18:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-29 14:15:35 +00:00
|
|
|
|
async onunload() {
|
2024-05-17 04:34:41 +00:00
|
|
|
|
console.log('unloading plugin');
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 10:19:16 +00:00
|
|
|
|
// 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();
|
|
|
|
|
|
});
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 10:19:16 +00:00
|
|
|
|
// Clear the leaves array
|
|
|
|
|
|
this.leaves = [];
|
2022-04-15 18:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-23 02:57:56 +00:00
|
|
|
|
async activateView() {
|
|
|
|
|
|
const { workspace } = this.app;
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 10:19:16 +00:00
|
|
|
|
// Clean up existing leaves first to prevent duplicates
|
|
|
|
|
|
const existingLeaves = workspace.getLeavesOfType(VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY);
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 10:19:16 +00:00
|
|
|
|
// Keep only the first leaf if multiple exist
|
|
|
|
|
|
if (existingLeaves.length > 1) {
|
|
|
|
|
|
for (let i = 1; i < existingLeaves.length; i++) {
|
|
|
|
|
|
existingLeaves[i].detach();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2024-03-23 02:57:56 +00:00
|
|
|
|
let leaf: WorkspaceLeaf | null = null;
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 10:19:16 +00:00
|
|
|
|
if (existingLeaves.length) {
|
2024-03-23 02:57:56 +00:00
|
|
|
|
// A leaf with our view already exists, use that
|
2025-03-08 10:19:16 +00:00
|
|
|
|
leaf = existingLeaves[0];
|
2024-03-23 02:57:56 +00:00
|
|
|
|
} 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) {
|
2024-12-11 06:23:28 +00:00
|
|
|
|
await leaf.setViewState({ type: VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY, active: true });
|
2025-03-08 10:19:16 +00:00
|
|
|
|
// Only add to leaves array if it's a new leaf
|
2024-12-11 06:23:28 +00:00
|
|
|
|
this.leaves.push(leaf);
|
2024-03-23 02:57:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!leaf) {
|
|
|
|
|
|
new Notice('Could not create a new leaf for the view');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
workspace.revealLeaf(leaf);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-23 05:46:45 +00:00
|
|
|
|
async refreshView() {
|
|
|
|
|
|
const { workspace } = this.app;
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Refresh view triggered");
|
2024-03-23 05:46:45 +00:00
|
|
|
|
const leaves = workspace.getLeavesOfType(VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY);
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Found leaves:", leaves.length);
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 10:19:16 +00:00
|
|
|
|
if (leaves.length > 1) {
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Multiple leaves found, cleaning up extras");
|
2024-12-29 14:16:55 +00:00
|
|
|
|
for (let i = 1; i < leaves.length; i++) {
|
|
|
|
|
|
leaves[i].detach();
|
|
|
|
|
|
}
|
2025-03-08 10:19:16 +00:00
|
|
|
|
}
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 10:19:16 +00:00
|
|
|
|
if (leaves.length === 1) {
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Updating existing view");
|
2025-06-12 15:53:03 +00:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
2025-03-10 14:36:34 +00:00
|
|
|
|
} else if (leaves.length === 0) {
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] No view found, creating new one");
|
2024-03-23 05:46:45 +00:00
|
|
|
|
this.activateView();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-15 18:13:31 +00:00
|
|
|
|
async loadSettings() {
|
2025-03-08 17:41:17 +00:00
|
|
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
2022-04-15 18:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async saveSettings() {
|
|
|
|
|
|
await this.saveData(this.settings);
|
|
|
|
|
|
}
|
2025-03-08 18:21:42 +00:00
|
|
|
|
|
|
|
|
|
|
// 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);
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 18:21:42 +00:00
|
|
|
|
// Register the stylesheet to be removed when the plugin unloads
|
|
|
|
|
|
this.register(() => styleEl.remove());
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 18:21:42 +00:00
|
|
|
|
// 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;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2022-04-15 18:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-23 02:57:56 +00:00
|
|
|
|
export const VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY = "current-folder-notes-view";
|
|
|
|
|
|
|
|
|
|
|
|
export class CurrentFolderNotesDisplayView extends ItemView {
|
|
|
|
|
|
plugin: CurrentFolderNotesDisplay;
|
2024-03-27 07:42:01 +00:00
|
|
|
|
|
2024-03-23 04:22:55 +00:00
|
|
|
|
constructor(leaf: WorkspaceLeaf, plugin: CurrentFolderNotesDisplay) {
|
2024-03-23 02:57:56 +00:00
|
|
|
|
super(leaf);
|
2024-03-23 04:22:55 +00:00
|
|
|
|
this.plugin = plugin;
|
2022-04-15 18:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-23 02:57:56 +00:00
|
|
|
|
getViewType() {
|
|
|
|
|
|
return VIEW_TYPE_CURRENT_FOLDER_NOTES_DISPLAY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getDisplayText() {
|
2024-03-27 07:42:01 +00:00
|
|
|
|
return "Current Folder Notes";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getIcon(): string {
|
|
|
|
|
|
return 'folder';
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2024-03-23 02:57:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async onOpen() {
|
2024-03-23 04:22:55 +00:00
|
|
|
|
|
|
|
|
|
|
await this.displayNotesInCurrentFolder();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async onClose() {
|
2025-03-08 10:19:16 +00:00
|
|
|
|
// 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-03-23 04:22:55 +00:00
|
|
|
|
|
2024-03-26 10:57:13 +00:00
|
|
|
|
// main.ts
|
|
|
|
|
|
|
|
|
|
|
|
// Function to create clickable headings
|
2024-08-23 19:09:22 +00:00
|
|
|
|
createClickableHeadings(container: HTMLElement, currentFileContent: string, currentFilePath: string, addExtraHeadingCSS: boolean): void {
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// 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.'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// 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');
|
|
|
|
|
|
}
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
p.addEventListener('click', (event) => {
|
|
|
|
|
|
// Prevent default behavior
|
|
|
|
|
|
event.preventDefault();
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// Use the openLinkText method to navigate to the heading
|
|
|
|
|
|
this.app.workspace.openLinkText('#' + headingText, currentFilePath);
|
2024-08-23 19:09:22 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// Clear any text selection
|
|
|
|
|
|
const selection = window.getSelection();
|
|
|
|
|
|
if (selection) {
|
|
|
|
|
|
selection.removeAllRanges();
|
2024-08-23 19:09:22 +00:00
|
|
|
|
}
|
2025-03-12 04:53:52 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 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');
|
2024-03-26 10:57:13 +00:00
|
|
|
|
}
|
2025-03-12 04:53:52 +00:00
|
|
|
|
|
|
|
|
|
|
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'
|
2024-03-26 10:57:13 +00:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-06-17 05:25:53 +00:00
|
|
|
|
// Function to extract alias from heading text
|
|
|
|
|
|
extractAlias(headingText: string): string {
|
|
|
|
|
|
const matches = headingText.match(/\[\[.*\|(.*?)\]\]/);
|
|
|
|
|
|
return matches ? matches[1] : headingText;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
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]}`;
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Try to find Y numbers next (e.g., Y1, Y2023)
|
|
|
|
|
|
const yMatch = basename.match(/Y(\d+)/);
|
|
|
|
|
|
if (yMatch) return `Y${yMatch[1]}`;
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Try to find any numbers
|
|
|
|
|
|
const numberMatch = basename.match(/\d+/);
|
|
|
|
|
|
if (numberMatch) return numberMatch[0];
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Try to find text after dash
|
|
|
|
|
|
const dashMatch = basename.match(/-\s*(.+)$/);
|
|
|
|
|
|
if (dashMatch) return dashMatch[1];
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// If nothing else matches, return the basename
|
|
|
|
|
|
return basename;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-23 05:46:45 +00:00
|
|
|
|
async displayNotesInCurrentFolder(): Promise<void> {
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Starting displayNotesInCurrentFolder");
|
2025-03-08 12:54:03 +00:00
|
|
|
|
const container = this.containerEl.children[1] as HTMLElement;
|
2024-03-23 02:57:56 +00:00
|
|
|
|
container.empty();
|
|
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Get display settings
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// const displayMode = this.plugin.settings.displayMode; // Removed
|
2025-03-08 12:54:03 +00:00
|
|
|
|
const styleMode = this.plugin.settings.styleMode;
|
|
|
|
|
|
const showNavigation = this.plugin.settings.showNavigation;
|
2024-03-23 02:57:56 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Create header
|
|
|
|
|
|
const headerContainer = container.createDiv({ cls: 'folder-view-header' });
|
|
|
|
|
|
headerContainer.createEl("h6", { text: "Current Folder Notes" });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
|
|
|
|
|
// Get current file info and folder path
|
2024-03-23 02:57:56 +00:00
|
|
|
|
const activeFile = this.app.workspace.getActiveFile();
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Active file:", activeFile?.path);
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2024-03-23 02:57:56 +00:00
|
|
|
|
const currentFilePath = activeFile ? activeFile.path : '';
|
2025-03-10 14:36:34 +00:00
|
|
|
|
let parentFolderPath = ''; // Don't default to root
|
|
|
|
|
|
|
|
|
|
|
|
if (currentFilePath) {
|
|
|
|
|
|
const lastSlashIndex = currentFilePath.lastIndexOf('/');
|
|
|
|
|
|
parentFolderPath = lastSlashIndex > 0 ? currentFilePath.substring(0, lastSlashIndex) : '';
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Parent folder path from current file:", parentFolderPath);
|
2025-03-10 14:36:34 +00:00
|
|
|
|
} else if (this.app.workspace.getActiveViewOfType(MarkdownView)) {
|
|
|
|
|
|
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Found active view:", activeView?.file?.path);
|
2025-03-10 14:36:34 +00:00
|
|
|
|
if (activeView?.file) {
|
|
|
|
|
|
const viewFilePath = activeView.file.path;
|
|
|
|
|
|
const lastSlashIndex = viewFilePath.lastIndexOf('/');
|
|
|
|
|
|
parentFolderPath = lastSlashIndex > 0 ? viewFilePath.substring(0, lastSlashIndex) : '';
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Parent folder path from view:", parentFolderPath);
|
2025-03-10 14:36:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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'
|
2025-03-08 12:54:03 +00:00
|
|
|
|
});
|
2025-03-10 14:36:34 +00:00
|
|
|
|
return;
|
2025-03-08 12:54:03 +00:00
|
|
|
|
}
|
2024-03-23 02:57:56 +00:00
|
|
|
|
|
2025-03-10 14:36:34 +00:00
|
|
|
|
// 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';
|
|
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Get and filter files
|
2024-05-17 04:34:41 +00:00
|
|
|
|
let folder = this.app.vault.getAbstractFileByPath(parentFolderPath);
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Found folder:", folder?.path);
|
2025-03-08 12:54:03 +00:00
|
|
|
|
let files: TFile[] = [];
|
2024-05-17 04:34:41 +00:00
|
|
|
|
if (folder instanceof TFolder) {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
files = folder.children.filter((file: any) => file instanceof TFile) as TFile[];
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Initial files count:", files.length);
|
2024-05-17 04:34:41 +00:00
|
|
|
|
}
|
2024-03-23 04:22:55 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
files = this.applyFilters(files, parentFolderPath);
|
2025-03-11 07:32:03 +00:00
|
|
|
|
// console.log("[CFN] Files after filtering:", files.length);
|
|
|
|
|
|
// console.log("[CFN] Current filters - Include:", this.plugin.settings.includeTitleFilter, "Exclude:", this.plugin.settings.excludeTitlesFilter);
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
if (files.length === 0) {
|
2025-03-08 17:41:17 +00:00
|
|
|
|
this.showEmptyState(container, activeFile, currentFilePath, files);
|
2025-03-08 12:54:03 +00:00
|
|
|
|
return;
|
2024-03-23 04:22:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Sort files by name/sequence
|
|
|
|
|
|
const sequenceWithPrefixOrLongest = (str: string) => {
|
|
|
|
|
|
const tMatches = str.match(/T(\d+)/);
|
2025-03-10 14:36:34 +00:00
|
|
|
|
if (tMatches) return parseInt(tMatches[1]);
|
2025-03-08 12:54:03 +00:00
|
|
|
|
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);
|
|
|
|
|
|
};
|
2024-03-26 10:57:13 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
files.sort((a, b) => sequenceWithPrefixOrLongest(a.basename) - sequenceWithPrefixOrLongest(b.basename));
|
|
|
|
|
|
const currentFileIndex = files.findIndex(file => file.path === currentFilePath);
|
2024-03-23 02:57:56 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Create main content container
|
|
|
|
|
|
const mainContent = container.createDiv({ cls: 'main-content' });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Show navigation section if enabled and we have a current file
|
|
|
|
|
|
if (showNavigation && currentFileIndex !== -1) {
|
|
|
|
|
|
const navigationSection = mainContent.createDiv({ cls: 'navigation-section' });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Navigation header with prev/next
|
|
|
|
|
|
const navHeader = navigationSection.createDiv({ cls: 'navigation-header' });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// 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 = `<svg viewBox="0 0 100 100" class="arrow-left" width="16" height="16"><path fill="currentColor" stroke="currentColor" d="M 60,20 L 30,50 L 60,80"></path></svg>`;
|
|
|
|
|
|
prevLink.createSpan({ cls: 'nav-direction', text: 'Previous' });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
const prevTitle = prevLink.createSpan({
|
|
|
|
|
|
cls: 'nav-title',
|
2025-03-08 12:54:03 +00:00
|
|
|
|
text: this.getShortNoteName(prevNote.basename)
|
2024-03-23 04:56:38 +00:00
|
|
|
|
});
|
2025-03-08 12:54:03 +00:00
|
|
|
|
prevLink.setAttribute('aria-label', `Previous: ${prevNote.basename}`);
|
|
|
|
|
|
prevLink.addEventListener('click', () => {
|
2025-05-17 11:28:51 +00:00
|
|
|
|
// Use the full path to handle duplicate file names
|
|
|
|
|
|
this.app.workspace.openLinkText(prevNote.path, '');
|
2025-03-08 12:54:03 +00:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2024-03-23 04:56:38 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// 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 = `<svg viewBox="0 0 100 100" class="arrow-right" width="16" height="16"><path fill="currentColor" stroke="currentColor" d="M 40,20 L 70,50 L 40,80"></path></svg>`;
|
2025-03-10 14:36:34 +00:00
|
|
|
|
const nextTitle = nextLink.createSpan({
|
|
|
|
|
|
cls: 'nav-title',
|
2025-03-08 12:54:03 +00:00
|
|
|
|
text: this.getShortNoteName(nextNote.basename)
|
|
|
|
|
|
});
|
|
|
|
|
|
nextLink.setAttribute('aria-label', `Next: ${nextNote.basename}`);
|
|
|
|
|
|
nextLink.addEventListener('click', () => {
|
2025-05-17 11:28:51 +00:00
|
|
|
|
// Use the full path to handle duplicate file names
|
|
|
|
|
|
this.app.workspace.openLinkText(nextNote.path, '');
|
2025-03-08 12:54:03 +00:00
|
|
|
|
});
|
2024-03-23 04:56:38 +00:00
|
|
|
|
}
|
2025-03-08 12:54:03 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// Current note outline - LAZY LOAD
|
2025-03-08 17:41:17 +00:00
|
|
|
|
if (this.plugin.settings.includeCurrentFileOutline) {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
const currentFile = files[currentFileIndex];
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// Create the outline section first
|
|
|
|
|
|
const outlineSection = navigationSection.createDiv({ cls: 'outline-section' });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// 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);
|
2024-03-23 04:22:55 +00:00
|
|
|
|
}
|
2024-03-23 02:57:56 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Add separator
|
|
|
|
|
|
mainContent.createDiv({ cls: 'section-separator' });
|
2024-03-23 04:22:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Always show flat list view
|
2025-03-08 17:41:17 +00:00
|
|
|
|
const listContainer = mainContent.createDiv({ cls: 'notes-flat-list' });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
listContainer.createEl('div', {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
cls: 'folder-section-header',
|
|
|
|
|
|
text: 'FOLDER NOTES'
|
|
|
|
|
|
});
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// OPTIMIZE: Limit the number of files displayed at once to prevent memory issues
|
2025-05-17 11:18:12 +00:00
|
|
|
|
const MAX_FILES_DISPLAY = this.plugin.settings.maxFilesDisplay; // Use the setting value
|
2025-03-12 04:53:52 +00:00
|
|
|
|
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);
|
2024-03-26 10:57:13 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// 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...'
|
|
|
|
|
|
});
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// 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");
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2024-08-23 19:09:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-03-12 04:53:52 +00:00
|
|
|
|
|
|
|
|
|
|
// 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);
|
2024-08-23 19:09:22 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Apply display classes
|
2025-03-12 04:53:52 +00:00
|
|
|
|
container.classList.add('compact-mode'); // Always use compact mode
|
|
|
|
|
|
// container.classList.toggle('expanded-mode', displayMode === 'expanded'); // Removed
|
2025-03-08 12:54:03 +00:00
|
|
|
|
container.classList.toggle('minimal-style', styleMode === 'minimal');
|
|
|
|
|
|
container.classList.toggle('fancy-style', styleMode === 'fancy');
|
2025-03-08 13:42:12 +00:00
|
|
|
|
container.classList.toggle('neobrutalist-style', styleMode === 'neobrutalist');
|
2025-03-10 14:36:34 +00:00
|
|
|
|
const shouldApplyBiggerText = this.plugin.settings.biggerText &&
|
2025-03-08 18:21:42 +00:00
|
|
|
|
(!this.plugin.settings.biggerTextMobileOnly || (this.app as any).isMobile);
|
|
|
|
|
|
container.classList.toggle('bigger-text', shouldApplyBiggerText);
|
2025-03-08 17:41:17 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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 });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
if (this.plugin.settings.prettyTitleCase) {
|
|
|
|
|
|
a.innerText = a.innerText.replace(/\w\S*/g, function(txt) {
|
|
|
|
|
|
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Apply styling
|
|
|
|
|
|
a.className = 'folder-notes-style';
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// If current file, add special styling
|
|
|
|
|
|
if (file.path === currentFilePath) {
|
|
|
|
|
|
a.className += ' current-file';
|
|
|
|
|
|
a.innerText = '\u2605 ' + a.innerText;
|
|
|
|
|
|
}
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Add hover effects
|
|
|
|
|
|
a.onmouseover = () => a.classList.add('hover-style-file');
|
|
|
|
|
|
a.onmouseout = () => a.classList.remove('hover-style-file');
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Add click handler to open the file
|
|
|
|
|
|
a.addEventListener('click', () => {
|
2025-05-17 11:28:51 +00:00
|
|
|
|
// Use the full path instead of just basename to handle duplicate file names
|
|
|
|
|
|
this.app.workspace.openLinkText(file.path, '');
|
2025-03-08 12:54:03 +00:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2024-08-23 19:09:22 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Helper method to show empty state
|
2025-03-08 17:41:17 +00:00
|
|
|
|
private showEmptyState(container: HTMLElement, activeFile: TFile | null, currentFilePath: string, files: TFile[]): void {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
const emptyStateDiv = container.createDiv({ cls: 'empty-state-message' });
|
2025-03-08 17:41:17 +00:00
|
|
|
|
if (files.length === 0) {
|
2025-03-10 14:36:34 +00:00
|
|
|
|
emptyStateDiv.createEl('p', {
|
2025-03-08 17:41:17 +00:00
|
|
|
|
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'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-03-08 12:54:03 +00:00
|
|
|
|
if (this.plugin.settings.includeTitleFilter) {
|
2025-03-10 14:36:34 +00:00
|
|
|
|
emptyStateDiv.createEl('p', {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
text: `🔍 No notes match the current filter "${this.plugin.settings.includeTitleFilter}".`,
|
|
|
|
|
|
cls: 'empty-state-highlight'
|
|
|
|
|
|
});
|
|
|
|
|
|
} else if (this.plugin.settings.excludeTitlesFilter) {
|
2025-03-10 14:36:34 +00:00
|
|
|
|
emptyStateDiv.createEl('p', {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
text: `All notes are currently filtered out by the exclude pattern "${this.plugin.settings.excludeTitlesFilter}"`,
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
2025-03-10 14:36:34 +00:00
|
|
|
|
emptyStateDiv.createEl('p', {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
text: 'This folder is empty',
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
if (currentFilePath && activeFile) {
|
|
|
|
|
|
emptyStateDiv.createEl('p', {
|
|
|
|
|
|
text: 'Showing outline of current note:',
|
|
|
|
|
|
cls: 'empty-state-subtext'
|
|
|
|
|
|
});
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// Add a loading indicator
|
|
|
|
|
|
const loadingEl = emptyStateDiv.createEl('div', {
|
|
|
|
|
|
cls: 'loading-indicator',
|
|
|
|
|
|
text: 'Loading outline...'
|
2025-03-08 12:54:03 +00:00
|
|
|
|
});
|
2025-03-12 04:53:52 +00:00
|
|
|
|
|
|
|
|
|
|
// 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);
|
2025-03-08 12:54:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-03-26 10:27:47 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
private applyFilters(files: TFile[], parentFolderPath: string): TFile[] {
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// 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();
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
2024-03-23 04:22:55 +00:00
|
|
|
|
}
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
2024-03-23 04:22:55 +00:00
|
|
|
|
}
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-12 04:53:52 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
});
|
2022-04-15 18:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-23 02:57:56 +00:00
|
|
|
|
class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab {
|
|
|
|
|
|
plugin: CurrentFolderNotesDisplay;
|
2022-04-15 18:13:31 +00:00
|
|
|
|
|
2024-03-23 02:57:56 +00:00
|
|
|
|
constructor(app: App, plugin: CurrentFolderNotesDisplay) {
|
2022-04-15 18:13:31 +00:00
|
|
|
|
super(app, plugin);
|
|
|
|
|
|
this.plugin = plugin;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
display(): void {
|
|
|
|
|
|
const {containerEl} = this;
|
|
|
|
|
|
|
|
|
|
|
|
containerEl.empty();
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
`
|
|
|
|
|
|
});
|
2022-04-15 18:13:31 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Title section with plugin description
|
|
|
|
|
|
const titleEl = containerEl.createDiv({ cls: 'settings-section' });
|
|
|
|
|
|
titleEl.createEl('h2', { text: 'Current Folder Notes Settings' });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
titleEl.createEl('p', {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
cls: 'settings-section-description',
|
|
|
|
|
|
text: 'Configure how notes from the current folder are displayed in the panel.'
|
|
|
|
|
|
});
|
2024-03-23 04:56:38 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Filter settings section
|
|
|
|
|
|
const filtersEl = containerEl.createDiv({ cls: 'settings-section' });
|
|
|
|
|
|
filtersEl.createEl('h3', { cls: 'settings-section-header', text: '📂 Filter Settings' });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
filtersEl.createEl('p', {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
cls: 'settings-section-description',
|
|
|
|
|
|
text: 'Control which notes appear in the folder notes panel.'
|
|
|
|
|
|
});
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
new Setting(filtersEl)
|
2024-03-23 04:56:38 +00:00
|
|
|
|
.setName('Exclude titles filter')
|
2025-03-08 12:54:03 +00:00
|
|
|
|
.setDesc('Notes containing these words will be hidden. Separate multiple words with commas.')
|
2022-04-15 18:13:31 +00:00
|
|
|
|
.addText(text => text
|
2025-03-08 12:54:03 +00:00
|
|
|
|
.setPlaceholder('_index, draft, template')
|
2024-03-23 04:22:55 +00:00
|
|
|
|
.setValue(this.plugin.settings.excludeTitlesFilter)
|
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
|
this.plugin.settings.excludeTitlesFilter = value;
|
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
|
}));
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
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.')
|
2024-03-23 04:22:55 +00:00
|
|
|
|
.addText(text => text
|
2025-03-08 12:54:03 +00:00
|
|
|
|
.setPlaceholder('chapter, section, lesson')
|
2024-03-23 04:22:55 +00:00
|
|
|
|
.setValue(this.plugin.settings.includeTitleFilter)
|
2022-04-15 18:13:31 +00:00
|
|
|
|
.onChange(async (value) => {
|
2024-03-23 04:22:55 +00:00
|
|
|
|
this.plugin.settings.includeTitleFilter = value;
|
2022-04-15 18:13:31 +00:00
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
|
}));
|
2025-03-08 12:54:03 +00:00
|
|
|
|
|
|
|
|
|
|
// Display options section
|
|
|
|
|
|
const displayEl = containerEl.createDiv({ cls: 'settings-section' });
|
|
|
|
|
|
displayEl.createEl('h3', { cls: 'settings-section-header', text: '🖥️ Display Options' });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
displayEl.createEl('p', {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
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();
|
|
|
|
|
|
}));
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
new Setting(displayEl)
|
2024-03-23 04:56:38 +00:00
|
|
|
|
.setName('Pretty title case')
|
2025-03-08 12:54:03 +00:00
|
|
|
|
.setDesc('Convert note titles to Title Case for better readability.')
|
2024-03-23 04:22:55 +00:00
|
|
|
|
.addToggle(toggle => toggle
|
|
|
|
|
|
.setValue(this.plugin.settings.prettyTitleCase)
|
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
|
this.plugin.settings.prettyTitleCase = value;
|
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
|
}));
|
2025-03-08 12:54:03 +00:00
|
|
|
|
|
|
|
|
|
|
new Setting(displayEl)
|
|
|
|
|
|
.setName('Style mode')
|
2025-03-08 13:42:12 +00:00
|
|
|
|
.setDesc('Choose between minimal, fancy, and neobrutalist styles.')
|
2025-03-08 12:54:03 +00:00
|
|
|
|
.addDropdown(dropdown => dropdown
|
|
|
|
|
|
.addOption('minimal', 'Minimal')
|
|
|
|
|
|
.addOption('fancy', 'Fancy')
|
2025-03-08 13:42:12 +00:00
|
|
|
|
.addOption('neobrutalist', 'Neobrutalist')
|
2025-03-08 12:54:03 +00:00
|
|
|
|
.setValue(this.plugin.settings.styleMode)
|
|
|
|
|
|
.onChange(async (value) => {
|
2025-03-08 13:42:12 +00:00
|
|
|
|
this.plugin.settings.styleMode = value as 'minimal' | 'fancy' | 'neobrutalist';
|
2025-03-08 12:54:03 +00:00
|
|
|
|
await this.plugin.saveSettings();
|
2025-03-08 13:42:12 +00:00
|
|
|
|
this.plugin.refreshView();
|
2025-03-08 12:54:03 +00:00
|
|
|
|
}));
|
|
|
|
|
|
|
2025-03-08 18:21:42 +00:00
|
|
|
|
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();
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// Content options section
|
|
|
|
|
|
const contentEl = containerEl.createDiv({ cls: 'settings-section' });
|
|
|
|
|
|
contentEl.createEl('h3', { cls: 'settings-section-header', text: '📝 Content Options' });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
contentEl.createEl('p', {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
cls: 'settings-section-description',
|
|
|
|
|
|
text: 'Configure what content is included in the folder notes panel.'
|
|
|
|
|
|
});
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
new Setting(contentEl)
|
2024-03-23 04:56:38 +00:00
|
|
|
|
.setName('Include subfolder notes')
|
2025-03-08 12:54:03 +00:00
|
|
|
|
.setDesc('Show notes from subfolders within the current folder.')
|
2024-03-23 04:22:55 +00:00
|
|
|
|
.addToggle(toggle => toggle
|
|
|
|
|
|
.setValue(this.plugin.settings.includeSubfolderNotes)
|
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
|
this.plugin.settings.includeSubfolderNotes = value;
|
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
|
}));
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 13:42:12 +00:00
|
|
|
|
new Setting(contentEl)
|
|
|
|
|
|
.setName('Show outline in current file section')
|
|
|
|
|
|
.setDesc('Display headings from the currently active file at the top of the panel.')
|
2024-03-26 10:57:13 +00:00
|
|
|
|
.addToggle(toggle => toggle
|
2025-03-08 17:41:17 +00:00
|
|
|
|
.setValue(this.plugin.settings.includeCurrentFileOutline)
|
2024-03-26 10:57:13 +00:00
|
|
|
|
.onChange(async (value) => {
|
2025-03-08 17:41:17 +00:00
|
|
|
|
this.plugin.settings.includeCurrentFileOutline = value;
|
2024-03-26 10:57:13 +00:00
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
|
}));
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
new Setting(contentEl)
|
2025-03-08 17:41:17 +00:00
|
|
|
|
.setName('Show outline in files list')
|
2025-03-08 13:42:12 +00:00
|
|
|
|
.setDesc('Display headings under each file in the folder notes list.')
|
2024-08-23 19:09:22 +00:00
|
|
|
|
.addToggle(toggle => toggle
|
2025-03-08 13:42:12 +00:00
|
|
|
|
.setValue(this.plugin.settings.includeListFileOutlines)
|
2024-08-23 19:09:22 +00:00
|
|
|
|
.onChange(async (value) => {
|
2025-03-08 13:42:12 +00:00
|
|
|
|
this.plugin.settings.includeListFileOutlines = value;
|
2024-08-23 19:09:22 +00:00
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
|
}));
|
2024-03-23 04:22:55 +00:00
|
|
|
|
|
2025-05-17 11:18:12 +00:00
|
|
|
|
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();
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// About section with help text
|
|
|
|
|
|
const aboutEl = containerEl.createDiv({ cls: 'settings-section' });
|
|
|
|
|
|
aboutEl.createEl('h3', { cls: 'settings-section-header', text: 'ℹ️ About' });
|
2025-03-10 14:36:34 +00:00
|
|
|
|
aboutEl.createEl('p', {
|
2025-03-08 12:54:03 +00:00
|
|
|
|
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.'
|
|
|
|
|
|
});
|
2025-03-10 14:36:34 +00:00
|
|
|
|
|
2025-03-08 12:54:03 +00:00
|
|
|
|
// 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');
|
|
|
|
|
|
}));
|
2022-04-15 18:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-03-08 18:21:42 +00:00
|
|
|
|
|