Files
obsidian-jekyll-publish/src/PublishModal.ts

150 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { App, Modal, Setting } from "obsidian";
import { Pair, composeFrontmatter, docFrontmatterToPairs, parseNote } from "./frontmatter";
import { Strategy } from "./images";
import { JekyllPublishSettings } from "./settings";
import { deriveDate, deriveSlug } from "./slug";
export interface ModalResult {
frontmatterPairs: Pair[];
slug: string;
date?: string;
strategy: Strategy;
commitMessage: string;
}
export class PublishModal extends Modal {
private result: ModalResult;
private mergeDoc = true;
/** Editable rows = preset fields (seeded from settings) + custom additions. */
private editableRows: Pair[];
/** The note's own frontmatter, shown read-only when merge is on. */
private readonly docPairs: Pair[];
private fmEl!: HTMLElement;
constructor(
app: App,
settings: JekyllPublishSettings,
private noteText: string,
filename: string,
private onSubmit: (r: ModalResult) => void
) {
super(app);
const { frontmatter } = parseNote(noteText);
const title = typeof frontmatter.title === "string" ? frontmatter.title : "";
this.docPairs = docFrontmatterToPairs(frontmatter);
// Deep-copy the presets so editing/deleting rows here never mutates the
// saved settings.
this.editableRows = settings.presetFrontmatter.map((p) => ({ key: p.key, value: p.value }));
this.result = {
frontmatterPairs: [],
slug: deriveSlug({ title, filename }),
date: deriveDate({ frontmatterDate: frontmatter.date }),
strategy: settings.defaultImageStrategy,
commitMessage: settings.commitMessageTemplate.replace("{{title}}", title || filename),
};
}
private recompute() {
this.result.frontmatterPairs = composeFrontmatter(this.docPairs, this.editableRows, this.mergeDoc);
}
onOpen() {
this.recompute();
const { contentEl } = this;
contentEl.createEl("h2", { text: "Publish to Jekyll" });
new Setting(contentEl).setName("Slug").addText((t) =>
t.setValue(this.result.slug).onChange((v) => (this.result.slug = v))
);
new Setting(contentEl).setName("Date").addText((t) =>
t.setValue(this.result.date ?? "").onChange((v) => (this.result.date = v))
).setDesc("The date to prepend to the filename. Useful for blog posts.");
new Setting(contentEl).setName("Image strategy").addDropdown((d) =>
d.addOption("flat-slug", "Flat, renamed to slug")
.addOption("per-post-folder", "Per-post subfolder")
.setValue(this.result.strategy)
.onChange((v) => (this.result.strategy = v as Strategy))
);
new Setting(contentEl)
.setName("Merge document frontmatter")
.setDesc("Include the note's own frontmatter (shown read-only below).")
.addToggle((t) =>
t.setValue(this.mergeDoc).onChange((v) => {
this.mergeDoc = v;
this.renderFrontmatter();
})
);
contentEl.createEl("h3", { text: "Frontmatter" });
this.fmEl = contentEl.createDiv();
this.renderFrontmatter();
new Setting(contentEl).addButton((b) =>
b.setButtonText("Add property").onClick(() => {
this.editableRows = [...this.editableRows, { key: "", value: "" }];
this.renderFrontmatter();
})
);
new Setting(contentEl).setName("Commit message").addText((t) =>
t.setValue(this.result.commitMessage).onChange((v) => (this.result.commitMessage = v))
);
new Setting(contentEl).addButton((b) =>
b.setButtonText("Publish").setCta().onClick(() => {
this.recompute();
this.close();
this.onSubmit(this.result);
})
);
}
private renderFrontmatter() {
this.recompute();
this.fmEl.empty();
const editableKeys = new Set(
this.editableRows.filter((r) => r.key.trim() !== "").map((r) => r.key)
);
// Document fields (read-only) first — only those not overridden by an
// editable row, so the modal never shows a key twice.
if (this.mergeDoc) {
const docRows = this.docPairs.filter((p) => !editableKeys.has(p.key));
for (const p of docRows) {
const row = this.fmEl.createDiv({ cls: "jekyll-publish-row" });
const k = row.createEl("input", { cls: "jekyll-publish-doc", value: p.key });
const v = row.createEl("input", { cls: "jekyll-publish-doc", value: p.value });
k.disabled = true;
v.disabled = true;
k.title = "From the note's frontmatter (edit the note to change)";
v.title = k.title;
}
}
// Editable rows (presets + custom) — same style, all editable and deletable.
this.editableRows.forEach((row, i) => {
const div = this.fmEl.createDiv({ cls: "jekyll-publish-row" });
const k = div.createEl("input", { value: row.key, placeholder: "key" });
const v = div.createEl("input", { value: row.value, placeholder: "value" });
k.oninput = () => {
this.editableRows = this.editableRows.map((r, j) => (j === i ? { ...r, key: k.value } : r));
this.recompute();
};
v.oninput = () => {
this.editableRows = this.editableRows.map((r, j) => (j === i ? { ...r, value: v.value } : r));
this.recompute();
};
const del = div.createEl("button", { cls: "jekyll-publish-del", text: "×" });
del.setAttr("aria-label", "Delete property");
del.onclick = () => {
this.editableRows = this.editableRows.filter((_, j) => j !== i);
this.renderFrontmatter();
};
});
}
onClose() {
this.contentEl.empty();
}
}