1
0
silverbullet/plugs/template/page.ts
Zef Hemel f30b1d3418
Templates 2.0 (#636)
Templates 2.0 and a whole bunch of other refactoring
2024-01-20 19:16:07 +01:00

227 lines
5.9 KiB
TypeScript

import { editor, handlebars, space } from "$sb/syscalls.ts";
import { PageMeta } from "$sb/types.ts";
import { getObjectByRef, queryObjects } from "../index/plug_api.ts";
import { FrontmatterConfig, TemplateObject } from "./types.ts";
import { renderTemplate } from "./api.ts";
export async function newPageCommand(
_cmdDef: any,
templateName?: string,
askName = true,
) {
if (!templateName) {
const allPageTemplates = await listPageTemplates();
// console.log("All page templates", allPageTemplates);
const selectedTemplate = await selectPageTemplate(allPageTemplates);
if (!selectedTemplate) {
return;
}
templateName = selectedTemplate.ref;
}
console.log("Selected template", templateName);
await instantiatePageTemplate(templateName!, undefined, askName);
}
function listPageTemplates() {
return queryObjects<TemplateObject>("template", {
// where hooks.newPage exists
filter: ["attr", ["attr", "hooks"], "newPage"],
});
}
// Invoked when a new page is created
export async function newPage(pageName: string) {
console.log("Asked to setup a new page for", pageName);
const allPageTemplatesMatchingPrefix = (await listPageTemplates()).filter(
(templateObject) => {
const forPrefix = templateObject.hooks?.newPage?.forPrefix;
return forPrefix && pageName.startsWith(forPrefix);
},
);
// console.log("Matching templates", allPageTemplatesMatchingPrefix);
if (allPageTemplatesMatchingPrefix.length === 0) {
// No matching templates, that's ok, we'll just start with an empty page, so let's just return
return;
}
if (allPageTemplatesMatchingPrefix.length === 1) {
// Only one matching template, let's use it
await instantiatePageTemplate(
allPageTemplatesMatchingPrefix[0].ref,
pageName,
false,
);
} else {
// Let's offer a choice
const selectedTemplate = await selectPageTemplate(
allPageTemplatesMatchingPrefix,
);
if (!selectedTemplate) {
// No choice made? We'll start out empty
return;
}
await instantiatePageTemplate(
selectedTemplate.ref,
pageName,
false,
);
}
}
function selectPageTemplate(options: TemplateObject[]) {
return editor.filterBox(
"Page template",
options.map((templateObj) => {
const niceName = templateObj.ref.split("/").pop()!;
return {
...templateObj,
description: templateObj.description || templateObj.ref,
name: templateObj.displayName || niceName,
};
}),
`Select the template to create a new page from`,
);
}
async function instantiatePageTemplate(
templateName: string,
intoCurrentPage: string | undefined,
askName: boolean,
) {
const templateText = await space.readPage(templateName!);
console.log(
"Instantiating page template",
templateName,
intoCurrentPage,
askName,
);
const tempPageMeta: PageMeta = {
tag: "page",
ref: "",
name: "",
created: "",
lastModified: "",
perm: "rw",
};
// Just used to extract the frontmatter
const { frontmatter } = await renderTemplate(
templateText,
tempPageMeta,
);
let frontmatterConfig: FrontmatterConfig;
try {
frontmatterConfig = FrontmatterConfig.parse(frontmatter!);
} catch (e: any) {
await editor.flashNotification(
`Error parsing template frontmatter for ${templateName}: ${e.message}`,
);
return;
}
const newPageConfig = frontmatterConfig.hooks!.newPage!;
let pageName: string | undefined = intoCurrentPage ||
await replaceTemplateVars(
newPageConfig.suggestedName || "",
tempPageMeta,
);
if (!intoCurrentPage && askName && newPageConfig.confirmName !== false) {
pageName = await editor.prompt(
"Name of new page",
await replaceTemplateVars(
newPageConfig.suggestedName || "",
tempPageMeta,
),
);
if (!pageName) {
return;
}
}
tempPageMeta.name = pageName;
if (!intoCurrentPage) {
// Check if page exists, but only if we're not forcing the name (which only happens when we know that we're creating a new page already)
try {
// Fails if doesn't exist
await space.getPageMeta(pageName);
// So, page exists
if (newPageConfig.openIfExists) {
console.log("Page already exists, navigating there");
await editor.navigate(pageName);
return;
}
// let's warn
if (
!await editor.confirm(
`Page ${pageName} already exists, are you sure you want to override it?`,
)
) {
// Just navigate there without instantiating
return editor.navigate(pageName);
}
} catch {
// The preferred scenario, let's keep going
}
}
const { text: pageText, renderedFrontmatter } = await renderTemplate(
templateText,
tempPageMeta,
);
let fullPageText = renderedFrontmatter
? "---\n" + renderedFrontmatter + "---\n" + pageText
: pageText;
const carretPos = fullPageText.indexOf("|^|");
fullPageText = fullPageText.replace("|^|", "");
if (intoCurrentPage) {
await editor.insertAtCursor(fullPageText);
if (carretPos !== -1) {
await editor.moveCursor(carretPos);
}
} else {
await space.writePage(
pageName,
fullPageText,
);
await editor.navigate(pageName, carretPos !== -1 ? carretPos : undefined);
}
}
export async function loadPageObject(pageName?: string): Promise<PageMeta> {
if (!pageName) {
return {
ref: "",
name: "",
tags: ["page"],
lastModified: "",
created: "",
} as PageMeta;
}
return (await getObjectByRef<PageMeta>(
pageName,
"page",
pageName,
)) || {
ref: pageName,
name: pageName,
tags: ["page"],
lastModified: "",
created: "",
} as PageMeta;
}
export function replaceTemplateVars(
s: string,
pageMeta: PageMeta,
): Promise<string> {
return handlebars.renderTemplate(s, {}, { page: pageMeta });
}