Big refactors and fixes
* Query regen * Fix anchor completion * Dependency fixes * Changelog update
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ functions:
|
||||
path: ./complete.ts:queryComplete
|
||||
events:
|
||||
- editor:complete
|
||||
handlebarHelperComplete:
|
||||
path: ./complete.ts:handlebarHelperComplete
|
||||
events:
|
||||
- editor:complete
|
||||
|
||||
# Templates
|
||||
insertQuery:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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],
|
||||
);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user