ux enhancements and bug fix for empty box on hex table

This commit is contained in:
isaprettycoolguy@protonmail.com 2026-03-09 19:10:19 -04:00
parent 48f0aa0d0d
commit e1da44eafe
5 changed files with 112 additions and 32 deletions

View file

@ -182,6 +182,42 @@ export class HexEditorModal extends Modal {
this.onChanged();
await refresh();
});
// Create-new row
const createRow = sectionEl.createDiv({ cls: "duckmage-editor-create-row" });
const createInput = createRow.createEl("input", { type: "text", cls: "duckmage-editor-create-input" });
createInput.placeholder = `New ${section.slice(0, -1).toLowerCase()}`;
const createBtn = createRow.createEl("button", { text: "Create", cls: "duckmage-editor-create-btn" });
const createAndLink = async () => {
const name = createInput.value.trim();
if (!name) return;
const folder = normalizeFolder(sourceFolder);
const newPath = folder ? `${folder}/${name}.md` : `${name}.md`;
let file = this.app.vault.getAbstractFileByPath(newPath);
if (!(file instanceof TFile)) {
try {
if (folder && !this.app.vault.getAbstractFileByPath(folder)) {
await this.app.vault.createFolder(folder);
}
file = await this.app.vault.create(newPath, "");
} catch (err) {
new Notice(`Could not create ${newPath}: ${err}`);
return;
}
}
const hexFile = await this.ensureHexNote();
if (!hexFile) { new Notice("Could not create hex note."); return; }
const linkText = `[[${this.app.metadataCache.fileToLinktext(file as TFile, path)}]]`;
await addLinkToSection(this.app, path, section, linkText);
await addBacklinkToFile(this.app, (file as TFile).path, path);
this.onChanged();
createInput.value = "";
await refresh();
};
createBtn.addEventListener("click", createAndLink);
createInput.addEventListener("keydown", (e: KeyboardEvent) => { if (e.key === "Enter") createAndLink(); });
}
private renderLinkSection(

View file

@ -84,6 +84,7 @@ class HexCellModal extends Modal {
private filePath?: string,
private sectionKey?: string,
private onSave?: (newContent: string) => void,
private beforeSave?: () => Promise<void>,
) {
super(app);
}
@ -109,6 +110,7 @@ class HexCellModal extends Modal {
saveBtn.addEventListener("click", async () => {
const newContent = textarea.value;
if (this.filePath && this.sectionKey) {
await this.beforeSave?.();
await setSectionContent(this.app, this.filePath, this.sectionKey, newContent);
this.onSave?.(newContent.trim());
}
@ -624,36 +626,40 @@ export class HexTableView extends ItemView {
}
} else {
const content = text.get(col.key) ?? "";
td.dataset.fullContent = content;
if (content) {
td.dataset.fullContent = content;
const display = content.length > TRUNCATE_LEN
? content.slice(0, TRUNCATE_LEN) + "…"
: content;
td.setText(display);
td.addClass("duckmage-hex-table-cell-clickable");
td.addEventListener("click", () => {
const current = td.dataset.fullContent ?? "";
new HexCellModal(
this.app, `${x},${y}${col.label}`, current, false,
path, col.key,
(saved) => {
td.dataset.fullContent = saved;
if (saved) {
const newDisplay = saved.length > TRUNCATE_LEN
? saved.slice(0, TRUNCATE_LEN) + "…"
: saved;
td.setText(newDisplay);
} else {
td.empty();
td.createSpan({ text: "", cls: "duckmage-hex-table-empty" });
td.removeClass("duckmage-hex-table-cell-clickable");
}
},
).open();
});
} else {
td.createSpan({ text: "", cls: "duckmage-hex-table-empty" });
}
td.addClass("duckmage-hex-table-cell-clickable");
td.addEventListener("click", () => {
const current = td.dataset.fullContent ?? "";
new HexCellModal(
this.app, `${x},${y}${col.label}`, current, false,
path, col.key,
(saved) => {
td.dataset.fullContent = saved;
td.empty();
if (saved) {
const newDisplay = saved.length > TRUNCATE_LEN
? saved.slice(0, TRUNCATE_LEN) + "…"
: saved;
td.setText(newDisplay);
} else {
td.createSpan({ text: "", cls: "duckmage-hex-table-empty" });
}
},
async () => {
if (!this.app.vault.getAbstractFileByPath(path)) {
await this.plugin.createHexNote(x, y);
}
},
).open();
});
}
}
}

View file

@ -8,16 +8,16 @@ terrain:
**Terrain:**
---
What the party sees and feels. Terrain, atmosphere, any obvious features.
### description
What the party sees and feels. Terrain, atmosphere, any obvious features.
---
The visible standout feature — spire, ruin, lighthouse, statue, village — that can be spotted or used for navigation.
### landmark
The visible standout feature — spire, ruin, lighthouse, statue, village — that can be spotted or used for navigation.
---
@ -32,16 +32,16 @@ The visible standout feature — spire, ruin, lighthouse, statue, village — th
### Features
---
Discoverable with exploration, tracking, or clues. Hidden lairs, ruins, tombs, camps, shortcuts.
### hidden
Discoverable with exploration, tracking, or clues. Hidden lairs, ruins, tombs, camps, shortcuts.
---
Revealed only through specific actions, NPCs, or investigation.
### secret
Revealed only through specific actions, NPCs, or investigation.
---
@ -51,13 +51,12 @@ Revealed only through specific actions, NPCs, or investigation.
- **Custom / notable:**
---
Normal for region, or special (e.g. always pleasant, sandstorms, magic zone effect).
### weather
Normal for region, or special (e.g. always pleasant, sandstorms, magic zone effect).
---
Seeds for adventures, things locals might mention, or what finding this hex could lead to.
### hooks & rumors
Seeds for adventures, things locals might mention, or what finding this hex could lead to.

View file

@ -149,7 +149,7 @@ export async function addBacklinkToFile(app: App, targetFilePath: string, hexFil
);
}
/** Replace the body of a named ### section in-place. */
/** Replace the body of a named ### section in-place, creating the section if absent. */
export async function setSectionContent(app: App, filePath: string, section: string, newText: string): Promise<void> {
const file = app.vault.getAbstractFileByPath(filePath);
if (!(file instanceof TFile)) return;
@ -157,12 +157,18 @@ export async function setSectionContent(app: App, filePath: string, section: str
const headingRegex = new RegExp(`^###\\s+${section}\\s*$`, "mi");
const match = headingRegex.exec(content);
if (!match) return;
if (!match) {
if (newText.trim()) {
content = content.trimEnd() + `\n\n### ${section}\n${newText.trim()}\n`;
await app.vault.modify(file, content);
}
return;
}
const afterHeading = match.index + match[0].length;
const nextBoundary = /\n(?:#{1,6} |-{3,})/m.exec(content.slice(afterHeading));
const sectionEnd = nextBoundary ? afterHeading + nextBoundary.index : content.length;
const replacement = newText.trim() ? `\n\n${newText.trim()}\n` : "\n";
const replacement = newText.trim() ? `\n${newText.trim()}\n` : "\n";
await app.vault.modify(file, content.slice(0, afterHeading) + replacement + content.slice(sectionEnd));
}

View file

@ -953,6 +953,39 @@
font-size: var(--font-ui-small);
}
/* ── Context menu create-new row ────────────────────────────────────────────── */
.duckmage-editor-create-row {
display: flex;
gap: 6px;
margin-top: 6px;
}
.duckmage-editor-create-input {
flex: 1;
padding: 3px 6px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
background: var(--background-secondary);
color: var(--text-normal);
font-size: var(--font-ui-small);
min-width: 0;
}
.duckmage-editor-create-btn {
padding: 3px 8px;
border-radius: 4px;
background: var(--interactive-accent);
color: var(--text-on-accent);
border: none;
cursor: pointer;
font-size: var(--font-ui-small);
white-space: nowrap;
}
.duckmage-editor-create-btn:hover {
background: var(--interactive-accent-hover);
}
/* ── Go-to-hex button & modal ───────────────────────────────────────────────── */
.duckmage-goto-btn {