SilverBullet pivot to become an offline-first PWA (#403)
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { editor, markdown, system } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { editor, markdown, sync } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import {
|
||||
ParseTree,
|
||||
removeParentPointers,
|
||||
renderToText,
|
||||
traverseTree,
|
||||
@@ -14,12 +13,17 @@ export async function updateDirectivesOnPageCommand(arg: any) {
|
||||
const pageName = await editor.getCurrentPage();
|
||||
const text = await editor.getText();
|
||||
const tree = await markdown.parseMarkdown(text);
|
||||
const metaData = extractFrontmatter(tree, ["$disableDirectives"]);
|
||||
const metaData = await extractFrontmatter(tree, ["$disableDirectives"]);
|
||||
if (metaData.$disableDirectives) {
|
||||
// Not updating, directives disabled
|
||||
return;
|
||||
}
|
||||
|
||||
// if (!(await sync.hasInitialSyncCompleted())) {
|
||||
// console.info("Initial sync hasn't completed yet, not updating directives.");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// If this page is shared ($share) via collab: disable directives as well
|
||||
// due to security concerns
|
||||
if (metaData.$share) {
|
||||
@@ -51,12 +55,7 @@ export async function updateDirectivesOnPageCommand(arg: any) {
|
||||
}
|
||||
const fullMatch = text.substring(tree.from!, tree.to!);
|
||||
try {
|
||||
const promise = system.invokeFunction(
|
||||
"server",
|
||||
"serverRenderDirective",
|
||||
pageName,
|
||||
tree,
|
||||
);
|
||||
const promise = renderDirectives(pageName, tree);
|
||||
replacements.push({
|
||||
textPromise: promise,
|
||||
fullMatch,
|
||||
@@ -116,17 +115,8 @@ export async function updateDirectivesOnPageCommand(arg: any) {
|
||||
}
|
||||
}
|
||||
|
||||
// Called from client, running on server
|
||||
// The text passed here is going to be a single directive block (not a full page)
|
||||
export function serverRenderDirective(
|
||||
pageName: string,
|
||||
tree: ParseTree,
|
||||
): Promise<string> {
|
||||
return renderDirectives(pageName, tree);
|
||||
}
|
||||
|
||||
// Pure server driven implementation of directive updating
|
||||
export async function serverUpdateDirectives(
|
||||
export async function updateDirectives(
|
||||
pageName: string,
|
||||
text: string,
|
||||
) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { events } from "$sb/plugos-syscall/mod.ts";
|
||||
import { CompleteEvent } from "../../plug-api/app_event.ts";
|
||||
import { CompleteEvent } from "$sb/app_event.ts";
|
||||
|
||||
export async function queryComplete(completeEvent: CompleteEvent) {
|
||||
const match = /#query ([\w\-_]+)*$/.exec(completeEvent.linePrefix);
|
||||
|
||||
+35
-33
@@ -5,46 +5,48 @@ import type { IndexTreeEvent, QueryProviderEvent } from "$sb/app_event.ts";
|
||||
import { index } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { collectNodesOfType, findNodeOfType } from "$sb/lib/tree.ts";
|
||||
import { applyQuery, removeQueries } from "$sb/lib/query.ts";
|
||||
import * as YAML from "yaml";
|
||||
import { YAML } from "$sb/plugos-syscall/mod.ts";
|
||||
|
||||
export async function indexData({ name, tree }: IndexTreeEvent) {
|
||||
const dataObjects: { key: string; value: any }[] = [];
|
||||
|
||||
removeQueries(tree);
|
||||
|
||||
collectNodesOfType(tree, "FencedCode").forEach((t) => {
|
||||
const codeInfoNode = findNodeOfType(t, "CodeInfo");
|
||||
if (!codeInfoNode) {
|
||||
return;
|
||||
}
|
||||
if (codeInfoNode.children![0].text !== "data") {
|
||||
return;
|
||||
}
|
||||
const codeTextNode = findNodeOfType(t, "CodeText");
|
||||
if (!codeTextNode) {
|
||||
// Honestly, this shouldn't happen
|
||||
return;
|
||||
}
|
||||
const codeText = codeTextNode.children![0].text!;
|
||||
try {
|
||||
const docs = codeText.split("---").map((d) => YAML.parse(d));
|
||||
// We support multiple YAML documents in one block
|
||||
for (let i = 0; i < docs.length; i++) {
|
||||
const doc = docs[i];
|
||||
if (!doc) {
|
||||
continue;
|
||||
}
|
||||
dataObjects.push({
|
||||
key: `data:${name}@${t.from! + i}`,
|
||||
value: doc,
|
||||
});
|
||||
await Promise.all(
|
||||
collectNodesOfType(tree, "FencedCode").map(async (t) => {
|
||||
const codeInfoNode = findNodeOfType(t, "CodeInfo");
|
||||
if (!codeInfoNode) {
|
||||
return;
|
||||
}
|
||||
// console.log("Parsed data", parsedData);
|
||||
} catch (e) {
|
||||
console.error("Could not parse data", codeText, "error:", e);
|
||||
return;
|
||||
}
|
||||
});
|
||||
if (codeInfoNode.children![0].text !== "data") {
|
||||
return;
|
||||
}
|
||||
const codeTextNode = findNodeOfType(t, "CodeText");
|
||||
if (!codeTextNode) {
|
||||
// Honestly, this shouldn't happen
|
||||
return;
|
||||
}
|
||||
const codeText = codeTextNode.children![0].text!;
|
||||
try {
|
||||
const docs = codeText.split("---");
|
||||
// We support multiple YAML documents in one block
|
||||
for (let i = 0; i < docs.length; i++) {
|
||||
const doc = await YAML.parse(docs[i]);
|
||||
if (!doc) {
|
||||
continue;
|
||||
}
|
||||
dataObjects.push({
|
||||
key: `data:${name}@${t.from! + i}`,
|
||||
value: doc,
|
||||
});
|
||||
}
|
||||
// console.log("Parsed data", parsedData);
|
||||
} catch (e) {
|
||||
console.error("Could not parse data", codeText, "error:", e);
|
||||
return;
|
||||
}
|
||||
}),
|
||||
);
|
||||
// console.log("Found", dataObjects.length, "data objects");
|
||||
await index.batchSet(name, dataObjects);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
name: directive
|
||||
imports:
|
||||
- https://get.silverbullet.md/global.plug.json
|
||||
requiredPermissions:
|
||||
- fetch
|
||||
functions:
|
||||
serverRenderDirective:
|
||||
path: ./command.ts:serverRenderDirective
|
||||
updateDirectivesOnPageCommand:
|
||||
path: ./command.ts:updateDirectivesOnPageCommand
|
||||
command:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ParseTree, renderToText } from "$sb/lib/tree.ts";
|
||||
import { sync } from "../../plug-api/silverbullet-syscall/mod.ts";
|
||||
|
||||
import { evalDirectiveRenderer } from "./eval_directive.ts";
|
||||
import { queryDirectiveRenderer } from "./query_directive.ts";
|
||||
@@ -52,6 +53,14 @@ 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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// This is some shocking stuff. My profession would kill me for this.
|
||||
|
||||
import * as YAML from "yaml";
|
||||
import { YAML } from "$sb/plugos-syscall/mod.ts";
|
||||
import { ParseTree } from "$sb/lib/tree.ts";
|
||||
import { jsonToMDTable, renderTemplate } from "./util.ts";
|
||||
|
||||
@@ -57,7 +57,7 @@ export async function evalDirectiveRenderer(
|
||||
} else if (Array.isArray(result)) {
|
||||
return jsonToMDTable(result);
|
||||
}
|
||||
return YAML.stringify(result);
|
||||
return await YAML.stringify(result);
|
||||
} catch (e: any) {
|
||||
return `**ERROR:** ${e.message}`;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { replaceTemplateVars } from "../core/template.ts";
|
||||
import { renderTemplate } from "./util.ts";
|
||||
import { parseQuery } from "./parser.ts";
|
||||
import { jsonToMDTable } from "./util.ts";
|
||||
import { ParseTree } from "../../plug-api/lib/tree.ts";
|
||||
import { ParseTree } from "$sb/lib/tree.ts";
|
||||
|
||||
export async function queryDirectiveRenderer(
|
||||
_directive: string,
|
||||
@@ -31,8 +31,12 @@ export async function queryDirectiveRenderer(
|
||||
// This means there was no handler for the event which means it's unsupported
|
||||
return `**Error:** Unsupported query source '${parsedQuery.table}'`;
|
||||
} else if (results.length === 1) {
|
||||
// console.log("Parsed query", parsedQuery);
|
||||
if (parsedQuery.render) {
|
||||
const rendered = await renderTemplate(parsedQuery.render, results[0]);
|
||||
const rendered = await renderTemplate(
|
||||
parsedQuery.render,
|
||||
results[0],
|
||||
);
|
||||
return rendered.trim();
|
||||
} else {
|
||||
if (results[0].length === 0) {
|
||||
|
||||
@@ -7,7 +7,8 @@ import Handlebars from "handlebars";
|
||||
import { replaceTemplateVars } from "../core/template.ts";
|
||||
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
||||
import { directiveRegex } from "./directives.ts";
|
||||
import { serverUpdateDirectives } from "./command.ts";
|
||||
import { updateDirectives } from "./command.ts";
|
||||
import { registerHandlebarsHelpers } from "./util.ts";
|
||||
|
||||
const templateRegex = /\[\[([^\]]+)\]\]\s*(.*)\s*/;
|
||||
|
||||
@@ -48,16 +49,20 @@ export async function templateDirectiveRenderer(
|
||||
// if it's a template injection (not a literal "include")
|
||||
if (directive === "use") {
|
||||
const tree = await markdown.parseMarkdown(templateText);
|
||||
extractFrontmatter(tree, ["$disableDirectives"]);
|
||||
await extractFrontmatter(tree, ["$disableDirectives"]);
|
||||
templateText = renderToText(tree);
|
||||
registerHandlebarsHelpers();
|
||||
const templateFn = Handlebars.compile(
|
||||
replaceTemplateVars(templateText, pageName),
|
||||
{ noEscape: true },
|
||||
);
|
||||
if (typeof parsedArgs !== "string") {
|
||||
(parsedArgs as any).page = pageName;
|
||||
}
|
||||
newBody = templateFn(parsedArgs);
|
||||
|
||||
// Recursively render directives
|
||||
newBody = await serverUpdateDirectives(pageName, newBody);
|
||||
newBody = await updateDirectives(pageName, newBody);
|
||||
}
|
||||
return newBody.trim();
|
||||
}
|
||||
|
||||
+27
-21
@@ -1,5 +1,4 @@
|
||||
import Handlebars from "handlebars";
|
||||
import * as YAML from "yaml";
|
||||
|
||||
import { space } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { niceDate } from "$sb/lib/dates.ts";
|
||||
@@ -83,8 +82,35 @@ export async function renderTemplate(
|
||||
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);
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -96,24 +122,4 @@ export async function renderTemplate(
|
||||
(s: string, from: number, to: number, elipsis = "") =>
|
||||
s.length > to - from ? s.substring(from, to) + elipsis : s,
|
||||
);
|
||||
|
||||
Handlebars.registerHelper("yaml", (v: any, prefix: string) => {
|
||||
if (typeof prefix === "string") {
|
||||
let yaml = 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user