Big refactors and fixes

* Query regen
* Fix anchor completion
* Dependency fixes
* Changelog update
This commit is contained in:
Zef Hemel
2023-07-02 11:25:32 +02:00
committed by GitHub
parent fee2c5928e
commit 7c825348b2
74 changed files with 935 additions and 567 deletions
+6 -5
View File
@@ -1,4 +1,4 @@
import { editor, markdown, sync } from "$sb/silverbullet-syscall/mod.ts";
import { editor, markdown, space, sync } from "$sb/silverbullet-syscall/mod.ts";
import {
removeParentPointers,
renderToText,
@@ -6,11 +6,12 @@ import {
} from "$sb/lib/tree.ts";
import { renderDirectives } from "./directives.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
import { PageMeta } from "../../web/types.ts";
export async function updateDirectivesOnPageCommand(arg: any) {
// If `arg` is a string, it's triggered automatically via an event, not explicitly via a command
const explicitCall = typeof arg !== "string";
const pageName = await editor.getCurrentPage();
const pageMeta = await space.getPageMeta(await editor.getCurrentPage());
const text = await editor.getText();
const tree = await markdown.parseMarkdown(text);
const metaData = await extractFrontmatter(tree, ["$disableDirectives"]);
@@ -55,7 +56,7 @@ export async function updateDirectivesOnPageCommand(arg: any) {
}
const fullMatch = text.substring(tree.from!, tree.to!);
try {
const promise = renderDirectives(pageName, tree);
const promise = renderDirectives(pageMeta, tree);
replacements.push({
textPromise: promise,
fullMatch,
@@ -117,7 +118,7 @@ export async function updateDirectivesOnPageCommand(arg: any) {
// Pure server driven implementation of directive updating
export async function updateDirectives(
pageName: string,
pageMeta: PageMeta,
text: string,
) {
const tree = await markdown.parseMarkdown(text);
@@ -134,7 +135,7 @@ export async function updateDirectives(
const fullMatch = text.substring(tree.from!, tree.to!);
try {
const promise = renderDirectives(
pageName,
pageMeta,
tree,
);
replacements.push({
+22
View File
@@ -1,5 +1,7 @@
import { events } from "$sb/plugos-syscall/mod.ts";
import { CompleteEvent } from "$sb/app_event.ts";
import { buildHandebarOptions, handlebarHelpers } from "./util.ts";
import { PageMeta } from "../../web/types.ts";
export async function queryComplete(completeEvent: CompleteEvent) {
const match = /#query ([\w\-_]+)*$/.exec(completeEvent.linePrefix);
@@ -18,3 +20,23 @@ export async function queryComplete(completeEvent: CompleteEvent) {
})),
};
}
export function handlebarHelperComplete(completeEvent: CompleteEvent) {
const match = /\{\{([\w@]*)$/.exec(completeEvent.linePrefix);
if (!match) {
return null;
}
const handlebarOptions = buildHandebarOptions({ name: "" } as PageMeta);
const allCompletions = Object.keys(handlebarOptions.helpers).concat(
Object.keys(handlebarOptions.data).map((key) => `@${key}`),
);
return {
from: completeEvent.pos - match[1].length,
options: allCompletions
.map((name) => ({
label: name,
})),
};
}
+4
View File
@@ -21,6 +21,10 @@ functions:
path: ./complete.ts:queryComplete
events:
- editor:complete
handlebarHelperComplete:
path: ./complete.ts:handlebarHelperComplete
events:
- editor:complete
# Templates
insertQuery:
+15 -14
View File
@@ -1,5 +1,6 @@
import { ParseTree, renderToText } from "$sb/lib/tree.ts";
import { sync } from "../../plug-api/silverbullet-syscall/mod.ts";
import { PageMeta } from "../../web/types.ts";
import { evalDirectiveRenderer } from "./eval_directive.ts";
import { queryDirectiveRenderer } from "./query_directive.ts";
@@ -17,13 +18,13 @@ export const directiveRegex =
* Looks for directives in the text dispatches them based on name
*/
export async function directiveDispatcher(
pageName: string,
pageMeta: PageMeta,
directiveTree: ParseTree,
directiveRenderers: Record<
string,
(
directive: string,
pageName: string,
pageMeta: PageMeta,
arg: string | ParseTree,
) => Promise<string>
>,
@@ -34,6 +35,14 @@ export async function directiveDispatcher(
const directiveStartText = renderToText(directiveStart).trim();
const directiveEndText = renderToText(directiveEnd).trim();
if (!(await sync.hasInitialSyncCompleted())) {
console.info(
"Initial sync hasn't completed yet, not updating directives.",
);
// Render the query directive as-is
return renderToText(directiveTree);
}
if (directiveStart.children!.length === 1) {
// Everything not #query
const match = directiveStartRegex.exec(directiveStart.children![0].text!);
@@ -44,7 +53,7 @@ export async function directiveDispatcher(
let [_fullMatch, type, arg] = match;
try {
arg = arg.trim();
const newBody = await directiveRenderers[type](type, pageName, arg);
const newBody = await directiveRenderers[type](type, pageMeta, arg);
const result =
`${directiveStartText}\n${newBody.trim()}\n${directiveEndText}`;
return result;
@@ -53,17 +62,9 @@ export async function directiveDispatcher(
}
} else {
// #query
if (!(await sync.hasInitialSyncCompleted())) {
console.info(
"Initial sync hasn't completed yet, not updating query directives.",
);
// Render the query directive as-is
return renderToText(directiveTree);
}
const newBody = await directiveRenderers["query"](
"query",
pageName,
pageMeta,
directiveStart.children![1], // The query ParseTree
);
const result =
@@ -73,10 +74,10 @@ export async function directiveDispatcher(
}
export async function renderDirectives(
pageName: string,
pageMeta: PageMeta,
directiveTree: ParseTree,
): Promise<string> {
const replacementText = await directiveDispatcher(pageName, directiveTree, {
const replacementText = await directiveDispatcher(pageMeta, directiveTree, {
use: templateDirectiveRenderer,
include: templateDirectiveRenderer,
query: queryDirectiveRenderer,
+6 -4
View File
@@ -3,6 +3,8 @@
import { YAML } from "$sb/plugos-syscall/mod.ts";
import { ParseTree } from "$sb/lib/tree.ts";
import { jsonToMDTable, renderTemplate } from "./util.ts";
import { PageMeta } from "../../web/types.ts";
import { replaceTemplateVars } from "../core/template.ts";
// Enables plugName.functionName(arg1, arg2) syntax in JS expressions
function translateJs(js: string): string {
@@ -20,13 +22,13 @@ const expressionRegex = /(.+?)(\s+render\s+\[\[([^\]]+)\]\])?$/;
// This is rather scary and fragile stuff, but it works.
export async function evalDirectiveRenderer(
_directive: string,
_pageName: string,
pageMeta: PageMeta,
expression: string | ParseTree,
): Promise<string> {
if (typeof expression !== "string") {
throw new Error("Expected a string");
}
console.log("Got JS expression", expression);
// console.log("Got JS expression", expression);
const match = expressionRegex.exec(expression);
if (!match) {
throw new Error(`Invalid eval directive: ${expression}`);
@@ -44,11 +46,11 @@ export async function evalDirectiveRenderer(
function invokeFunction(name, ...args) {
return syscall("system.invokeFunction", "server", name, ...args);
}
return ${translateJs(expression)};
return ${replaceTemplateVars(translateJs(expression), pageMeta)};
})()`,
);
if (template) {
return await renderTemplate(template, result);
return await renderTemplate(pageMeta, template, result);
}
if (typeof result === "string") {
return result;
+5 -3
View File
@@ -5,17 +5,18 @@ import { renderTemplate } from "./util.ts";
import { parseQuery } from "./parser.ts";
import { jsonToMDTable } from "./util.ts";
import { ParseTree } from "$sb/lib/tree.ts";
import { PageMeta } from "../../web/types.ts";
export async function queryDirectiveRenderer(
_directive: string,
pageName: string,
pageMeta: PageMeta,
query: string | ParseTree,
): Promise<string> {
if (typeof query === "string") {
throw new Error("Argument must be a ParseTree");
}
const parsedQuery = parseQuery(
JSON.parse(replaceTemplateVars(JSON.stringify(query), pageName)),
JSON.parse(replaceTemplateVars(JSON.stringify(query), pageMeta)),
);
const eventName = `query:${parsedQuery.table}`;
@@ -24,7 +25,7 @@ export async function queryDirectiveRenderer(
// Let's dispatch an event and see what happens
const results = await events.dispatchEvent(
eventName,
{ query: parsedQuery, pageName: pageName },
{ query: parsedQuery, pageName: pageMeta.name },
30 * 1000,
);
if (results.length === 0) {
@@ -34,6 +35,7 @@ export async function queryDirectiveRenderer(
// console.log("Parsed query", parsedQuery);
if (parsedQuery.render) {
const rendered = await renderTemplate(
pageMeta,
parsedQuery.render,
results[0],
);
+12 -11
View File
@@ -8,13 +8,14 @@ import { replaceTemplateVars } from "../core/template.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
import { directiveRegex } from "./directives.ts";
import { updateDirectives } from "./command.ts";
import { registerHandlebarsHelpers } from "./util.ts";
import { buildHandebarOptions } from "./util.ts";
import { PageMeta } from "../../web/types.ts";
const templateRegex = /\[\[([^\]]+)\]\]\s*(.*)\s*/;
export async function templateDirectiveRenderer(
directive: string,
pageName: string,
pageMeta: PageMeta,
arg: string | ParseTree,
): Promise<string> {
if (typeof arg !== "string") {
@@ -29,9 +30,13 @@ export async function templateDirectiveRenderer(
let parsedArgs = {};
if (args) {
try {
parsedArgs = JSON.parse(args);
parsedArgs = JSON.parse(replaceTemplateVars(args, pageMeta));
} catch {
throw new Error(`Failed to parse template instantiation args: ${arg}`);
throw new Error(
`Failed to parse template instantiation arg: ${
replaceTemplateVars(args, pageMeta)
}`,
);
}
}
let templateText = "";
@@ -51,18 +56,14 @@ export async function templateDirectiveRenderer(
// if it's a template injection (not a literal "include")
if (directive === "use") {
registerHandlebarsHelpers();
const templateFn = Handlebars.compile(
replaceTemplateVars(newBody, pageName),
newBody,
{ noEscape: true },
);
if (typeof parsedArgs !== "string") {
(parsedArgs as any).page = pageName;
}
newBody = templateFn(parsedArgs);
newBody = templateFn(parsedArgs, buildHandebarOptions(pageMeta));
// Recursively render directives
newBody = await updateDirectives(pageName, newBody);
newBody = await updateDirectives(pageMeta, newBody);
}
return newBody.trim();
}
+42 -33
View File
@@ -2,6 +2,7 @@ import Handlebars from "handlebars";
import { space } from "$sb/silverbullet-syscall/mod.ts";
import { niceDate } from "$sb/lib/dates.ts";
import { PageMeta } from "../../web/types.ts";
const maxWidth = 70;
@@ -79,47 +80,55 @@ export function jsonToMDTable(
}
export async function renderTemplate(
pageMeta: PageMeta,
renderTemplate: string,
data: any[],
): Promise<string> {
registerHandlebarsHelpers();
// Handlebars.registerHelper("yaml", (v: any, prefix: string) => {
// if (typeof prefix === "string") {
// let yaml = (await YAML.stringify(v))
// .split("\n")
// .join("\n" + prefix)
// .trim();
// if (Array.isArray(v)) {
// return "\n" + prefix + yaml;
// } else {
// return yaml;
// }
// } else {
// return YAML.stringify(v).trim();
// }
// });
let templateText = await space.readPage(renderTemplate);
templateText = `{{#each .}}\n${templateText}\n{{/each}}`;
const template = Handlebars.compile(templateText, { noEscape: true });
return template(data);
return template(data, buildHandebarOptions(pageMeta));
}
export function registerHandlebarsHelpers() {
Handlebars.registerHelper("json", (v: any) => JSON.stringify(v));
Handlebars.registerHelper("niceDate", (ts: any) => niceDate(new Date(ts)));
Handlebars.registerHelper("escapeRegexp", (ts: any) => {
return ts.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
});
Handlebars.registerHelper("prefixLines", (v: string, prefix: string) =>
v
.split("\n")
.map((l) => prefix + l)
.join("\n"));
export function buildHandebarOptions(pageMeta: PageMeta) {
return {
helpers: handlebarHelpers(pageMeta.name),
data: { page: pageMeta },
};
}
Handlebars.registerHelper(
"substring",
(s: string, from: number, to: number, elipsis = "") =>
export function handlebarHelpers(pageName: string) {
return {
json: (v: any) => JSON.stringify(v),
niceDate: (ts: any) => niceDate(new Date(ts)),
escapeRegexp: (ts: any) => {
return ts.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
},
prefixLines: (v: string, prefix: string) =>
v.split("\n").map((l) => prefix + l).join("\n"),
substring: (s: string, from: number, to: number, elipsis = "") =>
s.length > to - from ? s.substring(from, to) + elipsis : s,
);
today: () => niceDate(new Date()),
tomorrow: () => {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
return niceDate(tomorrow);
},
yesterday: () => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return niceDate(yesterday);
},
lastWeek: () => {
const lastWeek = new Date();
lastWeek.setDate(lastWeek.getDate() - 7);
return niceDate(lastWeek);
},
nextWeek: () => {
const nextWeek = new Date();
nextWeek.setDate(nextWeek.getDate() + 7);
return niceDate(nextWeek);
},
};
}