Massive restructure of plugin API

This commit is contained in:
Zef Hemel
2022-10-14 15:11:33 +02:00
parent 982623fc38
commit 7d28b53b75
70 changed files with 826 additions and 969 deletions
+32
View File
@@ -0,0 +1,32 @@
import type { ParseTree } from "./lib/tree.ts";
import { ParsedQuery } from "./lib/query.ts";
export type AppEvent =
| "page:click"
| "page:complete"
| "page:load"
| "editor:init"
| "plugs:loaded";
export type QueryProviderEvent = {
query: ParsedQuery;
pageName: string;
};
export type ClickEvent = {
page: string;
pos: number;
metaKey: boolean;
ctrlKey: boolean;
altKey: boolean;
};
export type IndexEvent = {
name: string;
text: string;
};
export type IndexTreeEvent = {
name: string;
tree: ParseTree;
};
+11
View File
@@ -0,0 +1,11 @@
export function niceDate(d: Date): string {
function pad(n: number) {
let s = String(n);
if (s.length === 1) {
s = "0" + s;
}
return s;
}
return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate());
}
+168
View File
@@ -0,0 +1,168 @@
import {
addParentPointers,
collectNodesMatching,
ParseTree,
renderToText,
} from "./tree.ts";
export const queryRegex =
/(<!--\s*#query\s+(.+?)-->)(.+?)(<!--\s*\/query\s*-->)/gs;
export const directiveStartRegex = /<!--\s*#([\w\-]+)\s+(.+?)-->/s;
export const directiveEndRegex = /<!--\s*\/([\w\-]+)\s*-->/s;
export type QueryFilter = {
op: string;
prop: string;
value: any;
};
export type ParsedQuery = {
table: string;
orderBy?: string;
orderDesc?: boolean;
limit?: number;
filter: QueryFilter[];
select?: string[];
render?: string;
};
export function applyQuery<T>(parsedQuery: ParsedQuery, records: T[]): T[] {
let resultRecords: any[] = [];
if (parsedQuery.filter.length === 0) {
resultRecords = records.slice();
} else {
recordLoop:
for (const record of records) {
const recordAny: any = record;
for (let { op, prop, value } of parsedQuery.filter) {
switch (op) {
case "=": {
const recordPropVal = recordAny[prop];
if (Array.isArray(recordPropVal) && !Array.isArray(value)) {
// Record property is an array, and value is a scalar: find the value in the array
if (!recordPropVal.includes(value)) {
continue recordLoop;
}
} else if (Array.isArray(recordPropVal) && Array.isArray(value)) {
// Record property is an array, and value is an array: find the value in the array
if (!recordPropVal.some((v) => value.includes(v))) {
continue recordLoop;
}
} else if (!(recordPropVal == value)) {
// Both are scalars: exact value
continue recordLoop;
}
break;
}
case "!=":
if (!(recordAny[prop] != value)) {
continue recordLoop;
}
break;
case "<":
if (!(recordAny[prop] < value)) {
continue recordLoop;
}
break;
case "<=":
if (!(recordAny[prop] <= value)) {
continue recordLoop;
}
break;
case ">":
if (!(recordAny[prop] > value)) {
continue recordLoop;
}
break;
case ">=":
if (!(recordAny[prop] >= value)) {
continue recordLoop;
}
break;
case "=~":
// TODO: Cache regexps somehow
if (!new RegExp(value).exec(recordAny[prop])) {
continue recordLoop;
}
break;
case "!=~":
if (new RegExp(value).exec(recordAny[prop])) {
continue recordLoop;
}
break;
case "in":
if (!value.includes(recordAny[prop])) {
continue recordLoop;
}
break;
}
}
resultRecords.push(recordAny);
}
}
// Now the sorting
if (parsedQuery.orderBy) {
resultRecords = resultRecords.sort((a: any, b: any) => {
const orderBy = parsedQuery.orderBy!;
const orderDesc = parsedQuery.orderDesc!;
if (a[orderBy] === b[orderBy]) {
return 0;
}
if (a[orderBy] < b[orderBy]) {
return orderDesc ? 1 : -1;
} else {
return orderDesc ? -1 : 1;
}
});
}
if (parsedQuery.limit) {
resultRecords = resultRecords.slice(0, parsedQuery.limit);
}
if (parsedQuery.select) {
resultRecords = resultRecords.map((rec) => {
let newRec: any = {};
for (let k of parsedQuery.select!) {
newRec[k] = rec[k];
}
return newRec;
});
}
return resultRecords;
}
export function removeQueries(pt: ParseTree) {
addParentPointers(pt);
collectNodesMatching(pt, (t) => {
if (t.type !== "CommentBlock") {
return false;
}
let text = t.children![0].text!;
let match = directiveStartRegex.exec(text);
if (!match) {
return false;
}
let directiveType = match[1];
let parentChildren = t.parent!.children!;
let index = parentChildren.indexOf(t);
let nodesToReplace: ParseTree[] = [];
for (let i = index + 1; i < parentChildren.length; i++) {
let n = parentChildren[i];
if (n.type === "CommentBlock") {
let text = n.children![0].text!;
let match = directiveEndRegex.exec(text);
if (match && match[1] === directiveType) {
break;
}
}
nodesToReplace.push(n);
}
let renderedText = nodesToReplace.map(renderToText).join("");
parentChildren.splice(index + 1, nodesToReplace.length, {
text: new Array(renderedText.length + 1).join(" "),
});
return true;
});
}
+25
View File
@@ -0,0 +1,25 @@
import { readYamlPage } from "./yaml_page.ts";
// Read SECRETS page and retrieve specific set of secret keys
// Note: in this implementation there's no encryption employed at all so it's just a matter
// of not decising this SECRETS page to other places
export async function readSecrets(keys: string[]): Promise<any[]> {
try {
let allSecrets = await readYamlPage("SECRETS", ["yaml", "secrets"]);
let collectedSecrets: any[] = [];
for (let key of keys) {
let secret = allSecrets[key];
if (secret) {
collectedSecrets.push(secret);
} else {
throw new Error(`No such secret: ${key}`);
}
}
return collectedSecrets;
} catch (e: any) {
if (e.message === "Page not found") {
throw new Error(`No such secret: ${keys[0]}`);
}
throw e;
}
}
+63
View File
@@ -0,0 +1,63 @@
import { readYamlPage } from "./yaml_page.ts";
import { notifyUser } from "./util.ts";
import * as YAML from "yaml";
import { space } from "$sb/silverbullet-syscall/mod.ts";
/**
* Convenience function to read a specific set of settings from the `SETTINGS` page as well as default values
* in case they are not specified.
* Example: `await readSettings({showPreview: false})` will return an object like `{showPreview: false}` (or `true`)
* in case this setting is specifically set in the `SETTINGS` page.
*
* @param settings object with settings to fetch and their default values
* @returns an object with the same shape as `settings` but with non-default values override based on `SETTINGS`
*/
const SETTINGS_PAGE = "SETTINGS";
export async function readSettings<T extends object>(settings: T): Promise<T> {
try {
const allSettings = (await readYamlPage(SETTINGS_PAGE, ["yaml"])) || {};
// TODO: I'm sure there's a better way to type this than "any"
const collectedSettings: any = {};
for (let [key, defaultVal] of Object.entries(settings)) {
if (key in allSettings) {
collectedSettings[key] = allSettings[key];
} else {
collectedSettings[key] = defaultVal;
}
}
return collectedSettings as T;
} catch (e: any) {
if (e.message === "Page not found") {
// No settings yet, return default values for all
return settings;
}
throw e;
}
}
/**
* Convenience function to write a specific set of settings from the `SETTINGS` page.
* If the SETTiNGS page doesn't exist it will create it.
* @param settings
*/
export async function writeSettings<T extends object>(settings: T) {
let readSettings = {};
try {
readSettings = (await readYamlPage(SETTINGS_PAGE, ["yaml"])) || {};
} catch {
await notifyUser("Creating a new SETTINGS page...", "info");
}
const writeSettings: any = { ...readSettings, ...settings };
// const doc = new YAML.Document();
// doc.contents = writeSettings;
const contents =
`This page contains settings for configuring SilverBullet and its Plugs.\nAny changes outside of the yaml block will be overwritten.\n\`\`\`yaml\n${
YAML.stringify(
writeSettings,
)
}\n\`\`\``; // might need \r\n for windows?
await space.writePage(SETTINGS_PAGE, contents);
}
+79
View File
@@ -0,0 +1,79 @@
// import { parse } from "./parse_tree.ts";
import {
addParentPointers,
collectNodesMatching,
findParentMatching,
nodeAtPos,
removeParentPointers,
renderToText,
replaceNodesMatching,
} from "./tree.ts";
import wikiMarkdownLang from "../../common/parser.ts";
import { assertEquals, assertNotEquals } from "../../test_deps.ts";
import { parse } from "../../common/parse_tree.ts";
const mdTest1 = `
# Heading
## Sub _heading_ cool
Hello, this is some **bold** text and *italic*. And [a link](http://zef.me).
%% My comment here
%% And second line
And an @mention
http://zef.plus
- This is a list [[PageLink]]
- With another item
- TODOs:
- [ ] A task that's not yet done
- [x] Hello
- And a _third_ one [[Wiki Page]] yo
`;
const mdTest2 = `
Hello
* Item 1
*
Sup`;
const mdTest3 = `
\`\`\`yaml
name: something
\`\`\`
`;
Deno.test("Run a Node sandbox", () => {
const lang = wikiMarkdownLang([]);
let mdTree = parse(lang, mdTest1);
addParentPointers(mdTree);
// console.log(JSON.stringify(mdTree, null, 2));
let wikiLink = nodeAtPos(mdTree, mdTest1.indexOf("Wiki Page"))!;
assertEquals(wikiLink.type, "WikiLink");
assertNotEquals(
findParentMatching(wikiLink, (n) => n.type === "BulletList"),
null,
);
let allTodos = collectNodesMatching(mdTree, (n) => n.type === "Task");
assertEquals(allTodos.length, 2);
// Render back into markdown should be equivalent
assertEquals(renderToText(mdTree), mdTest1);
removeParentPointers(mdTree);
replaceNodesMatching(mdTree, (n) => {
if (n.type === "Task") {
return {
type: "Tosk",
};
}
});
console.log(JSON.stringify(mdTree, null, 2));
let mdTree3 = parse(lang, mdTest3);
console.log(JSON.stringify(mdTree3, null, 2));
});
+150
View File
@@ -0,0 +1,150 @@
export type ParseTree = {
type?: string; // undefined === text node
from?: number;
to?: number;
text?: string;
children?: ParseTree[];
// Only present after running addParentPointers
parent?: ParseTree;
};
export function addParentPointers(tree: ParseTree) {
if (!tree.children) {
return;
}
for (let child of tree.children) {
if (child.parent) {
// Already added parent pointers before
return;
}
child.parent = tree;
addParentPointers(child);
}
}
export function removeParentPointers(tree: ParseTree) {
delete tree.parent;
if (!tree.children) {
return;
}
for (let child of tree.children) {
removeParentPointers(child);
}
}
export function findParentMatching(
tree: ParseTree,
matchFn: (tree: ParseTree) => boolean,
): ParseTree | null {
let node = tree.parent;
while (node) {
if (matchFn(node)) {
return node;
}
node = node.parent;
}
return null;
}
export function collectNodesOfType(
tree: ParseTree,
nodeType: string,
): ParseTree[] {
return collectNodesMatching(tree, (n) => n.type === nodeType);
}
export function collectNodesMatching(
tree: ParseTree,
matchFn: (tree: ParseTree) => boolean,
): ParseTree[] {
if (matchFn(tree)) {
return [tree];
}
let results: ParseTree[] = [];
if (tree.children) {
for (let child of tree.children) {
results = [...results, ...collectNodesMatching(child, matchFn)];
}
}
return results;
}
// return value: returning undefined = not matched, continue, null = delete, new node = replace
export function replaceNodesMatching(
tree: ParseTree,
substituteFn: (tree: ParseTree) => ParseTree | null | undefined,
) {
if (tree.children) {
let children = tree.children.slice();
for (let child of children) {
let subst = substituteFn(child);
if (subst !== undefined) {
let pos = tree.children.indexOf(child);
if (subst) {
tree.children.splice(pos, 1, subst);
} else {
// null = delete
tree.children.splice(pos, 1);
}
} else {
replaceNodesMatching(child, substituteFn);
}
}
}
}
export function findNodeMatching(
tree: ParseTree,
matchFn: (tree: ParseTree) => boolean,
): ParseTree | null {
return collectNodesMatching(tree, matchFn)[0];
}
export function findNodeOfType(
tree: ParseTree,
nodeType: string,
): ParseTree | null {
return collectNodesMatching(tree, (n) => n.type === nodeType)[0];
}
export function traverseTree(
tree: ParseTree,
// Return value = should stop traversal?
matchFn: (tree: ParseTree) => boolean,
): void {
// Do a collect, but ignore the result
collectNodesMatching(tree, matchFn);
}
// Finds non-text node at position
export function nodeAtPos(tree: ParseTree, pos: number): ParseTree | null {
if (pos < tree.from! || pos > tree.to!) {
return null;
}
if (!tree.children) {
return tree;
}
for (let child of tree.children) {
let n = nodeAtPos(child, pos);
if (n && n.text !== undefined) {
// Got a text node, let's return its parent
return tree;
} else if (n) {
// Got it
return n;
}
}
return null;
}
// Turn ParseTree back into text
export function renderToText(tree: ParseTree): string {
let pieces: string[] = [];
if (tree.text !== undefined) {
return tree.text;
}
for (let child of tree.children!) {
pieces.push(renderToText(child));
}
return pieces.join("");
}
+36
View File
@@ -0,0 +1,36 @@
import { editor } from "$sb/silverbullet-syscall/mod.ts";
export async function replaceAsync(
str: string,
regex: RegExp,
asyncFn: (match: string, ...args: any[]) => Promise<string>,
) {
const promises: Promise<string>[] = [];
str.replace(regex, (match: string, ...args: any[]): string => {
const promise = asyncFn(match, ...args);
promises.push(promise);
return "";
});
const data = await Promise.all(promises);
return str.replace(regex, () => data.shift()!);
}
export function isServer() {
return (
typeof window === "undefined" || typeof window.document === "undefined"
); // if something defines window the same way as the browser, this will fail.
}
// this helps keep if's condition as positive
export function isBrowser() {
return !isServer();
}
export function notifyUser(message: string, type?: "info" | "error") {
if (isBrowser()) {
return editor.flashNotification(message, type);
}
const log = type === "error" ? console.error : console.log;
log(message); // we should end up sending the message to the user, users dont read logs.
return;
}
+49
View File
@@ -0,0 +1,49 @@
import { findNodeOfType, traverseTree } from "$sb/lib/tree.ts";
import { markdown, space } from "$sb/silverbullet-syscall/mod.ts";
import * as YAML from "yaml";
export async function readYamlPage(
pageName: string,
allowedLanguages = ["yaml"],
): Promise<any> {
const text = await space.readPage(pageName);
const tree = await markdown.parseMarkdown(text);
let data: any = {};
traverseTree(tree, (t): boolean => {
// Find a fenced code block
if (t.type !== "FencedCode") {
return false;
}
const codeInfoNode = findNodeOfType(t, "CodeInfo");
if (!codeInfoNode) {
return false;
}
if (!allowedLanguages.includes(codeInfoNode.children![0].text!)) {
return false;
}
const codeTextNode = findNodeOfType(t, "CodeText");
if (!codeTextNode) {
// Honestly, this shouldn't happen
return false;
}
const codeText = codeTextNode.children![0].text!;
try {
data = YAML.parse(codeText);
} catch (e: any) {
console.error("YAML Page parser error", e);
throw new Error(`YAML Error: ${e.message}`);
}
return true;
});
return data;
}
export async function writeYamlPage(
pageName: string,
data: any,
): Promise<void> {
const text = YAML.stringify(data);
await space.writePage(pageName, "```yaml\n" + text + "\n```");
}
+15
View File
@@ -0,0 +1,15 @@
import { base64Decode } from "../../plugos/asset_bundle/base64.ts";
import { syscall } from "./syscall.ts";
export async function readAsset(
name: string,
encoding: "utf8" | "dataurl" = "utf8",
): Promise<string> {
const data = await syscall("asset.readAsset", name);
switch (encoding) {
case "utf8":
return new TextDecoder().decode(base64Decode(data));
case "dataurl":
return "data:application/octet-stream," + data;
}
}
+29
View File
@@ -0,0 +1,29 @@
import { syscall } from "./syscall.ts";
export function dispatchEvent(
eventName: string,
data: any,
timeout?: number,
): Promise<any[]> {
return new Promise((resolve, reject) => {
let timeouter: any = -1;
if (timeout) {
timeouter = setTimeout(() => {
console.log("Timeout!");
reject("timeout");
}, timeout);
}
syscall("event.dispatch", eventName, data)
.then((r) => {
if (timeouter !== -1) {
clearTimeout(timeouter);
}
resolve(r);
})
.catch(reject);
});
}
export function listEvents(): Promise<string[]> {
return syscall("event.list");
}
+36
View File
@@ -0,0 +1,36 @@
import { syscall } from "./syscall.ts";
export type FileMeta = {
name: string;
lastModified: number;
};
export function readFile(
path: string,
encoding: "utf8" | "dataurl" = "utf8",
): Promise<string> {
return syscall("fs.readFile", path, encoding);
}
export function getFileMeta(path: string): Promise<FileMeta> {
return syscall("fs.getFileMeta", path);
}
export function writeFile(
path: string,
text: string,
encoding: "utf8" | "dataurl" = "utf8",
): Promise<FileMeta> {
return syscall("fs.writeFile", path, text, encoding);
}
export function deleteFile(path: string): Promise<void> {
return syscall("fs.deleteFile", path);
}
export function listFiles(
dirName: string,
recursive = false,
): Promise<FileMeta[]> {
return syscall("fs.listFiles", dirName, recursive);
}
+13
View File
@@ -0,0 +1,13 @@
import { syscall } from "./syscall.ts";
export function fullTextIndex(key: string, value: string) {
return syscall("fulltext.index", key, value);
}
export function fullTextDelete(key: string) {
return syscall("fulltext.delete", key);
}
export function fullTextSearch(phrase: string, limit = 100) {
return syscall("fulltext.search", phrase, limit);
}
+8
View File
@@ -0,0 +1,8 @@
export * as asset from "./asset.ts";
export * as events from "./event.ts";
export * as fs from "./fs.ts";
export * as sandbox from "./sandbox.ts";
export * as fulltext from "./fulltext.ts";
export * as shell from "./shell.ts";
export * as store from "./store.ts";
export * from "./syscall.ts";
+5
View File
@@ -0,0 +1,5 @@
import type { LogEntry } from "../../plugos/sandbox.ts";
export function getLogs(): Promise<LogEntry[]> {
return syscall("sandbox.getLogs");
}
+8
View File
@@ -0,0 +1,8 @@
import { syscall } from "./syscall.ts";
export function run(
cmd: string,
args: string[],
): Promise<{ stdout: string; stderr: string }> {
return syscall("shell.run", cmd, args);
}
+54
View File
@@ -0,0 +1,54 @@
import { syscall } from "./syscall.ts";
export type KV = {
key: string;
value: any;
};
export type Query = {
filter?: Filter[];
orderBy?: string;
orderDesc?: boolean;
limit?: number;
select?: string[];
};
export type Filter = {
op: string;
prop: string;
value: any;
};
export function set(key: string, value: any): Promise<void> {
return syscall("store.set", key, value);
}
export function batchSet(kvs: KV[]): Promise<void> {
return syscall("store.batchSet", kvs);
}
export function get(key: string): Promise<any> {
return syscall("store.get", key);
}
export function del(key: string): Promise<void> {
return syscall("store.delete", key);
}
export function batchDel(keys: string[]): Promise<void> {
return syscall("store.batchDelete", keys);
}
export function queryPrefix(
prefix: string,
): Promise<{ key: string; value: any }[]> {
return syscall("store.queryPrefix", prefix);
}
export function deletePrefix(prefix: string): Promise<void> {
return syscall("store.deletePrefix", prefix);
}
export function deleteAll(): Promise<void> {
return syscall("store.deleteAll");
}
+5
View File
@@ -0,0 +1,5 @@
declare global {
function syscall(name: string, ...args: any[]): Promise<any>;
}
export const syscall = self.syscall;
@@ -0,0 +1,13 @@
import { syscall } from "./syscall.ts";
export function set(key: string, value: any): Promise<void> {
return syscall("clientStore.set", key, value);
}
export function get(key: string): Promise<any> {
return syscall("clientStore.get", key);
}
export function del(key: string): Promise<void> {
return syscall("clientStore.delete", key);
}
+116
View File
@@ -0,0 +1,116 @@
import { syscall } from "./syscall.ts";
import { FilterOption } from "../../common/types.ts";
export function getCurrentPage(): Promise<string> {
return syscall("editor.getCurrentPage");
}
export function setPage(newName: string): Promise<void> {
return syscall("editor.setPage", newName);
}
export function getText(): Promise<string> {
return syscall("editor.getText");
}
export function getCursor(): Promise<number> {
return syscall("editor.getCursor");
}
export function getSelection(): Promise<{ from: number; to: number }> {
return syscall("editor.getSelection");
}
export function setSelection(from: number, to: number): Promise<void> {
return syscall("editor.setSelection", from, to);
}
export function save(): Promise<void> {
return syscall("editor.save");
}
export function navigate(
name: string,
pos?: string | number,
replaceState = false,
): Promise<void> {
return syscall("editor.navigate", name, pos, replaceState);
}
export function reloadPage(): Promise<void> {
return syscall("editor.reloadPage");
}
export function openUrl(url: string): Promise<void> {
return syscall("editor.openUrl", url);
}
export function flashNotification(
message: string,
type: "info" | "error" = "info",
): Promise<void> {
return syscall("editor.flashNotification", message, type);
}
export function filterBox(
label: string,
options: FilterOption[],
helpText = "",
placeHolder = "",
): Promise<FilterOption | undefined> {
return syscall("editor.filterBox", label, options, helpText, placeHolder);
}
export function showPanel(
id: "lhs" | "rhs" | "bhs" | "modal",
mode: number,
html: string,
script = "",
): Promise<void> {
return syscall("editor.showPanel", id, mode, html, script);
}
export function hidePanel(id: "lhs" | "rhs" | "bhs" | "modal"): Promise<void> {
return syscall("editor.hidePanel", id);
}
export function insertAtPos(text: string, pos: number): Promise<void> {
return syscall("editor.insertAtPos", text, pos);
}
export function replaceRange(
from: number,
to: number,
text: string,
): Promise<void> {
return syscall("editor.replaceRange", from, to, text);
}
export function moveCursor(pos: number): Promise<void> {
return syscall("editor.moveCursor", pos);
}
export function insertAtCursor(text: string): Promise<void> {
return syscall("editor.insertAtCursor", text);
}
export function matchBefore(
re: string,
): Promise<{ from: number; to: number; text: string } | null> {
return syscall("editor.matchBefore", re);
}
export function dispatch(change: any): Promise<void> {
return syscall("editor.dispatch", change);
}
export function prompt(
message: string,
defaultValue = "",
): Promise<string | undefined> {
return syscall("editor.prompt", message, defaultValue);
}
export function enableReadOnlyMode(enabled: boolean) {
return syscall("editor.enableReadOnlyMode", enabled);
}
+54
View File
@@ -0,0 +1,54 @@
import type { Query } from "../plugos-syscall/store.ts";
import { syscall } from "./syscall.ts";
export type KV = {
key: string;
value: any;
};
export function set(
page: string,
key: string,
value: any,
): Promise<void> {
return syscall("index.set", page, key, value);
}
export function batchSet(page: string, kvs: KV[]): Promise<void> {
return syscall("index.batchSet", page, kvs);
}
export function get(page: string, key: string): Promise<any> {
return syscall("index.get", page, key);
}
export function del(page: string, key: string): Promise<void> {
return syscall("index.delete", page, key);
}
export function queryPrefix(
prefix: string,
): Promise<{ key: string; page: string; value: any }[]> {
return syscall("index.queryPrefix", prefix);
}
export function query(
query: Query,
): Promise<{ key: string; page: string; value: any }[]> {
return syscall("index.query", query);
}
export function clearPageIndexForPage(page: string): Promise<void> {
return syscall("index.clearPageIndexForPage", page);
}
export function deletePrefixForPage(
page: string,
prefix: string,
): Promise<void> {
return syscall("index.deletePrefixForPage", page, prefix);
}
export function clearPageIndex(): Promise<void> {
return syscall("index.clearPageIndex");
}
@@ -0,0 +1,7 @@
import { syscall } from "./syscall.ts";
import type { ParseTree } from "$sb/lib/tree.ts";
export function parseMarkdown(text: string): Promise<ParseTree> {
return syscall("markdown.parseMarkdown", text);
}
+7
View File
@@ -0,0 +1,7 @@
export * as clientStore from "./clientStore.ts";
export * as editor from "./editor.ts";
export * as index from "./index.ts";
export * as markdown from "./markdown.ts";
export * as sandbox from "./sandbox.ts";
export * as space from "./space.ts";
export * as system from "./system.ts";
+5
View File
@@ -0,0 +1,5 @@
import type { LogEntry } from "../../plugos/sandbox.ts";
export function getServerLogs(): Promise<LogEntry[]> {
return syscall("sandbox.getServerLogs");
}
+54
View File
@@ -0,0 +1,54 @@
import { syscall } from "./syscall.ts";
import { AttachmentMeta, PageMeta } from "../../common/types.ts";
export function listPages(unfiltered = false): Promise<PageMeta[]> {
return syscall("space.listPages", unfiltered);
}
export function getPageMeta(name: string): Promise<PageMeta> {
return syscall("space.getPageMeta", name);
}
export function readPage(
name: string,
): Promise<string> {
return syscall("space.readPage", name);
}
export function writePage(name: string, text: string): Promise<PageMeta> {
return syscall("space.writePage", name, text);
}
export function deletePage(name: string): Promise<void> {
return syscall("space.deletePage", name);
}
export function listPlugs(): Promise<string[]> {
return syscall("space.listPlugs");
}
export function listAttachments(): Promise<PageMeta[]> {
return syscall("space.listAttachments");
}
export function getAttachmentMeta(name: string): Promise<AttachmentMeta> {
return syscall("space.getAttachmentMeta", name);
}
export function readAttachment(
name: string,
): Promise<string> {
return syscall("space.readAttachment", name);
}
export function writeAttachment(
name: string,
encoding: "string" | "dataurl",
data: string,
): Promise<AttachmentMeta> {
return syscall("space.writeAttachment", name, encoding, data);
}
export function deleteAttachment(name: string): Promise<void> {
return syscall("space.deleteAttachment", name);
}
+16
View File
@@ -0,0 +1,16 @@
declare global {
function syscall(name: string, ...args: any[]): Promise<any>;
}
// This is the case when running tests only, so giving it a dummy syscall function
if (typeof self === "undefined") {
// @ts-ignore: test
// deno-lint-ignore no-global-assign
self = {
syscall: () => {
throw new Error("Not implemented here");
},
};
}
export const syscall = self.syscall;
+24
View File
@@ -0,0 +1,24 @@
import type { CommandDef } from "../../web/hooks/command.ts";
import { syscall } from "./syscall.ts";
export function invokeFunction(
env: string,
name: string,
...args: any[]
): Promise<any> {
return syscall("system.invokeFunction", env, name, ...args);
}
// Only available on the client
export function invokeCommand(name: string): Promise<any> {
return syscall("system.invokeCommand", name);
}
// Only available on the client
export function listCommands(): Promise<{ [key: string]: CommandDef }> {
return syscall("system.listCommands");
}
export function reloadPlugs() {
syscall("system.reloadPlugs");
}