1
0

Refactor of asset bundles

This commit is contained in:
Zef Hemel 2022-10-12 11:47:13 +02:00
parent 9dd94c5397
commit 4c19ab21f2
96 changed files with 6279 additions and 770 deletions

View File

@ -1,8 +1,18 @@
Copyright 2022, Zef Hemel
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -102,6 +102,13 @@ To prepare the initial web and plug build run:
deno task build
```
For convenience, you can install `plugons-bundle` and `silverbullet` into your
`~/.deno/bin` folder:
```shell
deno task install
```
You can then run the server in “watch mode” (automatically restarting when you
change source files) with:

View File

@ -2,12 +2,12 @@
// @deno-types="https://deno.land/x/esbuild@v0.14.54/mod.d.ts"
import * as esbuildWasm from "https://deno.land/x/esbuild@v0.14.54/wasm.js";
import * as esbuildNative from "https://deno.land/x/esbuild@v0.14.54/mod.js";
import { denoPlugin } from "./esbuild_deno_loader/mod.ts";
import { denoPlugin } from "https://deno.land/x/esbuild_deno_loader@0.6.0/mod.ts"; //"./esbuild_deno_loader/mod.ts";
import { copy } from "https://deno.land/std@0.158.0/fs/copy.ts";
import sass from "https://deno.land/x/denosass@1.0.4/mod.ts";
import { bundleFolder } from "./plugos/asset_bundle.ts";
import { patchDenoLibJS } from "./common/hack.ts";
import { bundleFolder } from "./plugos/asset_bundle/builder.ts";
import { patchDenoLibJS } from "./plugos/hack.ts";
import { bundle as plugOsBundle } from "./plugos/bin/plugos-bundle.ts";
import * as flags from "https://deno.land/std@0.158.0/flags/mod.ts";
@ -65,7 +65,6 @@ async function bundle(watch: boolean): Promise<void> {
esbuild.build({
entryPoints: {
client: "web/boot.ts",
worker: "plugos/environments/sandbox_worker.ts",
service_worker: "web/service_worker.ts",
},
outdir: "./dist_bundle/web",

View File

@ -1,3 +1,3 @@
#!/bin/sh
plugos-bundle --dist dist_bundle/_plug $1 --exclude=https://esm.sh/handlebars,https://deno.land/std/encoding/yaml.ts,https://esm.sh/@lezer/lr plugs/*/*.plug.yaml
deno run -A --unstable plugos/bin/plugos-bundle.ts --dist dist_bundle/_plug $1 --exclude=https://esm.sh/handlebars,https://deno.land/std/encoding/yaml.ts,https://esm.sh/@lezer/lr plugs/*/*.plug.yaml

View File

@ -1,10 +1,7 @@
import { Plug } from "../../plugos/plug.ts";
import {
AssetBundle,
assetReadFileSync,
} from "../../plugos/asset_bundle_reader.ts";
import { FileMeta } from "../types.ts";
import { FileData, FileEncoding, SpacePrimitives } from "./space_primitives.ts";
import { AssetBundle } from "../../plugos/asset_bundle/bundle.ts";
export class AssetBundlePlugSpacePrimitives implements SpacePrimitives {
constructor(
@ -15,17 +12,22 @@ export class AssetBundlePlugSpacePrimitives implements SpacePrimitives {
async fetchFileList(): Promise<FileMeta[]> {
const l = await this.wrapped.fetchFileList();
return Object.entries(this.assetBundle).filter(([k]) =>
k.startsWith("_plug/")
).map(([_, v]) => v.meta).concat(l);
return this.assetBundle.listFiles().filter((p) => p.startsWith("_plug/"))
.map((p) => ({
name: p,
contentType: "application/json",
lastModified: 0,
perm: "ro",
size: -1,
} as FileMeta)).concat(l);
}
readFile(
name: string,
encoding: FileEncoding,
): Promise<{ data: FileData; meta: FileMeta }> {
if (this.assetBundle[name]) {
const data = assetReadFileSync(this.assetBundle, name);
if (this.assetBundle.has(name)) {
const data = this.assetBundle.readFileSync(name);
// console.log("Requested encoding", encoding);
return Promise.resolve({
data: encoding === "string" ? new TextDecoder().decode(data) : data,
@ -41,8 +43,8 @@ export class AssetBundlePlugSpacePrimitives implements SpacePrimitives {
}
getFileMeta(name: string): Promise<FileMeta> {
if (this.assetBundle[name]) {
const data = assetReadFileSync(this.assetBundle, name);
if (this.assetBundle.has(name)) {
const data = this.assetBundle.readFileSync(name);
return Promise.resolve({
lastModified: 0,
size: data.byteLength,
@ -63,7 +65,7 @@ export class AssetBundlePlugSpacePrimitives implements SpacePrimitives {
}
deleteFile(name: string): Promise<void> {
if (this.assetBundle[name]) {
if (this.assetBundle.has(name)) {
// Quietly ignore
return Promise.resolve();
}

View File

@ -5,7 +5,10 @@ import { FileMeta } from "../types.ts";
import { FileData, FileEncoding, SpacePrimitives } from "./space_primitives.ts";
import { Plug } from "../../plugos/plug.ts";
import { mime } from "https://deno.land/x/mimetypes@v1.0.0/mod.ts";
import { base64Decode, base64Encode } from "../../plugos/base64.ts";
import {
base64Decode,
base64Encode,
} from "../../plugos/asset_bundle/base64.ts";
function lookupContentType(path: string): string {
return mime.getType(path) || "application/octet-stream";

View File

@ -19,14 +19,14 @@ export class EventedSpacePrimitives implements SpacePrimitives {
plug: Plug<any>,
env: string,
name: string,
args: any[]
args: any[],
): Promise<any> {
return this.wrapped.invokeFunction(plug, env, name, args);
}
readFile(
name: string,
encoding: FileEncoding
encoding: FileEncoding,
): Promise<{ data: FileData; meta: FileMeta }> {
return this.wrapped.readFile(name, encoding);
}
@ -35,13 +35,13 @@ export class EventedSpacePrimitives implements SpacePrimitives {
name: string,
encoding: FileEncoding,
data: FileData,
selfUpdate: boolean
selfUpdate: boolean,
): Promise<FileMeta> {
const newMeta = await this.wrapped.writeFile(
name,
encoding,
data,
selfUpdate
selfUpdate,
);
// This can happen async
if (name.endsWith(".md")) {

View File

@ -8,14 +8,14 @@ export interface SpacePrimitives {
fetchFileList(): Promise<FileMeta[]>;
readFile(
name: string,
encoding: FileEncoding
encoding: FileEncoding,
): Promise<{ data: FileData; meta: FileMeta }>;
getFileMeta(name: string): Promise<FileMeta>;
writeFile(
name: string,
encoding: FileEncoding,
data: FileData,
selfUpdate?: boolean
selfUpdate?: boolean,
): Promise<FileMeta>;
deleteFile(name: string): Promise<void>;
@ -25,6 +25,6 @@ export interface SpacePrimitives {
plug: Plug<any>,
env: string,
name: string,
args: any[]
args: any[],
): Promise<any>;
}

View File

@ -34,7 +34,7 @@ export function removeParentPointers(tree: ParseTree) {
export function findParentMatching(
tree: ParseTree,
matchFn: (tree: ParseTree) => boolean
matchFn: (tree: ParseTree) => boolean,
): ParseTree | null {
let node = tree.parent;
while (node) {
@ -48,14 +48,14 @@ export function findParentMatching(
export function collectNodesOfType(
tree: ParseTree,
nodeType: string
nodeType: string,
): ParseTree[] {
return collectNodesMatching(tree, (n) => n.type === nodeType);
}
export function collectNodesMatching(
tree: ParseTree,
matchFn: (tree: ParseTree) => boolean
matchFn: (tree: ParseTree) => boolean,
): ParseTree[] {
if (matchFn(tree)) {
return [tree];
@ -72,7 +72,7 @@ export function collectNodesMatching(
// return value: returning undefined = not matched, continue, null = delete, new node = replace
export function replaceNodesMatching(
tree: ParseTree,
substituteFn: (tree: ParseTree) => ParseTree | null | undefined
substituteFn: (tree: ParseTree) => ParseTree | null | undefined,
) {
if (tree.children) {
let children = tree.children.slice();
@ -95,14 +95,14 @@ export function replaceNodesMatching(
export function findNodeMatching(
tree: ParseTree,
matchFn: (tree: ParseTree) => boolean
matchFn: (tree: ParseTree) => boolean,
): ParseTree | null {
return collectNodesMatching(tree, matchFn)[0];
}
export function findNodeOfType(
tree: ParseTree,
nodeType: string
nodeType: string,
): ParseTree | null {
return collectNodesMatching(tree, (n) => n.type === nodeType)[0];
}
@ -110,7 +110,7 @@ export function findNodeOfType(
export function traverseTree(
tree: ParseTree,
// Return value = should stop traversal?
matchFn: (tree: ParseTree) => boolean
matchFn: (tree: ParseTree) => boolean,
): void {
// Do a collect, but ignore the result
collectNodesMatching(tree, matchFn);

View File

@ -1,28 +1,35 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"],
"jsx": "react-jsx",
"jsxImportSource": "https://esm.sh/preact@10.11.1"
"compilerOptions": {
"lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"],
"jsx": "react-jsx",
"jsxImportSource": "https://esm.sh/preact@10.11.1"
},
"importMap": "import_map.json",
"lint": {
"files": {
"exclude": [
"dist",
"dist_bundle"
]
},
"importMap": "import_map.json",
"lint": {
"files": {
"exclude": [
"dist", "dist_bundle"
]
},
"rules": {
"exclude": ["no-explicit-any"]
}
},
"tasks": {
"test": "deno test -A --unstable",
"build": "deno install -f -A --unstable plugos/bin/plugos-bundle.ts && ./build_plugs.sh && deno run -A --unstable --check build.ts && deno install -f -n silverbullet -A --unstable server/server.ts",
"watch-web": "deno run -A --unstable --check build.ts --watch",
"watch-server": "deno run -A --unstable --check --watch server/server.ts",
// The only reason to run a shell script is that deno task doesn't support globs yet (e.g. *.plug.yaml)
"watch-plugs": "./build_plugs.sh --watch",
"bundle": "deno bundle --importmap import_map.json server/server.ts dist/silverbullet.js"
"rules": {
"exclude": ["no-explicit-any"]
}
},
"fmt": {
"files": {
"exclude": ["website", "dist", "dist_bundle", "pages"]
}
},
"tasks": {
"install": "deno install -f -A --unstable plugos/bin/plugos-bundle.ts && deno install -f -n silverbullet -A --unstable server/server.ts",
"test": "deno test -A --unstable",
"build": "./build_plugs.sh && deno run -A --unstable --check build.ts",
"watch-web": "deno run -A --unstable --check build.ts --watch",
"watch-server": "deno run -A --unstable --check --watch server/server.ts",
// The only reason to run a shell script is that deno task doesn't support globs yet (e.g. *.plug.yaml)
"watch-plugs": "./build_plugs.sh --watch",
"bundle": "deno bundle --importmap import_map.json server/server.ts dist/silverbullet.js",
"generate": "deno run -A plugos/gen.ts"
}
}

View File

@ -1,6 +0,0 @@
{
"hello": "world",
"__proto__": {
"sky": "universe"
}
}

View File

@ -1 +0,0 @@
export * from "esbuild_deno_loader/testdata/mod.ts";

View File

@ -1,5 +0,0 @@
{
"imports": {
"mod": "./mod.ts"
}
}

View File

@ -1,2 +0,0 @@
const bool = "asd";
export { bool };

View File

@ -1,11 +0,0 @@
function createElement(fn) {
return fn();
}
const React = { createElement };
function Asd() {
return "foo";
}
export default <Asd />;

View File

@ -1,2 +0,0 @@
const bool = "asd";
export { bool };

View File

@ -1,4 +0,0 @@
let bool: string;
bool = "asd";
bool = "asd2";
export { bool };

View File

@ -1,4 +0,0 @@
let bool: string;
bool = "asd";
bool = "asd2";
export { bool };

View File

@ -1,11 +0,0 @@
function createElement(fn: () => string) {
return fn();
}
const React = { createElement };
function Asd() {
return "foo";
}
export default <Asd />;

View File

@ -1,51 +0,0 @@
import { mime } from "../server/deps.ts";
import { AssetBundle } from "./asset_bundle_reader.ts";
import { base64Encode } from "./base64.ts";
import { globToRegExp, path, walk } from "./deps.ts";
export async function bundleAssets(
rootPath: string,
patterns: string[],
): Promise<AssetBundle> {
const bundle: AssetBundle = {};
for await (
const file of walk(rootPath, {
match: patterns.map((pat) => globToRegExp(pat)),
})
) {
await loadIntoBundle(file.path, "", bundle);
}
return bundle;
}
export async function bundleFolder(rootPath: string, bundlePath: string) {
const bundle: AssetBundle = {};
await Deno.mkdir(path.dirname(bundlePath), { recursive: true });
for await (
const { path: filePath } of walk(rootPath, { includeDirs: false })
) {
console.log("Bundling", filePath);
await loadIntoBundle(filePath, `${rootPath}/`, bundle);
}
await Deno.writeTextFile(bundlePath, JSON.stringify(bundle, null, 2));
}
async function loadIntoBundle(
filePath: string,
rootPath: string,
bundle: AssetBundle,
) {
const b64content = base64Encode(await Deno.readFile(filePath));
const s = await Deno.stat(filePath);
const cleanPath = filePath.substring(rootPath.length);
bundle[cleanPath] = {
meta: {
name: cleanPath,
contentType: mime.getType(cleanPath) || "application/octet-stream",
size: s.size,
lastModified: s.mtime!.getTime(),
perm: "ro",
},
data: b64content,
};
}

View File

@ -1,4 +1,4 @@
import { assertEquals } from "../test_deps.ts";
import { assertEquals } from "../../test_deps.ts";
import { base64Decode } from "./base64.ts";
import { base64Encode } from "./base64.ts";

View File

@ -0,0 +1,34 @@
import { globToRegExp, path, walk } from "../deps.ts";
import { AssetBundle } from "./bundle.ts";
export async function bundleAssets(
rootPath: string,
patterns: string[],
): Promise<AssetBundle> {
const bundle = new AssetBundle();
for await (
const file of walk(rootPath, {
match: patterns.map((pat) => globToRegExp(pat)),
})
) {
const cleanPath = file.path.substring("".length);
await bundle.writeFileSync(cleanPath, await Deno.readFile(file.path));
}
return bundle;
}
export async function bundleFolder(rootPath: string, bundlePath: string) {
const bundle = new AssetBundle();
await Deno.mkdir(path.dirname(bundlePath), { recursive: true });
for await (
const { path: filePath } of walk(rootPath, { includeDirs: false })
) {
console.log("Bundling", filePath);
const cleanPath = filePath.substring(`${rootPath}/`.length);
await bundle.writeFileSync(cleanPath, await Deno.readFile(filePath));
}
await Deno.writeTextFile(
bundlePath,
JSON.stringify(bundle.toJSON(), null, 2),
);
}

View File

@ -0,0 +1,16 @@
import { AssetBundle } from "./bundle.ts";
import { assertEquals } from "../../test_deps.ts";
Deno.test("Asset bundle", () => {
const assetBundle = new AssetBundle();
assetBundle.writeTextFileSync("test.txt", "Sup yo");
assertEquals("text/plain", assetBundle.getMimeType("test.txt"));
assertEquals("Sup yo", assetBundle.readTextFileSync("test.txt"));
const buf = new Uint8Array(3);
buf[0] = 1;
buf[1] = 2;
buf[2] = 3;
assetBundle.writeFileSync("test.bin", buf);
assertEquals("application/octet-stream", assetBundle.getMimeType("test.bin"));
assertEquals(buf, assetBundle.readFileSync("test.bin"));
});

View File

@ -0,0 +1,69 @@
import { base64Decode, base64Encode } from "./base64.ts";
import { mime } from "../deps.ts";
export type AssetJson = Record<string, string>;
export class AssetBundle {
readonly bundle: AssetJson;
constructor(bundle: AssetJson = {}) {
this.bundle = bundle;
}
has(path: string): boolean {
return path in this.bundle;
}
listFiles(): string[] {
return Object.keys(this.bundle);
}
readFileSync(
path: string,
): Uint8Array {
const content = this.bundle[path];
if (!content) {
throw new Error(`No such file ${path}`);
}
const data = content.split(",", 2)[1];
return base64Decode(data);
}
readFileAsDataUrl(path: string): string {
const content = this.bundle[path];
if (!content) {
throw new Error(`No such file ${path}`);
}
return content;
}
readTextFileSync(
path: string,
): string {
return new TextDecoder().decode(this.readFileSync(path));
}
getMimeType(
path: string,
): string {
const content = this.bundle[path];
if (!content) {
throw new Error(`No such file ${path}`);
}
return content.split(";")[0].split(":")[1];
}
writeFileSync(path: string, data: Uint8Array) {
const encoded = base64Encode(data);
const mimeType = mime.getType(path);
this.bundle[path] = `data:${mimeType};base64,${encoded}`;
}
writeTextFileSync(path: string, s: string) {
this.writeFileSync(path, new TextEncoder().encode(s));
}
toJSON(): AssetJson {
return this.bundle;
}
}

View File

@ -1,40 +0,0 @@
import { base64Decode } from "./base64.ts";
export type FileMeta = {
name: string;
lastModified: number;
contentType: string;
size: number;
perm: "ro" | "rw";
};
export type AssetBundle = Record<string, { meta: FileMeta; data: string }>;
export function assetReadFileSync(
bundle: AssetBundle,
path: string,
): ArrayBuffer {
const content = bundle[path];
if (!content) {
throw new Error(`No such file ${path}`);
}
return base64Decode(content.data);
}
export function assetStatSync(
bundle: AssetBundle,
path: string,
): FileMeta {
const content = bundle[path];
if (!content) {
throw new Error(`No such file ${path}`);
}
return content.meta;
}
export function assetReadTextFileSync(
bundle: AssetBundle,
path: string,
): string {
return new TextDecoder().decode(assetReadFileSync(bundle, path));
}

View File

@ -11,7 +11,7 @@ import {
import { path } from "../../server/deps.ts";
import * as flags from "https://deno.land/std@0.158.0/flags/mod.ts";
import { bundleAssets } from "../../plugos/asset_bundle.ts";
import { bundleAssets } from "../asset_bundle/builder.ts";
export async function bundle(
manifestPath: string,
@ -41,7 +41,7 @@ export async function bundle(
rootPath,
manifest.assets as string[] || [],
);
manifest.assets = assetsBundle;
manifest.assets = assetsBundle.toJSON();
// Functions

View File

@ -6,9 +6,9 @@ export const esbuild: typeof esbuildWasm = Deno.run === undefined
? esbuildWasm
: esbuildNative;
import { path } from "../server/deps.ts";
import { denoPlugin } from "../esbuild_deno_loader/mod.ts";
import { patchDenoLibJS } from "../common/hack.ts";
import { path } from "./deps.ts";
import { denoPlugin } from "./forked/esbuild_deno_loader/mod.ts";
import { patchDenoLibJS } from "./hack.ts";
export type CompileOptions = {
debug?: boolean;
@ -54,20 +54,11 @@ export async function compile(
treeShaking: true,
plugins: [
denoPlugin({
// TODO do this differently
importMapURL: options.importMap ||
new URL("./../import_map.json", import.meta.url),
}),
],
loader: {
".css": "text",
".md": "text",
".txt": "text",
".html": "text",
".hbs": "text",
".png": "dataurl",
".gif": "dataurl",
".jpg": "dataurl",
},
absWorkingDir: path.resolve(path.dirname(inFile)),
});

View File

@ -1,3 +1,4 @@
export { globToRegExp } from "https://deno.land/std@0.158.0/path/glob.ts";
export { walk } from "https://deno.land/std@0.159.0/fs/mod.ts";
export * as path from "https://deno.land/std@0.158.0/path/mod.ts";
export { mime } from "https://deno.land/x/mimetypes@v1.0.0/mod.ts";

View File

@ -6,7 +6,7 @@ export class ConsoleLogger {
constructor(
callback: (level: LogLevel, entry: string) => void,
print: boolean = true
print: boolean = true,
) {
this.print = print;
this.callback = callback;

View File

@ -3,7 +3,7 @@ import { safeRun } from "../util.ts";
import { Sandbox } from "../sandbox.ts";
import { WorkerLike } from "./worker.ts";
import { Plug } from "../plug.ts";
import { AssetBundle, assetReadTextFileSync } from "../asset_bundle_reader.ts";
import { AssetBundle } from "../asset_bundle/bundle.ts";
class DenoWorkerWrapper implements WorkerLike {
private worker: Worker;
@ -13,7 +13,7 @@ class DenoWorkerWrapper implements WorkerLike {
constructor(worker: Worker) {
this.worker = worker;
this.worker.addEventListener("message", (evt: any) => {
let data = evt.data;
const data = evt.data;
if (!data) return;
safeRun(async () => {
await this.onMessage!(data);
@ -30,23 +30,23 @@ class DenoWorkerWrapper implements WorkerLike {
}
}
export function sandboxFactory(
assetBundle: AssetBundle,
): (plug: Plug<any>) => Sandbox {
return (plug: Plug<any>) => {
const workerHref = URL.createObjectURL(
new Blob([
assetReadTextFileSync(assetBundle, "web/worker.js"),
], {
type: "application/javascript",
}),
);
let worker = new Worker(
workerHref,
{
type: "module",
},
);
return new Sandbox(plug, new DenoWorkerWrapper(worker));
};
import workerBundleJson from "./worker_bundle.json" assert { type: "json" };
const workerBundle = new AssetBundle(workerBundleJson);
export function createSandbox(plug: Plug<any>) {
const workerHref = URL.createObjectURL(
new Blob([
workerBundle.readFileSync("worker.js"),
], {
type: "application/javascript",
}),
);
const worker = new Worker(
workerHref,
{
type: "module",
},
);
return new Sandbox(plug, new DenoWorkerWrapper(worker));
}

View File

@ -0,0 +1,3 @@
{
"worker.js": "data:application/javascript;base64,KCgpID0+IHsgdmFyIG1vZD0oKCk9PntmdW5jdGlvbiBjKHIpe3IoKS5jYXRjaChlPT57Y29uc29sZS5lcnJvcigiQ2F1Z2h0IGVycm9yIixlLm1lc3NhZ2UpfSl9dmFyIGE9Y2xhc3N7Y29uc3RydWN0b3IoZSxuPSEwKXt0aGlzLnByaW50PW4sdGhpcy5jYWxsYmFjaz1lfWxvZyguLi5lKXt0aGlzLnB1c2goImxvZyIsZSl9d2FybiguLi5lKXt0aGlzLnB1c2goIndhcm4iLGUpfWVycm9yKC4uLmUpe3RoaXMucHVzaCgiZXJyb3IiLGUpfWluZm8oLi4uZSl7dGhpcy5wdXNoKCJpbmZvIixlKX1wdXNoKGUsbil7dGhpcy5jYWxsYmFjayhlLHRoaXMubG9nTWVzc2FnZShuKSksdGhpcy5wcmludCYmY29uc29sZVtlXSguLi5uKX1sb2dNZXNzYWdlKGUpe2xldCBuPVtdO2ZvcihsZXQgdCBvZiBlKXN3aXRjaCh0eXBlb2YgdCl7Y2FzZSJzdHJpbmciOmNhc2UibnVtYmVyIjpuLnB1c2goIiIrdCk7YnJlYWs7Y2FzZSJ1bmRlZmluZWQiOm4ucHVzaCgidW5kZWZpbmVkIik7YnJlYWs7ZGVmYXVsdDp0cnl7bGV0IG89SlNPTi5zdHJpbmdpZnkodCxudWxsLDIpO28ubGVuZ3RoPjUwMCYmKG89by5zdWJzdHJpbmcoMCw1MDApKyIuLi4iKSxuLnB1c2gobyl9Y2F0Y2h7bi5wdXNoKCJbY2lyY3VsYXIgb2JqZWN0XSIpfX1yZXR1cm4gbi5qb2luKCIgIil9fTt0eXBlb2YgRGVubz4idSImJihzZWxmLkRlbm89e2FyZ3M6W10sYnVpbGQ6e2FyY2g6Ing4Nl82NCJ9LGVudjp7Z2V0KCl7fX19KTt2YXIgZD1uZXcgTWFwLGk9bmV3IE1hcDtmdW5jdGlvbiBzKHIpe3R5cGVvZiB3aW5kb3c8InUiJiZ3aW5kb3cucGFyZW50IT09d2luZG93P3dpbmRvdy5wYXJlbnQucG9zdE1lc3NhZ2UociwiKiIpOnNlbGYucG9zdE1lc3NhZ2Uocil9dmFyIGw9MDtzZWxmLnN5c2NhbGw9YXN5bmMociwuLi5lKT0+YXdhaXQgbmV3IFByb21pc2UoKG4sdCk9PntsKyssaS5zZXQobCx7cmVzb2x2ZTpuLHJlamVjdDp0fSkscyh7dHlwZToic3lzY2FsbCIsaWQ6bCxuYW1lOnIsYXJnczplfSl9KTt2YXIgdT1uZXcgTWFwO3NlbGYucmVxdWlyZT1yPT57bGV0IGU9dS5nZXQocik7aWYoIWUpdGhyb3cgbmV3IEVycm9yKGBEeW5hbWljYWxseSBpbXBvcnRpbmcgbm9uLXByZWxvYWRlZCBsaWJyYXJ5ICR7cn1gKTtyZXR1cm4gZX07c2VsZi5jb25zb2xlPW5ldyBhKChyLGUpPT57cyh7dHlwZToibG9nIixsZXZlbDpyLG1lc3NhZ2U6ZX0pfSwhMSk7ZnVuY3Rpb24gZyhyKXtyZXR1cm5gcmV0dXJuICgke3J9KVsiZGVmYXVsdCJdYH1zZWxmLmFkZEV2ZW50TGlzdGVuZXIoIm1lc3NhZ2UiLHI9PntjKGFzeW5jKCk9PntsZXQgZT1yLmRhdGE7c3dpdGNoKGUudHlwZSl7Y2FzZSJsb2FkIjp7bGV0IG49bmV3IEZ1bmN0aW9uKGcoZS5jb2RlKSk7ZC5zZXQoZS5uYW1lLG4oKSkscyh7dHlwZToiaW5pdGVkIixuYW1lOmUubmFtZX0pfWJyZWFrO2Nhc2UibG9hZC1kZXBlbmRlbmN5Ijp7bGV0IHQ9bmV3IEZ1bmN0aW9uKGByZXR1cm4gJHtlLmNvZGV9YCkoKTt1LnNldChlLm5hbWUsdCkscyh7dHlwZToiZGVwZW5kZW5jeS1pbml0ZWQiLG5hbWU6ZS5uYW1lfSl9YnJlYWs7Y2FzZSJpbnZva2UiOntsZXQgbj1kLmdldChlLm5hbWUpO2lmKCFuKXRocm93IG5ldyBFcnJvcihgRnVuY3Rpb24gbm90IGxvYWRlZDogJHtlLm5hbWV9YCk7dHJ5e2xldCB0PWF3YWl0IFByb21pc2UucmVzb2x2ZShuKC4uLmUuYXJnc3x8W10pKTtzKHt0eXBlOiJyZXN1bHQiLGlkOmUuaWQscmVzdWx0OnR9KX1jYXRjaCh0KXtzKHt0eXBlOiJyZXN1bHQiLGlkOmUuaWQsZXJyb3I6dC5tZXNzYWdlLHN0YWNrOnQuc3RhY2t9KX19YnJlYWs7Y2FzZSJzeXNjYWxsLXJlc3BvbnNlIjp7bGV0IG49ZS5pZCx0PWkuZ2V0KG4pO2lmKCF0KXRocm93IGNvbnNvbGUubG9nKCJDdXJyZW50IG91dHN0YW5kaW5nIHJlcXVlc3RzIixpLCJsb29raW5nIHVwIixuKSxFcnJvcigiSW52YWxpZCByZXF1ZXN0IGlkIik7aS5kZWxldGUobiksZS5lcnJvcj90LnJlamVjdChuZXcgRXJyb3IoZS5lcnJvcikpOnQucmVzb2x2ZShlLnJlc3VsdCl9YnJlYWt9fSl9KTt9KSgpOwogcmV0dXJuIG1vZDt9KSgp"
}

View File

@ -9,7 +9,7 @@ export interface LoadOptions {
export async function load(
infoCache: Map<string, deno.ModuleEntry>,
url: URL,
options: LoadOptions
options: LoadOptions,
): Promise<esbuild.OnLoadResult | null> {
switch (url.protocol) {
case "http:":
@ -28,7 +28,7 @@ export async function load(
async function loadFromCLI(
infoCache: Map<string, deno.ModuleEntry>,
specifier: URL,
options: LoadOptions
options: LoadOptions,
): Promise<esbuild.OnLoadResult> {
const specifierRaw = specifier.href;
if (!infoCache.has(specifierRaw)) {

View File

@ -22,7 +22,7 @@ export function mediaTypeToLoader(mediaType: MediaType): esbuild.Loader {
export function transformRawIntoContent(
raw: Uint8Array,
mediaType: MediaType
mediaType: MediaType,
): string | Uint8Array {
switch (mediaType) {
case "Json":

18
plugos/gen.ts Normal file
View File

@ -0,0 +1,18 @@
import { AssetBundle } from "./asset_bundle/bundle.ts";
import { compile } from "./compile.ts";
const bundlePath =
new URL("./environments/worker_bundle.json", import.meta.url).pathname;
const workerPath =
new URL("./environments/sandbox_worker.ts", import.meta.url).pathname;
const workerCode = await compile(workerPath);
const assetBundle = new AssetBundle();
assetBundle.writeTextFileSync("worker.js", workerCode);
Deno.writeTextFile(
bundlePath,
JSON.stringify(assetBundle.toJSON(), null, 2),
);
console.log(`Wrote updated bundle to ${bundlePath}`);
Deno.exit(0);

View File

@ -1,4 +1,4 @@
import { sandboxFactory } from "../environments/deno_sandbox.ts";
import { createSandbox } from "../environments/deno_sandbox.ts";
import { Manifest } from "../types.ts";
import { EndpointHook, EndpointHookT } from "./endpoint.ts";
import { System } from "../system.ts";
@ -6,13 +6,9 @@ import { System } from "../system.ts";
import { Application } from "../../server/deps.ts";
import { assertEquals } from "../../test_deps.ts";
import assetBundle from "../../dist/asset_bundle.json" assert { type: "json" };
import { AssetBundle } from "../asset_bundle_reader.ts";
Deno.test("Run a plugos endpoint server", async () => {
const createSandbox = sandboxFactory(assetBundle as AssetBundle);
let system = new System<EndpointHookT>("server");
let plug = await system.load(
const system = new System<EndpointHookT>("server");
await system.load(
{
name: "test",
functions: {

View File

@ -14,7 +14,7 @@ export class Plug<HookT> {
constructor(
system: System<HookT>,
name: string,
sandboxFactory: (plug: Plug<HookT>) => Sandbox
sandboxFactory: (plug: Plug<HookT>) => Sandbox,
) {
this.system = system;
this.name = name;
@ -55,7 +55,7 @@ export class Plug<HookT> {
}
if (!this.canInvoke(name)) {
throw new Error(
`Function ${name} is not available in ${this.runtimeEnv}`
`Function ${name} is not available in ${this.runtimeEnv}`,
);
}
await this.sandbox.load(name, funDef.code!);

View File

@ -1,4 +1,4 @@
import { sandboxFactory } from "./environments/deno_sandbox.ts";
import { createSandbox } from "./environments/deno_sandbox.ts";
import { System } from "./system.ts";
import {
@ -6,10 +6,7 @@ import {
assertEquals,
} from "https://deno.land/std@0.158.0/testing/asserts.ts";
import assetBundle from "../dist/asset_bundle.json" assert { type: "json" };
Deno.test("Run a deno sandbox", async () => {
const createSandbox = sandboxFactory(assetBundle as AssetBundle);
const system = new System("server");
system.registerSyscalls([], {
addNumbers: (_ctx, a, b) => {
@ -125,12 +122,10 @@ Deno.test("Run a deno sandbox", async () => {
import { bundle as plugOsBundle } from "./bin/plugos-bundle.ts";
import { esbuild } from "./compile.ts";
import { AssetBundle } from "./asset_bundle_reader.ts";
import { AssetBundle } from "./asset_bundle/bundle.ts";
const __dirname = new URL(".", import.meta.url).pathname;
Deno.test("Preload dependencies", async () => {
const createSandbox = sandboxFactory(assetBundle as AssetBundle);
const globalModules = await plugOsBundle(
`${__dirname}../plugs/global.plug.yaml`,
);

View File

@ -1,14 +1,14 @@
import { SysCallMapping, System } from "../system.ts";
import type { AssetBundle, FileMeta } from "../asset_bundle_reader.ts";
import { AssetBundle } from "../asset_bundle/bundle.ts";
export default function assetSyscalls(system: System<any>): SysCallMapping {
return {
"asset.readAsset": (
ctx,
name: string,
): { data: string; meta: FileMeta } => {
): string => {
return (system.loadedPlugs.get(ctx.plug.name)!.manifest!
.assets as AssetBundle)[name];
.assets as AssetBundle).readFileAsDataUrl(name);
},
};
}

View File

@ -1,7 +1,7 @@
import type { SysCallMapping } from "../system.ts";
import { mime, path } from "../../server/deps.ts";
import { base64Decode, base64Encode } from "../../plugos/base64.ts";
import type { FileMeta } from "../asset_bundle_reader.ts";
import { mime, path } from "../deps.ts";
import { base64Decode, base64Encode } from "../asset_bundle/base64.ts";
import { FileMeta } from "../../common/types.ts";
export default function fileSystemSyscalls(root = "/"): SysCallMapping {
function resolvedPath(p: string): string {

View File

@ -1,14 +1,10 @@
import { assertEquals } from "../../test_deps.ts";
import { SQLite } from "../../server/deps.ts";
import { sandboxFactory } from "../environments/deno_sandbox.ts";
import { createSandbox } from "../environments/deno_sandbox.ts";
import { System } from "../system.ts";
import { ensureTable, storeSyscalls } from "./store.deno.ts";
import assetBundle from "../../dist/asset_bundle.json" assert { type: "json" };
import { AssetBundle } from "../asset_bundle_reader.ts";
Deno.test("Test store", async () => {
const createSandbox = sandboxFactory(assetBundle as AssetBundle);
const db = new SQLite(":memory:");
await ensureTable(db, "test_table");
const system = new System("server");

View File

@ -1,10 +1,10 @@
import { AssetBundle } from "../plugos/asset_bundle_reader.ts";
import { System } from "./system.ts";
import { AssetJson } from "./asset_bundle/bundle.ts";
export interface Manifest<HookT> {
name: string;
requiredPermissions?: string[];
assets?: string[] | AssetBundle;
assets?: string[] | AssetJson;
dependencies?: {
[key: string]: string;
};

View File

@ -2,10 +2,10 @@ export function niceDate(d: Date): string {
function pad(n: number) {
let s = String(n);
if (s.length === 1) {
s = '0' + s;
s = "0" + s;
}
return s;
}
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate());
}

View File

@ -10,7 +10,7 @@ import { getServerLogs } from "../../syscall/silverbullet-syscall/sandbox.ts";
export async function parsePageCommand() {
console.log(
"AST",
JSON.stringify(await parseMarkdown(await getText()), null, 2)
JSON.stringify(await parseMarkdown(await getText()), null, 2),
);
}
@ -53,15 +53,19 @@ export async function showLogsCommand() {
</style>
<div id="client-log-header">Client logs (max 100)</div>
<div id="client-log">
<pre>${clientLogs
<pre>${
clientLogs
.map((le) => `[${le.level}] ${le.message}`)
.join("\n")}</pre>
.join("\n")
}</pre>
</div>
<div id="server-log-header">Server logs (max 100)</div>
<div id="server-log">
<pre>${serverLogs
<pre>${
serverLogs
.map((le) => `[${le.level}] ${le.message}`)
.join("\n")}</pre>
.join("\n")
}</pre>
</div>`,
`
var clientDiv = document.getElementById("client-log");
@ -74,7 +78,7 @@ export async function showLogsCommand() {
window.reloadInterval = setInterval(() => {
sendEvent("log:reload");
}, 1000);
`
`,
);
}

View File

@ -28,7 +28,7 @@ export async function unfurlCommand() {
let selectedUnfurl: any = await filterBox(
"Unfurl",
options,
"Select the unfurl strategy of your choice"
"Select the unfurl strategy of your choice",
);
if (!selectedUnfurl) {
return;
@ -38,7 +38,7 @@ export async function unfurlCommand() {
"server",
"unfurlExec",
selectedUnfurl.id,
url
url,
);
await replaceRange(nakedUrlNode?.from!, nakedUrlNode?.to!, replacement);
} catch (e: any) {

View File

@ -75,7 +75,7 @@ export async function pageQueryProvider({
}: QueryProviderEvent): Promise<any[]> {
let allPages = await listPages();
let allPageMap: Map<string, any> = new Map(
allPages.map((pm) => [pm.name, pm])
allPages.map((pm) => [pm.name, pm]),
);
for (let { page, value } of await queryPrefix("meta:")) {
let p = allPageMap.get(page);

View File

@ -35,7 +35,7 @@ export async function updatePlugs() {
plugList = plugListRead.filter((plug) => typeof plug === "string");
if (plugList.length !== plugListRead.length) {
throw new Error(
`Some of the plugs were not in a yaml list format, they were ignored`
`Some of the plugs were not in a yaml list format, they were ignored`,
);
}
} catch (e: any) {
@ -56,7 +56,7 @@ export async function updatePlugs() {
await writeAttachment(
`_plug/${manifest.name}.plug.json`,
"string",
JSON.stringify(manifest)
JSON.stringify(manifest),
);
}
@ -64,7 +64,7 @@ export async function updatePlugs() {
for (let existingPlug of await listPlugs()) {
let plugName = existingPlug.substring(
"_plug/".length,
existingPlug.length - ".plug.json".length
existingPlug.length - ".plug.json".length,
);
console.log("Considering", plugName);
if (!allPlugNames.includes(plugName)) {
@ -94,27 +94,28 @@ export async function getPlugGithub(identifier: string): Promise<Manifest> {
branch = "main"; // or "master"?
}
return getPlugHTTPS(
`//raw.githubusercontent.com/${owner}/${repoClean}/${branch}/${path}`
`//raw.githubusercontent.com/${owner}/${repoClean}/${branch}/${path}`,
);
}
export async function getPlugGithubRelease(
identifier: string
identifier: string,
): Promise<Manifest> {
let [owner, repo, version] = identifier.split("/");
if (!version || version === "latest") {
console.log("fetching the latest version");
const req = await fetch(
`https://api.github.com/repos/${owner}/${repo}/releases/latest`
`https://api.github.com/repos/${owner}/${repo}/releases/latest`,
);
if (req.status !== 200) {
throw new Error(
`Could not fetch latest relase manifest from ${identifier}}`
`Could not fetch latest relase manifest from ${identifier}}`,
);
}
const result = await req.json();
version = result.name;
}
const finalUrl = `//github.com/${owner}/${repo}/releases/download/${version}/${repo}.plug.json`;
const finalUrl =
`//github.com/${owner}/${repo}/releases/download/${version}/${repo}.plug.json`;
return getPlugHTTPS(finalUrl);
}

View File

@ -33,7 +33,7 @@ export async function queryProvider({
let results = await fullTextSearch(phraseFilter.value, 100);
let allPageMap: Map<string, any> = new Map(
results.map((r: any) => [r.name, r])
results.map((r: any) => [r.name, r]),
);
for (let { page, value } of await queryPrefix("meta:")) {
let p = allPageMap.get(page);
@ -59,13 +59,15 @@ export async function searchCommand() {
}
export async function readPageSearch(
name: string
name: string,
): Promise<{ text: string; meta: PageMeta }> {
let phrase = name.substring(searchPrefix.length);
let results = await fullTextSearch(phrase, 100);
const text = `# Search results for "${phrase}"\n${results
.map((r: any) => `* [[${r.name}]] (score: ${r.rank})`)
.join("\n")}
const text = `# Search results for "${phrase}"\n${
results
.map((r: any) => `* [[${r.name}]] (score: ${r.rank})`)
.join("\n")
}
`;
return {
text: text,

View File

@ -20,6 +20,6 @@ export async function statsCommand() {
const wordCount = countWords(text);
const time = readingTime(wordCount);
await flashNotification(
`${wordCount} words; ${time} minutes read; ${allPages.length} total pages in space.`
`${wordCount} words; ${time} minutes read; ${allPages.length} total pages in space.`,
);
}

View File

@ -19,7 +19,7 @@ export async function indexTags({ name, tree }: IndexTreeEvent) {
});
batchSet(
name,
[...allTags].map((t) => ({ key: `tag:${t}`, value: t }))
[...allTags].map((t) => ({ key: `tag:${t}`, value: t })),
);
}
@ -58,6 +58,6 @@ export async function tagProvider({ query }: QueryProviderEvent) {
[...allTags.entries()].map(([name, freq]) => ({
name,
freq,
}))
})),
);
}

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,7 @@ import { flashNotification } from "../../syscall/silverbullet-syscall/editor.ts"
export async function replaceAsync(
str: string,
regex: RegExp,
asyncFn: (match: string, ...args: any[]) => Promise<string>
asyncFn: (match: string, ...args: any[]) => Promise<string>,
) {
const promises: Promise<string>[] = [];
str.replace(regex, (match: string, ...args: any[]): string => {

View File

@ -1,16 +1,21 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import {LRParser} from "@lezer/lr"
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",
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",
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
})
topRules: { "Program": [0, 1] },
tokenPrec: 0,
});

View File

@ -1,6 +1,5 @@
// 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,
@ -19,4 +18,4 @@ export const
LimitClause = 17,
SelectClause = 18,
RenderClause = 19,
PageRef = 20
PageRef = 20;

View File

@ -50,7 +50,7 @@ const maxWidth = 70;
// Nicely format an array of JSON objects as a Markdown table
export function jsonToMDTable(
jsonArray: any[],
valueTransformer: (k: string, v: any) => string = (k, v) => "" + v
valueTransformer: (k: string, v: any) => string = (k, v) => "" + v,
): string {
let fieldWidths = new Map<string, number>();
for (let entry of jsonArray) {
@ -78,17 +78,17 @@ export function jsonToMDTable(
.map(
(headerName) =>
headerName +
charPad(" ", fieldWidths.get(headerName)! - headerName.length)
charPad(" ", fieldWidths.get(headerName)! - headerName.length),
)
.join("|") +
"|"
"|",
);
lines.push(
"|" +
headerList
.map((title) => charPad("-", fieldWidths.get(title)!))
.join("|") +
"|"
"|",
);
for (const val of jsonArray) {
let el = [];

View File

@ -8,7 +8,7 @@ async function getFiles(dir) {
dirents.map((dirent) => {
const res = resolve(dir, dirent.name);
return dirent.isDirectory() ? getFiles(res) : res;
})
}),
);
return Array.prototype.concat(...files);
}

View File

@ -1,4 +1,6 @@
This page contains settings for configuring SilverBullet and its plugs. Any changes outside of the yaml block will be overwritten.
This page contains settings for configuring SilverBullet and its plugs. Any
changes outside of the yaml block will be overwritten.
```yaml
indexPage: index
```
```

View File

@ -1,5 +1,3 @@
export * from "../common/deps.ts";
export { Database as SQLite } from "https://deno.land/x/sqlite3@0.6.1/mod.ts";
export { Application, Router } from "https://deno.land/x/oak@v11.1.0/mod.ts";
export { mime } from "https://deno.land/x/mimetypes@v1.0.0/mod.ts";

View File

@ -1,10 +1,4 @@
import { Application, path, Router, SQLite } from "./deps.ts";
import {
AssetBundle,
assetReadFileSync,
assetReadTextFileSync,
assetStatSync,
} from "../plugos/asset_bundle_reader.ts";
import { Manifest, SilverBulletHooks } from "../common/manifest.ts";
import { loadMarkdownExtensions } from "../common/markdown_ext.ts";
import buildMarkdown from "../common/parser.ts";
@ -14,7 +8,7 @@ import { Space } from "../common/spaces/space.ts";
import { SpacePrimitives } from "../common/spaces/space_primitives.ts";
import { markdownSyscalls } from "../common/syscalls/markdown.ts";
import { parseYamlSettings } from "../common/util.ts";
import { sandboxFactory } from "../plugos/environments/deno_sandbox.ts";
import { createSandbox } from "../plugos/environments/deno_sandbox.ts";
import { EndpointHook } from "../plugos/hooks/endpoint.ts";
import { EventHook } from "../plugos/hooks/event.ts";
import { DenoCronHook } from "../plugos/hooks/cron.deno.ts";
@ -39,7 +33,7 @@ import spaceSyscalls from "./syscalls/space.ts";
import { systemSyscalls } from "./syscalls/system.ts";
import { AssetBundlePlugSpacePrimitives } from "../common/spaces/asset_bundle_space_primitives.ts";
import assetSyscalls from "../plugos/syscalls/asset.ts";
import { FlashServer } from "https://deno.land/x/oak@v11.1.0/mod.ts";
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
export type ServerOptions = {
port: number;
@ -71,7 +65,7 @@ export class HttpServer {
this.password = options.password;
this.globalModules = JSON.parse(
assetReadTextFileSync(this.assetBundle, `web/global.plug.json`),
this.assetBundle.readTextFileSync(`web/global.plug.json`),
);
// Set up the PlugOS System
@ -177,7 +171,7 @@ export class HttpServer {
const { data } = await this.space.readAttachment(plugName, "string");
await this.system.load(
JSON.parse(data as string),
sandboxFactory(this.assetBundle),
createSandbox,
);
}
this.rebuildMdExtensions();
@ -214,30 +208,25 @@ export class HttpServer {
this.app.use(async ({ request, response }, next) => {
if (request.url.pathname === "/") {
response.headers.set("Content-type", "text/html");
response.body = assetReadTextFileSync(
this.assetBundle,
response.body = this.assetBundle.readTextFileSync(
"web/index.html",
);
return;
}
try {
const assetName = `web${request.url.pathname}`;
const meta = assetStatSync(this.assetBundle, assetName);
response.status = 200;
response.headers.set(
"Content-type",
meta.contentType,
this.assetBundle.getMimeType(assetName),
);
response.headers.set("Content-length", "" + meta.size);
response.headers.set(
"Last-Modified",
new Date(meta.lastModified).toUTCString(),
const data = this.assetBundle.readFileSync(
assetName,
);
response.headers.set("Content-length", "" + data.length);
if (request.method === "GET") {
response.body = assetReadFileSync(
this.assetBundle,
assetName,
);
response.body = data;
}
} catch {
await next();
@ -271,8 +260,7 @@ export class HttpServer {
// Fallback, serve index.html
this.app.use((ctx) => {
ctx.response.headers.set("Content-type", "text/html");
ctx.response.body = assetReadTextFileSync(
this.assetBundle,
ctx.response.body = this.assetBundle.readTextFileSync(
"web/index.html",
);
});
@ -350,7 +338,7 @@ export class HttpServer {
} catch {
await this.space.writePage(
"SETTINGS",
assetReadTextFileSync(this.assetBundle, "SETTINGS_template.md"),
this.assetBundle.readTextFileSync("SETTINGS_template.md"),
true,
);
}

View File

@ -21,14 +21,14 @@ const pagesPath = path.resolve(Deno.cwd(), args._[0] as string);
const port = +args.port;
import assetBundle from "../dist/asset_bundle.json" assert { type: "json" };
import { AssetBundle } from "../plugos/asset_bundle_reader.ts";
import { AssetBundle, AssetJson } from "../plugos/asset_bundle/bundle.ts";
console.log("Pages folder:", pagesPath);
const httpServer = new HttpServer({
port: port,
pagesPath: pagesPath,
assetBundle: assetBundle as AssetBundle,
assetBundle: new AssetBundle(assetBundle as AssetJson),
password: args.password,
});
httpServer.start().catch((e) => {

View File

@ -1,4 +1,4 @@
import { base64Decode } from "../../plugos/base64.ts";
import { base64Decode } from "../../plugos/asset_bundle/base64.ts";
import type { FileMeta } from "./fs.ts";
import { syscall } from "./syscall.ts";

View File

@ -13,27 +13,27 @@ export function insertMarker(marker: string): StateCommand {
changes.push(
isBoldBefore
? {
from: range.from - marker.length,
to: range.from,
insert: Text.of([""]),
}
from: range.from - marker.length,
to: range.from,
insert: Text.of([""]),
}
: {
from: range.from,
insert: Text.of([marker]),
}
from: range.from,
insert: Text.of([marker]),
},
);
changes.push(
isBoldAfter
? {
from: range.to,
to: range.to + marker.length,
insert: Text.of([""]),
}
from: range.to,
to: range.to + marker.length,
insert: Text.of([""]),
}
: {
from: range.to,
insert: Text.of([marker]),
}
from: range.to,
insert: Text.of([marker]),
},
);
const extendBefore = isBoldBefore ? -marker.length : marker.length;
@ -43,7 +43,7 @@ export function insertMarker(marker: string): StateCommand {
changes,
range: EditorSelection.range(
range.from + extendBefore,
range.to + extendAfter
range.to + extendAfter,
),
};
});
@ -52,7 +52,7 @@ export function insertMarker(marker: string): StateCommand {
state.update(changes, {
scrollIntoView: true,
annotations: Transaction.userEvent.of("input"),
})
}),
);
return true;

View File

@ -12,89 +12,80 @@ This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
---
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
## SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation efforts
of academic and linguistic communities, and to provide a free and open framework
in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
redistributed freely as long as they are not sold by themselves. The fonts,
including any derivative works, can be bundled, embedded, redistributed and/or
sold with any software provided that any reserved names are not used by
derivative works. The fonts and derivatives, however, cannot be released under
any other type of license. The requirement for fonts to remain under this
license does not apply to any document created using the fonts or their
derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
DEFINITIONS "Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may include source
files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Reserved Font Name" refers to any names specified as such after the copyright
statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Modified Version" refers to any derivative made by adding to, deleting, or
substituting -- in part or in whole -- any of the components of the Original
Version, by changing formats or by porting the Font Software to a new
environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
"Author" refers to any designer, engineer, programmer, technical writer or other
person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any
person obtaining a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
1. Neither the Font Software nor any of its individual components, in Original
or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
2. Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy contains
the above copyright notice and this license. These can be included either as
stand-alone text files, human-readable headers or in the appropriate
machine-readable metadata fields within text or binary files as long as those
fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
3. No Modified Version of the Font Software may use the Reserved Font Name(s)
unless explicit written permission is granted by the corresponding Copyright
Holder. This restriction only applies to the primary font name as presented
to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software
shall not be used to promote, endorse or advertise any Modified Version,
except to acknowledge the contribution(s) of the Copyright Holder(s) and the
Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
5. The Font Software, modified or unmodified, in part or in whole, must be
distributed entirely under this license, and must not be distributed under
any other license. The requirement for fonts to remain under this license
does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
TERMINATION This license becomes null and void if any of the above conditions
are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT
HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY
GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY
TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -1,10 +1,6 @@
import { Hook, Manifest } from "../../plugos/types.ts";
import { System } from "../../plugos/system.ts";
import {
Completion,
CompletionContext,
CompletionResult,
} from "../deps.ts";
import { Completion, CompletionContext, CompletionResult } from "../deps.ts";
import { safeRun } from "../../common/util.ts";
import { Editor } from "../editor.tsx";
import { syntaxTree } from "../deps.ts";

View File

@ -1,89 +1,155 @@
An attempt at documenting of the changes/new features introduced in each release.
An attempt at documenting of the changes/new features introduced in each
release.
---
## Deno release
* The entire repo has been migrated to [Deno](https://deno.land)
* This may temporarily break some things.
* If somehow youre 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 automatically loaded and no longer should be listed.
* 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 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 used.
- The entire repo has been migrated to [Deno](https://deno.land)
- This may temporarily break some things.
- If somehow youre 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
automatically loaded and no longer should be listed.
- 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
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
used.
---
## 0.0.35
* 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 re-loaded and pages indexed upon every upgrade.
* Various bug fixes (e.g. using HTML tags in a page before completely broke syntax highlighting)
* Exposed `fulltext.*` syscalls on the client
- 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
re-loaded and pages indexed upon every upgrade.
- Various bug fixes (e.g. using HTML tags in a page before completely broke
syntax highlighting)
- Exposed `fulltext.*` syscalls on the client
---
## 0.0.34
* 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, that exists.
- 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,
that exists.
---
## 0.0.33
* **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 Space]} command.
* `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).
- **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
Space]} command.
- `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 `![alt text](https://url.com/image.jpg)` notation and a preview of the image will appear automatically. Example:
- **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, top-right.
- **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 notation (anywhere on a page), then reference it locally via [[@anchor]] or cross page via [[CHANGELOG@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]].
---
## 0.0.31
* 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 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 `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 its scope
* 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 to the `page`)
* 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 mobile devices (where these are harder to type)
- 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
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
`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
its scope
- 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
to the `page`)
- 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
mobile devices (where these are harder to type)
---
## 0.0.30
* 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 present.
* 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 also make the title look nicer when visiting https://silverbullet.md
- 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
present.
- 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
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 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 `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 `<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 same stat on demand with the `Stats: Show` command.
* The page switcher is now maintaining its ordering based on, in order:
- 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
`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
`<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
same stat on demand with the `Stats: Show` command.
- 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 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 node.js (introduced in 0.0.28)
* Renamed Markdown-preview related commands to something more consistentnt
- 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
node.js (introduced in 0.0.28)
- Renamed Markdown-preview related commands to something more consistentnt

View File

@ -1,23 +1,35 @@
Work on the `mattermost-plugin` integration of SB into Mattermost as a product/plugin as a proof of concept.
Work on the `mattermost-plugin` integration of SB into Mattermost as a
product/plugin as a proof of concept.
To do:
* [ ] Bundle backend with node.js as part of plugin
* Various options investigated, including [nexe](https://github.com/nexe/nexe) and [pkg](https://github.com/vercel/pkg) but neither of these will work great due to dynamically loading and resolving of modules in the node.js sandbox implementation.
* Most straight-forward option is to simply bundle the `node` binary per platform + a trimmed down `node_modules` modeled how `npx` does this. Once per platform.
* [ ] Fix CSS styling issues
* [ ] Store pages in DB instead of text files (general SB feature, not MM specific)
* [ ] Switch over SB plugin to use MM database (MySQL, Postgres) as backing store rather than SQLite
* [ ] Freeze plug configuration (dont allow anybody or at most admins) to update plugs for security reasons. We may simply remove the `PLUGS` page.
* What about `SETTINGS`?
* Easy option: disable, dont use
* Fancier option: make them user specific with a layer on top of the FS
* What about `SECRETS`?
- [ ] Bundle backend with node.js as part of plugin
- Various options investigated, including [nexe](https://github.com/nexe/nexe)
and [pkg](https://github.com/vercel/pkg) but neither of these will work
great due to dynamically loading and resolving of modules in the node.js
sandbox implementation.
- Most straight-forward option is to simply bundle the `node` binary per
platform + a trimmed down `node_modules` modeled how `npx` does this. Once
per platform.
- [ ] Fix CSS styling issues
- [ ] Store pages in DB instead of text files (general SB feature, not MM
specific)
- [ ] Switch over SB plugin to use MM database (MySQL, Postgres) as backing
store rather than SQLite
- [ ] Freeze plug configuration (dont allow anybody or at most admins) to
update plugs for security reasons. We may simply remove the `PLUGS` page.
- What about `SETTINGS`?
- Easy option: disable, dont use
- Fancier option: make them user specific with a layer on top of the FS
- What about `SECRETS`?
To deliberate on:
* Consider page locking mechanisms, or re-implement real-time collaboration (would require introducing web sockets again and OT) — big project.
* Consider page revision options
* Scope of spaces, tied to:
* Personal (default SB PKMS use case, no permission, collaboration issues)
* Channel (old Boards model)
* Team
* Server
- Consider page locking mechanisms, or re-implement real-time collaboration
(would require introducing web sockets again and OT) — big project.
- Consider page revision options
- Scope of spaces, tied to:
- Personal (default SB PKMS use case, no permission, collaboration issues)
- Channel (old Boards model)
- Team
- Server

View File

@ -4,4 +4,3 @@ publishAll: true
indexPage: Silver Bullet
footerPage: website-footer
```

View File

@ -1,5 +1,6 @@
This page contains settings for configuring SilverBullet and its Plugs.
Any changes outside of the yaml block will be overwritten.
This page contains settings for configuring SilverBullet and its Plugs. Any
changes outside of the yaml block will be overwritten.
```yaml
indexPage: Silver Bullet
```
```

View File

@ -1,27 +1,51 @@
What does Silver Bullet look like? Well, have a look around. **Youre looking at it at this very moment!** 🤯
What does Silver Bullet look like? Well, have a look around. **Youre looking at
it at this very moment!** 🤯
Note that what youre looking at is not a fully functional version, because the _back-end is read-only_. That said, it should give you some feel for what its like to use SB before making the commitment of running a single `npx` command (see below) to download and run it locally in its fully functioning mode.
Note that what youre looking at is not a fully functional version, because the
_back-end is read-only_. That said, it should give you some feel for what its
like to use SB before making the commitment of running a single `npx` command
(see below) to download and run it locally in its fully functioning mode.
## Start playing
So, feel free to make some edits in this space. Dont worry, you wont break anything, nothing is saved (just reload the page to see).
So, feel free to make some edits in this space. Dont worry, you wont break
anything, nothing is saved (just reload the page to see).
Here are some things to try:
* Click on the page title (`index` for this particular one) at the top, or hit `Cmd-k` (Mac) or `Ctrl-k` (Linux and Windows) to open the **page switcher**. Type the name of a nonexistent page to create it (although it wont save in this environment).
* Click on the run button (top right) or hit `Cmd-/` (Mac) or `Ctrl-/` (Linux and Windows) to open the **command palette** (note not all commands will work in this quasi read-only mode).
* Select some text and hit `Alt-m` to ==highlight== it, or `Cmd-b` (Mac) or `Ctrl-b` to make it **bold**.
* Click a link somewhere in this page to navigate there.
* Start typing `[[` somewhere to insert a page link (with completion).
* [ ] Tap this box 👈 to mark this task as done.
* Start typing `:party` to trigger the emoji picker 🎉
* Type `/` somewhere in the text to invoke a **slash command**.
* Hit `Cmd-p` (Mac) or `Ctrl-p` (Windows, Linux) to show a live preview for the current page on the side, if your brain doesnt speak native Markdown yet.
* Open this site on your phone or tablet and… it just works!
* Are you using a browser with **PWA support** (e.g. any Chromium-based browser)? Click on that little icon to the right of your location bar that says “Install Silver Bullet” to give SB its own window frame and desktop icon, like it is a stand-alone app (not particularly useful on silverbullet.md, but definitely do this once you install it yourself).
- Click on the page title (`index` for this particular one) at the top, or hit
`Cmd-k` (Mac) or `Ctrl-k` (Linux and Windows) to open the **page switcher**.
Type the name of a nonexistent page to create it (although it wont save in
this environment).
- Click on the run button (top right) or hit `Cmd-/` (Mac) or `Ctrl-/` (Linux
and Windows) to open the **command palette** (note not all commands will work
in this quasi read-only mode).
- Select some text and hit `Alt-m` to ==highlight== it, or `Cmd-b` (Mac) or
`Ctrl-b` to make it **bold**.
- Click a link somewhere in this page to navigate there.
- Start typing `[[` somewhere to insert a page link (with completion).
- [ ] Tap this box 👈 to mark this task as done.
- Start typing `:party` to trigger the emoji picker 🎉
- Type `/` somewhere in the text to invoke a **slash command**.
- Hit `Cmd-p` (Mac) or `Ctrl-p` (Windows, Linux) to show a live preview for the
current page on the side, if your brain doesnt speak native Markdown yet.
- Open this site on your phone or tablet and… it just works!
- Are you using a browser with **PWA support** (e.g. any Chromium-based
browser)? Click on that little icon to the right of your location bar that
says “Install Silver Bullet” to give SB its own window frame and desktop icon,
like it is a stand-alone app (not particularly useful on silverbullet.md, but
definitely do this once you install it yourself).
There are a few features you dont get to fully experience in this environment, because they rely on a working back-end, such as:
There are a few features you dont get to fully experience in this environment,
because they rely on a working back-end, such as:
* Using SBs powerful page indexing and **query mechanism** where part of pages are automatically rendered and kept up to date by querying various data sources (such as pages and their metadata, back links, tasks embedded in pages, and list items) with an SQL like syntax, rendered with handlebars templates.
* Intelligent **page renaming**, automatically updating any pages that link to it.
* **Full text search**.
* **Extending** and updating SBs functionality by installing additional [[🔌 Plugs]] (SB parlance for plug-ins) and writing your own.
- Using SBs powerful page indexing and **query mechanism** where part of pages
are automatically rendered and kept up to date by querying various data
sources (such as pages and their metadata, back links, tasks embedded in
pages, and list items) with an SQL like syntax, rendered with handlebars
templates.
- Intelligent **page renaming**, automatically updating any pages that link to
it.
- **Full text search**.
- **Extending** and updating SBs functionality by installing additional [[🔌
Plugs]] (SB parlance for plug-ins) and writing your own.

View File

@ -1,77 +1,132 @@
## Markdown as a platform
Silver Bullet (SB) is highly-extensible, [open source](https://github.com/silverbulletmd/silverbullet) **personal knowledge management** software. Indeed, thats fancy language for “a note taking app with links.”
Silver Bullet (SB) is highly-extensible,
[open source](https://github.com/silverbulletmd/silverbullet) **personal
knowledge management** software. Indeed, thats 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).
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).
## 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!
Heres a list of (non-built-in) plugs documented in this space (note the `#query` ... `/query` notation used):
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!
Heres 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))
- [[🔌 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))
<!-- /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.
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.
## Explore more
Click on the links below to explore various aspects of Silver Bullet more in-depth:
* [[CHANGELOG]]
* [[🤯 Features]]
* [[💡 Inspiration]]
* [[🔌 Plugs]]
* [[🔨 Development]]
Click on the links below to explore various aspects of Silver Bullet more
in-depth:
- [[CHANGELOG]]
- [[🤯 Features]]
- [[💡 Inspiration]]
- [[🔌 Plugs]]
- [[🔨 Development]]
More of a video person? Here are two to get you started:
* [A Tour of Silver Bullets features](https://youtu.be/RYdc3UF9gok) — spoiler alert: its cool.
* [A look the SilverBullet architecture](https://youtu.be/mXCGau05p5o) — spoiler alert: its plugs all the way down.
- [A Tour of Silver Bullets features](https://youtu.be/RYdc3UF9gok) — spoiler
alert: its cool.
- [A look the SilverBullet architecture](https://youtu.be/mXCGau05p5o) — spoiler
alert: its plugs all the way down.
## Principles
Some core principles that underly Silver Bullets 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 doesnt have a separate view and edit mode. It doesnt have a “focus mode.” Youre always in focused edit mode, why wouldnt 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.
- **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 doesnt have a separate view and edit
mode. It doesnt have a “focus mode.” Youre always in focused edit mode, why
wouldnt 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
Silver Bullet is built using [Deno](https://deno.land). To install it, you will need to have Deno installed (tested on 1.26 or later). If you have homebrew on a Mac, this is just a single `brew install deno` away.
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:
Silver Bullet is built using [Deno](https://deno.land). To install it, you will
need to have Deno installed (tested on 1.26 or later). If you have homebrew on a
Mac, this is just a single `brew install deno` away.
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:
deno run -A --unstable https://get.silverbullet.md <pages-path>
However, because this command is not super easy to remember, you may install it as well:
However, because this command is not super easy to remember, you may install it
as well:
deno install -f --name silverbullet -A --unstable https://get.silverbullet.md
This will create a `silverbullet` (feel free to replace `silverbullet` in this command with whatever you like) alias in your `~/.deno/bin` folder. Make sure this path is in your `PATH` environment variable.
This will create a `silverbullet` (feel free to replace `silverbullet` in this
command with whatever you like) alias in your `~/.deno/bin` folder. Make sure
this path is in your `PATH` environment variable.
This allows you to install Silver Bullet simply as follows:
silverbullet <pages-path>
By default, SB will bind to port `3000`, to use a different port use the
`--port` flag. By default SB doesnt offer any sort of authentication, to add basic password authentication, pass the `--password` flag.
`--port` flag. By default SB doesnt 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 ).
Thats it! Enjoy.
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).
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).

View File

@ -1 +1 @@
Moved to [[Silver Bullet]]
Moved to [[Silver Bullet]]

View File

@ -1 +1 @@
* [[{{name}}]] by **{{author}}** ([repo]({{repo}}))
- [[{{name}}]] by **{{author}}** ([repo]({{repo}}))

View File

@ -1,3 +1,3 @@
{{#each .}}
* [{{#if done}}x{{else}} {{/if}}] [[{{page}}@{{pos}}]] {{name}}
{{/each}}
- [{{#if done}}x{{else}} {{/if}}] [[{{page}}@{{pos}}]] {{name}} {{/each}}

View File

@ -1,4 +0,0 @@
Syup
![](attachment/bla)
kljhaef

View File

@ -1,3 +1,10 @@
Inspiration for Silver Bullet comes primarily from [Obsidian](https://obsidian.md/) and its vast plug-in ecosystem (the work-in-progress plugs around querying and tasks are inspired by Obsidians tasks and dataview plugins), but also [Roam Research](https://roamresearch.com/) was an inspiration.
Inspiration for Silver Bullet comes primarily from
[Obsidian](https://obsidian.md/) and its vast plug-in ecosystem (the
work-in-progress plugs around querying and tasks are inspired by Obsidians
tasks and dataview plugins), but also [Roam Research](https://roamresearch.com/)
was an inspiration.
The way plugs are implemented is a further iteration on how this was done in a previous project of mine (now defunct) called [Zed](https://github.com/zedapp/zed) as well as [Matterless](https://github.com/zefhemel/matterless).
The way plugs are implemented is a further iteration on how this was done in a
previous project of mine (now defunct) called
[Zed](https://github.com/zedapp/zed) as well as
[Matterless](https://github.com/zefhemel/matterless).

View File

@ -6,15 +6,17 @@ author: Guillermo Vayá
```
<!-- #include "https://raw.githubusercontent.com/Willyfrog/silverbullet-backlinks/main/README.md" -->
# SilverBullet plug for Backlinks
Provides access to pages that link to the one currently being edited.
## Wait, SilverBullet?
If you don't know what it is, check its [webpage](https://silverbullet.md), but if
you want me to spoil the fun: it is an extensible note taking app with markdown and plain files at its core
(well... there is a bit of magic in there too, but what good it would be without a little magic?)
If you don't know what it is, check its [webpage](https://silverbullet.md), but
if you want me to spoil the fun: it is an extensible note taking app with
markdown and plain files at its core (well... there is a bit of magic in there
too, but what good it would be without a little magic?)
## Installation
@ -25,4 +27,5 @@ Open (`cmd+k`) your `PLUGS` note in SilverBullet and add this plug to the list:
```
Then run the `Plugs: Update` command and off you go!
<!-- /include -->
<!-- /include -->

View File

@ -9,16 +9,23 @@ $disableDirectives: true
This documentation is still a WIP.
## Templating
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.
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`.
The {[Template: Instantiate Page]} command enables you to create a new page
based on a page template.
Page templates have one “magic” type of page metadata that is used during instantiation:
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`.
* `$name` is used as the default value for a new page based on this template
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
In addition, any standard template placeholders are available (see below)
@ -30,26 +37,34 @@ For instance:
# {{page}}
As recorded on {{today}}.
## Introduction
## 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.
## Introduction
## Notes
## Conclusions
### 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`.
Snippet templates do not support the `$name` page meta, because it doesnt apply.
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`.
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.
Snippet templates do not support the `$name` page meta, because it doesnt
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.
For instance to replicate the `/query` slash command as a snippet:
@ -60,18 +75,24 @@ 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, its also possible to reuse templates that update dynamically (similar to [[🔌 Query]]). For this, you use the `#use` and `#use-verbose` directives.
In addition to using the `/snippet` slash command to insert a template as a
one-off, its 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.
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`).
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.
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:
@ -82,19 +103,31 @@ 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`.
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.
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.
### 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
Currently supported (hardcoded in the code):
* `{{today}}`: Todays date in the usual YYYY-MM-DD format
* `{{tomorrow}}`: Tomorrows date in the usual YYY-MM-DD format
* `{{yesterday}}`: Yesterdays date in the usual YYY-MM-DD format
* `{{lastWeek}}`: Current date - 7 days
* `{{page}}`: The name of the current page
- `{{today}}`: Todays date in the usual YYYY-MM-DD format
- `{{tomorrow}}`: Tomorrows date in the usual YYY-MM-DD format
- `{{yesterday}}`: Yesterdays date in the usual YYY-MM-DD format
- `{{lastWeek}}`: Current date - 7 days
- `{{page}}`: The name of the current page

View File

@ -4,30 +4,34 @@ uri: github:silverbulletmd/silverbullet-ghost/ghost.plug.json
repo: https://github.com/silverbulletmd/silverbullet-ghost
author: Zef Hemel
```
<!-- #include "https://raw.githubusercontent.com/silverbulletmd/silverbullet-ghost/main/README.md" -->
# Ghost plug for Silver Bullet
Note: Still very basic. To use:
In your `SETTINGS` specify the following settings:
```yaml
ghostUrl: https://your-ghost-blog.ghost.io
ghostPostPrefix: posts
ghostPagePrefix: pages
```
```yaml
ghostUrl: https://your-ghost-blog.ghost.io
ghostPostPrefix: posts
ghostPagePrefix: pages
```
And in your `SECRETS` file:
```yaml
ghostAdminKey: your:adminkey
```
```yaml
ghostAdminKey: your:adminkey
```
This will assume the naming pattern of `posts/my-post-slug` where the first top-level heading (`# Hello`) will be used as the post title.
This will assume the naming pattern of `posts/my-post-slug` where the first
top-level heading (`# Hello`) will be used as the post title.
Commands to use `Ghost: Publish`
## Installation
Open your `PLUGS` note in SilverBullet and add this plug to the list:
```
@ -35,4 +39,5 @@ Open your `PLUGS` note in SilverBullet and add this plug to the list:
```
Then run the `Plugs: Update` command and off you go!
<!-- /include -->

View File

@ -4,21 +4,28 @@ uri: github:silverbulletmd/silverbullet-github/github.plug.json
repo: https://github.com/silverbulletmd/silverbullet-github
author: Zef Hemel
```
<!-- #include "https://raw.githubusercontent.com/silverbulletmd/silverbullet-git/main/README.md" -->
# SilverBullet plug for Git
Very basic in functionality, it assumes you have git configured for push and pull in your space. What it does, roughly speaking:
Very basic in functionality, it assumes you have git configured for push and
pull in your space. What it does, roughly speaking:
`Git : Sync`:
* Adds all *.md files in your folder to git
* It commits them with a "Snapshot" commit message
* It `git pull`s changes from the remote server
* It `git push`es changes to the remote server
- Adds all *.md files in your folder to git
- It commits them with a "Snapshot" commit message
- It `git pull`s changes from the remote server
- It `git push`es changes to the remote server
`Git: Snapshot`:
* Asks you for a commit message
* Commits
- Asks you for a commit message
- Commits
## Installation
Open your `PLUGS` note in SilverBullet and add this plug to the list:
```
@ -26,5 +33,5 @@ Open your `PLUGS` note in SilverBullet and add this plug to the list:
```
Then run the `Plugs: Update` command and off you go!
<!-- /include -->
<!-- /include -->

View File

@ -4,11 +4,16 @@ uri: github:silverbulletmd/silverbullet-github/github.plug.json
repo: https://github.com/silverbulletmd/silverbullet-github
author: Zef Hemel
```
<!-- #include [[https://raw.githubusercontent.com/silverbulletmd/silverbullet-github/main/README.md]] -->
# SilverBullet plug for Github
Provides Github events, notifications and pull requests as query sources using SB's query mechanism
Provides Github events, notifications and pull requests as query sources using
SB's query mechanism
## Installation
Open your `PLUGS` note in SilverBullet and add this plug to the list:
```
@ -18,21 +23,24 @@ Open your `PLUGS` note in SilverBullet and add this plug to the list:
Then run the `Plugs: Update` command and off you go!
## Configuration
This step is optional for anything but the `gh-notification` source, but without it you may be rate limited by the Github API,
To configure, add a `githubToken` key to your `SECRETS` page, this should be a [personal access token](https://github.com/settings/tokens):
This step is optional for anything but the `gh-notification` source, but without
it you may be rate limited by the Github API,
```yaml
githubToken: your-github-token
```
To configure, add a `githubToken` key to your `SECRETS` page, this should be a
[personal access token](https://github.com/settings/tokens):
```yaml
githubToken: your-github-token
```
## Query sources
* `gh-event` required filters in the `where` clause:
* `username`: the user whose events to query
* `gh-pull`
* `repo`: the repo to query PRs for
* `gh-notification` requires a `githubToken` to be configured in `SECRETS`.
- `gh-event` required filters in the `where` clause:
- `username`: the user whose events to query
- `gh-pull`
- `repo`: the repo to query PRs for
- `gh-notification` requires a `githubToken` to be configured in `SECRETS`.
## Example
@ -51,4 +59,5 @@ Example uses:
Where the `template/gh-pull` looks as follows:
* ({{state}}) [{{title}}]({{html_url}})
<!-- /include -->

View File

@ -4,11 +4,16 @@ uri: github:silverbulletmd/silverbullet-mattermost/mattermost.plug.json
repo: https://github.com/silverbulletmd/silverbullet-mattermost
author: Zef Hemel
```
<!-- #include "https://raw.githubusercontent.com/silverbulletmd/silverbullet-mattermost/main/README.md" -->
# Mattermost plug for Silver Bullet
Provides an `mm-saved` query provider (and maybe more in the future). Please follow the installation, configuration sections, and have a look at the example.
Provides an `mm-saved` query provider (and maybe more in the future). Please
follow the installation, configuration sections, and have a look at the example.
## Installation
Open your `PLUGS` note in SilverBullet and add this plug to the list:
```
@ -18,20 +23,26 @@ Open your `PLUGS` note in SilverBullet and add this plug to the list:
Then run the `Plugs: Update` command and off you go!
## Configuration
You need two bits of configuration to make this plug work. In `SETTINGS` provide the `mattermostUrl` and `mattermostDefaultTeam` settings, they default to the following:
You need two bits of configuration to make this plug work. In `SETTINGS` provide
the `mattermostUrl` and `mattermostDefaultTeam` settings, they default to the
following:
```yaml
mattermostUrl: https://community.mattermost.com
mattermostDefaultTeam: core
```
In `SECRETS` provide a Mattermost personal access token (or hijack one from your current session):
In `SECRETS` provide a Mattermost personal access token (or hijack one from your
current session):
```yaml
mattermostToken: your-token
```
To make this look good, it's recommended you render your query results a template. Here is one to start with, you can keep it in e.g. `templates/mm-saved`:
To make this look good, it's recommended you render your query results a
template. Here is one to start with, you can keep it in e.g.
`templates/mm-saved`:
[{{username}}]({{desktopUrl}}) in **{{channelName}}** at {{updatedAt}} {[Unsave]}:
@ -39,11 +50,12 @@ To make this look good, it's recommended you render your query results a templat
---
Note that the `{[Unsaved]}` "button" when clicked, will unsave the post automatically 😎
Note that the `{[Unsaved]}` "button" when clicked, will unsave the post
automatically 😎
## Query sources
* `mm-saved` fetches (by default 15) saved posts in Mattermost
- `mm-saved` fetches (by default 15) saved posts in Mattermost
## Example
@ -52,4 +64,5 @@ Example uses (using the `template/mm-saved` template above):
<!-- #query mm-saved order by updatedAt desc limit 5 render "template/mm-saved" -->
<!-- /query -->
<!-- /include -->

View File

@ -4,11 +4,15 @@ uri: github:silverbulletmd/silverbullet-mount/mount.plug.json
repo: https://github.com/silverbulletmd/silverbullet-mount
author: Zef Hemel
```
<!-- #include "https://raw.githubusercontent.com/silverbulletmd/silverbullet-mount/main/README.md" -->
# Mounting of external systems into SB
Enables mounting of external folders or SB instances into your space.
## Installation
Open your `PLUGS` note in SilverBullet and add this plug to the list:
```
@ -18,15 +22,17 @@ Open your `PLUGS` note in SilverBullet and add this plug to the list:
Then run the `Plugs: Update` command and off you go!
## Configuration
Create a `MOUNTS` page:
```yaml
# Mounting another local folder with a docs/ prefix
- prefix: docs/
path: file:/Users/me/docs
# Mounting an external SB instance to remote/
- prefix: remote/
path: http://some-ip:3000
password: mypassword
```
```yaml
# Mounting another local folder with a docs/ prefix
- prefix: docs/
path: file:/Users/me/docs
# Mounting an external SB instance to remote/
- prefix: remote/
path: http://some-ip:3000
password: mypassword
```
<!-- /include -->

View File

@ -1,56 +1,105 @@
Silver Bullet at its core is bare bones in terms of functionality, most of its power it gains from **plugs**.
Silver Bullet at its core is bare bones in terms of functionality, most of its
power it gains from **plugs**.
Plugs are an extension mechanism (implemented using a library called `plugos` that runs plug code on the server in a sandboxed v8 node.js process, and in the browser using web workers). Plugs can hook into SB in various ways: plugs can extend the Markdown parser and its syntax, define new commands and keybindings, respond to various events triggered either on the server or client side, as well as run recurring and background tasks. Plugs can even define their own extension mechanisms through custom events. Each plug runs in its own sandboxed environment and communicates with SB via _syscalls_ that expose a vast range of functionality. Plugs can be loaded, unloaded and updated without having to restart SB itself.
Plugs are an extension mechanism (implemented using a library called `plugos`
that runs plug code on the server in a sandboxed v8 node.js process, and in the
browser using web workers). Plugs can hook into SB in various ways: plugs can
extend the Markdown parser and its syntax, define new commands and keybindings,
respond to various events triggered either on the server or client side, as well
as run recurring and background tasks. Plugs can even define their own extension
mechanisms through custom events. Each plug runs in its own sandboxed
environment and communicates with SB via _syscalls_ that expose a vast range of
functionality. Plugs can be loaded, unloaded and updated without having to
restart SB itself.
## Directory
<!-- #query page where type = "plug" order by name render [[template/plug]] -->
* [[🔌 Backlinks]] by **Guillermo Vayá** ([repo](https://github.com/Willyfrog/silverbullet-backlinks))
* [[🔌 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))
- [[🔌 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))
<!-- /query -->
## How to develop your own plug
At this stage, to get started, its probably easiest to fork one of the existing plugs found in the [SilverBullet github org](https://github.com/silverbulletmd), for instance the [github one](https://github.com/silverbulletmd/silverbullet-github).
Generally, every plug consists of a YAML manifest file named `yourplugname.plug.yml`. Its convenient to have a `package.json` file in your repo to add any dependencies. One dev dependency you will definitely need is [@plugos/plugos](https://www.npmjs.com/package/@plugos/plugos) which will supply you with the `plugos-bundle` command, which is used to “compile” your plug YAML file into its bundled `.plug.json` form, which Silver Bullet will be able to load and execute.
At this stage, to get started, its probably easiest to fork one of the existing
plugs found in the [SilverBullet github org](https://github.com/silverbulletmd),
for instance the
[github one](https://github.com/silverbulletmd/silverbullet-github).
Generally, every plug consists of a YAML manifest file named
`yourplugname.plug.yml`. Its convenient to have a `package.json` file in your
repo to add any dependencies. One dev dependency you will definitely need is
[@plugos/plugos](https://www.npmjs.com/package/@plugos/plugos) which will supply
you with the `plugos-bundle` command, which is used to “compile” your plug YAML
file into its bundled `.plug.json` form, which Silver Bullet will be able to
load and execute.
Generally, the way to invoke `plugos-bundle` is as follows:
plugos-bundle yourplugname.plug.yaml
This will write out a `yourplugname.plug.json` file into the same folder. For development its convenient to add a `-w` flag to automatically recompile when changes to the YAML or source files are detected.
This will write out a `yourplugname.plug.json` file into the same folder. For
development its convenient to add a `-w` flag to automatically recompile when
changes to the YAML or source files are detected.
In order to keep bundles somewhat small, a few dependencies come prebundled with SB. A the time of this writing:
In order to keep bundles somewhat small, a few dependencies come prebundled with
SB. A the time of this writing:
* `yaml` (a YAML reader and stringifier library)
* `@lezer/lr` (a parser library)
* `handlebars`
- `yaml` (a YAML reader and stringifier library)
- `@lezer/lr` (a parser library)
- `handlebars`
If you use any of these, you can add e.g. `--exclude handlebars` to _not_ have them be included in the bundle (they will be loaded from SB itself).
If you use any of these, you can add e.g. `--exclude handlebars` to _not_ have
them be included in the bundle (they will be loaded from SB itself).
Once you have a compiled `.plug.json` file you can load it into SB in a few ways by listing it in your spaces `PLUGS` page.
Once you have a compiled `.plug.json` file you can load it into SB in a few ways
by listing it in your spaces `PLUGS` page.
For development its easiest to use the `file:` prefix for this, by adding this in the `yaml` block section there to your existing list of plugs:
For development its easiest to use the `file:` prefix for this, by adding this
in the `yaml` block section there to your existing list of plugs:
- file:/home/me/git/yourplugname/yourplugname.plug.json
Reload your list of plugs via the `Plugs: Update` command (`Cmd-Shift-p` on Mac, `Ctrl-Shift-p` on Linux and Windows) to load the list of plugs from the various sources on the server and your browser client. No need to reload the page, your plugs are now active.
Reload your list of plugs via the `Plugs: Update` command (`Cmd-Shift-p` on Mac,
`Ctrl-Shift-p` on Linux and Windows) to load the list of plugs from the various
sources on the server and your browser client. No need to reload the page, your
plugs are now active.
Once youre happy with your plug, you can distribute it in various ways:
* You can put it on github by simply committing the resulting `.plug.json` file there and instructing users to point to by adding `- github:yourgithubuser/yourrepo/yourplugname.plug.json` to their `PLUGS` file
* Add a release in your github repo and instruct users to add the release as `- ghr:yourgithubuser/yourrepo` or if they need a spcecific release `- ghr:yourgithubuser/yourrepo/release-name`
* You can put it on any other web server, and tell people to load it via https, e.g. `- https://mydomain.com/mypugname.plug.json`.
- You can put it on github by simply committing the resulting `.plug.json` file
there and instructing users to point to by adding
`- github:yourgithubuser/yourrepo/yourplugname.plug.json` to their `PLUGS`
file
- Add a release in your github repo and instruct users to add the release as
`- ghr:yourgithubuser/yourrepo` or if they need a spcecific release
`- ghr:yourgithubuser/yourrepo/release-name`
- You can put it on any other web server, and tell people to load it via https,
e.g. `- https://mydomain.com/mypugname.plug.json`.
### Recommended development workflow
I develop plugs as follows: in one terminal I have `plugos-bundle -w` running at all times, constantly recompiling my code as I change it.
I develop plugs as follows: in one terminal I have `plugos-bundle -w` running at
all times, constantly recompiling my code as I change it.
I also have SB open with a `file:` based link in my `PLUGS` file.
Whenever I want to test a change, I switch to SB, hit `Cmd-Shift-p` and test if stuff works.
Whenever I want to test a change, I switch to SB, hit `Cmd-Shift-p` and test if
stuff works.
Often I also have the `Debug: Show Logs` command running to monitor both server and client logs for any errors and debug information.
Often I also have the `Debug: Show Logs` command running to monitor both server
and client logs for any errors and debug information.

View File

@ -6,60 +6,80 @@ 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.
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`
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).
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”`.
- `=` 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.
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. Plugs data sources
The best part about data sources: there is auto-completion. 🎉
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. 🤯
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.
- `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
@ -76,119 +96,170 @@ 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|
| 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
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.
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.
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.
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/).
- `*` 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:
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.
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.
We will walk you through a set of examples starting from a very basic one
through one formatting the data using templates.
For the sake of simplicity, we will use the `page` data source and limit the results not to spoil the page.
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.
**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á|
| 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.
**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 |
| 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.
**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|
| 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.
**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? 🚀
**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]] 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.
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]] -->

View File

@ -1 +1,3 @@
This is very much in progress, have a look at our [Github Issues](https://github.com/silverbulletmd/silverbullet/issues) page if youd like to help out!
This is very much in progress, have a look at our
[Github Issues](https://github.com/silverbulletmd/silverbullet/issues) page if
youd like to help out!

View File

@ -1,6 +1,15 @@
* **Powerful Markdown editor** at its core (powered by [CodeMirror](https://codemirror.net))
* **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 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 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 laptop/desktop machines as well as iPads with a keyboard).
* **Extensible** through [[🔌 Plugs]]
- **Powerful Markdown editor** at its core (powered by
[CodeMirror](https://codemirror.net))
- **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
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
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
laptop/desktop machines as well as iPads with a keyboard).
- **Extensible** through [[🔌 Plugs]]