Large "query" plug refactor into "directive"
This commit is contained in:
parent
366564f2ec
commit
540af411a0
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -3,5 +3,6 @@
|
||||
"typescriptHero.imports.stringQuoteStyle": "\"",
|
||||
"deno.enable": true,
|
||||
"deno.importMap": "import_map.json",
|
||||
"deno.config": "deno.jsonc"
|
||||
"deno.config": "deno.jsonc",
|
||||
"deno.unstable": true
|
||||
}
|
||||
|
@ -159,6 +159,7 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
||||
})
|
||||
) {
|
||||
const fullPath = file.path;
|
||||
try {
|
||||
const s = await Deno.stat(fullPath);
|
||||
allFiles.push({
|
||||
name: fullPath.substring(this.rootPath.length + 1),
|
||||
@ -167,6 +168,13 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
||||
size: s.size,
|
||||
perm: "rw",
|
||||
});
|
||||
} catch (e: any) {
|
||||
if (e instanceof Deno.errors.NotFound) {
|
||||
// Ignore, temporariy file already deleted by the time we got here
|
||||
} else {
|
||||
console.error("Failed to stat", fullPath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allFiles;
|
||||
|
@ -10,7 +10,8 @@
|
||||
"watch-plugs": "./build_plugs.sh -w",
|
||||
"bundle": "deno bundle --importmap import_map.json silverbullet.ts dist/silverbullet.js",
|
||||
// Regenerates some bundle files (checked into the repo)
|
||||
"generate": "deno run -A plugos/gen.ts"
|
||||
// Install lezer-generator with "npm install -g @lezer/generator"
|
||||
"generate": "deno run -A plugos/gen.ts && lezer-generator plugs/directive/query.grammar -o plugs/directive/parse-query.js"
|
||||
},
|
||||
|
||||
"compilerOptions": {
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
collectNodesMatching,
|
||||
ParseTree,
|
||||
renderToText,
|
||||
} from "./tree.ts";
|
||||
} from "$sb/lib/tree.ts";
|
||||
|
||||
export const queryRegex =
|
||||
/(<!--\s*#query\s+(.+?)-->)(.+?)(<!--\s*\/query\s*-->)/gs;
|
||||
|
@ -72,8 +72,11 @@ export async function bundle(
|
||||
|
||||
// Functions
|
||||
for (const def of Object.values(manifest.functions || {})) {
|
||||
if (!def.path) {
|
||||
continue;
|
||||
}
|
||||
let jsFunctionName = "default",
|
||||
filePath = path.join(rootPath, def.path!);
|
||||
filePath = path.join(rootPath, def.path);
|
||||
if (filePath.indexOf(":") !== -1) {
|
||||
[filePath, jsFunctionName] = filePath.split(":");
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ export function createSandbox(plug: Plug<any>) {
|
||||
permissions: {
|
||||
// Allow network access and servers (main use case: fetch)
|
||||
net: true,
|
||||
// This is required for console loggin to work, apparently?
|
||||
// This is required for console logging to work, apparently?
|
||||
env: true,
|
||||
// No talking to native code
|
||||
ffi: false,
|
||||
|
@ -52,12 +52,28 @@ export class Plug<HookT> {
|
||||
return !funDef.env || funDef.env === this.runtimeEnv;
|
||||
}
|
||||
|
||||
async invoke(name: string, args: Array<any>): Promise<any> {
|
||||
if (!this.sandbox.isLoaded(name)) {
|
||||
async invoke(name: string, args: any[]): Promise<any> {
|
||||
const funDef = this.manifest!.functions[name];
|
||||
if (!funDef) {
|
||||
throw new Error(`Function ${name} not found in manifest`);
|
||||
}
|
||||
if (funDef.redirect) {
|
||||
// Function redirect, look up
|
||||
// deno-lint-ignore no-this-alias
|
||||
let plug: Plug<HookT> | undefined = this;
|
||||
if (funDef.redirect.indexOf(".") !== -1) {
|
||||
const [plugName, functionName] = funDef.redirect.split(".");
|
||||
plug = this.system.loadedPlugs.get(plugName);
|
||||
if (!plug) {
|
||||
throw Error(`Plug ${plugName} redirected to not found`);
|
||||
}
|
||||
name = functionName;
|
||||
} else {
|
||||
name = funDef.redirect;
|
||||
}
|
||||
return plug.invoke(name, args);
|
||||
}
|
||||
if (!this.sandbox.isLoaded(name)) {
|
||||
if (!this.canInvoke(name)) {
|
||||
throw new Error(
|
||||
`Function ${name} is not available in ${this.runtimeEnv}`,
|
||||
|
@ -40,6 +40,12 @@ Deno.test("Run a deno sandbox", async () => {
|
||||
};
|
||||
})()`,
|
||||
},
|
||||
redirectTest: {
|
||||
redirect: "addTen",
|
||||
},
|
||||
redirectTest2: {
|
||||
redirect: "test.addTen",
|
||||
},
|
||||
addNumbersSyscall: {
|
||||
code: `(() => {
|
||||
return {
|
||||
@ -90,6 +96,8 @@ Deno.test("Run a deno sandbox", async () => {
|
||||
createSandbox,
|
||||
);
|
||||
assertEquals(await plug.invoke("addTen", [10]), 20);
|
||||
assertEquals(await plug.invoke("redirectTest", [10]), 20);
|
||||
assertEquals(await plug.invoke("redirectTest2", [10]), 20);
|
||||
for (let i = 0; i < 100; i++) {
|
||||
assertEquals(await plug.invoke("addNumbersSyscall", [10, i]), 10 + i);
|
||||
}
|
||||
|
@ -16,7 +16,12 @@ export interface Manifest<HookT> {
|
||||
}
|
||||
|
||||
export type FunctionDef<HookT> = {
|
||||
// Read the function from this path and inline it
|
||||
// Format: filename:functionName
|
||||
path?: string;
|
||||
// Reuse an
|
||||
// Format: plugName.functionName
|
||||
redirect?: string;
|
||||
code?: string;
|
||||
env?: RuntimeEnvironment;
|
||||
} & HookT;
|
||||
|
@ -169,8 +169,10 @@ functions:
|
||||
operation: getFileMeta
|
||||
|
||||
# Template commands
|
||||
insertPageMeta:
|
||||
insertTemplateText:
|
||||
path: "./template.ts:insertTemplateText"
|
||||
insertFrontMatter:
|
||||
redirect: insertTemplateText
|
||||
slashCommand:
|
||||
name: front-matter
|
||||
description: Insert page front matter
|
||||
@ -179,49 +181,13 @@ functions:
|
||||
|^|
|
||||
---
|
||||
insertTask:
|
||||
path: "./template.ts:insertTemplateText"
|
||||
redirect: insertTemplateText
|
||||
slashCommand:
|
||||
name: task
|
||||
description: Insert a task
|
||||
value: "* [ ] |^|"
|
||||
insertQuery:
|
||||
path: "./template.ts:insertTemplateText"
|
||||
slashCommand:
|
||||
name: query
|
||||
description: Insert a query
|
||||
value: |
|
||||
<!-- #query |^| -->
|
||||
|
||||
<!-- /query -->
|
||||
insertInclude:
|
||||
path: "./template.ts:insertTemplateText"
|
||||
slashCommand:
|
||||
name: include
|
||||
description: Include another page
|
||||
value: |
|
||||
<!-- #include [[|^|]] -->
|
||||
|
||||
<!-- /include -->
|
||||
insertInjectTemplate:
|
||||
path: "./template.ts:insertTemplateText"
|
||||
slashCommand:
|
||||
name: use
|
||||
description: Use a template
|
||||
value: |
|
||||
<!-- #use [[|^|]] {} -->
|
||||
|
||||
<!-- /use -->
|
||||
insertInjectCleanTemplate:
|
||||
path: "./template.ts:insertTemplateText"
|
||||
slashCommand:
|
||||
name: use-verbose
|
||||
description: Use a template (verbose mode)
|
||||
value: |
|
||||
<!-- #use-verbose [[|^|]] {} -->
|
||||
|
||||
<!-- /use-verbose -->
|
||||
insertHRTemplate:
|
||||
path: "./template.ts:insertTemplateText"
|
||||
redirect: insertTemplateText
|
||||
slashCommand:
|
||||
name: hr
|
||||
description: Insert a horizontal rule
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
replaceNodesMatching,
|
||||
} from "$sb/lib/tree.ts";
|
||||
import { applyQuery } from "$sb/lib/query.ts";
|
||||
import { extractMeta } from "../query/data.ts";
|
||||
import { extractMeta } from "../directive/data.ts";
|
||||
|
||||
// Key space:
|
||||
// pl:toPage:pos => pageName
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { editor, markdown, space } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { extractMeta } from "../query/data.ts";
|
||||
import { extractMeta } from "../directive/data.ts";
|
||||
import { renderToText } from "$sb/lib/tree.ts";
|
||||
import { niceDate } from "$sb/lib/dates.ts";
|
||||
import { readSettings } from "$sb/lib/settings_page.ts";
|
||||
|
41
plugs/directive/command.ts
Normal file
41
plugs/directive/command.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { editor, space } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { invokeFunction } from "$sb/silverbullet-syscall/system.ts";
|
||||
|
||||
import { renderDirectives } from "./directives.ts";
|
||||
|
||||
export async function updateDirectivesOnPageCommand() {
|
||||
const currentPage = await editor.getCurrentPage();
|
||||
await editor.save();
|
||||
if (
|
||||
await invokeFunction(
|
||||
"server",
|
||||
"updateDirectivesOnPage",
|
||||
currentPage,
|
||||
)
|
||||
) {
|
||||
await editor.reloadPage();
|
||||
}
|
||||
}
|
||||
|
||||
// Called from client, running on server
|
||||
export async function updateDirectivesOnPage(
|
||||
pageName: string,
|
||||
): Promise<boolean> {
|
||||
let text = "";
|
||||
try {
|
||||
text = await space.readPage(pageName);
|
||||
} catch {
|
||||
console.warn(
|
||||
"Could not read page",
|
||||
pageName,
|
||||
"perhaps it doesn't yet exist",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
const newText = await renderDirectives(pageName, text);
|
||||
if (text !== newText) {
|
||||
await space.writePage(pageName, newText);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
@ -13,7 +13,6 @@ import {
|
||||
} from "$sb/lib/tree.ts";
|
||||
import { applyQuery, removeQueries } from "$sb/lib/query.ts";
|
||||
import * as YAML from "yaml";
|
||||
import { text } from "https://esm.sh/v96/@fortawesome/fontawesome-svg-core@1.3.0/X-ZS9AZm9ydGF3ZXNvbWUvZm9udGF3ZXNvbWUtY29tbW9uLXR5cGVz/index.d.ts";
|
||||
|
||||
export async function indexData({ name, tree }: IndexTreeEvent) {
|
||||
const dataObjects: { key: string; value: any }[] = [];
|
72
plugs/directive/directive.plug.yaml
Normal file
72
plugs/directive/directive.plug.yaml
Normal file
@ -0,0 +1,72 @@
|
||||
name: directive
|
||||
imports:
|
||||
- https://get.silverbullet.md/global.plug.json
|
||||
functions:
|
||||
updateDirectivesOnPage:
|
||||
path: ./command.ts:updateDirectivesOnPage
|
||||
updateDirectivesOnPageCommand:
|
||||
path: ./command.ts:updateDirectivesOnPageCommand
|
||||
command:
|
||||
name: "Directives: Update"
|
||||
key: "Alt-q"
|
||||
events:
|
||||
- editor:pageLoaded
|
||||
indexData:
|
||||
path: ./data.ts:indexData
|
||||
events:
|
||||
- page:index
|
||||
dataQueryProvider:
|
||||
path: ./data.ts:queryProvider
|
||||
events:
|
||||
- query:data
|
||||
queryComplete:
|
||||
path: ./complete.ts:queryComplete
|
||||
events:
|
||||
- page:complete
|
||||
|
||||
# Templates
|
||||
insertQuery:
|
||||
redirect: core.insertTemplateText
|
||||
slashCommand:
|
||||
name: query
|
||||
description: Insert a query
|
||||
value: |
|
||||
<!-- #query |^| -->
|
||||
|
||||
<!-- /query -->
|
||||
insertInclude:
|
||||
redirect: core.insertTemplateText
|
||||
slashCommand:
|
||||
name: include
|
||||
description: Include another page
|
||||
value: |
|
||||
<!-- #include [[|^|]] -->
|
||||
|
||||
<!-- /include -->
|
||||
insertUseTemplate:
|
||||
redirect: core.insertTemplateText
|
||||
slashCommand:
|
||||
name: use
|
||||
description: Use a template
|
||||
value: |
|
||||
<!-- #use [[|^|]] {} -->
|
||||
|
||||
<!-- /use -->
|
||||
insertUseVerboseTemplate:
|
||||
redirect: core.insertTemplateText
|
||||
slashCommand:
|
||||
name: use-verbose
|
||||
description: Use a template (verbose mode)
|
||||
value: |
|
||||
<!-- #use-verbose [[|^|]] {} -->
|
||||
|
||||
<!-- /use-verbose -->
|
||||
insertEvalTemplate:
|
||||
redirect: core.insertTemplateText
|
||||
slashCommand:
|
||||
name: eval
|
||||
description: Evaluate a JavaScript expression
|
||||
value: |
|
||||
<!-- #eval |^| -->
|
||||
|
||||
<!-- /eval -->
|
70
plugs/directive/directives.ts
Normal file
70
plugs/directive/directives.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { nodeAtPos, ParseTree, renderToText } from "$sb/lib/tree.ts";
|
||||
import { replaceAsync } from "$sb/lib/util.ts";
|
||||
import { markdown } from "$sb/silverbullet-syscall/mod.ts";
|
||||
|
||||
import { extractMeta } from "./data.ts";
|
||||
import { evalDirectiveRenderer } from "./eval_directive.ts";
|
||||
import { queryDirectiveRenderer } from "./query_directive.ts";
|
||||
import {
|
||||
cleanTemplateInstantiations,
|
||||
templateDirectiveRenderer,
|
||||
} from "./template_directive.ts";
|
||||
|
||||
export const directiveRegex =
|
||||
/(<!--\s*#(use|use-verbose|include|eval|query)\s+(.*?)-->)(.+?)(<!--\s*\/\2\s*-->)/gs;
|
||||
|
||||
/**
|
||||
* Looks for directives in the text dispatches them based on name
|
||||
*/
|
||||
export async function directiveDispatcher(
|
||||
pageName: string,
|
||||
text: string,
|
||||
tree: ParseTree,
|
||||
directiveRenderers: Record<
|
||||
string,
|
||||
(directive: string, pageName: string, arg: string) => Promise<string>
|
||||
>,
|
||||
): Promise<string> {
|
||||
return replaceAsync(
|
||||
text,
|
||||
directiveRegex,
|
||||
async (fullMatch, startInst, type, arg, _body, endInst, index) => {
|
||||
const currentNode = nodeAtPos(tree, index + 1);
|
||||
// console.log("Node type", currentNode?.type);
|
||||
if (currentNode?.type !== "CommentBlock") {
|
||||
// If not a comment block, it's likely a code block, ignore
|
||||
// console.log("Not comment block, ingoring", fullMatch);
|
||||
return fullMatch;
|
||||
}
|
||||
arg = arg.trim();
|
||||
try {
|
||||
const newBody = await directiveRenderers[type](type, pageName, arg);
|
||||
return `${startInst}\n${newBody.trim()}\n${endInst}`;
|
||||
} catch (e: any) {
|
||||
return `${startInst}\n**ERROR:** ${e.message}\n${endInst}`;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function renderDirectives(
|
||||
pageName: string,
|
||||
text: string,
|
||||
): Promise<string> {
|
||||
const tree = await markdown.parseMarkdown(text);
|
||||
const metaData = extractMeta(tree, ["$disableDirectives"]);
|
||||
// console.log("Meta data", pageName, metaData);
|
||||
if (metaData.$disableDirectives) {
|
||||
return text;
|
||||
}
|
||||
text = renderToText(tree);
|
||||
text = await directiveDispatcher(pageName, text, tree, {
|
||||
use: templateDirectiveRenderer,
|
||||
"use-verbose": templateDirectiveRenderer,
|
||||
"include": templateDirectiveRenderer,
|
||||
query: queryDirectiveRenderer,
|
||||
eval: evalDirectiveRenderer,
|
||||
});
|
||||
|
||||
return await cleanTemplateInstantiations(text);
|
||||
}
|
60
plugs/directive/eval_directive.ts
Normal file
60
plugs/directive/eval_directive.ts
Normal file
@ -0,0 +1,60 @@
|
||||
// This is some shocking stuff. My profession would kill me for this.
|
||||
|
||||
import { YAML } from "../../common/deps.ts";
|
||||
import { jsonToMDTable, renderTemplate } from "./util.ts";
|
||||
|
||||
// Enables plugName.functionName(arg1, arg2) syntax in JS expressions
|
||||
function translateJs(js: string): string {
|
||||
return js.replaceAll(
|
||||
/(\w+\.\w+)\s*\(/g,
|
||||
'await invokeFunction("$1", ',
|
||||
);
|
||||
}
|
||||
|
||||
// Syntaxes to support:
|
||||
// - random JS expression
|
||||
// - random JS expression render [[some/template]]
|
||||
const expressionRegex = /(.+?)(\s+render\s+\[\[([^\]]+)\]\])?$/;
|
||||
|
||||
// This is rather scary and fragile stuff, but it works.
|
||||
export async function evalDirectiveRenderer(
|
||||
_directive: string,
|
||||
_pageName: string,
|
||||
expression: string,
|
||||
): Promise<string> {
|
||||
console.log("Got JS expression", expression);
|
||||
const match = expressionRegex.exec(expression);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid eval directive: ${expression}`);
|
||||
}
|
||||
let template = "";
|
||||
if (match[3]) {
|
||||
// This is the template reference
|
||||
expression = match[1];
|
||||
template = match[3];
|
||||
}
|
||||
try {
|
||||
// Why the weird "eval" call? https://esbuild.github.io/content-types/#direct-eval
|
||||
const result = await (0, eval)(
|
||||
`(async () => {
|
||||
function invokeFunction(name, ...args) {
|
||||
return syscall("system.invokeFunction", "server", name, ...args);
|
||||
}
|
||||
return ${translateJs(expression)};
|
||||
})()`,
|
||||
);
|
||||
if (template) {
|
||||
return await renderTemplate(template, result);
|
||||
}
|
||||
if (typeof result === "string") {
|
||||
return result;
|
||||
} else if (typeof result === "number") {
|
||||
return "" + result;
|
||||
} else if (Array.isArray(result)) {
|
||||
return jsonToMDTable(result);
|
||||
}
|
||||
return YAML.stringify(result);
|
||||
} catch (e: any) {
|
||||
return `**ERROR:** ${e.message}`;
|
||||
}
|
||||
}
|
16
plugs/directive/parse-query.js
Normal file
16
plugs/directive/parse-query.js
Normal file
@ -0,0 +1,16 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
import {LRParser} from "@lezer/lr"
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "&fOVQPOOOmQQO'#C^QOQPOOOtQPO'#C`OyQQO'#CkO!OQPO'#CmO!TQPO'#CnO!YQPO'#CoOOQO'#Cq'#CqO!bQQO,58xO!iQQO'#CcO#WQQO'#CaOOQO'#Ca'#CaOOQO,58z,58zO#oQPO,59VOOQO,59X,59XO#tQQO'#DaOOQO,59Y,59YOOQO,59Z,59ZOOQO-E6o-E6oO$]QQO,58}OtQPO,58|O$tQQO1G.qO%`QPO'#CsO%eQQO,59{OOQO'#Cg'#CgOOQO'#Ci'#CiO$]QQO'#CjOOQO'#Cd'#CdOOQO1G.i1G.iOOQO1G.h1G.hOOQO'#Cl'#ClOOQO7+$]7+$]OOQO,59_,59_OOQO-E6q-E6qO%|QPO'#C}O&UQPO,59UO$]QQO'#CrO&ZQPO,59iOOQO1G.p1G.pOOQO,59^,59^OOQO-E6p-E6p",
|
||||
stateData: "&c~OjOS~ORPO~OkRO}SO!RTO!SUO!UVO~OhQX~P[ORYO~O!O^O~OX_O~OR`O~OYbOdbO~OhQa~P[OldOtdOudOvdOwdOxdOydOzdO{dO~O|eOhTXkTX}TX!RTX!STX!UTX~ORfO~OrgOh!TXk!TX}!TX!R!TX!S!TX!U!TX~OXlOYlO[lOmiOniOojOpkO~O!PoO!QoOh_ik_i}_i!R_i!S_i!U_i~ORqO~OrgOh!Tak!Ta}!Ta!R!Ta!S!Ta!U!Ta~OruOsqX~OswO~OruOsqa~O",
|
||||
goto: "#e!UPP!VP!Y!^!a!d!jPP!sP!s!s!Y!x!Y!Y!YP!{#R#XPPPPPPPPP#_PPPPPPPPPPPPPPPPP#bRQOTWPXR]RR[RQZRRneQmdQskRxuVldkuRpfQXPRcXQvsRyvQh`RrhRtkRaU",
|
||||
nodeNames: "⚠ Program Query Name WhereClause LogicalExpr AndExpr FilterExpr Value Number String Bool Regex Null List OrderClause Order LimitClause SelectClause RenderClause PageRef",
|
||||
maxTerm: 52,
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 3,
|
||||
tokenData: "B[~R}X^$Opq$Oqr$srs%W|}%r}!O%w!P!Q&Y!Q!['P!^!_'X!_!`'f!`!a's!c!}%w!}#O(Q#P#Q(q#R#S%w#T#U(v#U#V+]#V#W%w#W#X,X#X#Y%w#Y#Z.T#Z#]%w#]#^0e#^#`%w#`#a1a#a#b%w#b#c3t#c#d5p#d#f%w#f#g8T#g#h;P#h#i={#i#k%w#k#l?w#l#o%w#y#z$O$f$g$O#BY#BZ$O$IS$I_$O$Ip$Iq%W$Iq$Ir%W$I|$JO$O$JT$JU$O$KV$KW$O&FU&FV$O~$TYj~X^$Opq$O#y#z$O$f$g$O#BY#BZ$O$IS$I_$O$I|$JO$O$JT$JU$O$KV$KW$O&FU&FV$O~$vP!_!`$y~%OPv~#r#s%R~%WOz~~%ZUOr%Wrs%ms$Ip%W$Ip$Iq%m$Iq$Ir%m$Ir~%W~%rOY~~%wOr~P%|SRP}!O%w!c!}%w#R#S%w#T#o%w~&_V[~OY&YZ]&Y^!P&Y!P!Q&t!Q#O&Y#O#P&y#P~&Y~&yO[~~&|PO~&Y~'UPX~!Q!['P~'^Pl~!_!`'a~'fOt~~'kPu~#r#s'n~'sOy~~'xPx~!_!`'{~(QOw~R(VPpQ!}#O(YP(]RO#P(Y#P#Q(f#Q~(YP(iP#P#Q(lP(qOdP~(vOs~R({WRP}!O%w!c!}%w#R#S%w#T#b%w#b#c)e#c#g%w#g#h*a#h#o%wR)jURP}!O%w!c!}%w#R#S%w#T#W%w#W#X)|#X#o%wR*TS|QRP}!O%w!c!}%w#R#S%w#T#o%wR*fURP}!O%w!c!}%w#R#S%w#T#V%w#V#W*x#W#o%wR+PS!QQRP}!O%w!c!}%w#R#S%w#T#o%wR+bURP}!O%w!c!}%w#R#S%w#T#m%w#m#n+t#n#o%wR+{S!OQRP}!O%w!c!}%w#R#S%w#T#o%wR,^URP}!O%w!c!}%w#R#S%w#T#X%w#X#Y,p#Y#o%wR,uURP}!O%w!c!}%w#R#S%w#T#g%w#g#h-X#h#o%wR-^URP}!O%w!c!}%w#R#S%w#T#V%w#V#W-p#W#o%wR-wS!PQRP}!O%w!c!}%w#R#S%w#T#o%wR.YTRP}!O%w!c!}%w#R#S%w#T#U.i#U#o%wR.nURP}!O%w!c!}%w#R#S%w#T#`%w#`#a/Q#a#o%wR/VURP}!O%w!c!}%w#R#S%w#T#g%w#g#h/i#h#o%wR/nURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y0Q#Y#o%wR0XSnQRP}!O%w!c!}%w#R#S%w#T#o%wR0jURP}!O%w!c!}%w#R#S%w#T#b%w#b#c0|#c#o%wR1TS{QRP}!O%w!c!}%w#R#S%w#T#o%wR1fURP}!O%w!c!}%w#R#S%w#T#]%w#]#^1x#^#o%wR1}URP}!O%w!c!}%w#R#S%w#T#a%w#a#b2a#b#o%wR2fURP}!O%w!c!}%w#R#S%w#T#]%w#]#^2x#^#o%wR2}URP}!O%w!c!}%w#R#S%w#T#h%w#h#i3a#i#o%wR3hS!RQRP}!O%w!c!}%w#R#S%w#T#o%wR3yURP}!O%w!c!}%w#R#S%w#T#i%w#i#j4]#j#o%wR4bURP}!O%w!c!}%w#R#S%w#T#`%w#`#a4t#a#o%wR4yURP}!O%w!c!}%w#R#S%w#T#`%w#`#a5]#a#o%wR5dSoQRP}!O%w!c!}%w#R#S%w#T#o%wR5uURP}!O%w!c!}%w#R#S%w#T#f%w#f#g6X#g#o%wR6^URP}!O%w!c!}%w#R#S%w#T#W%w#W#X6p#X#o%wR6uURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y7X#Y#o%wR7^URP}!O%w!c!}%w#R#S%w#T#f%w#f#g7p#g#o%wR7wS}QRP}!O%w!c!}%w#R#S%w#T#o%wR8YURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y8l#Y#o%wR8qURP}!O%w!c!}%w#R#S%w#T#b%w#b#c9T#c#o%wR9YURP}!O%w!c!}%w#R#S%w#T#W%w#W#X9l#X#o%wR9qURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y:T#Y#o%wR:YURP}!O%w!c!}%w#R#S%w#T#f%w#f#g:l#g#o%wR:sS!UQRP}!O%w!c!}%w#R#S%w#T#o%wR;UURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y;h#Y#o%wR;mURP}!O%w!c!}%w#R#S%w#T#`%w#`#a<P#a#o%wR<UURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y<h#Y#o%wR<mURP}!O%w!c!}%w#R#S%w#T#V%w#V#W=P#W#o%wR=UURP}!O%w!c!}%w#R#S%w#T#h%w#h#i=h#i#o%wR=oS!SQRP}!O%w!c!}%w#R#S%w#T#o%wR>QURP}!O%w!c!}%w#R#S%w#T#f%w#f#g>d#g#o%wR>iURP}!O%w!c!}%w#R#S%w#T#i%w#i#j>{#j#o%wR?QURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y?d#Y#o%wR?kSmQRP}!O%w!c!}%w#R#S%w#T#o%wR?|URP}!O%w!c!}%w#R#S%w#T#[%w#[#]@`#]#o%wR@eURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y@w#Y#o%wR@|URP}!O%w!c!}%w#R#S%w#T#f%w#f#gA`#g#o%wRAeURP}!O%w!c!}%w#R#S%w#T#X%w#X#YAw#Y#o%wRBOSkQRP}!O%w!c!}%w#R#S%w#T#o%w",
|
||||
tokenizers: [0, 1],
|
||||
topRules: {"Program":[0,1]},
|
||||
tokenPrec: 0
|
||||
})
|
@ -1,5 +1,6 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
export const Program = 1,
|
||||
export const
|
||||
Program = 1,
|
||||
Query = 2,
|
||||
Name = 3,
|
||||
WhereClause = 4,
|
||||
@ -18,4 +19,4 @@ export const Program = 1,
|
||||
LimitClause = 17,
|
||||
SelectClause = 18,
|
||||
RenderClause = 19,
|
||||
PageRef = 20;
|
||||
PageRef = 20
|
@ -1,10 +1,10 @@
|
||||
import {
|
||||
collectNodesOfType,
|
||||
findNodeOfType,
|
||||
ParseTree,
|
||||
replaceNodesMatching,
|
||||
} from "$sb/lib/tree.ts";
|
||||
import { lezerToParseTree } from "../../common/parse_tree.ts";
|
||||
import { valueNodeToVal } from "./engine.ts";
|
||||
|
||||
// @ts-ignore auto generated
|
||||
import { parser } from "./parse-query.js";
|
||||
@ -75,3 +75,33 @@ export function parseQuery(query: string): ParsedQuery {
|
||||
|
||||
return parsedQuery;
|
||||
}
|
||||
|
||||
export function valueNodeToVal(valNode: ParseTree): any {
|
||||
switch (valNode.type) {
|
||||
case "Number":
|
||||
return +valNode.children![0].text!;
|
||||
case "Bool":
|
||||
return valNode.children![0].text! === "true";
|
||||
case "Null":
|
||||
return null;
|
||||
case "Name":
|
||||
return valNode.children![0].text!;
|
||||
case "Regex": {
|
||||
const val = valNode.children![0].text!;
|
||||
return val.substring(1, val.length - 1);
|
||||
}
|
||||
case "String": {
|
||||
const stringVal = valNode.children![0].text!;
|
||||
return stringVal.substring(1, stringVal.length - 1);
|
||||
}
|
||||
case "PageRef": {
|
||||
const pageRefVal = valNode.children![0].text!;
|
||||
return pageRefVal.substring(2, pageRefVal.length - 2);
|
||||
}
|
||||
case "List": {
|
||||
return collectNodesOfType(valNode, "Value").map((t) =>
|
||||
valueNodeToVal(t.children![0])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
34
plugs/directive/query_directive.ts
Normal file
34
plugs/directive/query_directive.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { events } from "$sb/plugos-syscall/mod.ts";
|
||||
|
||||
import { replaceTemplateVars } from "../core/template.ts";
|
||||
import { renderTemplate } from "./util.ts";
|
||||
import { parseQuery } from "./parser.ts";
|
||||
import { jsonToMDTable } from "./util.ts";
|
||||
|
||||
export async function queryDirectiveRenderer(
|
||||
_directive: string,
|
||||
pageName: string,
|
||||
query: string,
|
||||
): Promise<string> {
|
||||
const parsedQuery = parseQuery(replaceTemplateVars(query, pageName));
|
||||
|
||||
console.log("Parsed query", parsedQuery);
|
||||
// Let's dispatch an event and see what happens
|
||||
const results = await events.dispatchEvent(
|
||||
`query:${parsedQuery.table}`,
|
||||
{ query: parsedQuery, pageName: pageName },
|
||||
10 * 1000,
|
||||
);
|
||||
if (results.length === 0) {
|
||||
return "";
|
||||
} else if (results.length === 1) {
|
||||
if (parsedQuery.render) {
|
||||
const rendered = await renderTemplate(parsedQuery.render, results[0]);
|
||||
return rendered.trim();
|
||||
} else {
|
||||
return jsonToMDTable(results[0]);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Too many query results: ${results.length}`);
|
||||
}
|
||||
}
|
89
plugs/directive/template_directive.ts
Normal file
89
plugs/directive/template_directive.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { queryRegex } from "$sb/lib/query.ts";
|
||||
import { renderToText } from "$sb/lib/tree.ts";
|
||||
import { replaceAsync } from "$sb/lib/util.ts";
|
||||
import { markdown, space } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import Handlebars from "handlebars";
|
||||
|
||||
import { replaceTemplateVars } from "../core/template.ts";
|
||||
import { extractMeta } from "./data.ts";
|
||||
import { directiveRegex, renderDirectives } from "./directives.ts";
|
||||
|
||||
const templateRegex = /\[\[([^\]]+)\]\]\s*(.*)\s*/;
|
||||
|
||||
export async function templateDirectiveRenderer(
|
||||
directive: string,
|
||||
pageName: string,
|
||||
arg: string,
|
||||
): Promise<string> {
|
||||
const match = arg.match(templateRegex);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid template directive: ${arg}`);
|
||||
}
|
||||
const template = match[1];
|
||||
const args = match[2];
|
||||
let parsedArgs = {};
|
||||
if (args) {
|
||||
try {
|
||||
parsedArgs = JSON.parse(args);
|
||||
} catch {
|
||||
throw new Error(`Failed to parse template instantiation args: ${arg}`);
|
||||
}
|
||||
}
|
||||
let templateText = "";
|
||||
if (template.startsWith("http://") || template.startsWith("https://")) {
|
||||
try {
|
||||
const req = await fetch(template);
|
||||
templateText = await req.text();
|
||||
} catch (e: any) {
|
||||
templateText = `ERROR: ${e.message}`;
|
||||
}
|
||||
} else {
|
||||
templateText = await space.readPage(template);
|
||||
}
|
||||
let newBody = templateText;
|
||||
// if it's a template injection (not a literal "include")
|
||||
if (directive === "use" || directive === "use-verbose") {
|
||||
const tree = await markdown.parseMarkdown(templateText);
|
||||
extractMeta(tree, ["$disableDirectives"]);
|
||||
templateText = renderToText(tree);
|
||||
const templateFn = Handlebars.compile(
|
||||
replaceTemplateVars(templateText, pageName),
|
||||
{ noEscape: true },
|
||||
);
|
||||
newBody = templateFn(parsedArgs);
|
||||
|
||||
// Recursively render directives
|
||||
newBody = await renderDirectives(pageName, newBody);
|
||||
}
|
||||
return newBody.trim();
|
||||
}
|
||||
|
||||
export function cleanTemplateInstantiations(text: string): Promise<string> {
|
||||
return replaceAsync(
|
||||
text,
|
||||
directiveRegex,
|
||||
(
|
||||
_fullMatch,
|
||||
startInst,
|
||||
type,
|
||||
_args,
|
||||
body,
|
||||
endInst,
|
||||
): Promise<string> => {
|
||||
if (type === "use") {
|
||||
body = body.replaceAll(
|
||||
queryRegex,
|
||||
(
|
||||
_fullMatch: string,
|
||||
_startQuery: string,
|
||||
_query: string,
|
||||
body: string,
|
||||
) => {
|
||||
return body.trim();
|
||||
},
|
||||
);
|
||||
}
|
||||
return Promise.resolve(`${startInst}${body}${endInst}`);
|
||||
},
|
||||
);
|
||||
}
|
@ -1,3 +1,9 @@
|
||||
import Handlebars from "handlebars";
|
||||
import * as YAML from "yaml";
|
||||
|
||||
import { space } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { niceDate } from "$sb/lib/dates.ts";
|
||||
|
||||
const maxWidth = 70;
|
||||
// Nicely format an array of JSON objects as a Markdown table
|
||||
export function jsonToMDTable(
|
||||
@ -64,3 +70,42 @@ export function jsonToMDTable(
|
||||
return new Array(length + 1).join(ch);
|
||||
}
|
||||
}
|
||||
|
||||
export async function renderTemplate(
|
||||
renderTemplate: string,
|
||||
data: any[],
|
||||
): Promise<string> {
|
||||
Handlebars.registerHelper("json", (v: any) => JSON.stringify(v));
|
||||
Handlebars.registerHelper("niceDate", (ts: any) => niceDate(new Date(ts)));
|
||||
Handlebars.registerHelper("prefixLines", (v: string, prefix: string) =>
|
||||
v
|
||||
.split("\n")
|
||||
.map((l) => prefix + l)
|
||||
.join("\n"));
|
||||
|
||||
Handlebars.registerHelper(
|
||||
"substring",
|
||||
(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);
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import { collectNodesOfType, ParseTree } from "$sb/lib/tree.ts";
|
||||
import Handlebars from "handlebars";
|
||||
import * as YAML from "yaml";
|
||||
|
||||
import { space } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { niceDate } from "$sb/lib/dates.ts";
|
||||
import { ParsedQuery } from "$sb/lib/query.ts";
|
||||
|
||||
export function valueNodeToVal(valNode: ParseTree): any {
|
||||
switch (valNode.type) {
|
||||
case "Number":
|
||||
return +valNode.children![0].text!;
|
||||
case "Bool":
|
||||
return valNode.children![0].text! === "true";
|
||||
case "Null":
|
||||
return null;
|
||||
case "Name":
|
||||
return valNode.children![0].text!;
|
||||
case "Regex": {
|
||||
const val = valNode.children![0].text!;
|
||||
return val.substring(1, val.length - 1);
|
||||
}
|
||||
case "String": {
|
||||
const stringVal = valNode.children![0].text!;
|
||||
return stringVal.substring(1, stringVal.length - 1);
|
||||
}
|
||||
case "PageRef": {
|
||||
const pageRefVal = valNode.children![0].text!;
|
||||
return pageRefVal.substring(2, pageRefVal.length - 2);
|
||||
}
|
||||
case "List": {
|
||||
return collectNodesOfType(valNode, "Value").map((t) =>
|
||||
valueNodeToVal(t.children![0])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function renderQuery(
|
||||
parsedQuery: ParsedQuery,
|
||||
data: any[],
|
||||
): Promise<string> {
|
||||
if (parsedQuery.render) {
|
||||
Handlebars.registerHelper("json", (v: any) => JSON.stringify(v));
|
||||
Handlebars.registerHelper("niceDate", (ts: any) => niceDate(new Date(ts)));
|
||||
Handlebars.registerHelper("prefixLines", (v: string, prefix: string) =>
|
||||
v
|
||||
.split("\n")
|
||||
.map((l) => prefix + l)
|
||||
.join("\n"));
|
||||
|
||||
Handlebars.registerHelper(
|
||||
"substring",
|
||||
(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(parsedQuery.render);
|
||||
templateText = `{{#each .}}\n${templateText}\n{{/each}}`;
|
||||
const template = Handlebars.compile(templateText, { noEscape: true });
|
||||
return template(data);
|
||||
}
|
||||
|
||||
return "ERROR";
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
import { editor } from "$sb/silverbullet-syscall/mod.ts";
|
||||
|
||||
import Handlebars from "handlebars";
|
||||
|
||||
import { markdown, space } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { invokeFunction } from "$sb/silverbullet-syscall/system.ts";
|
||||
import { renderQuery } from "./engine.ts";
|
||||
import { parseQuery } from "./parser.ts";
|
||||
import { replaceTemplateVars } from "../core/template.ts";
|
||||
import { jsonToMDTable } from "./util.ts";
|
||||
import { queryRegex } from "$sb/lib/query.ts";
|
||||
import { events } from "$sb/plugos-syscall/mod.ts";
|
||||
import { replaceAsync } from "$sb/lib/util.ts";
|
||||
import { nodeAtPos, renderToText } from "$sb/lib/tree.ts";
|
||||
import { extractMeta } from "./data.ts";
|
||||
|
||||
export async function updateMaterializedQueriesCommand() {
|
||||
const currentPage = await editor.getCurrentPage();
|
||||
await editor.save();
|
||||
if (
|
||||
await invokeFunction(
|
||||
"server",
|
||||
"updateMaterializedQueriesOnPage",
|
||||
currentPage,
|
||||
)
|
||||
) {
|
||||
await editor.reloadPage();
|
||||
}
|
||||
}
|
||||
|
||||
export const templateInstRegex =
|
||||
/(<!--\s*#(use|use-verbose|include)\s+\[\[([^\]]+)\]\](.*?)-->)(.+?)(<!--\s*\/\2\s*-->)/gs;
|
||||
|
||||
function updateTemplateInstantiations(
|
||||
text: string,
|
||||
pageName: string,
|
||||
): Promise<string> {
|
||||
return replaceAsync(
|
||||
text,
|
||||
templateInstRegex,
|
||||
async (fullMatch, startInst, type, template, args, _body, endInst) => {
|
||||
args = args.trim();
|
||||
let parsedArgs = {};
|
||||
if (args) {
|
||||
try {
|
||||
parsedArgs = JSON.parse(args);
|
||||
} catch {
|
||||
console.error("Failed to parse template instantiation args", args);
|
||||
return fullMatch;
|
||||
}
|
||||
}
|
||||
let templateText = "";
|
||||
if (template.startsWith("http://") || template.startsWith("https://")) {
|
||||
try {
|
||||
const req = await fetch(template);
|
||||
templateText = await req.text();
|
||||
} catch (e: any) {
|
||||
templateText = `ERROR: ${e.message}`;
|
||||
}
|
||||
} else {
|
||||
templateText = await space.readPage(template);
|
||||
}
|
||||
let newBody = templateText;
|
||||
// if it's a template injection (not a literal "include")
|
||||
if (type === "use" || type === "use-verbose") {
|
||||
const tree = await markdown.parseMarkdown(templateText);
|
||||
extractMeta(tree, ["$disableDirectives"]);
|
||||
templateText = renderToText(tree);
|
||||
const templateFn = Handlebars.compile(
|
||||
replaceTemplateVars(templateText, pageName),
|
||||
{ noEscape: true },
|
||||
);
|
||||
newBody = templateFn(parsedArgs);
|
||||
}
|
||||
return `${startInst}\n${newBody.trim()}\n${endInst}`;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function cleanTemplateInstantiations(text: string): Promise<string> {
|
||||
return replaceAsync(
|
||||
text,
|
||||
templateInstRegex,
|
||||
(
|
||||
_fullMatch,
|
||||
startInst,
|
||||
type,
|
||||
_template,
|
||||
_args,
|
||||
body,
|
||||
endInst,
|
||||
): Promise<string> => {
|
||||
if (type === "use") {
|
||||
body = body.replaceAll(
|
||||
queryRegex,
|
||||
(
|
||||
_fullMatch: string,
|
||||
_startQuery: string,
|
||||
_query: string,
|
||||
body: string,
|
||||
) => {
|
||||
return body.trim();
|
||||
},
|
||||
);
|
||||
}
|
||||
return Promise.resolve(`${startInst}${body}${endInst}`);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Called from client, running on server
|
||||
export async function updateMaterializedQueriesOnPage(
|
||||
pageName: string,
|
||||
): Promise<boolean> {
|
||||
// console.log("Updating queries");
|
||||
let text = "";
|
||||
try {
|
||||
text = await space.readPage(pageName);
|
||||
} catch {
|
||||
console.warn(
|
||||
"Could not read page",
|
||||
pageName,
|
||||
"perhaps it doesn't yet exist",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
let newText = await updateTemplateInstantiations(text, pageName);
|
||||
const tree = await markdown.parseMarkdown(newText);
|
||||
const metaData = extractMeta(tree, ["$disableDirectives"]);
|
||||
// console.log("Meta data", pageName, metaData);
|
||||
if (metaData.$disableDirectives) {
|
||||
console.log("Directives disabled, skipping");
|
||||
return false;
|
||||
}
|
||||
newText = renderToText(tree);
|
||||
|
||||
newText = await replaceAsync(
|
||||
newText,
|
||||
queryRegex,
|
||||
async (fullMatch, startQuery, query, _body, endQuery, index) => {
|
||||
const currentNode = nodeAtPos(tree, index + 1);
|
||||
if (currentNode?.type !== "CommentBlock") {
|
||||
// If not a comment block, it's likely a code block, ignore
|
||||
return fullMatch;
|
||||
}
|
||||
|
||||
const parsedQuery = parseQuery(replaceTemplateVars(query, pageName));
|
||||
|
||||
// console.log("Parsed query", parsedQuery);
|
||||
// Let's dispatch an event and see what happens
|
||||
const results = await events.dispatchEvent(
|
||||
`query:${parsedQuery.table}`,
|
||||
{ query: parsedQuery, pageName: pageName },
|
||||
10 * 1000,
|
||||
);
|
||||
if (results.length === 0) {
|
||||
return `${startQuery}\n${endQuery}`;
|
||||
} else if (results.length === 1) {
|
||||
if (parsedQuery.render) {
|
||||
const rendered = await renderQuery(parsedQuery, results[0]);
|
||||
return `${startQuery}\n${rendered.trim()}\n${endQuery}`;
|
||||
} else {
|
||||
return `${startQuery}\n${jsonToMDTable(results[0])}\n${endQuery}`;
|
||||
}
|
||||
} else {
|
||||
console.error("Too many query results", results);
|
||||
return fullMatch;
|
||||
}
|
||||
},
|
||||
);
|
||||
newText = await cleanTemplateInstantiations(newText);
|
||||
if (text !== newText) {
|
||||
await space.writePage(pageName, newText);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
import { LRParser } from "@lezer/lr";
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states:
|
||||
"&fOVQPOOOmQQO'#C^QOQPOOOtQPO'#C`OyQQO'#CkO!OQPO'#CmO!TQPO'#CnO!YQPO'#CoOOQO'#Cq'#CqO!bQQO,58xO!iQQO'#CcO#WQQO'#CaOOQO'#Ca'#CaOOQO,58z,58zO#oQPO,59VOOQO,59X,59XO#tQQO'#DaOOQO,59Y,59YOOQO,59Z,59ZOOQO-E6o-E6oO$]QQO,58}OtQPO,58|O$tQQO1G.qO%`QPO'#CsO%eQQO,59{OOQO'#Cg'#CgOOQO'#Ci'#CiO$]QQO'#CjOOQO'#Cd'#CdOOQO1G.i1G.iOOQO1G.h1G.hOOQO'#Cl'#ClOOQO7+$]7+$]OOQO,59_,59_OOQO-E6q-E6qO%|QPO'#C}O&UQPO,59UO$]QQO'#CrO&ZQPO,59iOOQO1G.p1G.pOOQO,59^,59^OOQO-E6p-E6p",
|
||||
stateData:
|
||||
"&c~OjOS~ORPO~OkRO}SO!RTO!SUO!UVO~OhQX~P[ORYO~O!O^O~OX_O~OR`O~OYbOdbO~OhQa~P[OldOtdOudOvdOwdOxdOydOzdO{dO~O|eOhTXkTX}TX!RTX!STX!UTX~ORfO~OrgOh!TXk!TX}!TX!R!TX!S!TX!U!TX~OXlOYlO[lOmiOniOojOpkO~O!PoO!QoOh_ik_i}_i!R_i!S_i!U_i~ORqO~OrgOh!Tak!Ta}!Ta!R!Ta!S!Ta!U!Ta~OruOsqX~OswO~OruOsqa~O",
|
||||
goto:
|
||||
"#e!UPP!VP!Y!^!a!d!jPP!sP!s!s!Y!x!Y!Y!YP!{#R#XPPPPPPPPP#_PPPPPPPPPPPPPPPPP#bRQOTWPXR]RR[RQZRRneQmdQskRxuVldkuRpfQXPRcXQvsRyvQh`RrhRtkRaU",
|
||||
nodeNames:
|
||||
"⚠ Program Query Name WhereClause LogicalExpr AndExpr FilterExpr Value Number String Bool Regex Null List OrderClause Order LimitClause SelectClause RenderClause PageRef",
|
||||
maxTerm: 52,
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 3,
|
||||
tokenData:
|
||||
"B[~R}X^$Opq$Oqr$srs%W|}%r}!O%w!P!Q&Y!Q!['P!^!_'X!_!`'f!`!a's!c!}%w!}#O(Q#P#Q(q#R#S%w#T#U(v#U#V+]#V#W%w#W#X,X#X#Y%w#Y#Z.T#Z#]%w#]#^0e#^#`%w#`#a1a#a#b%w#b#c3t#c#d5p#d#f%w#f#g8T#g#h;P#h#i={#i#k%w#k#l?w#l#o%w#y#z$O$f$g$O#BY#BZ$O$IS$I_$O$Ip$Iq%W$Iq$Ir%W$I|$JO$O$JT$JU$O$KV$KW$O&FU&FV$O~$TYj~X^$Opq$O#y#z$O$f$g$O#BY#BZ$O$IS$I_$O$I|$JO$O$JT$JU$O$KV$KW$O&FU&FV$O~$vP!_!`$y~%OPv~#r#s%R~%WOz~~%ZUOr%Wrs%ms$Ip%W$Ip$Iq%m$Iq$Ir%m$Ir~%W~%rOY~~%wOr~P%|SRP}!O%w!c!}%w#R#S%w#T#o%w~&_V[~OY&YZ]&Y^!P&Y!P!Q&t!Q#O&Y#O#P&y#P~&Y~&yO[~~&|PO~&Y~'UPX~!Q!['P~'^Pl~!_!`'a~'fOt~~'kPu~#r#s'n~'sOy~~'xPx~!_!`'{~(QOw~R(VPpQ!}#O(YP(]RO#P(Y#P#Q(f#Q~(YP(iP#P#Q(lP(qOdP~(vOs~R({WRP}!O%w!c!}%w#R#S%w#T#b%w#b#c)e#c#g%w#g#h*a#h#o%wR)jURP}!O%w!c!}%w#R#S%w#T#W%w#W#X)|#X#o%wR*TS|QRP}!O%w!c!}%w#R#S%w#T#o%wR*fURP}!O%w!c!}%w#R#S%w#T#V%w#V#W*x#W#o%wR+PS!QQRP}!O%w!c!}%w#R#S%w#T#o%wR+bURP}!O%w!c!}%w#R#S%w#T#m%w#m#n+t#n#o%wR+{S!OQRP}!O%w!c!}%w#R#S%w#T#o%wR,^URP}!O%w!c!}%w#R#S%w#T#X%w#X#Y,p#Y#o%wR,uURP}!O%w!c!}%w#R#S%w#T#g%w#g#h-X#h#o%wR-^URP}!O%w!c!}%w#R#S%w#T#V%w#V#W-p#W#o%wR-wS!PQRP}!O%w!c!}%w#R#S%w#T#o%wR.YTRP}!O%w!c!}%w#R#S%w#T#U.i#U#o%wR.nURP}!O%w!c!}%w#R#S%w#T#`%w#`#a/Q#a#o%wR/VURP}!O%w!c!}%w#R#S%w#T#g%w#g#h/i#h#o%wR/nURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y0Q#Y#o%wR0XSnQRP}!O%w!c!}%w#R#S%w#T#o%wR0jURP}!O%w!c!}%w#R#S%w#T#b%w#b#c0|#c#o%wR1TS{QRP}!O%w!c!}%w#R#S%w#T#o%wR1fURP}!O%w!c!}%w#R#S%w#T#]%w#]#^1x#^#o%wR1}URP}!O%w!c!}%w#R#S%w#T#a%w#a#b2a#b#o%wR2fURP}!O%w!c!}%w#R#S%w#T#]%w#]#^2x#^#o%wR2}URP}!O%w!c!}%w#R#S%w#T#h%w#h#i3a#i#o%wR3hS!RQRP}!O%w!c!}%w#R#S%w#T#o%wR3yURP}!O%w!c!}%w#R#S%w#T#i%w#i#j4]#j#o%wR4bURP}!O%w!c!}%w#R#S%w#T#`%w#`#a4t#a#o%wR4yURP}!O%w!c!}%w#R#S%w#T#`%w#`#a5]#a#o%wR5dSoQRP}!O%w!c!}%w#R#S%w#T#o%wR5uURP}!O%w!c!}%w#R#S%w#T#f%w#f#g6X#g#o%wR6^URP}!O%w!c!}%w#R#S%w#T#W%w#W#X6p#X#o%wR6uURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y7X#Y#o%wR7^URP}!O%w!c!}%w#R#S%w#T#f%w#f#g7p#g#o%wR7wS}QRP}!O%w!c!}%w#R#S%w#T#o%wR8YURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y8l#Y#o%wR8qURP}!O%w!c!}%w#R#S%w#T#b%w#b#c9T#c#o%wR9YURP}!O%w!c!}%w#R#S%w#T#W%w#W#X9l#X#o%wR9qURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y:T#Y#o%wR:YURP}!O%w!c!}%w#R#S%w#T#f%w#f#g:l#g#o%wR:sS!UQRP}!O%w!c!}%w#R#S%w#T#o%wR;UURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y;h#Y#o%wR;mURP}!O%w!c!}%w#R#S%w#T#`%w#`#a<P#a#o%wR<UURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y<h#Y#o%wR<mURP}!O%w!c!}%w#R#S%w#T#V%w#V#W=P#W#o%wR=UURP}!O%w!c!}%w#R#S%w#T#h%w#h#i=h#i#o%wR=oS!SQRP}!O%w!c!}%w#R#S%w#T#o%wR>QURP}!O%w!c!}%w#R#S%w#T#f%w#f#g>d#g#o%wR>iURP}!O%w!c!}%w#R#S%w#T#i%w#i#j>{#j#o%wR?QURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y?d#Y#o%wR?kSmQRP}!O%w!c!}%w#R#S%w#T#o%wR?|URP}!O%w!c!}%w#R#S%w#T#[%w#[#]@`#]#o%wR@eURP}!O%w!c!}%w#R#S%w#T#X%w#X#Y@w#Y#o%wR@|URP}!O%w!c!}%w#R#S%w#T#f%w#f#gA`#g#o%wRAeURP}!O%w!c!}%w#R#S%w#T#X%w#X#YAw#Y#o%wRBOSkQRP}!O%w!c!}%w#R#S%w#T#o%w",
|
||||
tokenizers: [0, 1],
|
||||
topRules: { "Program": [0, 1] },
|
||||
tokenPrec: 0,
|
||||
});
|
@ -1,25 +0,0 @@
|
||||
name: query
|
||||
imports:
|
||||
- https://get.silverbullet.md/global.plug.json
|
||||
functions:
|
||||
updateMaterializedQueriesOnPage:
|
||||
path: ./materialized_queries.ts:updateMaterializedQueriesOnPage
|
||||
updateMaterializedQueriesCommand:
|
||||
path: ./materialized_queries.ts:updateMaterializedQueriesCommand
|
||||
command:
|
||||
name: "Materialized Queries: Update"
|
||||
key: "Alt-q"
|
||||
events:
|
||||
- editor:pageLoaded
|
||||
indexData:
|
||||
path: ./data.ts:indexData
|
||||
events:
|
||||
- page:index
|
||||
dataQueryProvider:
|
||||
path: ./data.ts:queryProvider
|
||||
events:
|
||||
- query:data
|
||||
queryComplete:
|
||||
path: ./complete.ts:queryComplete
|
||||
events:
|
||||
- page:complete
|
@ -125,7 +125,7 @@ export class HttpServer {
|
||||
eventSyscalls(this.eventHook),
|
||||
markdownSyscalls(buildMarkdown([])),
|
||||
esbuildSyscalls([this.globalModules]),
|
||||
systemSyscalls(this),
|
||||
systemSyscalls(this, this.system),
|
||||
sandboxSyscalls(this.system),
|
||||
assetSyscalls(this.system),
|
||||
// jwtSyscalls(),
|
||||
|
@ -1,18 +1,33 @@
|
||||
import { SysCallMapping } from "../../plugos/system.ts";
|
||||
import { Plug } from "../../plugos/plug.ts";
|
||||
import { SysCallMapping, System } from "../../plugos/system.ts";
|
||||
import type { HttpServer } from "../http_server.ts";
|
||||
|
||||
export function systemSyscalls(httpServer: HttpServer): SysCallMapping {
|
||||
export function systemSyscalls(
|
||||
httpServer: HttpServer,
|
||||
system: System<any>,
|
||||
): SysCallMapping {
|
||||
return {
|
||||
"system.invokeFunction": (
|
||||
ctx,
|
||||
env: string,
|
||||
// Ignored in this context, always assuming server (this place)
|
||||
_env: string,
|
||||
name: string,
|
||||
...args: any[]
|
||||
) => {
|
||||
if (!ctx.plug) {
|
||||
throw Error("No plug associated with context");
|
||||
}
|
||||
return ctx.plug.invoke(name, args);
|
||||
let plug: Plug<any> | undefined = ctx.plug;
|
||||
if (name.indexOf(".") !== -1) {
|
||||
// plug name in the name
|
||||
const [plugName, functionName] = name.split(".");
|
||||
plug = system.loadedPlugs.get(plugName);
|
||||
if (!plug) {
|
||||
throw Error(`Plug ${plugName} not found`);
|
||||
}
|
||||
name = functionName;
|
||||
}
|
||||
return plug.invoke(name, args);
|
||||
},
|
||||
"system.reloadPlugs": () => {
|
||||
return httpServer.reloadPlugs();
|
||||
|
@ -2,8 +2,6 @@ import {
|
||||
preactRender,
|
||||
useEffect,
|
||||
useReducer,
|
||||
Y,
|
||||
yCollab,
|
||||
yUndoManagerKeymap,
|
||||
} from "./deps.ts";
|
||||
|
||||
@ -160,7 +158,7 @@ export class Editor {
|
||||
spaceSyscalls(this),
|
||||
indexerSyscalls(this.space),
|
||||
fulltextSyscalls(this.space),
|
||||
systemSyscalls(this),
|
||||
systemSyscalls(this, this.system),
|
||||
markdownSyscalls(buildMarkdown(this.mdExtensions)),
|
||||
clientStoreSyscalls(),
|
||||
storeSyscalls(this.space),
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { SysCallMapping } from "../../plugos/system.ts";
|
||||
import type { Plug } from "../../plugos/plug.ts";
|
||||
import { SysCallMapping, System } from "../../plugos/system.ts";
|
||||
import type { Editor } from "../editor.tsx";
|
||||
import { CommandDef } from "../hooks/command.ts";
|
||||
|
||||
export function systemSyscalls(editor: Editor): SysCallMapping {
|
||||
export function systemSyscalls(
|
||||
editor: Editor,
|
||||
system: System<any>,
|
||||
): SysCallMapping {
|
||||
return {
|
||||
"system.invokeFunction": (
|
||||
ctx,
|
||||
@ -14,11 +18,21 @@ export function systemSyscalls(editor: Editor): SysCallMapping {
|
||||
throw Error("No plug associated with context");
|
||||
}
|
||||
|
||||
let plug: Plug<any> | undefined = ctx.plug;
|
||||
if (name.indexOf(".") !== -1) {
|
||||
// plug name in the name
|
||||
const [plugName, functionName] = name.split(".");
|
||||
plug = system.loadedPlugs.get(plugName);
|
||||
if (!plug) {
|
||||
throw Error(`Plug ${plugName} not found`);
|
||||
}
|
||||
name = functionName;
|
||||
}
|
||||
if (env === "client") {
|
||||
return ctx.plug.invoke(name, args);
|
||||
return plug.invoke(name, args);
|
||||
}
|
||||
|
||||
return editor.space.invokeFunction(ctx.plug, env, name, args);
|
||||
return editor.space.invokeFunction(plug, env, name, args);
|
||||
},
|
||||
"system.invokeCommand": (ctx, name: string) => {
|
||||
return editor.runCommandByName(name);
|
||||
|
@ -5,122 +5,121 @@ release.
|
||||
|
||||
## 0.1.4
|
||||
|
||||
- Breaking change (for those who used it): the named anchor syntax has changed
|
||||
from `@anchorname` to `$anchorname`. This is to avoid conflicts with
|
||||
potentialy future use of `@` for other purposes (like mentioning people).
|
||||
Linking to an anchor still uses the `[[page@anchorname]]` syntax. So, you
|
||||
create an anchor $likethis you can then reference it [[@likethis]].
|
||||
* Breaking change (for those who used it): the named anchor syntax has changed from `@anchorname` to `$anchorname`. This is to avoid conflicts with potentialy future use of `@` for other purposes (like mentioning people). Linking to an anchor still uses the `[[page@anchorname]]` syntax. So, you create an anchor $likethis you can then reference it [[@likethis]].
|
||||
* The `query` plug has been renamed to `directive` (because it supports many other features now) and significantly refactored. New docs: [[🔌 Directive]]
|
||||
* New directive `#eval` see [[🔌 Directive@eval]]
|
||||
* New PlugOS feature: redirecting function calls. Instead of specifying a `path` for a function, you can now specify `redirect` pointing to another function name, either in the same plug using the `plugName.functionName` syntax.
|
||||
|
||||
---
|
||||
|
||||
## 0.1.3
|
||||
|
||||
- Silver Bullet now runs on Windows!
|
||||
- Frontmatter support! You can now use front matter in your markdown, to do this
|
||||
* Silver Bullet now runs on Windows!
|
||||
* Frontmatter support! You can now use front matter in your markdown, to do this
|
||||
start your page with `---` and end it with `---`. This will now be the
|
||||
preferred way to define page meta data (although the old way will still work).
|
||||
The old `/meta` slash command has now been replaced with `/front-matter`.
|
||||
- Tags are now indexed as page meta without the prefixing `#` character, the
|
||||
* Tags are now indexed as page meta without the prefixing `#` character, the
|
||||
reason is to make this compatible with Obsidian. You can now attach tags to
|
||||
your page either by just using a `#tag` at the top level of your page, or by
|
||||
adding a `tags` attribute to your front matter.
|
||||
- {[Search Space]} works again. You may have to {[Space: Reindex]} to get
|
||||
* {[Search Space]} works again. You may have to {[Space: Reindex]} to get
|
||||
results. Search results now also snow a snippet of the page, with the phrase
|
||||
highlighted.
|
||||
- Faster page indexing.
|
||||
- `silverbullet` now has sub-commands. It defaults to just running the server
|
||||
* Faster page indexing.
|
||||
* `silverbullet` now has sub-commands. It defaults to just running the server
|
||||
(when passed a path to a directory), but you can also run
|
||||
`silverbullet --help` to see the available commands. Commands currently
|
||||
available:
|
||||
- `silverbullet upgrade` to perform a self upgrade
|
||||
- `silverbullet fix` to attempt to solve any issues with your space (deletes
|
||||
* `silverbullet upgrade` to perform a self upgrade
|
||||
* `silverbullet fix` to attempt to solve any issues with your space (deletes
|
||||
your `_plug` directory and `data.db` file)
|
||||
- `silverbullet plug:compile` replaces the old `plugos-bundle` command.
|
||||
- `silverbullet version` prints the current version
|
||||
* `silverbullet plug:compile` replaces the old `plugos-bundle` command.
|
||||
* `silverbullet version` prints the current version
|
||||
|
||||
---
|
||||
|
||||
## 0.1.2
|
||||
|
||||
- Breaking plugs API change: `readPage`, `readAttachment`, `readFile` now return
|
||||
* Breaking plugs API change: `readPage`, `readAttachment`, `readFile` now return
|
||||
the read data object directly, without it being wrapped with a text object.
|
||||
- A whole bunch of deprecated syscalls have been removed
|
||||
* A whole bunch of deprecated syscalls have been removed
|
||||
|
||||
---
|
||||
|
||||
## 0.1.0 First Deno release
|
||||
|
||||
- The entire repo has been migrated to [Deno](https://deno.land)
|
||||
- This may temporarily break some things.
|
||||
- If somehow you’re experiencing trouble, try the following:
|
||||
- Delete all files under `_plug` in your pages folder, e.g. with
|
||||
* The entire repo has been migrated to [Deno](https://deno.land)
|
||||
* This may temporarily break some things.
|
||||
* If somehow you’re experiencing trouble, try the following:
|
||||
* Delete all files under `_plug` in your pages folder, e.g. with
|
||||
`rm -rf pages/_plug`.
|
||||
- Delete your `data.db`
|
||||
- Changes:
|
||||
- `PLUGS` is now longer required
|
||||
- `PLUGS` no longer supports `builtin:` plug URLs, all builtins are
|
||||
* Delete your `data.db`
|
||||
* Changes:
|
||||
* `PLUGS` is now longer required
|
||||
* `PLUGS` no longer supports `builtin:` plug URLs, all builtins are
|
||||
automatically loaded and no longer should be listed.
|
||||
- Plugs no longer should be built with node and npm, PRs will be issued to all
|
||||
* Plugs no longer should be built with node and npm, PRs will be issued to all
|
||||
existing plugs later to help with this transition.
|
||||
- Know breakages:
|
||||
- Full text search is not yet implemented (the SQLite used does not support it
|
||||
* Know breakages:
|
||||
* Full text search is not yet implemented (the SQLite used does not support it
|
||||
right now)
|
||||
- Github auth has not been ported (yet)
|
||||
- Technical changes:
|
||||
- Server runs on Deno (and Oak instead of Express)
|
||||
- Client is now built with ESBuild
|
||||
- React has been replaced with Preact
|
||||
- Package management in Deno works based on http imports, so npm is no longer
|
||||
* Github auth has not been ported (yet)
|
||||
* Technical changes:
|
||||
* Server runs on Deno (and Oak instead of Express)
|
||||
* Client is now built with ESBuild
|
||||
* React has been replaced with Preact
|
||||
* Package management in Deno works based on http imports, so npm is no longer
|
||||
used.
|
||||
|
||||
---
|
||||
|
||||
## 0.0.35
|
||||
|
||||
- Big refactor of the internal Space API unifying attachment and page handling.
|
||||
* Big refactor of the internal Space API unifying attachment and page handling.
|
||||
This shouldn't affect (most) existing code and plugs (except some more exotic
|
||||
areas), but if stuff breaks, please report it.
|
||||
- Technical change: Upgrades are now detected on the server-side, and plugs
|
||||
* Technical change: Upgrades are now detected on the server-side, and plugs
|
||||
re-loaded and pages indexed upon every upgrade.
|
||||
- Various bug fixes (e.g. using HTML tags in a page before completely broke
|
||||
* Various bug fixes (e.g. using HTML tags in a page before completely broke
|
||||
syntax highlighting)
|
||||
- Exposed `fulltext.*` syscalls on the client
|
||||
* Exposed `fulltext.*` syscalls on the client
|
||||
|
||||
---
|
||||
|
||||
## 0.0.34
|
||||
|
||||
- Change to attachment handling: the `attachment/` prefix for links and images
|
||||
* Change to attachment handling: the `attachment/` prefix for links and images
|
||||
is no longer used, if you already had links to attachments in your notes, you
|
||||
will need to remove the `attachment/` prefix manually. Sorry about that.
|
||||
- Improved styling for completion (especially slash commands)
|
||||
- Completion for commands using the (undocumented) `{[Command Syntax]}` — yep,
|
||||
* Improved styling for completion (especially slash commands)
|
||||
* Completion for commands using the (undocumented) `{[Command Syntax]}` — yep,
|
||||
that exists.
|
||||
|
||||
---
|
||||
|
||||
## 0.0.33
|
||||
|
||||
- **Attachments**: you can now copy & paste, or drag & drop files (images, PDF,
|
||||
* **Attachments**: you can now copy & paste, or drag & drop files (images, PDF,
|
||||
whatever you like) into a page and it will be uploaded and appropriately
|
||||
linked from your page. Attachment size is currently limited to 100mb.
|
||||
- Changed full-text search page prefix from `@search/` to `🔍` for the {[Search
|
||||
* Changed full-text search page prefix from `@search/` to `🔍` for the {[Search
|
||||
Space]} command.
|
||||
- `page`, `plug` and `attachment` are now _reserved page names_, you cannot name
|
||||
* `page`, `plug` and `attachment` are now _reserved page names_, you cannot name
|
||||
your pages these (you will get an error when explicitly navigating to them).
|
||||
|
||||
---
|
||||
|
||||
## 0.0.32
|
||||
|
||||
- **Inline image previews**: use the standard
|
||||
* **Inline image previews**: use the standard
|
||||
`![alt text](https://url.com/image.jpg)` notation and a preview of the image
|
||||
will appear automatically. Example:
|
||||
![Inline image preview](https://user-images.githubusercontent.com/812886/186218876-6d8a4a71-af8b-4e9e-83eb-4ac89607a6b4.png)
|
||||
- **Dark mode**. Toggle between the dark and light mode using a new button,
|
||||
* **Dark mode**. Toggle between the dark and light mode using a new button,
|
||||
top-right.
|
||||
![Dark mode screenshot](https://user-images.githubusercontent.com/6335792/187000151-ba06ce55-ad27-494b-bfe9-6b19ef62145b.png)
|
||||
- **Named anchors** and references, create an anchor with the new @anchor
|
||||
* **Named anchors** and references, create an anchor with the new @anchor
|
||||
notation (anywhere on a page), then reference it locally via [[@anchor]] or
|
||||
cross page via [[CHANGELOG@anchor]].
|
||||
|
||||
@ -128,73 +127,73 @@ release.
|
||||
|
||||
## 0.0.31
|
||||
|
||||
- Update to the query language: the `render` clause now uses page reference
|
||||
* Update to the query language: the `render` clause now uses page reference
|
||||
syntax `[[page]]`. For example `render [[template/task]]` rather than
|
||||
`render "template/task"`. The old syntax still works, but is deprecated,
|
||||
completion for the old syntax has been removed.
|
||||
- Updates to templates:
|
||||
- For the `Template: Instantiate Page` command, the page meta value `$name` is
|
||||
* Updates to templates:
|
||||
* For the `Template: Instantiate Page` command, the page meta value `$name` is
|
||||
now used to configure the page name (was `name` before). Also if `$name` is
|
||||
the only page meta defined, it will remove the page meta entirely when
|
||||
instantiating.
|
||||
- You can now configure a daily note prefix with `dailyNotePrefix` in
|
||||
* You can now configure a daily note prefix with `dailyNotePrefix` in
|
||||
`SETTINGS` and create a template for your daily note under
|
||||
`template/page/Daily Note` (configurable via the `dailyNoteTemplate`
|
||||
setting).
|
||||
- You can now set a quick note prefix with `quickNotePrefix` in `SETTINGS`.
|
||||
- Directives (e.g. `#query`, `#import`, `#use`) changes:
|
||||
- Renamed `#template` directive to `#use-verbose`
|
||||
- New `#use` directive will clean all the embedded queries and templates in
|
||||
* You can now set a quick note prefix with `quickNotePrefix` in `SETTINGS`.
|
||||
* Directives (e.g. `#query`, `#import`, `#use`) changes:
|
||||
* Renamed `#template` directive to `#use-verbose`
|
||||
* New `#use` directive will clean all the embedded queries and templates in
|
||||
its scope
|
||||
- All directives now use the page reference syntax `[[page name]]` instead of
|
||||
* All directives now use the page reference syntax `[[page name]]` instead of
|
||||
`"page name"`, this includes `#use` and `#use-verbose` as well as `#import`.
|
||||
- The `link` query provider now also returns the `pos` of a link (in addition
|
||||
* The `link` query provider now also returns the `pos` of a link (in addition
|
||||
to the `page`)
|
||||
- New `$disableDirectives` page metadata attribute can be used to disable
|
||||
* New `$disableDirectives` page metadata attribute can be used to disable
|
||||
directives processing in a page (useful for templates)
|
||||
- Added a new `/hr` slash command to insert a horizontal rule (`---`) useful for
|
||||
* Added a new `/hr` slash command to insert a horizontal rule (`---`) useful for
|
||||
mobile devices (where these are harder to type)
|
||||
|
||||
---
|
||||
|
||||
## 0.0.30
|
||||
|
||||
- Slash commands now only trigger after a non-word character to avoid "false
|
||||
* Slash commands now only trigger after a non-word character to avoid "false
|
||||
positives" like "hello/world".
|
||||
- Page auto complete now works with slashes in the name.
|
||||
- Having a `SETTINGS` page is now mandatory. One is auto generated if none is
|
||||
* Page auto complete now works with slashes in the name.
|
||||
* Having a `SETTINGS` page is now mandatory. One is auto generated if none is
|
||||
present.
|
||||
- Added an `indexPage` setting to set the index page for the space (which by
|
||||
* Added an `indexPage` setting to set the index page for the space (which by
|
||||
default is `index`). When navigating to this page, the page name will
|
||||
"disappear" from the URL. That is, the index URL will simply be
|
||||
`http://localhost:3000/`.
|
||||
- This feature is now used in `website` and set to `Silver Bullet` there. To
|
||||
* This feature is now used in `website` and set to `Silver Bullet` there. To
|
||||
also make the title look nicer when visiting https://silverbullet.md
|
||||
|
||||
---
|
||||
|
||||
## 0.0.29
|
||||
|
||||
- Added the `Link: Unfurl` command, which is scoped to only work (and be
|
||||
* Added the `Link: Unfurl` command, which is scoped to only work (and be
|
||||
visible) when used on “naked URLs”, that is: URLs not embedded in a link or
|
||||
other place, such as this one: https://silverbullet.md
|
||||
- Plugs can implement their own unfurlers by responding to the
|
||||
* Plugs can implement their own unfurlers by responding to the
|
||||
`unfurl:options` event (see the
|
||||
[Twitter plug](https://github.com/silverbulletmd/silverbullet-twitter) for
|
||||
an example).
|
||||
- Core implements only one unfurl option: “Extract title” which pulls the
|
||||
* Core implements only one unfurl option: “Extract title” which pulls the
|
||||
`<title>` tag from the linked URL and replaces it with a `[bla](URL)` style
|
||||
link.
|
||||
- Removed status bar: to further simplify the SB UI. You can still pull up the
|
||||
* Removed status bar: to further simplify the SB UI. You can still pull up the
|
||||
same stat on demand with the `Stats: Show` command.
|
||||
- The page switcher is now maintaining its ordering based on, in order:
|
||||
* The page switcher is now maintaining its ordering based on, in order:
|
||||
1. Last opened pages (in current session)
|
||||
2. Last modified date (on disk)
|
||||
3. Everything else
|
||||
4. The currently open page (at the bottom)
|
||||
- Filter boxes (used for the page switching and command palette among other
|
||||
* Filter boxes (used for the page switching and command palette among other
|
||||
things) now also support PgUp, PgDown, Home and End and have some visual
|
||||
glitches fixed as well.
|
||||
- Reverted exposing an empty `window` object to sandboxes running in workers and
|
||||
* Reverted exposing an empty `window` object to sandboxes running in workers and
|
||||
node.js (introduced in 0.0.28)
|
||||
- Renamed Markdown-preview related commands to something more consistentnt
|
||||
* Renamed Markdown-preview related commands to something more consistentnt
|
||||
|
@ -1,63 +1,37 @@
|
||||
## Markdown as a platform
|
||||
|
||||
Silver Bullet (SB) is highly-extensible,
|
||||
[open source](https://github.com/silverbulletmd/silverbullet) **personal
|
||||
knowledge management** software. Indeed, that’s fancy language for “a note
|
||||
taking app with links.”
|
||||
knowledge management** software. Indeed, that’s fancy language for “a note taking app with links.”
|
||||
|
||||
Here is a screenshot:
|
||||
|
||||
![Silver Bullet PWA screenshot](silverbullet-pwa.png)
|
||||
|
||||
At its core, SB is a Markdown editor that stores _pages_ (notes) as plain
|
||||
markdown files in a folder referred to as a _space_. Pages can be cross-linked
|
||||
using the `[[link to other page]]` syntax. However, once you leverage its
|
||||
various extensions (called _plugs_) it can feel more like a _knowledge
|
||||
platform_, allowing you to annotate, combine and query your accumulated
|
||||
knowledge in creative ways, specific to you. To get a good feel for it,
|
||||
[watch this video](https://youtu.be/RYdc3UF9gok).
|
||||
|
||||
Or [try it in a sandbox demo environment](https://demo.silverbullet.md/Sandbox).
|
||||
markdown files in a folder referred to as a _space_. Pages can be cross-linked using the `[[link to other page]]` syntax. However, once you leverage its various extensions (called _plugs_) it can feel more like a _knowledge platform_, allowing you to annotate, combine and query your accumulated knowledge in creative ways, specific to you. To get a good feel for it, [watch this video](https://youtu.be/RYdc3UF9gok).
|
||||
|
||||
## Extensions
|
||||
|
||||
What type of extensions, you ask? Let us demonstrate this in a very meta way: by
|
||||
querying a list of plugs and injecting it into this page!
|
||||
What type of extensions, you ask? Let us demonstrate this in a very meta way: by querying a list of plugs and injecting it into this page!
|
||||
|
||||
Here’s a list of (non-built-in) plugs documented in this space (note the
|
||||
`#query` ... `/query` notation used):
|
||||
|
||||
<!-- #query page where type = "plug" order by name render [[template/plug]] -->
|
||||
|
||||
- [[🔌 Backlinks]] by **Guillermo Vayá**
|
||||
([repo](https://github.com/Willyfrog/silverbullet-backlinks))
|
||||
- [[🔌 Core]] by **Silver Bullet Authors**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
- [[🔌 Ghost]] by **Zef Hemel**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet-ghost))
|
||||
- [[🔌 Git]] by **Zef Hemel**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet-github))
|
||||
- [[🔌 Github]] by **Zef Hemel**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet-github))
|
||||
- [[🔌 Mattermost]] by **Zef Hemel**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet-mattermost))
|
||||
- [[🔌 Mount]] by **Zef Hemel**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet-mount))
|
||||
- [[🔌 Query]] by **Silver Bullet Authors**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
|
||||
* [[🔌 Backlinks]] by **Guillermo Vayá** ([repo](https://github.com/Willyfrog/silverbullet-backlinks))
|
||||
* [[🔌 Core]] by **Silver Bullet Authors** ([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
* [[🔌 Directive]] by **Silver Bullet Authors** ([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
* [[🔌 Ghost]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-ghost))
|
||||
* [[🔌 Git]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-github))
|
||||
* [[🔌 Github]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-github))
|
||||
* [[🔌 Mattermost]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-mattermost))
|
||||
* [[🔌 Mount]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-mount))
|
||||
<!-- /query -->
|
||||
|
||||
In a regular SB installation, the body of this query 👆 (in between the
|
||||
placeholders) would automatically be kept up to date as new pages are added to
|
||||
the space that match the query. 🤯 Have a look at the [[template/plug]]
|
||||
_template_ (referenced in the `render` clause) to see how the results are
|
||||
rendered using handlebars syntax and have a look at one of the linked pages to
|
||||
see how the _metadata_ is specified, which is subsequently used to query and
|
||||
render in this page. And to learn about the specific plug, of course.
|
||||
placeholders) would automatically be kept up to date as new pages are added to the space that match the query. 🤯 Have a look at the [[template/plug]] _template_ (referenced in the `render` clause) to see how the results are rendered using handlebars syntax and have a look at one of the linked pages to see how the _metadata_ is specified, which is subsequently used to query and render in this page. And to learn about the specific plug, of course.
|
||||
|
||||
## Explore more
|
||||
|
||||
Click on the links below to explore various aspects of Silver Bullet more
|
||||
in-depth:
|
||||
|
||||
@ -69,28 +43,17 @@ in-depth:
|
||||
|
||||
More of a video person? Here are two to get you started:
|
||||
|
||||
- [A Tour of Silver Bullet’s features](https://youtu.be/RYdc3UF9gok) — spoiler
|
||||
alert: it’s cool.
|
||||
- [A look the SilverBullet architecture](https://youtu.be/mXCGau05p5o) — spoiler
|
||||
alert: it’s plugs all the way down.
|
||||
- [A Tour of Silver Bullet’s features](https://youtu.be/RYdc3UF9gok) — spoiler alert: it’s cool.
|
||||
- [A look the SilverBullet architecture](https://youtu.be/mXCGau05p5o) — spoiler alert: it’s plugs all the way down.
|
||||
|
||||
## Principles
|
||||
|
||||
Some core principles that underly Silver Bullet’s philosophy:
|
||||
|
||||
- **Free and open source**. Silver Bullet is MIT licensed.
|
||||
- **The truth is in the markdown.** Markdown is simply text files, stored on
|
||||
disk. Nothing fancy. No proprietary formats or lock in. While SB uses a
|
||||
database for indexing and caching some data, all of that can be rebuilt from
|
||||
its markdown source at any time. If SB would ever go away, you can still read
|
||||
your pages with any text editor.
|
||||
- **Single, distraction-free mode.** SB doesn’t have a separate view and edit
|
||||
mode. It doesn’t have a “focus mode.” You’re always in focused edit mode, why
|
||||
wouldn’t you?
|
||||
- **Keyboard oriented**. You can use SB fully using the keyboard, typin’ the
|
||||
keys.
|
||||
- **Extend it your way**. SB is highly extensible with [[🔌 Plugs]], and you can
|
||||
customize it to your liking and your workflows.
|
||||
- **The truth is in the markdown.** Markdown is simply text files, stored on disk. Nothing fancy. No proprietary formats or lock in. While SB uses a database for indexing and caching some data, all of that can be rebuilt from its markdown source at any time. If SB would ever go away, you can still read your pages with any text editor.
|
||||
- **Single, distraction-free mode.** SB doesn’t have a separate view and edit mode. It doesn’t have a “focus mode.” You’re always in focused edit mode, why wouldn’t you?
|
||||
- **Keyboard oriented**. You can use SB fully using the keyboard, typin’ the keys.
|
||||
- **Extend it your way**. SB is highly extensible with [[🔌 Plugs]], and you can customize it to your liking and your workflows.
|
||||
|
||||
## Installing Silver Bullet
|
||||
|
||||
@ -120,32 +83,22 @@ With Deno installed (see instruction above), run:
|
||||
deno install -f --name silverbullet -A --unstable https://get.silverbullet.md
|
||||
```
|
||||
|
||||
This will install `silverbullet` into your `~/.deno/bin` folder (which should
|
||||
already be in your path if you installed Deno following the previous
|
||||
instructions).
|
||||
This will install `silverbullet` into your `~/.deno/bin` folder (which should already be in your path if you installed Deno following the previous instructions).
|
||||
|
||||
To run Silver Bullet create a folder for your pages (it can be empty, or be an
|
||||
existing folder with `.md` files) and run the following command in your
|
||||
terminal:
|
||||
To run Silver Bullet create a folder for your pages (it can be empty, or be an existing folder with `.md` files) and run the following command in your terminal:
|
||||
|
||||
```shell
|
||||
silverbullet <pages-path>
|
||||
```
|
||||
|
||||
By default, SB will bind to port `3000`, to use a different port use the
|
||||
`--port` flag. By default SB doesn’t offer any sort of authentication, to add
|
||||
basic password authentication, pass the `--password` flag.
|
||||
`--port` flag. By default SB doesn’t offer any sort of authentication, to add basic password authentication, pass the `--password` flag.
|
||||
|
||||
Once downloaded and booted, SB will print out a URL to open SB in your browser
|
||||
(spoiler alert: by default this will be http://localhost:3000 ).
|
||||
Once downloaded and booted, SB will print out a URL to open SB in your browser (spoiler alert: by default this will be http://localhost:3000 ).
|
||||
|
||||
#protip: If you have a PWA enabled browser (like any browser based on Chromium)
|
||||
hit that little button right of the location bar to install SB, and give it its
|
||||
own window frame (sans location bar) and desktop/dock icon. At last the PWA has
|
||||
found its killer app.
|
||||
#protip: If you have a PWA enabled browser (like any browser based on Chromium) hit that little button right of the location bar to install SB, and give it its own window frame (sans location bar) and desktop/dock icon. At last the PWA has found its killer app.
|
||||
|
||||
## Upgrading Silver Bullet
|
||||
|
||||
Simply run this:
|
||||
|
||||
silverbullet upgrade
|
||||
@ -154,11 +107,8 @@ And restart Silver Bullet. You should be good to go.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you upgraded to the new Deno-based Silver Bullet from an old version, you may
|
||||
have to use the `silverbullet fix <pages-path>` command to flush out your old
|
||||
database and plugs. Plugs will likely need to be updated.
|
||||
If you upgraded to the new Deno-based Silver Bullet from an old version, you may have to use the `silverbullet fix <pages-path>` command to flush out your old database and plugs. Plugs will likely need to be updated.
|
||||
|
||||
If you (hypothetically) find bugs or have feature requests, post them in
|
||||
[our issue tracker](https://github.com/silverbulletmd/silverbullet/issues). Want
|
||||
to contribute?
|
||||
[Check out the code](https://github.com/silverbulletmd/silverbullet).
|
||||
to contribute? [Check out the code](https://github.com/silverbulletmd/silverbullet).
|
||||
|
4
website/template/debug.md
Normal file
4
website/template/debug.md
Normal file
@ -0,0 +1,4 @@
|
||||
{{#each .}}
|
||||
{{@key}}: {{.}}
|
||||
{{/each}}
|
||||
---
|
@ -1 +1 @@
|
||||
- [[{{name}}]] by **{{author}}** ([repo]({{repo}}))
|
||||
* [[{{name}}]] by **{{author}}** ([repo]({{repo}}))
|
7
website/template/tagged-tasks.md
Normal file
7
website/template/tagged-tasks.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
$disableDirectives: true
|
||||
---
|
||||
|
||||
<!-- #query task where tags = "{{.}}" and done = false render [[template/task]] -->
|
||||
|
||||
<!-- /query -->
|
1
website/template/task.md
Normal file
1
website/template/task.md
Normal file
@ -0,0 +1 @@
|
||||
* [{{#if done}}x{{else}} {{/if}}] [[{{page}}@{{pos}}]] {{name}} {{#if deadline}}📅 {{deadline}}{{/if}} {{#each tags}}{{.}} {{/each}}
|
@ -1,3 +0,0 @@
|
||||
{{#each .}}
|
||||
|
||||
- [{{#if done}}x{{else}} {{/if}}] [[{{page}}@{{pos}}]] {{name}} {{/each}}
|
@ -1,10 +1,9 @@
|
||||
```meta
|
||||
---
|
||||
type: plug
|
||||
uri: builtin:core
|
||||
repo: https://github.com/silverbulletmd/silverbullet
|
||||
author: Silver Bullet Authors
|
||||
$disableDirectives: true
|
||||
```
|
||||
---
|
||||
|
||||
This documentation is still a WIP.
|
||||
|
||||
@ -14,26 +13,22 @@ The core plug implements a few templating mechanisms.
|
||||
|
||||
### Page Templates
|
||||
|
||||
The {[Template: Instantiate Page]} command enables you to create a new page
|
||||
based on a page template.
|
||||
The {[Template: Instantiate Page]} command enables you to create a new page based on a page template.
|
||||
|
||||
Page templates, by default, are looked for in the `template/page/` prefix. So
|
||||
creating e.g. a `template/page/Meeting Notes` page will create a “Meeting Notes”
|
||||
template. You can override this prefix by setting the `pageTemplatePrefix` in
|
||||
`SETTINGS`.
|
||||
Page templates, by default, are looked for in the `template/page/` prefix. So creating e.g. a `template/page/Meeting Notes` page will create a “Meeting Notes” template. You can override this prefix by setting the `pageTemplatePrefix` in `SETTINGS`.
|
||||
|
||||
Page templates have one “magic” type of page metadata that is used during
|
||||
instantiation:
|
||||
|
||||
- `$name` is used as the default value for a new page based on this template
|
||||
* `$name` is used as the default value for a new page based on this template
|
||||
|
||||
In addition, any standard template placeholders are available (see below)
|
||||
|
||||
For instance:
|
||||
|
||||
```meta
|
||||
---
|
||||
$name: "📕 "
|
||||
```
|
||||
---
|
||||
|
||||
# {{page}}
|
||||
As recorded on {{today}}.
|
||||
@ -42,8 +37,7 @@ For instance:
|
||||
## Notes
|
||||
## Conclusions
|
||||
|
||||
Will prompt you to pick a page name (defaulting to “📕 “), and then create the
|
||||
following page (on 2022-08-08) when you pick “📕 Harry Potter” as a page name:
|
||||
Will prompt you to pick a page name (defaulting to “📕 “), and then create the following page (on 2022-08-08) when you pick “📕 Harry Potter” as a page name:
|
||||
|
||||
# 📕 Harry Potter
|
||||
As recorded on 2022-08-08.
|
||||
@ -54,17 +48,11 @@ following page (on 2022-08-08) when you pick “📕 Harry Potter” as a page n
|
||||
|
||||
### Snippets
|
||||
|
||||
Snippets are similar to page templates, except you insert them into an existing
|
||||
page with the `/snippet` slash command. The default prefix is `snippet/` which
|
||||
is configurable via the `snippetPrefix` setting in `SETTINGS`.
|
||||
Snippets are similar to page templates, except you insert them into an existing page with the `/snippet` slash command. The default prefix is `snippet/` which is configurable via the `snippetPrefix` setting in `SETTINGS`.
|
||||
|
||||
Snippet templates do not support the `$name` page meta, because it doesn’t
|
||||
apply.
|
||||
Snippet templates do not support the `$name` page meta, because it doesn’t apply.
|
||||
|
||||
However, snippets do support the special `|^|` placeholder for placing the
|
||||
cursor caret after injecting the snippet. If you leave it out, the cursor will
|
||||
simply be placed at the end, but if you like to insert the cursor elsewhere,
|
||||
that position can be set with the `|^|` placeholder.
|
||||
However, snippets do support the special `|^|` placeholder for placing the cursor caret after injecting the snippet. If you leave it out, the cursor will simply be placed at the end, but if you like to insert the cursor elsewhere, that position can be set with the `|^|` placeholder.
|
||||
|
||||
For instance to replicate the `/query` slash command as a snippet:
|
||||
|
||||
@ -74,53 +62,14 @@ For instance to replicate the `/query` slash command as a snippet:
|
||||
|
||||
Which would insert the cursor right after `#query`.
|
||||
|
||||
### Dynamic template injection
|
||||
|
||||
In addition to using the `/snippet` slash command to insert a template as a
|
||||
one-off, it’s also possible to reuse templates that update dynamically (similar
|
||||
to [[🔌 Query]]). For this, you use the `#use` and `#use-verbose` directives.
|
||||
|
||||
In its most basic form:
|
||||
|
||||
<!-- #use [[template/plug]] -->
|
||||
<!-- /use -->
|
||||
|
||||
Upon load (or when updating materialized queries) the body of this dynamic
|
||||
section will be replaced with the content of the referenced template.
|
||||
|
||||
The referenced template will be treated as a Handlebars template (just like when
|
||||
using a `render` clause with `#query`).
|
||||
|
||||
Optionally, you can pass any JSON-formatted value as an argument, which will be
|
||||
exposed in the template as the top-level value.
|
||||
|
||||
For example, given the following template:
|
||||
|
||||
Hello there {{name}} you are {{age}} years old!
|
||||
|
||||
You can reference and instantiate as follows:
|
||||
|
||||
<!-- #use [[template/plug]] {"name": "Pete", "age": 50} -->
|
||||
<!-- /use -->
|
||||
|
||||
If a template contains any dynamic sections with directives, these will all be
|
||||
removed before injecting the content into the page. This makes things look
|
||||
cleaner. If you want to preserve them, use `#use-verbose` instead of `#use`.
|
||||
|
||||
### Daily Note
|
||||
|
||||
The {[Open Daily Note]} command navigates (or creates) a daily note prefixed
|
||||
with a 📅 emoji by default, but this is configurable via the `dailyNotePrefix`
|
||||
setting in `SETTINGS`. If you have a page template (see above) named
|
||||
`Daily Note` it will use this as a template, otherwise, the page will just be
|
||||
empty.
|
||||
with a 📅 emoji by default, but this is configurable via the `dailyNotePrefix` setting in `SETTINGS`. If you have a page template (see above) named `Daily Note` it will use this as a template, otherwise, the page will just be empty.
|
||||
|
||||
### Quick Note
|
||||
|
||||
The {[Quick Note]} command will navigate to an empty page named with the current
|
||||
date and time prefixed with a 📥 emoji, but this is configurable via the
|
||||
`quickNotePrefix` in `SETTINGS`. The use case is to take a quick note outside of
|
||||
your current context.
|
||||
The {[Quick Note]} command will navigate to an empty page named with the current date and time prefixed with a 📥 emoji, but this is configurable via the `quickNotePrefix` in `SETTINGS`. The use case is to take a quick note outside of your current context.
|
||||
|
||||
### Template placeholders
|
||||
|
||||
|
75
website/🔌 Directive.md
Normal file
75
website/🔌 Directive.md
Normal file
@ -0,0 +1,75 @@
|
||||
---
|
||||
type: plug
|
||||
repo: https://github.com/silverbulletmd/silverbullet
|
||||
author: Silver Bullet Authors
|
||||
---
|
||||
|
||||
The directive plug is a built-in plug implementing various so-called “directive” that all take the form of `<!-- #directiveName ... -->` and close with `<!-- /directiveName -->`. Currently the following directives are supported:
|
||||
|
||||
* `#query` to perform queries: [[🔌 Directive/Query]]
|
||||
* `#include` to inline the content of another page verbatim: [[@include]]
|
||||
* `#use` to use the content of another as a [handlebars](https://handlebarsjs.com/) template: [[@use]]
|
||||
* `#eval` to evaluate an arbitrary JavaScript expression and inline the result: [[@eval]]
|
||||
|
||||
|
||||
## Include
|
||||
$include
|
||||
The `#include` directive can be used to embed another page into your existing one. The syntax is as follows:
|
||||
|
||||
<!-- #include [[page reference]] -->
|
||||
|
||||
<!-- /include -->
|
||||
|
||||
Whenever the directives are updated, the body of the directive will be replaced with the latest version of the reference page.
|
||||
|
||||
## Use
|
||||
$use
|
||||
The `#use` directive can be used to use a referenced page as a handbars template. Optionally, a JSON object can be passed as argument to the template:
|
||||
|
||||
<!-- #use [[template/plug]] {"name": "Example plug", "repo": "https://google.com"} -->
|
||||
<!-- /use -->
|
||||
|
||||
which renders as follows:
|
||||
<!-- #use [[template/plug]] {"name": "Example plug", "repo": "https://google.com"} -->
|
||||
* [[Example plug]] by **** ([repo](https://google.com))
|
||||
<!-- /use -->
|
||||
|
||||
Note that a string is also a valid JSON value:
|
||||
|
||||
* [ ] #test This is a test task
|
||||
|
||||
<!-- #use [[template/tagged-tasks]] "#test" -->
|
||||
* [ ] [[🔌 Directive@1340]] This is a test task #test
|
||||
<!-- /use -->
|
||||
|
||||
which renders as:
|
||||
|
||||
<!-- #use [[template/tagged-tasks]] "#test" -->
|
||||
* [ ] [[🔌 Directive@1492]] This is a test task #test
|
||||
<!-- /use -->
|
||||
|
||||
## Eval
|
||||
$eval
|
||||
The `#eval` directive can be used to evaluate arbitrary JavaScript expressions. It’s also possible to invoke arbitrary plug functions this way.
|
||||
|
||||
A simple example is multiplying numbers:
|
||||
|
||||
<!-- #eval 10 * 10 -->
|
||||
100
|
||||
<!-- /eval -->
|
||||
|
||||
However, you can also invoke arbitrary plug functions, e.g. the `titleUnfurlOptions` function in the `core` plug:
|
||||
|
||||
<!-- #eval core.titleUnfurlOptions() -->
|
||||
|id |name |
|
||||
|------------|-------------|
|
||||
|title-unfurl|Extract title|
|
||||
<!-- /eval -->
|
||||
|
||||
Optionally, you can use a `render` clause to render the result as a template, similar to [[🔌 Directive/Query]]:
|
||||
|
||||
<!-- #eval core.titleUnfurlOptions() render [[template/debug]] -->
|
||||
id: title-unfurl
|
||||
name: Extract title
|
||||
---
|
||||
<!-- /eval -->
|
229
website/🔌 Directive/Query.md
Normal file
229
website/🔌 Directive/Query.md
Normal file
@ -0,0 +1,229 @@
|
||||
## Query
|
||||
The `#query` is the most widely used directive. It can be used to query various data sources and render results in various ways.
|
||||
|
||||
### Syntax
|
||||
|
||||
1. _start with_: `<!-- #query [QUERY GOES HERE] -->`
|
||||
2. _end with_: `<!-- /query -->`
|
||||
3. _write your query_: replace `[QUERY GOES HERE]` with any query you want using the options below.
|
||||
4. _available query options_: Usage of options is similar to SQL except for thespecial `render` option. The `render` option is used to display the data in a format that you created in a separate template.
|
||||
* `where`
|
||||
* `order by`
|
||||
* `limit`
|
||||
* `select`
|
||||
* `render`
|
||||
|
||||
P.S.: If you are a developer or have a technical knowledge to read a code and would like to know more about syntax, please check out
|
||||
[query grammar](https://github.com/silverbulletmd/silverbullet/blob/main/packages/plugs/query/query.grammar).
|
||||
|
||||
#### 2.1. Available query operators:
|
||||
|
||||
- `=` equals
|
||||
- `!=` not equals
|
||||
- `<` less than
|
||||
- `<=` less than or equals
|
||||
- `>` greater than
|
||||
- `>=` greater than or equals
|
||||
- `=~` to match against a regular expression
|
||||
- `!=~` does not match this regular expression
|
||||
- `in` member of a list (e.g. `prop in ["foo", "bar"]`)
|
||||
|
||||
Further, you can combine multiple of these with `and`. Example
|
||||
`prop =~ /something/ and prop != “something”`.
|
||||
|
||||
### 3. How to run a query?
|
||||
After writing the query, there are three options:
|
||||
|
||||
1. Open the **command palette** and run {[Directives: Update]}
|
||||
2. Use shortcut: hit **Alt-q** (Windows, Linux) or **Option-q** (Mac)
|
||||
3. Go to another page and come back to the page where the query is located, it always updates when a page is loaded
|
||||
|
||||
After using one of the options, the “body” of the query is replaced with the new results of the query data will be displayed.
|
||||
|
||||
### 4. Data sources
|
||||
|
||||
Available data sources can be categorized as:
|
||||
|
||||
1. Builtin data sources
|
||||
2. Data that can be inserted by users
|
||||
3. Plug’s data sources
|
||||
|
||||
The best part about data sources: there is auto-completion. 🎉
|
||||
|
||||
Start writing `<!— #query` or simply use `/query` slash command, it will show you all available data sources. 🤯
|
||||
|
||||
#### 4.1. Available data sources
|
||||
|
||||
- `page`: list of all pages
|
||||
- `task`: list of all tasks created with `[ ]`
|
||||
- `full-text`: use it with `where phrase = "SOME_TEXT"`. List of all pages where `SOME_TEXT` is mentioned
|
||||
- `item`: list of ordered and unordered items such as bulleted lists
|
||||
- `tag`: list of all hashtags used in all pages
|
||||
- `link`: list of all pages giving a link to the page where query is written
|
||||
- `data`: You can insert data using the syntax below. You can query the data using the `data` source.
|
||||
|
||||
```data
|
||||
name: John
|
||||
age: 50
|
||||
city: Milan
|
||||
country: Italy
|
||||
---
|
||||
name: Jane
|
||||
age: 53
|
||||
city: Rome
|
||||
country: Italy
|
||||
---
|
||||
name: Francesco
|
||||
age: 28
|
||||
city: Berlin
|
||||
country: Germany
|
||||
```
|
||||
|
||||
Example:
|
||||
<!-- #query data where age > 20 and country = "Italy" -->
|
||||
|name|age|city |country|page |pos|
|
||||
|----|--|-----|-----|------------|-|
|
||||
|John|50|Milan|Italy|🔌 Directive|0|
|
||||
|Jane|53|Rome |Italy|🔌 Directive|1|
|
||||
<!-- /query -->
|
||||
|
||||
#### 4.2 Plugs’ data sources
|
||||
|
||||
Certain plugs can also provide special data sources to query specific data. Some examples are:
|
||||
|
||||
- [[🔌 Github]] provides `gh-pull` to query PRs for selected repo
|
||||
- [[🔌 Mattermost]] provides `mm-saved` to fetch (by default 15) saved posts in
|
||||
Mattermost
|
||||
|
||||
For a complete list of data sources, please check plugs’ own pages.
|
||||
|
||||
### 5. Templates
|
||||
|
||||
Templates are predefined formats to render the body of the query.
|
||||
|
||||
#### 5.1 How to create a template?
|
||||
|
||||
It is pretty easy. You just need to create a new page. However, it is
|
||||
recommended to create your templates using `template/[TEMPLATE_NAME]`
|
||||
convention. For this guide, we will create `template/plug` to display list of Plugs available in Silver Bullet. We will use this template in the Examples section below.
|
||||
|
||||
#### 5.2 What is the syntax?
|
||||
|
||||
We are using Handlebars which is a simple templating language. It is using double curly braces and the name of the parameter to be injected. For our `template/plug`, we are using simple template like below.
|
||||
|
||||
* [[{{name}}]] by **{{author}}** ([repo]({{repo}}))
|
||||
|
||||
Let me break it down for you
|
||||
|
||||
- `*` is creating a bullet point for each item in Silver Bullet
|
||||
- `[[{{name}}]]` is injecting the name of Plug and creating an internal link to
|
||||
the page of the Plug
|
||||
- `**{{author}}**` is injecting the author of the Plug and making it bold
|
||||
- `([repo]({{repo}}))` is injecting the name of the Plug and creating an
|
||||
external link to the GitHub page of the Plug
|
||||
|
||||
For more information on the Handlebars syntax, you can read the
|
||||
[official documentation](https://handlebarsjs.com/).
|
||||
|
||||
#### 5.3 How to use the template?
|
||||
|
||||
You just need to add the `render` keyword followed by the link of the template to the query like below:
|
||||
|
||||
`#query page where type = "plug" render [[template/plug]]`
|
||||
|
||||
You can see the usage of our template in example 6.4 below.
|
||||
|
||||
### 6. Examples
|
||||
|
||||
We will walk you through a set of examples starting from a very basic one
|
||||
through one formatting the data using templates.
|
||||
|
||||
Our goal in this exercise is to (i) get all plug pages (ii) ordered by last
|
||||
modified time and (iii) display in a nice format.
|
||||
|
||||
For the sake of simplicity, we will use the `page` data source and limit the
|
||||
results not to spoil the page.
|
||||
|
||||
#### 6.1 Simple query without any condition
|
||||
|
||||
**Goal:** We would like to get the list of all pages.
|
||||
|
||||
**Result:** Look at the data. This is more than we need. The query even gives us
|
||||
template pages. Let's try to limit it in the next step.
|
||||
|
||||
<!-- #query page limit 10 -->
|
||||
|name |lastModified |perm|tags |type|uri |repo |author |
|
||||
|--|--|--|--|--|--|--|--|
|
||||
|SETTINGS |1665558946821|rw|undefined|undefined|undefined |undefined |undefined |
|
||||
|Silver Bullet |1666964349821|rw|protip|undefined|undefined |undefined |undefined |
|
||||
|Sandbox |1665558946826|rw|undefined|undefined|undefined |undefined |undefined |
|
||||
|🔌 Core |1666963501687|rw|undefined|plug|builtin:core |https://github.com/silverbulletmd/silverbullet |Silver Bullet Authors|
|
||||
|CHANGELOG |1666959942128|rw|undefined|undefined|undefined |undefined |undefined |
|
||||
|💡 Inspiration|1665558946820|rw|undefined|undefined|undefined |undefined |undefined |
|
||||
|🔌 Mount |1665567345520|rw|undefined|plug|github:silverbulletmd/silverbullet-mount/mount.plug.json|https://github.com/silverbulletmd/silverbullet-mount|Zef Hemel |
|
||||
|🤯 Features |1665567345521|rw|undefined|undefined|undefined |undefined |undefined |
|
||||
|🔌 Ghost |1665558946819|rw|undefined|plug|github:silverbulletmd/silverbullet-ghost/ghost.plug.json|https://github.com/silverbulletmd/silverbullet-ghost|Zef Hemel |
|
||||
|PUBLISH |1665558946821|rw|undefined|undefined|undefined |undefined |undefined |
|
||||
<!-- /query -->
|
||||
|
||||
#### 6.2 Simple query with a condition
|
||||
|
||||
**Goal:** We would like to get all plug pages sorted by last modified time.
|
||||
|
||||
**Result:** Okay, this is what we wanted but there is also information such as
|
||||
perm, type and lastModified that we don't need.
|
||||
|
||||
<!-- #query page where type = "plug" order by lastModified desc limit 5 -->
|
||||
|name |lastModified |perm|size |contentType |type|repo |author |uri |
|
||||
|--|--|--|--|--|--|--|--|--|
|
||||
|🔌 Directive |1666965349992|rw|13004|text/markdown|plug|https://github.com/silverbulletmd/silverbullet |Silver Bullet Authors|undefined |
|
||||
|🔌 Core |1666963501687|rw|undefined|undefined |plug|https://github.com/silverbulletmd/silverbullet |Silver Bullet Authors|builtin:core |
|
||||
|🔌 Mattermost|1665567345533|rw|undefined|undefined |plug|https://github.com/silverbulletmd/silverbullet-mattermost|Zef Hemel |github:silverbulletmd/silverbullet-mattermost/mattermost.plug.json|
|
||||
|🔌 Github |1665567345532|rw|undefined|undefined |plug|https://github.com/silverbulletmd/silverbullet-github |Zef Hemel |github:silverbulletmd/silverbullet-github/github.plug.json |
|
||||
|🔌 Backlinks |1665567345530|rw|undefined|undefined |plug|https://github.com/Willyfrog/silverbullet-backlinks |Guillermo Vayá |ghr:Willyfrog/silverbullet-backlinks |
|
||||
<!-- /query -->
|
||||
|
||||
#### 6.3 Query to select only certain fields
|
||||
|
||||
**Goal:** We would like to get all plug pages, selecting only `name`, `author`
|
||||
and `repo` columns and then sort by last modified time.
|
||||
|
||||
**Result:** Okay, this is much better. However, I believe this needs a touch
|
||||
from a visual perspective.
|
||||
|
||||
<!-- #query page select name author repo uri where type = "plug" order by lastModified desc limit 5 -->
|
||||
|name |author |repo |ri |
|
||||
|--|--|--|--|
|
||||
|🔌 Directive |Silver Bullet Authors|https://github.com/silverbulletmd/silverbullet |undefined|
|
||||
|🔌 Core |Silver Bullet Authors|https://github.com/silverbulletmd/silverbullet |undefined|
|
||||
|🔌 Mattermost|Zef Hemel |https://github.com/silverbulletmd/silverbullet-mattermost|undefined|
|
||||
|🔌 Github |Zef Hemel |https://github.com/silverbulletmd/silverbullet-github |undefined|
|
||||
|🔌 Backlinks |Guillermo Vayá |https://github.com/Willyfrog/silverbullet-backlinks |undefined|
|
||||
<!-- /query -->
|
||||
|
||||
#### 6.4 Display the data in a format defined by a template
|
||||
|
||||
**Goal:** We would like to display the data from step 5.3 in a nice format using bullet points with links to Plug pages, with the author name and a link to their GitHub repo.
|
||||
|
||||
**Result:** Here you go. This is the result we would like to achieve 🎉. Did you see how I used `render` and `template/plug` in a query? 🚀
|
||||
|
||||
<!-- #query page select name author repo uri where type = "plug" order by lastModified desc limit 5 render [[template/plug]] -->
|
||||
* [[🔌 Directive]] by **Silver Bullet Authors** ([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
* [[🔌 Core]] by **Silver Bullet Authors** ([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
* [[🔌 Mattermost]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-mattermost))
|
||||
* [[🔌 Github]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-github))
|
||||
* [[🔌 Backlinks]] by **Guillermo Vayá** ([repo](https://github.com/Willyfrog/silverbullet-backlinks))
|
||||
<!-- /query -->
|
||||
|
||||
PS: You don't need to select only certain fields to use templates. Templates are
|
||||
smart enough to get only the information needed to render the data. Therefore,
|
||||
the following queries are the same in terms of end result when using the
|
||||
templates.
|
||||
|
||||
```yaml
|
||||
<!-- #query page select name author repo uri where type = "plug" order by lastModified desc limit 5 render [[template/plug]] -->
|
||||
```
|
||||
|
||||
```yaml
|
||||
<!-- #query page where type = "plug" order by lastModified desc limit 5 render [[template/plug]] -->
|
||||
```
|
@ -28,7 +28,7 @@ restart SB itself.
|
||||
([repo](https://github.com/silverbulletmd/silverbullet-mattermost))
|
||||
- [[🔌 Mount]] by **Zef Hemel**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet-mount))
|
||||
- [[🔌 Query]] by **Silver Bullet Authors**
|
||||
- [[🔌 Directive]] by **Silver Bullet Authors**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
|
||||
<!-- /query -->
|
||||
|
@ -1,270 +0,0 @@
|
||||
```meta
|
||||
type: plug
|
||||
uri: core:query
|
||||
repo: https://github.com/silverbulletmd/silverbullet
|
||||
author: Silver Bullet Authors
|
||||
```
|
||||
|
||||
### 1. What?
|
||||
|
||||
The query plug is a built-in plug implementing the `<!-- #query -->` mechanism.
|
||||
You can use the query plug to automatically receive information from your pages.
|
||||
|
||||
### 2. Syntax
|
||||
|
||||
1. _start with_: `<!-- #query [QUERY GOES HERE] -->`
|
||||
2. _end with_: `<!-- /query -->`
|
||||
3. _write your query_: replace `[QUERY GOES HERE]` with any query you want using
|
||||
the options below
|
||||
4. _available query options_: Usage of options is similar to SQL except for the
|
||||
special `render` option. The `render` option is used to display the data in a
|
||||
format that you created in a separate template.
|
||||
- `where`
|
||||
- `order by`
|
||||
- `limit`
|
||||
- `select`
|
||||
- `render`
|
||||
|
||||
P.S.: If you are a developer or have a technical knowledge to read a code and
|
||||
would like to know more about syntax, please check out
|
||||
[query grammar](https://github.com/silverbulletmd/silverbullet/blob/main/packages/plugs/query/query.grammar).
|
||||
|
||||
#### 2.1. Available query operators:
|
||||
|
||||
- `=` equals
|
||||
- `!=` not equals
|
||||
- `<` less than
|
||||
- `<=` less than or equals
|
||||
- `>` greater than
|
||||
- `>=` greater than or equals
|
||||
- `=~` to match against a regular expression
|
||||
- `!=~` does not match this regular expression
|
||||
- `in` member of a list (e.g. `prop in ["foo", "bar"]`)
|
||||
|
||||
Further, you can combine multiple of these with `and`. Example
|
||||
`prop =~ /something/ and prop != “something”`.
|
||||
|
||||
### 3. How to run a query?
|
||||
|
||||
After writing the query, there are three options:
|
||||
|
||||
- Open the **command palette** and run **Materialized Queries: Update**
|
||||
- Use shortcut: hit **Alt-q** (Windows, Linux) or **Option-q** (Mac)
|
||||
- Go to another page and come back to the page where the query is located
|
||||
|
||||
After using one of the options, the “body” of the query is replaced with the new
|
||||
results of the query data will be displayed.
|
||||
|
||||
### 4. Data sources
|
||||
|
||||
Available data sources can be categorized as:
|
||||
|
||||
1. Builtin data sources
|
||||
2. Data that can be inserted by users
|
||||
3. Plug’s data sources
|
||||
|
||||
The best part about data sources: there is auto-completion. 🎉
|
||||
|
||||
Start writing `<!— #query` or simply use `/query` slash command, it will show
|
||||
you all available data sources. 🤯
|
||||
|
||||
#### 4.1. Available data sources
|
||||
|
||||
- `page`: list of all pages 📄
|
||||
- `task`: list of all tasks created with `[]` syntax ✅
|
||||
- `full-text`: use it with `where phrase = "SOME_TEXT"`. List of all pages where
|
||||
`SOME_TEXT` is mentioned ✍️
|
||||
- `item`: list of ordered and unordered items such as bulleted lists ⏺️
|
||||
- `tags`: list of all hashtags used in all pages ⚡
|
||||
- `link`: list of all pages giving a link to the page where query is written 🔗
|
||||
- `data`: You can insert data using the syntax below 🖥️. You can query the data
|
||||
using `data` option.
|
||||
|
||||
```data
|
||||
name: John
|
||||
age: 50
|
||||
city: Milan
|
||||
country: Italy
|
||||
---
|
||||
name: Jane
|
||||
age: 53
|
||||
city: Rome
|
||||
country: Italy
|
||||
---
|
||||
name: Francesco
|
||||
age: 28
|
||||
city: Berlin
|
||||
country: Germany
|
||||
```
|
||||
|
||||
<!-- #query data where age > 20 and country = "Italy" -->
|
||||
|
||||
| name | age | city | country | page | pos |
|
||||
| ---- | --- | ----- | ------- | ------- | ---- |
|
||||
| John | 50 | Milan | Italy | 🔌 Query | 2696 |
|
||||
| Jane | 53 | Rome | Italy | 🔌 Query | 2742 |
|
||||
|
||||
<!-- /query -->
|
||||
|
||||
#### 4.2 Plugs’ data sources
|
||||
|
||||
Certain plugs can also provide special data sources to query specific data. Some
|
||||
examples are:
|
||||
|
||||
- [[🔌 Github]] provides `gh-pull` to query PRs for selected repo
|
||||
- [[🔌 Mattermost]] provides `mm-saved` to fetch (by default 15) saved posts in
|
||||
Mattermost
|
||||
|
||||
For a complete list of data sources, please check plugs’ own pages.
|
||||
|
||||
### 5. Templates
|
||||
|
||||
Templates are predefined formats to render the body of the query.
|
||||
|
||||
#### 5.1 How to create a template?
|
||||
|
||||
It is pretty easy. You just need to create a new page. However, it is
|
||||
recommended to create your templates using `template/[TEMPLATE_NAME]`
|
||||
convention. For this guide, we will create `template/plug` to display list of
|
||||
Plugs available in Silver Bullet. We will use this template in the Examples
|
||||
section below.
|
||||
|
||||
#### 5.2 What is the syntax?
|
||||
|
||||
We are using Handlebars which is a simple templating language. It is using
|
||||
double curly braces and the name of the parameter to be injected. For our
|
||||
`template/plug`, we are using simple template like below.
|
||||
|
||||
`* [[{{name}}]] by **{{author}}** ([repo]({{repo}}))`
|
||||
|
||||
Let me break it down for you
|
||||
|
||||
- `*` is creating a bullet point for each item in Silver Bullet
|
||||
- `[[{{name}}]]` is injecting the name of Plug and creating an internal link to
|
||||
the page of the Plug
|
||||
- `**{{author}}**` is injecting the author of the Plug and making it bold
|
||||
- `([repo]({{repo}}))` is injecting the name of the Plug and creating an
|
||||
external link to the GitHub page of the Plug
|
||||
|
||||
For more information on the Handlebars syntax, you can read the
|
||||
[official documentation](https://handlebarsjs.com/).
|
||||
|
||||
#### 5.3 How to use the template?
|
||||
|
||||
You just need to add the `render` keyword followed by the link of the template
|
||||
to the query like below:
|
||||
|
||||
`#query page where type = "plug" render [[template/plug]]`
|
||||
|
||||
You can see the usage of our template in example 6.4 below.
|
||||
|
||||
### 6. Examples
|
||||
|
||||
We will walk you through a set of examples starting from a very basic one
|
||||
through one formatting the data using templates.
|
||||
|
||||
Our goal in this exercise is to (i) get all plug pages (ii) ordered by last
|
||||
modified time and (iii) display in a nice format.
|
||||
|
||||
For the sake of simplicity, we will use the `page` data source and limit the
|
||||
results not to spoil the page.
|
||||
|
||||
#### 6.1 Simple query without any condition
|
||||
|
||||
**Goal:** We would like to get the list of all pages.
|
||||
|
||||
**Result:** Look at the data. This is more than we need. The query even gives us
|
||||
template pages. Let's try to limit it in the next step.
|
||||
|
||||
<!-- #query page limit 10 -->
|
||||
|
||||
| name | lastModified | perm | tags | type | uri | repo | author |
|
||||
| ----------------- | ------------- | ---- | --------- | --------- | ------------------------------------ | --------------------------------------------------- | -------------- |
|
||||
| SETTINGS | 1661112513714 | rw | undefined | undefined | undefined | undefined | undefined |
|
||||
| Silver Bullet | 1661112513714 | rw | undefined | undefined | undefined | undefined | undefined |
|
||||
| CHANGELOG | 1661112513714 | rw | undefined | undefined | undefined | undefined | undefined |
|
||||
| Mattermost Plugin | 1661112513714 | rw | undefined | undefined | undefined | undefined | undefined |
|
||||
| PLUGS | 1661112513714 | rw | undefined | undefined | undefined | undefined | undefined |
|
||||
| index | 1661112513714 | rw | undefined | undefined | undefined | undefined | undefined |
|
||||
| template/plug | 1661112513718 | rw | undefined | undefined | undefined | undefined | undefined |
|
||||
| template/tasks | 1661112513718 | rw | #each | undefined | undefined | undefined | undefined |
|
||||
| 💡 Inspiration | 1661112513718 | rw | undefined | undefined | undefined | undefined | undefined |
|
||||
| 🔌 Backlinks | 1661112513718 | rw | undefined | plug | ghr:Willyfrog/silverbullet-backlinks | https://github.com/Willyfrog/silverbullet-backlinks | Guillermo Vayá |
|
||||
|
||||
<!-- /query -->
|
||||
|
||||
#### 6.2 Simple query with a condition
|
||||
|
||||
**Goal:** We would like to get all plug pages sorted by last modified time.
|
||||
|
||||
**Result:** Okay, this is what we wanted but there is also information such as
|
||||
perm, type and lastModified that we don't need.
|
||||
|
||||
<!-- #query page where type = "plug" order by lastModified desc limit 5 -->
|
||||
|
||||
| name | lastModified | perm | type | uri | repo | author |
|
||||
| ----------- | ------------- | ---- | ---- | ---------------------------------------------------------- | ----------------------------------------------------- | --------------------- |
|
||||
| 🔌 Query | 1661114193972 | rw | plug | core:query | https://github.com/silverbulletmd/silverbullet | Silver Bullet Authors |
|
||||
| 🔌 Backlinks | 1661112513718 | rw | plug | ghr:Willyfrog/silverbullet-backlinks | https://github.com/Willyfrog/silverbullet-backlinks | Guillermo Vayá |
|
||||
| 🔌 Core | 1661112513718 | rw | plug | builtin:core | https://github.com/silverbulletmd/silverbullet | Silver Bullet Authors |
|
||||
| 🔌 Ghost | 1661112513718 | rw | plug | github:silverbulletmd/silverbullet-ghost/ghost.plug.json | https://github.com/silverbulletmd/silverbullet-ghost | Zef Hemel |
|
||||
| 🔌 Git | 1661112513718 | rw | plug | github:silverbulletmd/silverbullet-github/github.plug.json | https://github.com/silverbulletmd/silverbullet-github | Zef Hemel |
|
||||
|
||||
<!-- /query -->
|
||||
|
||||
#### 6.3 Query to select only certain fields
|
||||
|
||||
**Goal:** We would like to get all plug pages, selecting only `name`, `author`
|
||||
and `repo` columns and then sort by last modified time.
|
||||
|
||||
**Result:** Okay, this is much better. However, I believe this needs a touch
|
||||
from a visual perspective.
|
||||
|
||||
<!-- #query page select name author repo uri where type = "plug" order by lastModified desc limit 5 -->
|
||||
|
||||
| name | author | repo |
|
||||
| ----------- | --------------------- | ----------------------------------------------------- |
|
||||
| 🔌 Query | Silver Bullet Authors | https://github.com/silverbulletmd/silverbullet |
|
||||
| 🔌 Backlinks | Guillermo Vayá | https://github.com/Willyfrog/silverbullet-backlinks |
|
||||
| 🔌 Core | Silver Bullet Authors | https://github.com/silverbulletmd/silverbullet |
|
||||
| 🔌 Ghost | Zef Hemel | https://github.com/silverbulletmd/silverbullet-ghost |
|
||||
| 🔌 Git | Zef Hemel | https://github.com/silverbulletmd/silverbullet-github |
|
||||
|
||||
<!-- /query -->
|
||||
|
||||
#### 6.4 Display the data in a format defined by a template
|
||||
|
||||
**Goal:** We would like to display the data from step 5.3 in a nice format using
|
||||
bullet points with links to Plug pages, with the author name and a link to their
|
||||
GitHub repo.
|
||||
|
||||
**Result:** Here you go. This is the result we would like to achieve 🎉. Did you
|
||||
see how I used `render` and `template/plug` in a query? 🚀
|
||||
|
||||
<!-- #query page select name author repo uri where type = "plug" order by lastModified desc limit 5 render [[template/plug]] -->
|
||||
|
||||
- [[🔌 Query]] by **Silver Bullet Authors**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
- [[🔌 Backlinks]] by **Guillermo Vayá**
|
||||
([repo](https://github.com/Willyfrog/silverbullet-backlinks))
|
||||
- [[🔌 Core]] by **Silver Bullet Authors**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
- [[🔌 Ghost]] by **Zef Hemel**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet-ghost))
|
||||
- [[🔌 Git]] by **Zef Hemel**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet-github))
|
||||
|
||||
<!-- /query -->
|
||||
|
||||
PS: You don't need to select only certain fields to use templates. Templates are
|
||||
smart enough to get only the information needed to render the data. Therefore,
|
||||
the following queries are the same in terms of end result when using the
|
||||
templates.
|
||||
|
||||
```yaml
|
||||
<!-- #query page select name author repo uri where type = "plug" order by lastModified desc limit 5 render [[template/plug]] -->
|
||||
```
|
||||
|
||||
```yaml
|
||||
<!-- #query page where type = "plug" order by lastModified desc limit 5 render [[template/plug]] -->
|
||||
```
|
@ -1,15 +1,15 @@
|
||||
- **Powerful Markdown editor** at its core (powered by
|
||||
* **Powerful Markdown editor** at its core (powered by
|
||||
[CodeMirror](https://codemirror.net))
|
||||
- **Distraction-free** UI with
|
||||
* **Distraction-free** UI with
|
||||
[What You See is What You Mean](https://en.wikipedia.org/wiki/WYSIWYM)
|
||||
Markdown editing.
|
||||
- **Future proof**: stores all notes in a regular folder with markdown files, no
|
||||
* **Future proof**: stores all notes in a regular folder with markdown files, no
|
||||
proprietary file formats. While SB uses an SQLite database for indexes, this
|
||||
database can be wiped and rebuilt based on your pages at any time. Your
|
||||
Markdown files are the single source of truth.
|
||||
- **Run anywhere**: run it on your local machine, or install it on a server. You
|
||||
* **Run anywhere**: run it on your local machine, or install it on a server. You
|
||||
access it via your web browser (desktop or mobile), or install it as a PWA
|
||||
(giving it its own window frame and dock/launcher/dock icon).
|
||||
- **Keyboard oriented:** you can fully operate SB via the keyboard (on
|
||||
* **Keyboard oriented:** you can fully operate SB via the keyboard (on
|
||||
laptop/desktop machines as well as iPads with a keyboard).
|
||||
- **Extensible** through [[🔌 Plugs]]
|
||||
* **Extensible** through [[🔌 Plugs]]
|
||||
|
Loading…
Reference in New Issue
Block a user