mirror of
https://github.com/sbuffkin/hexmaker.git
synced 2026-07-22 14:30:24 +00:00
ux enhancements and bug fix for empty box on hex table
This commit is contained in:
parent
48f0aa0d0d
commit
e1da44eafe
5 changed files with 112 additions and 32 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
33
styles.css
33
styles.css
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue