SilverBullet pivot to become an offline-first PWA (#403)

This commit is contained in:
Zef Hemel
2023-05-23 20:53:53 +02:00
committed by GitHub
parent b256269897
commit 5f484bed57
389 changed files with 4484 additions and 291129 deletions
+9 -19
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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);
}
+2 -4
View File
@@ -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:
+9
View File
@@ -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,
+2 -2
View File
@@ -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}`;
}
+6 -2
View File
@@ -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) {
+8 -3
View File
@@ -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
View File
@@ -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);
}