optimizing the code

This commit is contained in:
caffae 2025-03-12 12:53:52 +08:00
parent ecb0aaa97d
commit ba1ef2f69a

369
main.ts
View file

@ -10,7 +10,6 @@ interface CurrentFolderNotesDisplaySettings {
includeSubfolderNotes: boolean;
includeCurrentFileOutline: boolean;
includeListFileOutlines: boolean;
displayMode: 'compact' | 'expanded';
styleMode: 'minimal' | 'fancy' | 'neobrutalist';
showNavigation: boolean;
biggerText: boolean; // Add this line
@ -24,7 +23,6 @@ const DEFAULT_SETTINGS: Partial<CurrentFolderNotesDisplaySettings> = {
includeSubfolderNotes: false,
includeCurrentFileOutline: true,
includeListFileOutlines: false,
displayMode: 'expanded',
styleMode: 'fancy',
showNavigation: false,
biggerText: false,
@ -67,25 +65,20 @@ export default class CurrentFolderNotesDisplay extends Plugin {
}
});
// when file is changes (opened) in the editor, update the view
this.registerEvent(this.app.workspace.on('file-open', async (file) => {
this.refreshView();
}));
// 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
};
// when a file is deleted, update the view
this.registerEvent(this.app.vault.on('delete', async (file) => {
this.refreshView();
}));
// when a file is created, update the view
this.registerEvent(this.app.vault.on('create', async (file) => {
this.refreshView();
}));
// when a file is renamed, update the view
this.registerEvent(this.app.vault.on('rename', async (file) => {
this.refreshView();
}));
// 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() {
@ -233,50 +226,81 @@ export class CurrentFolderNotesDisplayView extends ItemView {
// Function to create clickable headings
createClickableHeadings(container: HTMLElement, currentFileContent: string, currentFilePath: string, addExtraHeadingCSS: boolean): void {
const headings: RegExpMatchArray | null = currentFileContent.match(/^(#+)\s+(.*)$/gm);
if (headings) {
headings.forEach((heading: string) => {
const headingLevelMatch: RegExpMatchArray | null = heading.match(/^(#+)/);
if (headingLevelMatch) {
const headingLevel: number = headingLevelMatch[0].length;
let headingText: string = heading.replace(/^(#+)\s+/, '');
// Set a maximum limit on the file content to process for safety
const MAX_CONTENT_SIZE = 500000; // 500KB max processing size
// 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;
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.'
});
}
const p: HTMLElement = container.createEl('p', { text: headingLabel });
p.classList.add('basic-heading');
p.classList.add(`heading-level-${headingLevel}`);
// 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
if (addExtraHeadingCSS) {
p.classList.add('extra-heading-style');
}
// 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];
p.addEventListener('click', async (event) => {
// Prevent default behavior
event.preventDefault();
// 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;
// Use the openLinkText method to navigate to the heading
this.app.workspace.openLinkText('#' + headingText, currentFilePath);
const p: HTMLElement = container.createEl('p', { text: headingLabel });
p.classList.add('basic-heading');
p.classList.add(`heading-level-${headingLevel}`);
// 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');
}
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'
});
}
}
@ -314,7 +338,7 @@ export class CurrentFolderNotesDisplayView extends ItemView {
container.empty();
// Get display settings
const displayMode = this.plugin.settings.displayMode;
// const displayMode = this.plugin.settings.displayMode; // Removed
const styleMode = this.plugin.settings.styleMode;
const showNavigation = this.plugin.settings.showNavigation;
@ -443,25 +467,41 @@ export class CurrentFolderNotesDisplayView extends ItemView {
});
}
// Current note outline
// Current note outline - LAZY LOAD
if (this.plugin.settings.includeCurrentFileOutline) {
const currentFile = files[currentFileIndex];
const fileContent = await this.app.vault.read(currentFile);
if (fileContent) {
const outlineSection = navigationSection.createDiv({ cls: 'outline-section' });
// 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
});
// 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'
});
this.createClickableHeadings(outlineSection, fileContent, currentFile.path, true);
}
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
@ -475,25 +515,69 @@ export class CurrentFolderNotesDisplayView extends ItemView {
text: 'FOLDER NOTES'
});
for (const file of files) {
const isCurrentFile = file.path === currentFilePath;
const fileContainer = listContainer.createDiv({
cls: isCurrentFile ? 'file-container current' : 'file-container'
});
// OPTIMIZE: Limit the number of files displayed at once to prevent memory issues
const MAX_FILES_DISPLAY = 100; // Adjust based on testing
const displayedFiles = files.slice(0, MAX_FILES_DISPLAY);
const hasMoreFiles = files.length > MAX_FILES_DISPLAY;
this.createFileLink(fileContainer, file, currentFilePath, parentFolderPath);
// 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);
if (this.plugin.settings.includeListFileOutlines) {
const fileContent = await this.app.vault.read(file);
if (fileContent) {
this.createClickableHeadings(fileContainer, fileContent, file.path, isCurrentFile);
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.toggle('compact-mode', displayMode === 'compact');
container.classList.toggle('expanded-mode', displayMode === 'expanded');
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');
@ -563,52 +647,79 @@ export class CurrentFolderNotesDisplayView extends ItemView {
cls: 'empty-state-subtext'
});
this.app.vault.read(activeFile).then(fileContent => {
if (fileContent) {
this.createClickableHeadings(container as HTMLElement, fileContent, currentFilePath, true);
}
// 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[] {
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 =>
includeWords.some(word => file.basename.toLowerCase().includes(word))
);
// console.log("[CFN] After include filter:", filteredFiles.length, "removed:", beforeCount - filteredFiles.length);
// 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;
}
}
// 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 =>
!excludeWords.some(word => file.basename.toLowerCase().includes(word))
);
// console.log("[CFN] After exclude filter:", filteredFiles.length, "removed:", beforeCount - filteredFiles.length);
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;
}
}
}
return filteredFiles;
// 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;
});
}
}
@ -721,18 +832,6 @@ class CurrentFolderNotesDisplaySettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
}));
new Setting(displayEl)
.setName('Display mode')
.setDesc('Choose between compact and expanded display modes.')
.addDropdown(dropdown => dropdown
.addOption('compact', 'Compact')
.addOption('expanded', 'Expanded')
.setValue(this.plugin.settings.displayMode)
.onChange(async (value) => {
this.plugin.settings.displayMode = value as 'compact' | 'expanded';
await this.plugin.saveSettings();
}));
new Setting(displayEl)
.setName('Style mode')
.setDesc('Choose between minimal, fancy, and neobrutalist styles.')