Factor out markdown widget rendering

pull/555/head
Zef Hemel 2023-10-29 10:02:50 +01:00
parent 23e99b0c46
commit 50caba8522
17 changed files with 102 additions and 77 deletions

View File

@ -54,6 +54,7 @@ export type CompleteEvent = {
export type WidgetContent = { export type WidgetContent = {
html?: string; html?: string;
script?: string; script?: string;
markdown?: string;
url?: string; url?: string;
height?: number; height?: number;
width?: number; width?: number;

View File

@ -115,3 +115,14 @@ export type ObjectValue<T> = {
} & T; } & T;
export type ObjectQuery = Omit<Query, "prefix">; export type ObjectQuery = Omit<Query, "prefix">;
// Code widget stuff
export type CodeWidgetCallback = (
bodyText: string,
) => Promise<CodeWidgetContent>;
export type CodeWidgetContent = {
html?: string;
markdown?: string;
script?: string;
};

View File

@ -2,6 +2,13 @@ name: markdown
assets: assets:
- "assets/*" - "assets/*"
functions: functions:
# API
markdownContentWidget:
path: markdown_content_widget.ts:markdownContentWidget
# User facing
toggle: toggle:
path: "./markdown.ts:togglePreview" path: "./markdown.ts:togglePreview"
command: command:

View File

@ -1,9 +1,25 @@
import { asset } from "$sb/syscalls.ts"; import { WidgetContent } from "$sb/app_event.ts";
import { asset, markdown } from "$sb/syscalls.ts";
import { panelHtml } from "../../web/components/panel_html.ts"; import { panelHtml } from "../../web/components/panel_html.ts";
import { renderMarkdownToHtml } from "./markdown_render.ts";
export async function markdownContentWidget(
markdownText: string,
): Promise<WidgetContent> {
// Parse markdown to a ParseTree
const mdTree = await markdown.parseMarkdown(markdownText);
// And then render it to HTML
const html = renderMarkdownToHtml(mdTree, { smartHardBreak: true });
return {
html: await wrapHTML(html),
script: await prepareJS(),
// And add back the markdown text so we can render it in a different way if desired
markdown: markdownText,
};
}
export async function prepareJS() { export async function prepareJS() {
const iframeJS = await asset.readAsset("assets/common.js"); const iframeJS = await asset.readAsset("assets/markdown_widget.js");
return ` return `
const panelHtml = \`${panelHtml}\`; const panelHtml = \`${panelHtml}\`;
${iframeJS} ${iframeJS}
@ -11,7 +27,7 @@ export async function prepareJS() {
} }
export async function wrapHTML(html: string): Promise<string> { export async function wrapHTML(html: string): Promise<string> {
const css = await asset.readAsset("assets/style.css"); const css = await asset.readAsset("assets/markdown_widget.css");
return ` return `
<!-- Load SB's own CSS here too --> <!-- Load SB's own CSS here too -->

View File

@ -10,8 +10,8 @@ export async function updateMarkdownPreview() {
const text = await editor.getText(); const text = await editor.getText();
const mdTree = await markdown.parseMarkdown(text); const mdTree = await markdown.parseMarkdown(text);
// const cleanMd = await cleanMarkdown(text); // const cleanMd = await cleanMarkdown(text);
const css = await asset.readAsset("assets/styles.css"); const css = await asset.readAsset("assets/preview.css");
const js = await asset.readAsset("assets/handler.js"); const js = await asset.readAsset("assets/preview.js");
const html = renderMarkdownToHtml(mdTree, { const html = renderMarkdownToHtml(mdTree, {
smartHardBreak: true, smartHardBreak: true,
annotationPositions: true, annotationPositions: true,

View File

@ -1,6 +1,4 @@
name: query name: query
assets:
- "assets/*"
functions: functions:
queryWidget: queryWidget:
path: query.ts:widget path: query.ts:widget

View File

@ -1,11 +1,9 @@
import type { WidgetContent } from "$sb/app_event.ts"; 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 { parseTreeToAST } from "$sb/lib/tree.ts";
import { astToKvQuery } from "$sb/lib/parse-query.ts"; import { astToKvQuery } from "$sb/lib/parse-query.ts";
import { jsonToMDTable, renderTemplate } from "../directive/util.ts"; import { jsonToMDTable, renderTemplate } from "../directive/util.ts";
import { renderMarkdownToHtml } from "../markdown/markdown_render.ts";
import { replaceTemplateVars } from "../template/template.ts"; import { replaceTemplateVars } from "../template/template.ts";
import { prepareJS, wrapHTML } from "./util.ts";
export async function widget(bodyText: string): Promise<WidgetContent> { export async function widget(bodyText: string): Promise<WidgetContent> {
const pageMeta = await space.getPageMeta(await editor.getCurrentPage()); 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 return system.invokeFunction(
const mdTree = await markdown.parseMarkdown(resultMarkdown); "markdown.markdownContentWidget",
// And then render it to HTML resultMarkdown,
const html = renderMarkdownToHtml(mdTree, { smartHardBreak: true }); );
return {
html: await wrapHTML(`
${parsedQuery.render ? "" : `<div class="sb-table-widget">`}
${html}
${parsedQuery.render ? "" : `</div>`}
`),
script: await prepareJS(),
};
} catch (e: any) { } catch (e: any) {
return { return system.invokeFunction(
html: await wrapHTML(`<b>Error:</b> ${e.message}`), "markdown.markdownContentWidget",
}; `**Error:** ${e.message}`,
);
} }
} }

View File

@ -1,9 +1,15 @@
import { WidgetContent } from "$sb/app_event.ts"; 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 { 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 { replaceTemplateVars } from "../template/template.ts";
import { renderToText } from "$sb/lib/tree.ts";
type TemplateConfig = { type TemplateConfig = {
// Pull the template from a page // Pull the template from a page
@ -36,31 +42,28 @@ export async function widget(bodyText: string): Promise<WidgetContent> {
) )
: undefined; : undefined;
const rendered = config.raw let rendered = config.raw ? templateText : await handlebars.renderTemplate(
? templateText templateText,
: await handlebars.renderTemplate( value,
templateText, {
value, page: pageMeta,
{ },
page: pageMeta, );
},
);
const parsedMarkdown = await markdown.parseMarkdown(rendered);
if (templatePage) { if (templatePage) {
const parsedMarkdown = await markdown.parseMarkdown(rendered);
rewritePageRefs(parsedMarkdown, templatePage); rewritePageRefs(parsedMarkdown, templatePage);
rendered = renderToText(parsedMarkdown);
} }
const html = renderMarkdownToHtml(parsedMarkdown, {
smartHardBreak: true,
});
return { return system.invokeFunction(
html: await wrapHTML(html), "markdown.markdownContentWidget",
script: await prepareJS(), rendered,
}; );
} catch (e: any) { } catch (e: any) {
return { return system.invokeFunction(
html: `<b>Error:</b> ${e.message}`, "markdown.markdownContentWidget",
}; `**Error:** ${e.message}`,
);
} }
} }

View File

@ -1,13 +1,13 @@
import { WidgetContent } from "../../plug-api/app_event.ts"; import { WidgetContent } from "../../plug-api/app_event.ts";
import { Decoration, EditorState, syntaxTree, WidgetType } from "../deps.ts"; import { Decoration, EditorState, syntaxTree, WidgetType } from "../deps.ts";
import type { Client } from "../client.ts"; import type { Client } from "../client.ts";
import { CodeWidgetCallback } from "../hooks/code_widget.ts";
import { import {
decoratorStateField, decoratorStateField,
invisibleDecoration, invisibleDecoration,
isCursorInRange, isCursorInRange,
} from "./util.ts"; } from "./util.ts";
import { createWidgetSandboxIFrame } from "../components/widget_sandbox_iframe.ts"; import { createWidgetSandboxIFrame } from "../components/widget_sandbox_iframe.ts";
import type { CodeWidgetCallback } from "$sb/types.ts";
class IFrameWidget extends WidgetType { class IFrameWidget extends WidgetType {
iframe?: HTMLIFrameElement; iframe?: HTMLIFrameElement;

View File

@ -1,14 +1,11 @@
import { Hook, Manifest } from "../../plugos/types.ts"; import { Hook, Manifest } from "../../plugos/types.ts";
import { System } from "../../plugos/system.ts"; import { System } from "../../plugos/system.ts";
import { CodeWidgetCallback } from "$sb/types.ts";
export type CodeWidgetT = { export type CodeWidgetT = {
codeWidget?: string; codeWidget?: string;
}; };
export type CodeWidgetCallback = (
bodyText: string,
) => Promise<{ html: string; script: string }>;
export class CodeWidgetHook implements Hook<CodeWidgetT> { export class CodeWidgetHook implements Hook<CodeWidgetT> {
codeWidgetCallbacks = new Map<string, CodeWidgetCallback>(); codeWidgetCallbacks = new Map<string, CodeWidgetCallback>();

View File

@ -216,15 +216,13 @@
color: var(--editor-line-meta-color); color: var(--editor-line-meta-color);
} }
.sb-table-widget { thead tr {
thead tr { background-color: var(--editor-table-head-background-color);
background-color: var(--editor-table-head-background-color); color: var(--editor-table-head-color);
color: var(--editor-table-head-color); }
}
tbody tr:nth-of-type(even) { tbody tr:nth-of-type(even) {
background-color: var(--editor-table-even-background-color); background-color: var(--editor-table-even-background-color);
}
} }
.sb-line-blockquote { .sb-line-blockquote {

View File

@ -393,22 +393,24 @@
margin-bottom: -3rem; margin-bottom: -3rem;
overflow: auto; overflow: auto;
table {
width: 100%;
border-spacing: 0;
}
thead tr {
font-weight: bold;
}
th,
td {
padding: 8px;
white-space: nowrap;
}
} }
table {
width: 100%;
border-spacing: 0;
}
thead tr {
font-weight: bold;
}
th,
td {
padding: 8px;
white-space: nowrap;
}
// dont apply background color twice for (fenced) code blocks // dont apply background color twice for (fenced) code blocks
.sb-line-code .sb-code { .sb-line-code .sb-code {
background-color: transparent; background-color: transparent;

View File

@ -1,3 +1,4 @@
import { CodeWidgetContent } from "$sb/types.ts";
import { SysCallMapping } from "../../plugos/system.ts"; import { SysCallMapping } from "../../plugos/system.ts";
import { Client } from "../client.ts"; import { Client } from "../client.ts";
@ -9,7 +10,7 @@ export function widgetSyscalls(
_ctx, _ctx,
lang: string, lang: string,
body: string, body: string,
): Promise<{ html: string; script: string }> => { ): Promise<CodeWidgetContent> => {
const langCallback = client.system.codeWidgetHook.codeWidgetCallbacks.get( const langCallback = client.system.codeWidgetHook.codeWidgetCallbacks.get(
lang, lang,
); );