fix(obsidian): implement recursive directory scanning for nested folders

The Obsidian Local REST API /vault/ endpoint returns folder entries with
trailing slashes (e.g., "01 Projects/"). The previous code filtered these
out, only keeping .md files, which meant nested files were never discovered.

Changes:
- Add scanDirectory() function to recursively scan folders
- Detect folders by trailing "/" character
- Recursively query each folder with /vault/{foldername}/
- Build full paths as we traverse (e.g., "State/Province/City.md")
- Remove search endpoint attempt (not needed)
- Add timing logs to measure performance

This now correctly discovers ALL markdown files in the vault, regardless
of how deeply nested they are in folders.

Fixes nested folder display issue.
This commit is contained in:
Claude 2025-11-14 05:07:56 +00:00
parent 3d30e2481e
commit 996b653421
No known key found for this signature in database

View file

@ -61,6 +61,39 @@ const ObsidianBridge = (() => {
}
}
// Recursively scan a directory and all subdirectories for .md files
async function scanDirectory(path = "") {
const response = await fetch(`${config.apiUrl}/vault/${encodeURIComponent(path)}`, {
headers: {
Authorization: `Bearer ${config.apiKey}`
}
});
if (!response.ok) {
throw new Error(`Failed to fetch directory ${path}: ${response.status}`);
}
const data = await response.json();
const entries = data.files || [];
const mdFiles = [];
for (const entry of entries) {
const fullPath = path ? `${path}${entry}` : entry;
if (entry.endsWith("/")) {
// It's a directory - recurse into it
DEBUG && console.log(`Scanning directory: ${fullPath}`);
const subFiles = await scanDirectory(fullPath);
mdFiles.push(...subFiles);
} else if (entry.endsWith(".md")) {
// It's a markdown file - add it
mdFiles.push(fullPath);
}
}
return mdFiles;
}
// Get all markdown files from vault (recursively)
async function getVaultFiles() {
if (!config.enabled) {
@ -68,62 +101,20 @@ const ObsidianBridge = (() => {
}
try {
// Try using the search endpoint to get all .md files recursively
// The /search/ endpoint with an empty query or wildcard should return all files
const searchResponse = await fetch(`${config.apiUrl}/search/?query=.md`, {
headers: {
Authorization: `Bearer ${config.apiKey}`
}
});
TIME && console.time("getVaultFiles");
if (searchResponse.ok) {
const searchData = await searchResponse.json();
INFO && console.log("Using search endpoint, found:", searchData);
// Recursively scan all directories
const mdFiles = await scanDirectory("");
// Extract file paths from search results
// The format might be different, check what we get
const files = searchData.files || searchData.results || searchData;
if (Array.isArray(files)) {
const mdFiles = files
.filter(f => {
const path = typeof f === 'string' ? f : f.path || f.filename;
return path && path.endsWith(".md");
})
.map(f => typeof f === 'string' ? f : f.path || f.filename);
INFO && console.log(`getVaultFiles (search): Found ${mdFiles.length} markdown files`);
DEBUG && console.log("Sample files:", mdFiles.slice(0, 10));
return mdFiles;
}
}
// Fallback to /vault/ endpoint if search doesn't work
INFO && console.log("Search endpoint failed, falling back to /vault/");
const response = await fetch(`${config.apiUrl}/vault/`, {
headers: {
Authorization: `Bearer ${config.apiKey}`
}
});
if (!response.ok) {
throw new Error(`Failed to fetch vault files: ${response.status}`);
}
const data = await response.json();
const files = data.files || [];
// Filter to only .md files
const mdFiles = files.filter(f => f.endsWith(".md"));
INFO && console.log(`getVaultFiles (vault): Found ${files.length} total files, ${mdFiles.length} markdown files`);
INFO && console.log(`getVaultFiles: Found ${mdFiles.length} markdown files (recursive scan)`);
DEBUG && console.log("Sample files:", mdFiles.slice(0, 10));
WARN && console.warn("Note: /vault/ endpoint may only return root-level files. Nested folders may not be visible.");
TIME && console.timeEnd("getVaultFiles");
return mdFiles;
} catch (error) {
ERROR && console.error("Failed to get vault files:", error);
TIME && console.timeEnd("getVaultFiles");
throw error;
}
}