Factor out markdown widget rendering

This commit is contained in:
Zef Hemel
2023-10-29 10:02:50 +01:00
parent 23e99b0c46
commit 50caba8522
17 changed files with 102 additions and 77 deletions
-104
View File
@@ -1,104 +0,0 @@
async function init() {
// Make edit button send the "blur" API call so that the MD code is visible
document.getElementById("edit-button").addEventListener("click", () => {
api({ type: "blur" });
});
document.getElementById("reload-button").addEventListener("click", () => {
api({ type: "reload" });
});
document.querySelectorAll("a[data-ref]").forEach((el) => {
el.addEventListener("click", (e) => {
e.preventDefault();
syscall("editor.navigate", el.dataset.ref);
});
});
// Find all fenced code blocks and replace them with iframes (if a code widget is defined for them)
const allWidgets = document.querySelectorAll("pre[data-lang]");
for (const widget of allWidgets) {
const lang = widget.getAttribute("data-lang");
const body = widget.innerText;
try {
const result = await syscall("widget.render", lang, body);
const iframe = document.createElement("iframe");
iframe.src = "about:blank";
iframe.onload = () => {
iframe.contentDocument.write(panelHtml);
iframe.contentWindow.postMessage({
type: "html",
theme: document.getElementsByTagName("html")[0].getAttribute(
"data-theme",
),
...result,
}, "*");
};
widget.parentNode.replaceChild(iframe, widget);
globalThis.addEventListener("message", (e) => {
if (e.source !== iframe.contentWindow) {
return;
}
const messageData = e.data;
switch (messageData.type) {
case "setHeight":
iframe.style.height = messageData.height + "px";
// Propagate height setting to parent
updateHeight();
break;
case "syscall": {
// Intercept syscall messages and send them to the parent
const { id, name, args } = messageData;
syscall(name, ...args).then((result) => {
iframe.contentWindow.postMessage(
{ id, type: "syscall-response", result },
"*",
);
}).catch((error) => {
iframe.contentWindow.postMessage({
id,
type: "syscall-response",
error,
}, "*");
});
break;
}
default:
// Bubble up any other messages to parent iframe
window.parent.postMessage(messageData, "*");
}
});
} catch (e) {
if (e.message.includes("not found")) {
// Not a code widget, ignore
} else {
console.error("Error rendering widget", e);
}
}
}
// Find all task toggles and propagate their state
document.querySelectorAll("span[data-external-task-ref]").forEach((el) => {
const taskRef = el.dataset.externalTaskRef;
el.querySelector("input[type=checkbox]").addEventListener("change", (e) => {
const oldState = e.target.dataset.state;
const newState = oldState === " " ? "x" : " ";
// Update state in DOM as well for future toggles
e.target.dataset.state = newState;
console.log("Toggling task", taskRef);
syscall(
"system.invokeFunction",
"tasks.updateTaskState",
taskRef,
oldState,
newState,
).catch(
console.error,
);
});
});
}
init().catch(console.error);
-65
View File
@@ -1,65 +0,0 @@
/* Reset SB styles */
html,
body {
height: initial !important;
overflow: initial !important;
}
#sb-main {
height: initial !important;
display: initial !important;
}
#sb-editor {
flex: initial !important;
height: initial !important;
}
.cm-editor {
height: initial !important;
}
body {
font-family: var(--editor-font);
background-color: var(--root-background-color);
color: var(--root-color);
}
ul li p {
margin: 0;
}
body:hover #button-bar,
body:active #button-bar {
display: block;
}
#button-bar {
position: absolute;
right: 12px;
top: 3px;
display: none;
background: rgb(255 255 255 / 0.9);
padding: 0 3px;
}
#button-bar button {
border: none;
background: none;
cursor: pointer;
color: var(--root-color);
}
#edit-button {
margin-left: -10px;
}
li code {
font-size: 80%;
color: #a5a4a4;
}
iframe {
border: none;
width: 100%;
}
-2
View File
@@ -1,6 +1,4 @@
name: query
assets:
- "assets/*"
functions:
queryWidget:
path: query.ts:widget
+9 -18
View File
@@ -1,11 +1,9 @@
import type { WidgetContent } from "$sb/app_event.ts";
import { editor, events, language, markdown, space } from "$sb/syscalls.ts";
import { editor, events, language, space, system } from "$sb/syscalls.ts";
import { parseTreeToAST } from "$sb/lib/tree.ts";
import { astToKvQuery } from "$sb/lib/parse-query.ts";
import { jsonToMDTable, renderTemplate } from "../directive/util.ts";
import { renderMarkdownToHtml } from "../markdown/markdown_render.ts";
import { replaceTemplateVars } from "../template/template.ts";
import { prepareJS, wrapHTML } from "./util.ts";
export async function widget(bodyText: string): Promise<WidgetContent> {
const pageMeta = await space.getPageMeta(await editor.getCurrentPage());
@@ -57,21 +55,14 @@ export async function widget(bodyText: string): Promise<WidgetContent> {
}
}
// Parse markdown to a ParseTree
const mdTree = await markdown.parseMarkdown(resultMarkdown);
// And then render it to HTML
const html = renderMarkdownToHtml(mdTree, { smartHardBreak: true });
return {
html: await wrapHTML(`
${parsedQuery.render ? "" : `<div class="sb-table-widget">`}
${html}
${parsedQuery.render ? "" : `</div>`}
`),
script: await prepareJS(),
};
return system.invokeFunction(
"markdown.markdownContentWidget",
resultMarkdown,
);
} catch (e: any) {
return {
html: await wrapHTML(`<b>Error:</b> ${e.message}`),
};
return system.invokeFunction(
"markdown.markdownContentWidget",
`**Error:** ${e.message}`,
);
}
}
+26 -23
View File
@@ -1,9 +1,15 @@
import { WidgetContent } from "$sb/app_event.ts";
import { editor, handlebars, markdown, space, YAML } from "$sb/syscalls.ts";
import {
editor,
handlebars,
markdown,
space,
system,
YAML,
} from "$sb/syscalls.ts";
import { rewritePageRefs } from "$sb/lib/resolve.ts";
import { renderMarkdownToHtml } from "../markdown/markdown_render.ts";
import { prepareJS, wrapHTML } from "./util.ts";
import { replaceTemplateVars } from "../template/template.ts";
import { renderToText } from "$sb/lib/tree.ts";
type TemplateConfig = {
// Pull the template from a page
@@ -36,31 +42,28 @@ export async function widget(bodyText: string): Promise<WidgetContent> {
)
: undefined;
const rendered = config.raw
? templateText
: await handlebars.renderTemplate(
templateText,
value,
{
page: pageMeta,
},
);
const parsedMarkdown = await markdown.parseMarkdown(rendered);
let rendered = config.raw ? templateText : await handlebars.renderTemplate(
templateText,
value,
{
page: pageMeta,
},
);
if (templatePage) {
const parsedMarkdown = await markdown.parseMarkdown(rendered);
rewritePageRefs(parsedMarkdown, templatePage);
rendered = renderToText(parsedMarkdown);
}
const html = renderMarkdownToHtml(parsedMarkdown, {
smartHardBreak: true,
});
return {
html: await wrapHTML(html),
script: await prepareJS(),
};
return system.invokeFunction(
"markdown.markdownContentWidget",
rendered,
);
} catch (e: any) {
return {
html: `<b>Error:</b> ${e.message}`,
};
return system.invokeFunction(
"markdown.markdownContentWidget",
`**Error:** ${e.message}`,
);
}
}
-31
View File
@@ -1,31 +0,0 @@
import { asset } from "$sb/syscalls.ts";
import { panelHtml } from "../../web/components/panel_html.ts";
export async function prepareJS() {
const iframeJS = await asset.readAsset("assets/common.js");
return `
const panelHtml = \`${panelHtml}\`;
${iframeJS}
`;
}
export async function wrapHTML(html: string): Promise<string> {
const css = await asset.readAsset("assets/style.css");
return `
<!-- Load SB's own CSS here too -->
<link rel="stylesheet" href="/.client/main.css" />
<!-- In addition to some custom CSS -->
<style>${css}</style>
<!-- Wrap the whole thing in something SB-like to get access to styles -->
<div id="sb-main"><div id="sb-editor"><div class="cm-editor">
<!-- And add an edit button -->
<div id="button-bar">
<button id="reload-button" title="Reload"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg></button>
<button id="edit-button" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg></button>
</div>
${html}
</div></div></div>
`;
}