Refactor of asset bundles
This commit is contained in:
parent
9dd94c5397
commit
4c19ab21f2
18
LICENSE.md
18
LICENSE.md
@ -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.
|
||||
|
@ -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:
|
||||
|
||||
|
7
build.ts
7
build.ts
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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")) {
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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);
|
||||
|
55
deno.jsonc
55
deno.jsonc
@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
|
6
esbuild_deno_loader/testdata/data.json
vendored
6
esbuild_deno_loader/testdata/data.json
vendored
@ -1,6 +0,0 @@
|
||||
{
|
||||
"hello": "world",
|
||||
"__proto__": {
|
||||
"sky": "universe"
|
||||
}
|
||||
}
|
1
esbuild_deno_loader/testdata/importmap.js
vendored
1
esbuild_deno_loader/testdata/importmap.js
vendored
@ -1 +0,0 @@
|
||||
export * from "esbuild_deno_loader/testdata/mod.ts";
|
5
esbuild_deno_loader/testdata/importmap.json
vendored
5
esbuild_deno_loader/testdata/importmap.json
vendored
@ -1,5 +0,0 @@
|
||||
{
|
||||
"imports": {
|
||||
"mod": "./mod.ts"
|
||||
}
|
||||
}
|
2
esbuild_deno_loader/testdata/mod.js
vendored
2
esbuild_deno_loader/testdata/mod.js
vendored
@ -1,2 +0,0 @@
|
||||
const bool = "asd";
|
||||
export { bool };
|
11
esbuild_deno_loader/testdata/mod.jsx
vendored
11
esbuild_deno_loader/testdata/mod.jsx
vendored
@ -1,11 +0,0 @@
|
||||
function createElement(fn) {
|
||||
return fn();
|
||||
}
|
||||
|
||||
const React = { createElement };
|
||||
|
||||
function Asd() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
export default <Asd />;
|
2
esbuild_deno_loader/testdata/mod.mjs
vendored
2
esbuild_deno_loader/testdata/mod.mjs
vendored
@ -1,2 +0,0 @@
|
||||
const bool = "asd";
|
||||
export { bool };
|
4
esbuild_deno_loader/testdata/mod.mts
vendored
4
esbuild_deno_loader/testdata/mod.mts
vendored
@ -1,4 +0,0 @@
|
||||
let bool: string;
|
||||
bool = "asd";
|
||||
bool = "asd2";
|
||||
export { bool };
|
4
esbuild_deno_loader/testdata/mod.ts
vendored
4
esbuild_deno_loader/testdata/mod.ts
vendored
@ -1,4 +0,0 @@
|
||||
let bool: string;
|
||||
bool = "asd";
|
||||
bool = "asd2";
|
||||
export { bool };
|
11
esbuild_deno_loader/testdata/mod.tsx
vendored
11
esbuild_deno_loader/testdata/mod.tsx
vendored
@ -1,11 +0,0 @@
|
||||
function createElement(fn: () => string) {
|
||||
return fn();
|
||||
}
|
||||
|
||||
const React = { createElement };
|
||||
|
||||
function Asd() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
export default <Asd />;
|
@ -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,
|
||||
};
|
||||
}
|
@ -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";
|
||||
|
34
plugos/asset_bundle/builder.ts
Normal file
34
plugos/asset_bundle/builder.ts
Normal 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),
|
||||
);
|
||||
}
|
16
plugos/asset_bundle/bundle.test.ts
Normal file
16
plugos/asset_bundle/bundle.test.ts
Normal 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"));
|
||||
});
|
69
plugos/asset_bundle/bundle.ts
Normal file
69
plugos/asset_bundle/bundle.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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)),
|
||||
});
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
3
plugos/environments/worker_bundle.json
Normal file
3
plugos/environments/worker_bundle.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"worker.js": "data:application/javascript;base64,KCgpID0+IHsgdmFyIG1vZD0oKCk9PntmdW5jdGlvbiBjKHIpe3IoKS5jYXRjaChlPT57Y29uc29sZS5lcnJvcigiQ2F1Z2h0IGVycm9yIixlLm1lc3NhZ2UpfSl9dmFyIGE9Y2xhc3N7Y29uc3RydWN0b3IoZSxuPSEwKXt0aGlzLnByaW50PW4sdGhpcy5jYWxsYmFjaz1lfWxvZyguLi5lKXt0aGlzLnB1c2goImxvZyIsZSl9d2FybiguLi5lKXt0aGlzLnB1c2goIndhcm4iLGUpfWVycm9yKC4uLmUpe3RoaXMucHVzaCgiZXJyb3IiLGUpfWluZm8oLi4uZSl7dGhpcy5wdXNoKCJpbmZvIixlKX1wdXNoKGUsbil7dGhpcy5jYWxsYmFjayhlLHRoaXMubG9nTWVzc2FnZShuKSksdGhpcy5wcmludCYmY29uc29sZVtlXSguLi5uKX1sb2dNZXNzYWdlKGUpe2xldCBuPVtdO2ZvcihsZXQgdCBvZiBlKXN3aXRjaCh0eXBlb2YgdCl7Y2FzZSJzdHJpbmciOmNhc2UibnVtYmVyIjpuLnB1c2goIiIrdCk7YnJlYWs7Y2FzZSJ1bmRlZmluZWQiOm4ucHVzaCgidW5kZWZpbmVkIik7YnJlYWs7ZGVmYXVsdDp0cnl7bGV0IG89SlNPTi5zdHJpbmdpZnkodCxudWxsLDIpO28ubGVuZ3RoPjUwMCYmKG89by5zdWJzdHJpbmcoMCw1MDApKyIuLi4iKSxuLnB1c2gobyl9Y2F0Y2h7bi5wdXNoKCJbY2lyY3VsYXIgb2JqZWN0XSIpfX1yZXR1cm4gbi5qb2luKCIgIil9fTt0eXBlb2YgRGVubz4idSImJihzZWxmLkRlbm89e2FyZ3M6W10sYnVpbGQ6e2FyY2g6Ing4Nl82NCJ9LGVudjp7Z2V0KCl7fX19KTt2YXIgZD1uZXcgTWFwLGk9bmV3IE1hcDtmdW5jdGlvbiBzKHIpe3R5cGVvZiB3aW5kb3c8InUiJiZ3aW5kb3cucGFyZW50IT09d2luZG93P3dpbmRvdy5wYXJlbnQucG9zdE1lc3NhZ2UociwiKiIpOnNlbGYucG9zdE1lc3NhZ2Uocil9dmFyIGw9MDtzZWxmLnN5c2NhbGw9YXN5bmMociwuLi5lKT0+YXdhaXQgbmV3IFByb21pc2UoKG4sdCk9PntsKyssaS5zZXQobCx7cmVzb2x2ZTpuLHJlamVjdDp0fSkscyh7dHlwZToic3lzY2FsbCIsaWQ6bCxuYW1lOnIsYXJnczplfSl9KTt2YXIgdT1uZXcgTWFwO3NlbGYucmVxdWlyZT1yPT57bGV0IGU9dS5nZXQocik7aWYoIWUpdGhyb3cgbmV3IEVycm9yKGBEeW5hbWljYWxseSBpbXBvcnRpbmcgbm9uLXByZWxvYWRlZCBsaWJyYXJ5ICR7cn1gKTtyZXR1cm4gZX07c2VsZi5jb25zb2xlPW5ldyBhKChyLGUpPT57cyh7dHlwZToibG9nIixsZXZlbDpyLG1lc3NhZ2U6ZX0pfSwhMSk7ZnVuY3Rpb24gZyhyKXtyZXR1cm5gcmV0dXJuICgke3J9KVsiZGVmYXVsdCJdYH1zZWxmLmFkZEV2ZW50TGlzdGVuZXIoIm1lc3NhZ2UiLHI9PntjKGFzeW5jKCk9PntsZXQgZT1yLmRhdGE7c3dpdGNoKGUudHlwZSl7Y2FzZSJsb2FkIjp7bGV0IG49bmV3IEZ1bmN0aW9uKGcoZS5jb2RlKSk7ZC5zZXQoZS5uYW1lLG4oKSkscyh7dHlwZToiaW5pdGVkIixuYW1lOmUubmFtZX0pfWJyZWFrO2Nhc2UibG9hZC1kZXBlbmRlbmN5Ijp7bGV0IHQ9bmV3IEZ1bmN0aW9uKGByZXR1cm4gJHtlLmNvZGV9YCkoKTt1LnNldChlLm5hbWUsdCkscyh7dHlwZToiZGVwZW5kZW5jeS1pbml0ZWQiLG5hbWU6ZS5uYW1lfSl9YnJlYWs7Y2FzZSJpbnZva2UiOntsZXQgbj1kLmdldChlLm5hbWUpO2lmKCFuKXRocm93IG5ldyBFcnJvcihgRnVuY3Rpb24gbm90IGxvYWRlZDogJHtlLm5hbWV9YCk7dHJ5e2xldCB0PWF3YWl0IFByb21pc2UucmVzb2x2ZShuKC4uLmUuYXJnc3x8W10pKTtzKHt0eXBlOiJyZXN1bHQiLGlkOmUuaWQscmVzdWx0OnR9KX1jYXRjaCh0KXtzKHt0eXBlOiJyZXN1bHQiLGlkOmUuaWQsZXJyb3I6dC5tZXNzYWdlLHN0YWNrOnQuc3RhY2t9KX19YnJlYWs7Y2FzZSJzeXNjYWxsLXJlc3BvbnNlIjp7bGV0IG49ZS5pZCx0PWkuZ2V0KG4pO2lmKCF0KXRocm93IGNvbnNvbGUubG9nKCJDdXJyZW50IG91dHN0YW5kaW5nIHJlcXVlc3RzIixpLCJsb29raW5nIHVwIixuKSxFcnJvcigiSW52YWxpZCByZXF1ZXN0IGlkIik7aS5kZWxldGUobiksZS5lcnJvcj90LnJlamVjdChuZXcgRXJyb3IoZS5lcnJvcikpOnQucmVzb2x2ZShlLnJlc3VsdCl9YnJlYWt9fSl9KTt9KSgpOwogcmV0dXJuIG1vZDt9KSgp"
|
||||
}
|
@ -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)) {
|
@ -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
18
plugos/gen.ts
Normal 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);
|
@ -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: {
|
||||
|
@ -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!);
|
||||
|
@ -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`,
|
||||
);
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
`
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.`,
|
||||
);
|
||||
}
|
||||
|
@ -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
@ -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 => {
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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 = [];
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
```
|
||||
```
|
||||
|
@ -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";
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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) => {
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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";
|
||||
|
@ -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 you’re experiencing trouble, try the following:
|
||||
* Delete all files under `_plug` in your pages folder, e.g. with `rm -rf pages/_plug`.
|
||||
* Delete your `data.db`
|
||||
* Changes:
|
||||
* `PLUGS` is now longer required
|
||||
* `PLUGS` no longer supports `builtin:` plug URLs, all builtins are 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 you’re experiencing trouble, try the following:
|
||||
- Delete all files under `_plug` in your pages folder, e.g. with
|
||||
`rm -rf pages/_plug`.
|
||||
- Delete your `data.db`
|
||||
- Changes:
|
||||
- `PLUGS` is now longer required
|
||||
- `PLUGS` no longer supports `builtin:` plug URLs, all builtins are
|
||||
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
|
||||
|
@ -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 (don’t 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, don’t 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 (don’t 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, don’t 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
|
||||
|
@ -4,4 +4,3 @@ publishAll: true
|
||||
indexPage: Silver Bullet
|
||||
footerPage: website-footer
|
||||
```
|
||||
|
||||
|
@ -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
|
||||
```
|
||||
```
|
||||
|
@ -1,27 +1,51 @@
|
||||
What does Silver Bullet look like? Well, have a look around. **You’re looking at it at this very moment!** 🤯
|
||||
What does Silver Bullet look like? Well, have a look around. **You’re looking at
|
||||
it at this very moment!** 🤯
|
||||
|
||||
Note that what you’re 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 it’s 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 you’re 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 it’s
|
||||
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. Don’t worry, you won’t break anything, nothing is saved (just reload the page to see).
|
||||
|
||||
So, feel free to make some edits in this space. Don’t worry, you won’t 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 won’t 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 doesn’t 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 won’t 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 doesn’t 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 don’t get to fully experience in this environment, because they rely on a working back-end, such as:
|
||||
There are a few features you don’t get to fully experience in this environment,
|
||||
because they rely on a working back-end, such as:
|
||||
|
||||
* Using SB’s 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 SB’s functionality by installing additional [[🔌 Plugs]] (SB parlance for plug-ins) and writing your own.
|
||||
- Using SB’s 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 SB’s functionality by installing additional [[🔌
|
||||
Plugs]] (SB parlance for plug-ins) and writing your own.
|
||||
|
@ -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, that’s 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, that’s fancy language for “a note
|
||||
taking app with links.”
|
||||
|
||||
Here is a screenshot:
|
||||
|
||||
![Silver Bullet PWA screenshot](silverbullet-pwa.png)
|
||||
|
||||
At its core, SB is a Markdown editor that stores _pages_ (notes) as plain markdown files in a folder referred to as a _space_. Pages can be cross-linked using the `[[link to other page]]` syntax. However, once you leverage its various extensions (called _plugs_) it can feel more like a _knowledge platform_, allowing you to annotate, combine and query your accumulated knowledge in creative ways, specific to you. To get a good feel for it, [watch this video](https://youtu.be/RYdc3UF9gok).
|
||||
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!
|
||||
|
||||
Here’s 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!
|
||||
|
||||
Here’s a list of (non-built-in) plugs documented in this space (note the
|
||||
`#query` ... `/query` notation used):
|
||||
|
||||
<!-- #query page where type = "plug" order by name render [[template/plug]] -->
|
||||
* [[🔌 Backlinks]] by **Guillermo Vayá** ([repo](https://github.com/Willyfrog/silverbullet-backlinks))
|
||||
* [[🔌 Core]] by **Silver Bullet Authors** ([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
* [[🔌 Ghost]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-ghost))
|
||||
* [[🔌 Git]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-github))
|
||||
* [[🔌 Github]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-github))
|
||||
* [[🔌 Mattermost]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-mattermost))
|
||||
* [[🔌 Mount]] by **Zef Hemel** ([repo](https://github.com/silverbulletmd/silverbullet-mount))
|
||||
* [[🔌 Query]] by **Silver Bullet Authors** ([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
|
||||
- [[🔌 Backlinks]] by **Guillermo Vayá**
|
||||
([repo](https://github.com/Willyfrog/silverbullet-backlinks))
|
||||
- [[🔌 Core]] by **Silver Bullet Authors**
|
||||
([repo](https://github.com/silverbulletmd/silverbullet))
|
||||
- [[🔌 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 Bullet’s features](https://youtu.be/RYdc3UF9gok) — spoiler alert: it’s cool.
|
||||
* [A look the SilverBullet architecture](https://youtu.be/mXCGau05p5o) — spoiler alert: it’s plugs all the way down.
|
||||
- [A Tour of Silver Bullet’s features](https://youtu.be/RYdc3UF9gok) — spoiler
|
||||
alert: it’s cool.
|
||||
- [A look the SilverBullet architecture](https://youtu.be/mXCGau05p5o) — spoiler
|
||||
alert: it’s plugs all the way down.
|
||||
|
||||
## Principles
|
||||
|
||||
Some core principles that underly Silver Bullet’s philosophy:
|
||||
|
||||
* **Free and open source**. Silver Bullet is MIT licensed.
|
||||
* **The truth is in the markdown.** Markdown is simply text files, stored on disk. Nothing fancy. No proprietary formats or lock in. While SB uses a database for indexing and caching some data, all of that can be rebuilt from its markdown source at any time. If SB would ever go away, you can still read your pages with any text editor.
|
||||
* **Single, distraction-free mode.** SB doesn’t have a separate view and edit mode. It doesn’t have a “focus mode.” You’re always in focused edit mode, why wouldn’t you?
|
||||
* **Keyboard oriented**. You can use SB fully using the keyboard, typin’ the keys.
|
||||
* **Extend it your way**. SB is highly extensible with [[🔌 Plugs]], and you can customize it to your liking and your workflows.
|
||||
- **Free and open source**. Silver Bullet is MIT licensed.
|
||||
- **The truth is in the markdown.** Markdown is simply text files, stored on
|
||||
disk. Nothing fancy. No proprietary formats or lock in. While SB uses a
|
||||
database for indexing and caching some data, all of that can be rebuilt from
|
||||
its markdown source at any time. If SB would ever go away, you can still read
|
||||
your pages with any text editor.
|
||||
- **Single, distraction-free mode.** SB doesn’t have a separate view and edit
|
||||
mode. It doesn’t have a “focus mode.” You’re always in focused edit mode, why
|
||||
wouldn’t you?
|
||||
- **Keyboard oriented**. You can use SB fully using the keyboard, typin’ the
|
||||
keys.
|
||||
- **Extend it your way**. SB is highly extensible with [[🔌 Plugs]], and you can
|
||||
customize it to your liking and your workflows.
|
||||
|
||||
## 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 doesn’t offer any sort of authentication, to add basic password authentication, pass the `--password` flag.
|
||||
`--port` flag. By default SB doesn’t offer any sort of authentication, to add
|
||||
basic password authentication, pass the `--password` flag.
|
||||
|
||||
Once downloaded and booted, SB will print out a URL to open SB in your browser (spoiler alert: by default this will be http://localhost:3000 ).
|
||||
Once downloaded and booted, SB will print out a URL to open SB in your browser
|
||||
(spoiler alert: by default this will be http://localhost:3000 ).
|
||||
|
||||
That’s 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).
|
||||
|
@ -1 +1 @@
|
||||
Moved to [[Silver Bullet]]
|
||||
Moved to [[Silver Bullet]]
|
||||
|
@ -1 +1 @@
|
||||
* [[{{name}}]] by **{{author}}** ([repo]({{repo}}))
|
||||
- [[{{name}}]] by **{{author}}** ([repo]({{repo}}))
|
||||
|
@ -1,3 +1,3 @@
|
||||
{{#each .}}
|
||||
* [{{#if done}}x{{else}} {{/if}}] [[{{page}}@{{pos}}]] {{name}}
|
||||
{{/each}}
|
||||
|
||||
- [{{#if done}}x{{else}} {{/if}}] [[{{page}}@{{pos}}]] {{name}} {{/each}}
|
||||
|
@ -1,4 +0,0 @@
|
||||
Syup
|
||||
|
||||
![](attachment/bla)
|
||||
kljhaef
|
@ -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 Obsidian’s 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 Obsidian’s
|
||||
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).
|
||||
|
@ -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 -->
|
||||
|
@ -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 doesn’t 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 doesn’t
|
||||
apply.
|
||||
|
||||
However, snippets do support the special `|^|` placeholder for placing the
|
||||
cursor caret after injecting the snippet. If you leave it out, the cursor will
|
||||
simply be placed at the end, but if you like to insert the cursor elsewhere,
|
||||
that position can be set with the `|^|` placeholder.
|
||||
|
||||
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, it’s 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, it’s also possible to reuse templates that update dynamically (similar
|
||||
to [[🔌 Query]]). For this, you use the `#use` and `#use-verbose` directives.
|
||||
|
||||
In its most basic form:
|
||||
|
||||
<!-- #use [[template/plug]] -->
|
||||
<!-- /use -->
|
||||
|
||||
Upon load (or when updating materialized queries) the body of this dynamic section will be replaced with the content of the referenced template.
|
||||
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}}`: Today’s date in the usual YYYY-MM-DD format
|
||||
* `{{tomorrow}}`: Tomorrow’s date in the usual YYY-MM-DD format
|
||||
* `{{yesterday}}`: Yesterday’s date in the usual YYY-MM-DD format
|
||||
* `{{lastWeek}}`: Current date - 7 days
|
||||
* `{{page}}`: The name of the current page
|
||||
- `{{today}}`: Today’s date in the usual YYYY-MM-DD format
|
||||
- `{{tomorrow}}`: Tomorrow’s date in the usual YYY-MM-DD format
|
||||
- `{{yesterday}}`: Yesterday’s date in the usual YYY-MM-DD format
|
||||
- `{{lastWeek}}`: Current date - 7 days
|
||||
- `{{page}}`: The name of the current page
|
||||
|
@ -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 -->
|
||||
|
@ -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 -->
|
||||
|
@ -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 -->
|
||||
|
@ -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 -->
|
||||
|
@ -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 -->
|
||||
|
@ -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, it’s 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`. It’s 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, it’s 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`. It’s 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 it’s 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 it’s 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 space’s `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 space’s `PLUGS` page.
|
||||
|
||||
For development it’s 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 it’s 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 you’re 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.
|
||||
|
@ -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. Plug’s 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]] -->
|
||||
|
@ -1 +1,3 @@
|
||||
This is very much in progress, have a look at our [Github Issues](https://github.com/silverbulletmd/silverbullet/issues) page if you’d 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
|
||||
you’d like to help out!
|
||||
|
@ -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]]
|
||||
|
Loading…
Reference in New Issue
Block a user