mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
Merge pull request #8 from n8k99/claude/claude-md-mhy85sj7tlvzwb5w-01QzBpdgGJXE5Qk3JaNupuxM
Claude/claude md mhy85sj7tlvzwb5w 01 qz bpdg gjxe5 qk3 ja nupux m
This commit is contained in:
commit
f2c9efebdc
3 changed files with 298 additions and 22 deletions
|
|
@ -8281,8 +8281,8 @@
|
|||
<script defer src="modules/io/load.js?v=1.108.13"></script>
|
||||
<script defer src="modules/io/cloud.js?v=1.106.0"></script>
|
||||
<script defer src="modules/io/export.js?v=1.108.11"></script>
|
||||
<script defer src="modules/io/obsidian-bridge.js?v=1.108.13"></script>
|
||||
<script defer src="modules/ui/obsidian-notes-editor.js?v=1.108.13"></script>
|
||||
<script defer src="modules/io/obsidian-bridge.js?v=1.108.13.3"></script>
|
||||
<script defer src="modules/ui/obsidian-notes-editor.js?v=1.108.13.3"></script>
|
||||
<script defer src="modules/ui/obsidian-config.js?v=1.108.13"></script>
|
||||
|
||||
<script defer src="modules/renderers/draw-features.js?v=1.108.2"></script>
|
||||
|
|
|
|||
|
|
@ -343,13 +343,13 @@ const ObsidianBridge = (() => {
|
|||
}
|
||||
|
||||
// Generate note template for FMG element
|
||||
function generateNoteTemplate(element, type) {
|
||||
function generateNoteTemplate(element, type, elementId) {
|
||||
const {x, y} = element;
|
||||
const lat = pack.cells.lat?.[element.cell] || 0;
|
||||
const lon = pack.cells.lon?.[element.cell] || 0;
|
||||
|
||||
const frontmatter = {
|
||||
"fmg-id": element.id || `${type}${element.i}`,
|
||||
"fmg-id": elementId || element.id || `${type}${element.i}`,
|
||||
"fmg-type": type,
|
||||
coordinates: {x, y, lat, lon},
|
||||
tags: [type],
|
||||
|
|
@ -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
|
||||
};
|
||||
})();
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ function editObsidianNote(elementId, elementType, coordinates) {
|
|||
// Try to find note by FMG ID first, then by coordinates
|
||||
findOrCreateNote(elementId, elementType, coordinates)
|
||||
.then(noteData => {
|
||||
showMarkdownEditor(noteData, elementType);
|
||||
showMarkdownEditor(noteData, elementType, elementId, coordinates);
|
||||
})
|
||||
.catch(error => {
|
||||
ERROR && console.error("Failed to load note:", error);
|
||||
|
|
@ -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 += `<div style="color: #666; font-size: 0.9em;">State: ${element.state}</div>`;
|
||||
}
|
||||
if (element.province) {
|
||||
contextInfo += `<div style="color: #666; font-size: 0.9em;">Province: ${element.province}</div>`;
|
||||
}
|
||||
|
||||
// Pre-fill search with state or element name
|
||||
const defaultSearch = element.state || element.name || "";
|
||||
|
||||
alertMessage.innerHTML = `
|
||||
<p>No matching notes found. Create a new note in your Obsidian vault?</p>
|
||||
<div style="margin: 1em 0;">
|
||||
<label for="newNoteName" style="display: block; margin-bottom: 0.5em;">Note name:</label>
|
||||
<input id="newNoteName" type="text" value="${suggestedName}" style="width: 100%; padding: 8px; font-size: 1em;"/>
|
||||
<div style="margin-bottom: 1.5em;">
|
||||
<p><strong>${element.name || elementId}</strong></p>
|
||||
${contextInfo}
|
||||
<p style="margin-top: 0.5em;">No matching notes found by coordinates.</p>
|
||||
</div>
|
||||
<div style="margin: 1em 0;">
|
||||
<label for="newNotePath" style="display: block; margin-bottom: 0.5em;">Folder (optional):</label>
|
||||
<input id="newNotePath" type="text" placeholder="e.g., Locations/Cities" style="width: 100%; padding: 8px; font-size: 1em;"/>
|
||||
|
||||
<div style="margin: 1.5em 0; padding: 1em; background: #f5f5f5; border-radius: 4px;">
|
||||
<label for="obsidianSearch" style="display: block; margin-bottom: 0.5em; font-weight: bold;">Search your vault:</label>
|
||||
<input id="obsidianSearch" type="text" placeholder="Type to search..." value="${defaultSearch}" style="width: 100%; padding: 8px; font-size: 1em; margin-bottom: 8px;"/>
|
||||
<button id="obsidianSearchBtn" style="padding: 6px 12px;">Search</button>
|
||||
<button id="obsidianBrowseBtn" style="padding: 6px 12px; margin-left: 8px;">Browse All Notes</button>
|
||||
<div id="obsidianSearchResults" style="margin-top: 1em; max-height: 200px; overflow-y: auto;"></div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1.5em; padding-top: 1.5em; border-top: 1px solid #ddd;">
|
||||
<p style="font-weight: bold; margin-bottom: 1em;">Or create a new note:</p>
|
||||
<div style="margin: 1em 0;">
|
||||
<label for="newNoteName" style="display: block; margin-bottom: 0.5em;">Note name:</label>
|
||||
<input id="newNoteName" type="text" value="${suggestedName}" style="width: 100%; padding: 8px; font-size: 1em;"/>
|
||||
</div>
|
||||
<div style="margin: 1em 0;">
|
||||
<label for="newNotePath" style="display: block; margin-bottom: 0.5em;">Folder (optional):</label>
|
||||
<input id="newNotePath" type="text" placeholder="e.g., Locations/Cities" style="width: 100%; padding: 8px; font-size: 1em;"/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$("#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();
|
||||
|
|
@ -183,7 +211,7 @@ async function promptCreateNewNote(elementId, elementType, coordinates) {
|
|||
$(this).dialog("close");
|
||||
|
||||
try {
|
||||
const template = ObsidianBridge.generateNoteTemplate(element, elementType);
|
||||
const template = ObsidianBridge.generateNoteTemplate(element, elementType, elementId);
|
||||
await ObsidianBridge.createNote(notePath, template);
|
||||
|
||||
const {frontmatter} = ObsidianBridge.parseFrontmatter(template);
|
||||
|
|
@ -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 = "<p style='color: #999;'>Enter a search term</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
resultsDiv.innerHTML = "<p>Searching...</p>";
|
||||
|
||||
try {
|
||||
const results = await ObsidianBridge.searchNotes(query);
|
||||
|
||||
if (results.length === 0) {
|
||||
resultsDiv.innerHTML = "<p style='color: #999;'>No matching notes found</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
resultsDiv.innerHTML = results
|
||||
.map(
|
||||
(note, index) => `
|
||||
<div class="search-result" data-index="${index}" style="
|
||||
padding: 8px;
|
||||
margin: 4px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background: white;
|
||||
" onmouseover="this.style.background='#e8e8e8'" onmouseout="this.style.background='white'">
|
||||
<div style="font-weight: bold;">${note.name}</div>
|
||||
<div style="font-size: 0.85em; color: #666;">${note.path}</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.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 = `<p style='color: red;'>Search failed: ${error.message}</p>`;
|
||||
}
|
||||
};
|
||||
|
||||
const showBrowse = async () => {
|
||||
resultsDiv.innerHTML = "<p>Loading all notes...</p>";
|
||||
|
||||
try {
|
||||
const allNotes = await ObsidianBridge.listAllNotes();
|
||||
|
||||
if (allNotes.length === 0) {
|
||||
resultsDiv.innerHTML = "<p style='color: #999;'>No notes in vault</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
resultsDiv.innerHTML = allNotes
|
||||
.map(
|
||||
(note, index) => `
|
||||
<div class="browse-result" data-index="${index}" style="
|
||||
padding: 8px;
|
||||
margin: 4px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background: white;
|
||||
" onmouseover="this.style.background='#e8e8e8'" onmouseout="this.style.background='white'">
|
||||
<div style="font-weight: bold;">${note.name}</div>
|
||||
<div style="font-size: 0.85em; color: #666;">${note.path}</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.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 = `<p style='color: red;'>Failed to load notes: ${error.message}</p>`;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
|
@ -229,7 +404,7 @@ function getElementData(elementId, elementType) {
|
|||
}
|
||||
}
|
||||
|
||||
function showMarkdownEditor(noteData, elementType) {
|
||||
function showMarkdownEditor(noteData, elementType, elementId, coordinates) {
|
||||
const {path, name, content, frontmatter, isNew} = noteData;
|
||||
|
||||
// Extract frontmatter and body
|
||||
|
|
@ -241,9 +416,12 @@ function showMarkdownEditor(noteData, elementType) {
|
|||
byId("obsidianMarkdownEditor").value = content;
|
||||
byId("obsidianMarkdownPreview").innerHTML = renderMarkdown(bodyContent);
|
||||
|
||||
// Store current note data
|
||||
// Store current note data and FMG element info
|
||||
showMarkdownEditor.currentNote = noteData;
|
||||
showMarkdownEditor.originalContent = content;
|
||||
showMarkdownEditor.elementId = elementId;
|
||||
showMarkdownEditor.elementType = elementType;
|
||||
showMarkdownEditor.coordinates = coordinates;
|
||||
|
||||
$("#obsidianNotesEditor").dialog({
|
||||
title: `Obsidian Note: ${name}`,
|
||||
|
|
@ -316,19 +494,55 @@ async function saveObsidianNote() {
|
|||
return;
|
||||
}
|
||||
|
||||
const content = byId("obsidianMarkdownEditor").value;
|
||||
let content = byId("obsidianMarkdownEditor").value;
|
||||
const {path} = showMarkdownEditor.currentNote;
|
||||
const elementId = showMarkdownEditor.elementId;
|
||||
const coordinates = showMarkdownEditor.coordinates;
|
||||
|
||||
// Update/add frontmatter with FMG ID and coordinates
|
||||
if (elementId && coordinates) {
|
||||
content = updateFrontmatterWithFmgData(content, elementId, coordinates);
|
||||
}
|
||||
|
||||
try {
|
||||
await ObsidianBridge.updateNote(path, content);
|
||||
showMarkdownEditor.originalContent = content;
|
||||
tip("Note saved to Obsidian vault", true, "success", 2000);
|
||||
// Update the editor to show the new frontmatter
|
||||
byId("obsidianMarkdownEditor").value = content;
|
||||
tip("Note saved to Obsidian vault (linked to FMG element)", true, "success", 3000);
|
||||
} catch (error) {
|
||||
ERROR && console.error("Failed to save note:", error);
|
||||
tip("Failed to save note: " + error.message, true, "error", 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function updateFrontmatterWithFmgData(content, elementId, coordinates) {
|
||||
const {x, y} = coordinates;
|
||||
const {frontmatter, content: bodyContent} = ObsidianBridge.parseFrontmatter(content);
|
||||
|
||||
// Update frontmatter with FMG data
|
||||
frontmatter["fmg-id"] = elementId;
|
||||
frontmatter["x"] = Math.round(x * 100) / 100;
|
||||
frontmatter["y"] = Math.round(y * 100) / 100;
|
||||
|
||||
// Rebuild frontmatter
|
||||
let frontmatterLines = ["---"];
|
||||
for (const [key, value] of Object.entries(frontmatter)) {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
// Handle nested objects
|
||||
frontmatterLines.push(`${key}:`);
|
||||
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
||||
frontmatterLines.push(` ${nestedKey}: ${nestedValue}`);
|
||||
}
|
||||
} else {
|
||||
frontmatterLines.push(`${key}: ${value}`);
|
||||
}
|
||||
}
|
||||
frontmatterLines.push("---");
|
||||
|
||||
return frontmatterLines.join("\n") + "\n" + bodyContent;
|
||||
}
|
||||
|
||||
function openInObsidian() {
|
||||
if (!showMarkdownEditor.currentNote) return;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue