feat: add support for non-markdown files and improve drawings UI

- Added scanning and metadata tracking for non-markdown files (images, PDFs, videos, code files)
- Redesigned drawings editor header with new toolbar layout and dropdown menus
- Added file picker dropdown to easily open existing .excalidraw files
- Implemented new file creation flow with auto-generated filenames
- Added export options menu with PNG/SVG/JSON export variants
- Updated proxy config to support vault file access
- Adde
This commit is contained in:
2025-10-30 12:05:00 -04:00
parent b1f142c4f7
commit 6f01d65411
42 changed files with 2182 additions and 504 deletions
+65
View File
@@ -344,6 +344,61 @@ const buildFileMetadata = (notes) =>
updatedAt: note.updatedAt,
}));
// Scan vault for NON-markdown files to include in metadata (images/pdf/video/code/others)
const scanVaultNonNotes = (vaultPath) => {
const items = [];
const includeExt = new Set([
// images
'.png','.jpg','.jpeg','.gif','.svg','.webp','.bmp','.ico',
// pdf
'.pdf',
// video
'.mp4','.mov','.avi','.mkv','.webm',
// code/text configs
'.json','.js','.ts','.jsx','.tsx','.html','.css','.yml','.yaml','.toml','.ini','.cfg','.conf','.sh','.bash','.ps1','.bat','.csv','.txt'
]);
const walk = (dir) => {
let entries = [];
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch {
return;
}
for (const entry of entries) {
const entryPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(entryPath);
continue;
}
if (!entry.isFile()) continue;
const lower = entry.name.toLowerCase();
if (lower.endsWith('.md') || lower.endsWith('.excalidraw.md')) continue; // handled elsewhere
const ext = path.extname(lower);
if (!includeExt.has(ext)) continue;
try {
const stats = fs.statSync(entryPath);
const relPath = path.relative(vaultPath, entryPath).replace(/\\/g, '/');
const id = slugifyPath(relPath.replace(/\.[^.]+$/i, ''));
const title = path.basename(relPath);
items.push({
id,
title,
path: relPath,
createdAt: new Date(stats.birthtimeMs ? stats.birthtimeMs : stats.ctimeMs).toISOString(),
updatedAt: new Date(stats.mtimeMs).toISOString(),
});
} catch {}
}
};
walk(vaultPath);
return items;
};
const normalizeDateInput = (value) => {
if (!value) {
return null;
@@ -656,6 +711,7 @@ app.get('/api/files/metadata', async (req, res) => {
// Merge .excalidraw files discovered via FS
const drawings = scanVaultDrawings(vaultDir);
const nonNotes = scanVaultNonNotes(vaultDir);
const byPath = new Map(items.map(it => [String(it.path).toLowerCase(), it]));
for (const d of drawings) {
const key = String(d.path).toLowerCase();
@@ -663,6 +719,10 @@ app.get('/api/files/metadata', async (req, res) => {
byPath.set(key, d);
}
}
for (const f of nonNotes) {
const key = String(f.path).toLowerCase();
if (!byPath.has(key)) byPath.set(key, f);
}
return res.json(Array.from(byPath.values()));
}
@@ -671,11 +731,16 @@ app.get('/api/files/metadata', async (req, res) => {
const notes = await loadVaultNotes(vaultDir);
const base = buildFileMetadata(notes);
const drawings = scanVaultDrawings(vaultDir);
const nonNotes = scanVaultNonNotes(vaultDir);
const byPath = new Map(base.map(it => [String(it.path).toLowerCase(), it]));
for (const d of drawings) {
const key = String(d.path).toLowerCase();
if (!byPath.has(key)) byPath.set(key, d);
}
for (const f of nonNotes) {
const key = String(f.path).toLowerCase();
if (!byPath.has(key)) byPath.set(key, f);
}
return res.json(Array.from(byPath.values()));
} catch (error) {
console.error('Failed to load file metadata:', error);