108 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			108 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import fs from 'fs';
 | 
						|
import path from 'path';
 | 
						|
 | 
						|
/**
 | 
						|
 * Fast metadata loader - no enrichment, no content
 | 
						|
 * Returns only: id, title, path, createdAt, updatedAt
 | 
						|
 * Used for initial UI load to minimize startup time
 | 
						|
 * 
 | 
						|
 * Performance: ~100ms for 1000 files (vs 5-10s for loadVaultNotes)
 | 
						|
 */
 | 
						|
export const loadVaultMetadataOnly = async (vaultPath) => {
 | 
						|
  const notes = [];
 | 
						|
 | 
						|
  const isMarkdownFile = (entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.md');
 | 
						|
 | 
						|
  const normalizeString = (value) => {
 | 
						|
    return value
 | 
						|
      .normalize('NFKD')
 | 
						|
      .replace(/[\u0300-\u036f]/g, '')
 | 
						|
      .trim();
 | 
						|
  };
 | 
						|
 | 
						|
  const slugifySegment = (segment) => {
 | 
						|
    const normalized = normalizeString(segment);
 | 
						|
    const slug = normalized
 | 
						|
      .toLowerCase()
 | 
						|
      .replace(/[^a-z0-9]+/g, '-')
 | 
						|
      .replace(/^-+|-+$/g, '');
 | 
						|
    return slug || normalized.toLowerCase() || segment.toLowerCase();
 | 
						|
  };
 | 
						|
 | 
						|
  const slugifyPath = (relativePath) => {
 | 
						|
    return relativePath
 | 
						|
      .split('/')
 | 
						|
      .map((segment) => slugifySegment(segment))
 | 
						|
      .filter(Boolean)
 | 
						|
      .join('/');
 | 
						|
  };
 | 
						|
 | 
						|
  const extractTitle = (content, fallback) => {
 | 
						|
    const headingMatch = content.match(/^\s*#\s+(.+)$/m);
 | 
						|
    if (headingMatch) {
 | 
						|
      return headingMatch[1].trim();
 | 
						|
    }
 | 
						|
    return fallback;
 | 
						|
  };
 | 
						|
 | 
						|
  const walk = async (currentDir) => {
 | 
						|
    if (!fs.existsSync(currentDir)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let entries = [];
 | 
						|
    try {
 | 
						|
      entries = fs.readdirSync(currentDir, { withFileTypes: true });
 | 
						|
    } catch (err) {
 | 
						|
      console.error(`Failed to read directory ${currentDir}:`, err);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    for (const entry of entries) {
 | 
						|
      const entryPath = path.join(currentDir, entry.name);
 | 
						|
 | 
						|
      if (entry.isDirectory()) {
 | 
						|
        await walk(entryPath);
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      if (!isMarkdownFile(entry)) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      try {
 | 
						|
        // Read file WITHOUT enrichment (fast path)
 | 
						|
        const content = fs.readFileSync(entryPath, 'utf-8');
 | 
						|
        const stats = fs.statSync(entryPath);
 | 
						|
        
 | 
						|
        const relativePathWithExt = path.relative(vaultPath, entryPath).replace(/\\/g, '/');
 | 
						|
        const relativePath = relativePathWithExt.replace(/\.md$/i, '');
 | 
						|
        const id = slugifyPath(relativePath);
 | 
						|
        const fileNameWithExt = entry.name;
 | 
						|
 | 
						|
        const fallbackTitle = path.basename(relativePath);
 | 
						|
        const title = extractTitle(content, fallbackTitle);
 | 
						|
        const finalId = id || slugifySegment(fallbackTitle) || fallbackTitle;
 | 
						|
        const createdDate = stats.birthtimeMs ? new Date(stats.birthtimeMs) : new Date(stats.ctimeMs);
 | 
						|
        const updatedDate = new Date(stats.mtimeMs);
 | 
						|
 | 
						|
        notes.push({
 | 
						|
          id: finalId,
 | 
						|
          title,
 | 
						|
          mtime: stats.mtimeMs,
 | 
						|
          fileName: fileNameWithExt,
 | 
						|
          filePath: relativePathWithExt,
 | 
						|
          originalPath: relativePath,
 | 
						|
          createdAt: createdDate.toISOString(),
 | 
						|
          updatedAt: updatedDate.toISOString()
 | 
						|
        });
 | 
						|
      } catch (err) {
 | 
						|
        console.error(`Failed to read metadata for ${entryPath}:`, err);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  await walk(vaultPath);
 | 
						|
  return notes;
 | 
						|
};
 |