Fixes #1236
parent
7abb096382
commit
cf81594b02
|
@ -0,0 +1,143 @@
|
|||
import {
|
||||
findNodeOfType,
|
||||
type ParseTree,
|
||||
renderToText,
|
||||
replaceNodesMatchingAsync,
|
||||
} from "@silverbulletmd/silverbullet/lib/tree";
|
||||
import {
|
||||
parsePageRef,
|
||||
validatePageName,
|
||||
} from "@silverbulletmd/silverbullet/lib/page_ref";
|
||||
import { renderExpressionResult } from "../plugs/template/util.ts";
|
||||
import { parseMarkdown } from "$common/markdown_parser/parser.ts";
|
||||
import type { LuaExpression } from "$common/space_lua/ast.ts";
|
||||
import { evalExpression } from "$common/space_lua/eval.ts";
|
||||
import type { LuaEnv, LuaStackFrame } from "$common/space_lua/runtime.ts";
|
||||
import { parseExpressionString } from "$common/space_lua/parse.ts";
|
||||
import type { CodeWidgetHook } from "../web/hooks/code_widget.ts";
|
||||
|
||||
/**
|
||||
* Finds code widgets, runs their plug code to render and inlines their content in the parse tree
|
||||
* @param mdTree parsed markdown tree
|
||||
* @param pageName name of the current page
|
||||
* @returns modified mdTree
|
||||
*/
|
||||
export async function expandCodeWidgets(
|
||||
codeWidgetHook: CodeWidgetHook,
|
||||
mdTree: ParseTree,
|
||||
pageName: string,
|
||||
env: LuaEnv,
|
||||
sf: LuaStackFrame,
|
||||
): Promise<ParseTree> {
|
||||
await replaceNodesMatchingAsync(mdTree, async (n) => {
|
||||
if (n.type === "FencedCode") {
|
||||
const codeInfo = findNodeOfType(n, "CodeInfo");
|
||||
if (!codeInfo) {
|
||||
return;
|
||||
}
|
||||
const codeType = codeInfo.children![0].text!;
|
||||
const codeTextNode = findNodeOfType(n, "CodeText");
|
||||
try {
|
||||
// This will error out if this is not a code wiget, which is fine
|
||||
const langCallback = codeWidgetHook.codeWidgetCallbacks.get(codeType);
|
||||
if (!langCallback) {
|
||||
return {
|
||||
text: "",
|
||||
};
|
||||
}
|
||||
const result = await langCallback(
|
||||
renderToText(codeTextNode!),
|
||||
pageName,
|
||||
);
|
||||
if (!result) {
|
||||
return {
|
||||
text: "",
|
||||
};
|
||||
}
|
||||
// Only do this for "markdown" widgets, that is: that can render to markdown
|
||||
if (result.markdown !== undefined) {
|
||||
const parsedBody = parseMarkdown(result.markdown);
|
||||
// Recursively process
|
||||
return expandCodeWidgets(
|
||||
codeWidgetHook,
|
||||
parsedBody,
|
||||
pageName,
|
||||
env,
|
||||
sf,
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
// 'not found' is to be expected (no code widget configured for this language)
|
||||
// Every other error should probably be reported
|
||||
if (!e.message.includes("not found")) {
|
||||
console.trace();
|
||||
console.error("Error rendering code", e.message);
|
||||
}
|
||||
}
|
||||
} else if (n.type === "Image") {
|
||||
// Let's scan for ![[embeds]] that are codified as Images, confusingly
|
||||
const wikiLinkMark = findNodeOfType(n, "WikiLinkMark");
|
||||
if (!wikiLinkMark) {
|
||||
return;
|
||||
}
|
||||
const wikiLinkPage = findNodeOfType(n, "WikiLinkPage");
|
||||
if (!wikiLinkPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const page = wikiLinkPage.children![0].text!;
|
||||
|
||||
// Check if this is likely a page link (based on the path format, e.g. if it contains an extension, it's probably not a page link)
|
||||
try {
|
||||
const ref = parsePageRef(page);
|
||||
validatePageName(ref.page);
|
||||
} catch {
|
||||
// Not a valid page name, so not a page reference
|
||||
return;
|
||||
}
|
||||
|
||||
// Internally translate this to a template that inlines a page, then render that
|
||||
const langCallback = codeWidgetHook.codeWidgetCallbacks.get("template")!;
|
||||
const result = await langCallback(`{{[[${page}]]}}`, pageName);
|
||||
if (!result) {
|
||||
return {
|
||||
text: "",
|
||||
};
|
||||
}
|
||||
// Only do this for "markdown" widgets, that is: that can render to markdown
|
||||
if (result.markdown !== undefined) {
|
||||
const parsedBody = await parseMarkdown(result.markdown);
|
||||
// Recursively process
|
||||
return expandCodeWidgets(
|
||||
codeWidgetHook,
|
||||
parsedBody,
|
||||
page,
|
||||
env,
|
||||
sf,
|
||||
);
|
||||
}
|
||||
} else if (n.type === "LuaDirective") {
|
||||
const expr = findNodeOfType(n, "LuaExpressionDirective") as
|
||||
| LuaExpression
|
||||
| null;
|
||||
if (!expr) {
|
||||
return;
|
||||
}
|
||||
const exprText = renderToText(expr);
|
||||
|
||||
let result = await evalExpression(
|
||||
parseExpressionString(exprText),
|
||||
env,
|
||||
sf,
|
||||
);
|
||||
|
||||
if (result?.markdown) {
|
||||
result = result.markdown;
|
||||
}
|
||||
|
||||
const markdown = await renderExpressionResult(result);
|
||||
return parseMarkdown(markdown);
|
||||
}
|
||||
});
|
||||
return mdTree;
|
||||
}
|
|
@ -106,6 +106,15 @@ export class LuaStackFrame {
|
|||
}
|
||||
|
||||
static lostFrame = new LuaStackFrame(new LuaEnv(), null);
|
||||
|
||||
static createWithGlobalEnv(
|
||||
globalEnv: any,
|
||||
ctx: ASTCtx | null = null,
|
||||
): LuaStackFrame {
|
||||
const env = new LuaEnv();
|
||||
env.setLocal("_GLOBAL", globalEnv);
|
||||
return new LuaStackFrame(env, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
export class LuaMultiRes {
|
||||
|
|
|
@ -29,16 +29,21 @@ export function luaSyscalls(commonSystem: CommonSystem): SysCallMapping {
|
|||
commonSystem.spaceLuaEnv.env,
|
||||
);
|
||||
const sf = new LuaStackFrame(env, null);
|
||||
const result = evalExpression(ast, commonSystem.spaceLuaEnv.env, sf);
|
||||
if (isSendable(result)) {
|
||||
return luaValueToJS(result);
|
||||
const luaResult = await evalExpression(
|
||||
ast,
|
||||
commonSystem.spaceLuaEnv.env,
|
||||
sf,
|
||||
);
|
||||
const jsResult = luaValueToJS(luaResult);
|
||||
if (isSendable(jsResult)) {
|
||||
return jsResult;
|
||||
} else {
|
||||
// This may evaluate to e.g. a function, which is not sendable, in this case we'll console.warn and return a stringified version of the result
|
||||
console.warn(
|
||||
"Lua eval result is not sendable, returning stringified version",
|
||||
result,
|
||||
jsResult,
|
||||
);
|
||||
return luaToString(result);
|
||||
return luaToString(luaResult);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error("Lua eval error: ", e.message, e.sf?.astCtx);
|
||||
|
|
|
@ -11,6 +11,8 @@ import { renderToText } from "@silverbulletmd/silverbullet/lib/tree";
|
|||
import { activeWidgets } from "./markdown_widget.ts";
|
||||
import { attachWidgetEventHandlers } from "./widget_util.ts";
|
||||
import { renderExpressionResult } from "../../plugs/template/util.ts";
|
||||
import { expandCodeWidgets } from "$common/markdown.ts";
|
||||
import { LuaEnv, LuaStackFrame } from "$common/space_lua/runtime.ts";
|
||||
|
||||
export type LuaWidgetCallback = (
|
||||
bodyText: string,
|
||||
|
@ -101,13 +103,16 @@ export class LuaWidget extends WidgetType {
|
|||
extendedMarkdownLanguage,
|
||||
mdContent,
|
||||
);
|
||||
mdTree = await this.client.clientSystem.localSyscall(
|
||||
"system.invokeFunction",
|
||||
[
|
||||
"markdown.expandCodeWidgets",
|
||||
|
||||
const tl = new LuaEnv();
|
||||
const sf = new LuaStackFrame(tl, null, LuaStackFrame.lostFrame);
|
||||
tl.setLocal("_GLOBAL", client.clientSystem.spaceLuaEnv.env);
|
||||
mdTree = await expandCodeWidgets(
|
||||
client.clientSystem.codeWidgetHook,
|
||||
mdTree,
|
||||
this.client.currentPage,
|
||||
],
|
||||
client.clientSystem.spaceLuaEnv.env,
|
||||
sf,
|
||||
);
|
||||
const trimmedMarkdown = renderToText(mdTree).trim();
|
||||
|
||||
|
|
Loading…
Reference in New Issue