From 28cf8db82d1fa1d6c084d47041c89e90c2360699 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Nov 2025 04:18:20 +0000 Subject: [PATCH] feat(obsidian): add text search and browse features for note finding Enhanced the Obsidian note finding system with: **ObsidianBridge:** - searchNotes(query): Search vault by filename - listAllNotes(): Browse all notes in vault **Obsidian Notes Editor:** - Text search box with auto-populated state/province - "Browse All Notes" button to see full vault - Display burg's state and province in dialog - Pre-fill search with state name for easier finding - Enter key triggers search - Click any result to load that note **getElementData enhancement:** - Extract and include state/province names for burgs - Extract state/province from cell data for markers Now users can: 1. See which state/province the burg belongs to 2. Search by state name (pre-filled) 3. Search by any text in filename 4. Browse all notes manually 5. Click to select matching note This addresses the user's organization structure where notes are stored in State/Province folders matching the map structure. --- index.html | 4 +- modules/io/obsidian-bridge.js | 64 ++++++++- modules/ui/obsidian-notes-editor.js | 197 ++++++++++++++++++++++++++-- 3 files changed, 251 insertions(+), 14 deletions(-) diff --git a/index.html b/index.html index 1a7b2bd0..71b80876 100644 --- a/index.html +++ b/index.html @@ -8281,8 +8281,8 @@ - - + + diff --git a/modules/io/obsidian-bridge.js b/modules/io/obsidian-bridge.js index 1d8325fc..06643621 100644 --- a/modules/io/obsidian-bridge.js +++ b/modules/io/obsidian-bridge.js @@ -396,6 +396,66 @@ Add your lore here... `; } + // Search notes by text query (searches in filename and frontmatter) + async function searchNotes(query) { + if (!query || query.trim() === "") { + return []; + } + + const allFiles = await getVaultFiles(); + const searchTerm = query.toLowerCase(); + const results = []; + + for (const filePath of allFiles) { + const fileName = filePath.split("/").pop().replace(".md", "").toLowerCase(); + + // Check if filename matches + if (fileName.includes(searchTerm)) { + try { + const content = await getNote(filePath); + const {frontmatter} = parseFrontmatter(content); + + results.push({ + path: filePath, + name: filePath.split("/").pop().replace(".md", ""), + frontmatter, + matchType: "filename" + }); + } catch (error) { + WARN && console.warn(`Could not read file ${filePath}:`, error); + } + } + } + + return results; + } + + // List all notes with basic info + async function listAllNotes() { + const allFiles = await getVaultFiles(); + const notes = []; + + for (const filePath of allFiles) { + try { + const content = await getNote(filePath); + const {frontmatter} = parseFrontmatter(content); + + notes.push({ + path: filePath, + name: filePath.split("/").pop().replace(".md", ""), + frontmatter, + folder: filePath.includes("/") ? filePath.substring(0, filePath.lastIndexOf("/")) : "" + }); + } catch (error) { + WARN && console.warn(`Could not read file ${filePath}:`, error); + } + } + + // Sort by path + notes.sort((a, b) => a.path.localeCompare(b.path)); + return notes; + } + return { init, config, @@ -408,7 +468,9 @@ Add your lore here... parseFrontmatter, findNotesByCoordinates, findNoteByFmgId, - generateNoteTemplate + generateNoteTemplate, + searchNotes, + listAllNotes }; })(); diff --git a/modules/ui/obsidian-notes-editor.js b/modules/ui/obsidian-notes-editor.js index 78fa133e..b034bb6a 100644 --- a/modules/ui/obsidian-notes-editor.js +++ b/modules/ui/obsidian-notes-editor.js @@ -153,21 +153,49 @@ async function promptCreateNewNote(elementId, elementType, coordinates) { const element = getElementData(elementId, elementType); const suggestedName = element.name || `${elementType}-${element.i}`; + // Build context info for the element + let contextInfo = ""; + if (element.state) { + contextInfo += `
State: ${element.state}
`; + } + if (element.province) { + contextInfo += `
Province: ${element.province}
`; + } + + // Pre-fill search with state or element name + const defaultSearch = element.state || element.name || ""; + alertMessage.innerHTML = ` -

No matching notes found. Create a new note in your Obsidian vault?

-
- - +
+

${element.name || elementId}

+ ${contextInfo} +

No matching notes found by coordinates.

-
- - + +
+ + + + +
+
+ +
+

Or create a new note:

+
+ + +
+
+ + +
`; $("#alert").dialog({ - title: "Create New Note", - width: "500px", + title: "Find or Create Note", + width: "600px", buttons: { Create: async function () { const name = byId("newNoteName").value.trim(); @@ -206,6 +234,128 @@ async function promptCreateNewNote(elementId, elementType, coordinates) { }, position: {my: "center", at: "center", of: "svg"} }); + + // Add event handlers for search and browse + const searchBtn = byId("obsidianSearchBtn"); + const browseBtn = byId("obsidianBrowseBtn"); + const searchInput = byId("obsidianSearch"); + const resultsDiv = byId("obsidianSearchResults"); + + const performSearch = async () => { + const query = searchInput.value.trim(); + if (!query) { + resultsDiv.innerHTML = "

Enter a search term

"; + return; + } + + resultsDiv.innerHTML = "

Searching...

"; + + try { + const results = await ObsidianBridge.searchNotes(query); + + if (results.length === 0) { + resultsDiv.innerHTML = "

No matching notes found

"; + return; + } + + resultsDiv.innerHTML = results + .map( + (note, index) => ` +
+
${note.name}
+
${note.path}
+
+ ` + ) + .join(""); + + // Add click handlers + document.querySelectorAll(".search-result").forEach((el, index) => { + el.addEventListener("click", async () => { + $("#alert").dialog("close"); + try { + const note = results[index]; + const content = await ObsidianBridge.getNote(note.path); + resolve({ + path: note.path, + name: note.name, + content, + frontmatter: note.frontmatter + }); + } catch (error) { + reject(error); + } + }); + }); + } catch (error) { + resultsDiv.innerHTML = `

Search failed: ${error.message}

`; + } + }; + + const showBrowse = async () => { + resultsDiv.innerHTML = "

Loading all notes...

"; + + try { + const allNotes = await ObsidianBridge.listAllNotes(); + + if (allNotes.length === 0) { + resultsDiv.innerHTML = "

No notes in vault

"; + return; + } + + resultsDiv.innerHTML = allNotes + .map( + (note, index) => ` +
+
${note.name}
+
${note.path}
+
+ ` + ) + .join(""); + + // Add click handlers + document.querySelectorAll(".browse-result").forEach((el, index) => { + el.addEventListener("click", async () => { + $("#alert").dialog("close"); + try { + const note = allNotes[index]; + const content = await ObsidianBridge.getNote(note.path); + resolve({ + path: note.path, + name: note.name, + content, + frontmatter: note.frontmatter + }); + } catch (error) { + reject(error); + } + }); + }); + } catch (error) { + resultsDiv.innerHTML = `

Failed to load notes: ${error.message}

`; + } + }; + + searchBtn.addEventListener("click", performSearch); + browseBtn.addEventListener("click", showBrowse); + searchInput.addEventListener("keypress", e => { + if (e.key === "Enter") performSearch(); + }); }); } @@ -213,10 +363,35 @@ function getElementData(elementId, elementType) { // Extract element data based on type if (elementType === "burg") { const burgId = parseInt(elementId.replace("burg", "")); - return pack.burgs[burgId]; + const burg = pack.burgs[burgId]; + + // Enhance with state and province names + const stateId = burg.state; + const provinceId = burg.province; + + return { + ...burg, + state: stateId && pack.states[stateId] ? pack.states[stateId].name : null, + province: provinceId && pack.provinces[provinceId] ? pack.provinces[provinceId].name : null + }; } else if (elementType === "marker") { const markerId = parseInt(elementId.replace("marker", "")); - return pack.markers[markerId]; + const marker = pack.markers[markerId]; + + // Enhance with state and province if marker has a cell + if (marker.cell) { + const cell = pack.cells; + const stateId = cell.state[marker.cell]; + const provinceId = cell.province[marker.cell]; + + return { + ...marker, + state: stateId && pack.states[stateId] ? pack.states[stateId].name : null, + province: provinceId && pack.provinces[provinceId] ? pack.provinces[provinceId].name : null + }; + } + + return marker; } else { // Generic element const el = document.getElementById(elementId);