1
0

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

View File

@ -1,4 +1,4 @@
import { ParseTree } from "./tree.ts"; import { ParseTree } from "$sb/lib/tree.ts";
import type { SyntaxNode } from "./deps.ts"; import type { SyntaxNode } from "./deps.ts";
import type { Language } from "./deps.ts"; import type { Language } from "./deps.ts";

View File

@ -1,7 +1,7 @@
import { SysCallMapping } from "../../plugos/system.ts"; import { SysCallMapping } from "../../plugos/system.ts";
import { parse } from "../parse_tree.ts"; import { parse } from "../parse_tree.ts";
import { Language } from "../deps.ts"; import { Language } from "../deps.ts";
import type { ParseTree } from "../tree.ts"; import type { ParseTree } from "$sb/lib/tree.ts";
export function markdownSyscalls(lang: Language): SysCallMapping { export function markdownSyscalls(lang: Language): SysCallMapping {
return { return {

View File

@ -10,7 +10,7 @@
"@lezer/highlight": "https://esm.sh/@lezer/highlight@1.1.1?external=@lezer/common", "@lezer/highlight": "https://esm.sh/@lezer/highlight@1.1.1?external=@lezer/common",
"@codemirror/autocomplete": "https://esm.sh/@codemirror/autocomplete@6.3.0?external=@codemirror/state,@lezer/common", "@codemirror/autocomplete": "https://esm.sh/@codemirror/autocomplete@6.3.0?external=@codemirror/state,@lezer/common",
"@codemirror/lint": "https://esm.sh/@codemirror/lint@6.0.0?external=@codemirror/state,@lezer/common", "@codemirror/lint": "https://esm.sh/@codemirror/lint@6.0.0?external=@codemirror/state,@lezer/common",
"$sb/": "./syscall/", "$sb/": "./plug-api/",
"handlebars": "https://esm.sh/handlebars", "handlebars": "https://esm.sh/handlebars",
"@lezer/lr": "https://esm.sh/@lezer/lr", "@lezer/lr": "https://esm.sh/@lezer/lr",
"yaml": "https://deno.land/std/encoding/yaml.ts" "yaml": "https://deno.land/std/encoding/yaml.ts"

View File

@ -1,4 +1,5 @@
import type { ParseTree } from "../common/tree.ts"; import type { ParseTree } from "./lib/tree.ts";
import { ParsedQuery } from "./lib/query.ts";
export type AppEvent = export type AppEvent =
| "page:click" | "page:click"
@ -7,6 +8,11 @@ export type AppEvent =
| "editor:init" | "editor:init"
| "plugs:loaded"; | "plugs:loaded";
export type QueryProviderEvent = {
query: ParsedQuery;
pageName: string;
};
export type ClickEvent = { export type ClickEvent = {
page: string; page: string;
pos: number; pos: number;

168
plug-api/lib/query.ts Normal file
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;
});
}

View File

@ -2,7 +2,7 @@ import { readYamlPage } from "./yaml_page.ts";
import { notifyUser } from "./util.ts"; import { notifyUser } from "./util.ts";
import * as YAML from "yaml"; import * as YAML from "yaml";
import { writePage } from "../../syscall/silverbullet-syscall/space.ts"; 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 * Convenience function to read a specific set of settings from the `SETTINGS` page as well as default values
@ -18,9 +18,9 @@ const SETTINGS_PAGE = "SETTINGS";
export async function readSettings<T extends object>(settings: T): Promise<T> { export async function readSettings<T extends object>(settings: T): Promise<T> {
try { try {
let allSettings = (await readYamlPage(SETTINGS_PAGE, ["yaml"])) || {}; const allSettings = (await readYamlPage(SETTINGS_PAGE, ["yaml"])) || {};
// TODO: I'm sure there's a better way to type this than "any" // TODO: I'm sure there's a better way to type this than "any"
let collectedSettings: any = {}; const collectedSettings: any = {};
for (let [key, defaultVal] of Object.entries(settings)) { for (let [key, defaultVal] of Object.entries(settings)) {
if (key in allSettings) { if (key in allSettings) {
collectedSettings[key] = allSettings[key]; collectedSettings[key] = allSettings[key];
@ -47,10 +47,10 @@ export async function writeSettings<T extends object>(settings: T) {
let readSettings = {}; let readSettings = {};
try { try {
readSettings = (await readYamlPage(SETTINGS_PAGE, ["yaml"])) || {}; readSettings = (await readYamlPage(SETTINGS_PAGE, ["yaml"])) || {};
} catch (e: any) { } catch {
await notifyUser("Creating a new SETTINGS page...", "info"); await notifyUser("Creating a new SETTINGS page...", "info");
} }
const writeSettings = { ...readSettings, ...settings }; const writeSettings: any = { ...readSettings, ...settings };
// const doc = new YAML.Document(); // const doc = new YAML.Document();
// doc.contents = writeSettings; // doc.contents = writeSettings;
const contents = const contents =
@ -59,5 +59,5 @@ export async function writeSettings<T extends object>(settings: T) {
writeSettings, writeSettings,
) )
}\n\`\`\``; // might need \r\n for windows? }\n\`\`\``; // might need \r\n for windows?
await writePage(SETTINGS_PAGE, contents); await space.writePage(SETTINGS_PAGE, contents);
} }

View File

@ -1,4 +1,4 @@
import { parse } from "./parse_tree.ts"; // import { parse } from "./parse_tree.ts";
import { import {
addParentPointers, addParentPointers,
collectNodesMatching, collectNodesMatching,
@ -8,8 +8,9 @@ import {
renderToText, renderToText,
replaceNodesMatching, replaceNodesMatching,
} from "./tree.ts"; } from "./tree.ts";
import wikiMarkdownLang from "./parser.ts"; import wikiMarkdownLang from "../../common/parser.ts";
import { assertEquals, assertNotEquals } from "../test_deps.ts"; import { assertEquals, assertNotEquals } from "../../test_deps.ts";
import { parse } from "../../common/parse_tree.ts";
const mdTest1 = ` const mdTest1 = `
# Heading # Heading

View File

@ -1,4 +1,4 @@
import { flashNotification } from "../../syscall/silverbullet-syscall/editor.ts"; import { editor } from "$sb/silverbullet-syscall/mod.ts";
export async function replaceAsync( export async function replaceAsync(
str: string, str: string,
@ -26,9 +26,9 @@ export function isBrowser() {
return !isServer(); return !isServer();
} }
export async function notifyUser(message: string, type?: "info" | "error") { export function notifyUser(message: string, type?: "info" | "error") {
if (isBrowser()) { if (isBrowser()) {
return flashNotification(message, type); return editor.flashNotification(message, type);
} }
const log = type === "error" ? console.error : console.log; const log = type === "error" ? console.error : console.log;
log(message); // we should end up sending the message to the user, users dont read logs. log(message); // we should end up sending the message to the user, users dont read logs.

View File

@ -1,17 +1,13 @@
import { findNodeOfType, traverseTree } from "../../common/tree.ts"; import { findNodeOfType, traverseTree } from "$sb/lib/tree.ts";
import { parseMarkdown } from "../../syscall/silverbullet-syscall/markdown.ts"; import { markdown, space } from "$sb/silverbullet-syscall/mod.ts";
import {
readPage,
writePage,
} from "../../syscall/silverbullet-syscall/space.ts";
import * as YAML from "yaml"; import * as YAML from "yaml";
export async function readYamlPage( export async function readYamlPage(
pageName: string, pageName: string,
allowedLanguages = ["yaml"], allowedLanguages = ["yaml"],
): Promise<any> { ): Promise<any> {
const { text } = await readPage(pageName); const text = await space.readPage(pageName);
let tree = await parseMarkdown(text); const tree = await markdown.parseMarkdown(text);
let data: any = {}; let data: any = {};
traverseTree(tree, (t): boolean => { traverseTree(tree, (t): boolean => {
@ -19,19 +15,19 @@ export async function readYamlPage(
if (t.type !== "FencedCode") { if (t.type !== "FencedCode") {
return false; return false;
} }
let codeInfoNode = findNodeOfType(t, "CodeInfo"); const codeInfoNode = findNodeOfType(t, "CodeInfo");
if (!codeInfoNode) { if (!codeInfoNode) {
return false; return false;
} }
if (!allowedLanguages.includes(codeInfoNode.children![0].text!)) { if (!allowedLanguages.includes(codeInfoNode.children![0].text!)) {
return false; return false;
} }
let codeTextNode = findNodeOfType(t, "CodeText"); const codeTextNode = findNodeOfType(t, "CodeText");
if (!codeTextNode) { if (!codeTextNode) {
// Honestly, this shouldn't happen // Honestly, this shouldn't happen
return false; return false;
} }
let codeText = codeTextNode.children![0].text!; const codeText = codeTextNode.children![0].text!;
try { try {
data = YAML.parse(codeText); data = YAML.parse(codeText);
} catch (e: any) { } catch (e: any) {
@ -49,5 +45,5 @@ export async function writeYamlPage(
data: any, data: any,
): Promise<void> { ): Promise<void> {
const text = YAML.stringify(data); const text = YAML.stringify(data);
await writePage(pageName, "```yaml\n" + text + "\n```"); await space.writePage(pageName, "```yaml\n" + text + "\n```");
} }

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;
}
}

View File

@ -1,6 +1,6 @@
import { syscall } from "./syscall.ts"; import { syscall } from "./syscall.ts";
export function dispatch( export function dispatchEvent(
eventName: string, eventName: string,
data: any, data: any,
timeout?: number, timeout?: number,

View File

@ -8,7 +8,7 @@ export type FileMeta = {
export function readFile( export function readFile(
path: string, path: string,
encoding: "utf8" | "dataurl" = "utf8", encoding: "utf8" | "dataurl" = "utf8",
): Promise<{ text: string; meta: FileMeta }> { ): Promise<string> {
return syscall("fs.readFile", path, encoding); return syscall("fs.readFile", path, encoding);
} }

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";

View File

@ -114,43 +114,3 @@ export function prompt(
export function enableReadOnlyMode(enabled: boolean) { export function enableReadOnlyMode(enabled: boolean) {
return syscall("editor.enableReadOnlyMode", enabled); return syscall("editor.enableReadOnlyMode", enabled);
} }
// DEPRECATED in favor of showPanel and hidePanel
export function showRhs(
html: string,
script?: string,
flex = 1,
): Promise<void> {
return syscall("editor.showRhs", html, script, flex);
}
export function hideRhs(): Promise<void> {
return syscall("editor.hideRhs");
}
export function showLhs(
html: string,
script?: string,
flex = 1,
): Promise<void> {
return syscall("editor.showLhs", html, script, flex);
}
export function hideLhs(): Promise<void> {
return syscall("editor.hideLhs");
}
export function showBhs(
html: string,
script?: string,
flex = 1,
): Promise<void> {
return syscall("editor.showBhs", html, script, flex);
}
export function hideBhs(): Promise<void> {
return syscall("editor.hideBhs");
}
// End deprecation

View File

@ -1,6 +1,6 @@
import { syscall } from "./syscall.ts"; import { syscall } from "./syscall.ts";
import type { ParseTree } from "../../common/tree.ts"; import type { ParseTree } from "$sb/lib/tree.ts";
export function parseMarkdown(text: string): Promise<ParseTree> { export function parseMarkdown(text: string): Promise<ParseTree> {
return syscall("markdown.parseMarkdown", text); return syscall("markdown.parseMarkdown", text);

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";

View File

@ -11,7 +11,7 @@ export function getPageMeta(name: string): Promise<PageMeta> {
export function readPage( export function readPage(
name: string, name: string,
): Promise<{ text: string; meta: PageMeta }> { ): Promise<string> {
return syscall("space.readPage", name); return syscall("space.readPage", name);
} }
@ -37,7 +37,7 @@ export function getAttachmentMeta(name: string): Promise<AttachmentMeta> {
export function readAttachment( export function readAttachment(
name: string, name: string,
): Promise<{ data: string; meta: AttachmentMeta }> { ): Promise<string> {
return syscall("space.readAttachment", name); return syscall("space.readAttachment", name);
} }

View File

@ -17,7 +17,7 @@ export default function fileSystemSyscalls(root = "/"): SysCallMapping {
_ctx, _ctx,
filePath: string, filePath: string,
encoding: "utf8" | "dataurl" = "utf8", encoding: "utf8" | "dataurl" = "utf8",
): Promise<{ text: string; meta: FileMeta }> => { ): Promise<string> => {
const p = resolvedPath(filePath); const p = resolvedPath(filePath);
let text = ""; let text = "";
if (encoding === "utf8") { if (encoding === "utf8") {
@ -27,17 +27,7 @@ export default function fileSystemSyscalls(root = "/"): SysCallMapping {
base64Encode(await Deno.readFile(p)) base64Encode(await Deno.readFile(p))
}`; }`;
} }
const s = await Deno.stat(p); return text;
return {
text,
meta: {
name: filePath,
lastModified: s.mtime!.getTime(),
contentType: mime.getType(filePath) || "application/octet-stream",
size: s.size,
perm: "rw",
},
};
}, },
"fs.getFileMeta": async (_ctx, filePath: string): Promise<FileMeta> => { "fs.getFileMeta": async (_ctx, filePath: string): Promise<FileMeta> => {
const p = resolvedPath(filePath); const p = resolvedPath(filePath);

View File

@ -1,44 +1,37 @@
import { collectNodesOfType } from "../../common/tree.ts"; import { collectNodesOfType } from "$sb/lib/tree.ts";
import { import { editor, index } from "$sb/silverbullet-syscall/mod.ts";
batchSet, import type { IndexTreeEvent } from "$sb/app_event.ts";
queryPrefix, import { removeQueries } from "$sb/lib/query.ts";
} from "../../syscall/silverbullet-syscall/index.ts";
import {
getCurrentPage,
matchBefore,
} from "../../syscall/silverbullet-syscall/editor.ts";
import type { IndexTreeEvent } from "../../web/app_event.ts";
import { removeQueries } from "../query/util.ts";
// Key space // Key space
// a:pageName:anchorName => pos // a:pageName:anchorName => pos
export async function indexAnchors({ name: pageName, tree }: IndexTreeEvent) { export async function indexAnchors({ name: pageName, tree }: IndexTreeEvent) {
removeQueries(tree); removeQueries(tree);
let anchors: { key: string; value: string }[] = []; const anchors: { key: string; value: string }[] = [];
collectNodesOfType(tree, "NamedAnchor").forEach((n) => { collectNodesOfType(tree, "NamedAnchor").forEach((n) => {
let aName = n.children![0].text!; const aName = n.children![0].text!;
anchors.push({ anchors.push({
key: `a:${pageName}:${aName}`, key: `a:${pageName}:${aName}`,
value: "" + n.from, value: "" + n.from,
}); });
}); });
console.log("Found", anchors.length, "anchors(s)"); console.log("Found", anchors.length, "anchors(s)");
await batchSet(pageName, anchors); await index.batchSet(pageName, anchors);
} }
export async function anchorComplete() { export async function anchorComplete() {
let prefix = await matchBefore("\\[\\[[^\\]@:]*@[\\w\\.\\-\\/]*"); const prefix = await editor.matchBefore("\\[\\[[^\\]@:]*@[\\w\\.\\-\\/]*");
if (!prefix) { if (!prefix) {
return null; return null;
} }
const [pageRefPrefix, anchorRef] = prefix.text.split("@"); const [pageRefPrefix, anchorRef] = prefix.text.split("@");
let pageRef = pageRefPrefix.substring(2); let pageRef = pageRefPrefix.substring(2);
if (!pageRef) { if (!pageRef) {
pageRef = await getCurrentPage(); pageRef = await editor.getCurrentPage();
} }
let allAnchors = await queryPrefix(`a:${pageRef}:@${anchorRef}`); const allAnchors = await index.queryPrefix(`a:${pageRef}:@${anchorRef}`);
return { return {
from: prefix.from + pageRefPrefix.length + 1, from: prefix.from + pageRefPrefix.length + 1,
options: allAnchors.map((a) => ({ options: allAnchors.map((a) => ({

View File

@ -2,9 +2,9 @@ import type {
FileData, FileData,
FileEncoding, FileEncoding,
} from "../../common/spaces/space_primitives.ts"; } from "../../common/spaces/space_primitives.ts";
import { renderToText, replaceNodesMatching } from "../../common/tree.ts"; import { renderToText, replaceNodesMatching } from "$sb/lib/tree.ts";
import type { FileMeta } from "../../common/types.ts"; import type { FileMeta } from "../../common/types.ts";
import { parseMarkdown } from "../../syscall/silverbullet-syscall/markdown.ts"; import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
const pagePrefix = "💭 "; const pagePrefix = "💭 ";
@ -55,7 +55,7 @@ async function translateLinksWithPrefix(
text: string, text: string,
prefix: string, prefix: string,
): Promise<string> { ): Promise<string> {
let tree = await parseMarkdown(text); const tree = await parseMarkdown(text);
replaceNodesMatching(tree, (tree) => { replaceNodesMatching(tree, (tree) => {
if (tree.type === "WikiLinkPage") { if (tree.type === "WikiLinkPage") {
// Add the prefix in the link text // Add the prefix in the link text
@ -67,12 +67,12 @@ async function translateLinksWithPrefix(
return text; return text;
} }
export async function getFileMetaCloud(name: string): Promise<FileMeta> { export function getFileMetaCloud(name: string): Promise<FileMeta> {
return { return Promise.resolve({
name, name,
size: 0, size: 0,
contentType: "text/markdown", contentType: "text/markdown",
lastModified: 0, lastModified: 0,
perm: "ro", perm: "ro",
}; });
} }

View File

@ -1,12 +1,11 @@
import { matchBefore } from "../../syscall/silverbullet-syscall/editor.ts"; import { editor, system } from "$sb/silverbullet-syscall/mod.ts";
import { listCommands } from "../../syscall/silverbullet-syscall/system.ts";
export async function commandComplete() { export async function commandComplete() {
let prefix = await matchBefore("\\{\\[[^\\]]*"); const prefix = await editor.matchBefore("\\{\\[[^\\]]*");
if (!prefix) { if (!prefix) {
return null; return null;
} }
let allCommands = await listCommands(); const allCommands = await system.listCommands();
return { return {
from: prefix.from + 2, from: prefix.from + 2,

View File

@ -139,11 +139,11 @@ functions:
# Full text search # Full text search
# searchIndex: # searchIndex:
# path: ./search.ts:index # path: ./search.ts:pageIndex
# events: # events:
# - page:index # - page:index
# searchUnindex: # searchUnindex:
# path: "./search.ts:unindex" # path: "./search.ts:pageUnindex"
# env: server # env: server
# events: # events:
# - page:deleted # - page:deleted

View File

@ -1,24 +1,26 @@
import { getLogs } from "../../syscall/plugos-syscall/sandbox.ts"; import { sandbox } from "$sb/plugos-syscall/mod.ts";
import { import {
getText, editor,
hidePanel, markdown,
showPanel, sandbox as serverSandbox,
} from "../../syscall/silverbullet-syscall/editor.ts"; } from "$sb/silverbullet-syscall/mod.ts";
import { parseMarkdown } from "../../syscall/silverbullet-syscall/markdown.ts";
import { getServerLogs } from "../../syscall/silverbullet-syscall/sandbox.ts";
export async function parsePageCommand() { export async function parsePageCommand() {
console.log( console.log(
"AST", "AST",
JSON.stringify(await parseMarkdown(await getText()), null, 2), JSON.stringify(
await markdown.parseMarkdown(await editor.getText()),
null,
2,
),
); );
} }
export async function showLogsCommand() { export async function showLogsCommand() {
let clientLogs = await getLogs(); const clientLogs = await sandbox.getLogs();
let serverLogs = await getServerLogs(); const serverLogs = await serverSandbox.getServerLogs();
await showPanel( await editor.showPanel(
"bhs", "bhs",
1, 1,
` `
@ -83,5 +85,5 @@ export async function showLogsCommand() {
} }
export async function hideBhsCommand() { export async function hideBhsCommand() {
await hidePanel("bhs"); await editor.hidePanel("bhs");
} }

View File

@ -1,16 +1,15 @@
import * as clientStore from "../../syscall/silverbullet-syscall/clientStore.ts"; import { clientStore, editor } from "$sb/silverbullet-syscall/mod.ts";
import { enableReadOnlyMode } from "../../syscall/silverbullet-syscall/editor.ts";
export async function editorLoad() { export async function editorLoad() {
let readOnlyMode = await clientStore.get("readOnlyMode"); const readOnlyMode = await clientStore.get("readOnlyMode");
if (readOnlyMode) { if (readOnlyMode) {
await enableReadOnlyMode(true); await editor.enableReadOnlyMode(true);
} }
} }
export async function toggleReadOnlyMode() { export async function toggleReadOnlyMode() {
let readOnlyMode = await clientStore.get("readOnlyMode"); let readOnlyMode = await clientStore.get("readOnlyMode");
readOnlyMode = !readOnlyMode; readOnlyMode = !readOnlyMode;
await enableReadOnlyMode(readOnlyMode); await editor.enableReadOnlyMode(readOnlyMode);
await clientStore.set("readOnlyMode", readOnlyMode); await clientStore.set("readOnlyMode", readOnlyMode);
} }

View File

@ -1,16 +1,8 @@
import type { IndexTreeEvent } from "../../web/app_event.ts"; import type { IndexTreeEvent, QueryProviderEvent } from "$sb/app_event.ts";
import { import { index } from "$sb/silverbullet-syscall/mod.ts";
batchSet, import { collectNodesOfType, ParseTree, renderToText } from "$sb/lib/tree.ts";
queryPrefix, import { applyQuery, removeQueries } from "$sb/lib/query.ts";
} from "../../syscall/silverbullet-syscall/index.ts";
import {
collectNodesOfType,
ParseTree,
renderToText,
} from "../../common/tree.ts";
import { removeQueries } from "../query/util.ts";
import { applyQuery, QueryProviderEvent } from "../query/engine.ts";
export type Item = { export type Item = {
name: string; name: string;
@ -22,12 +14,12 @@ export type Item = {
}; };
export async function indexItems({ name, tree }: IndexTreeEvent) { export async function indexItems({ name, tree }: IndexTreeEvent) {
let items: { key: string; value: Item }[] = []; const items: { key: string; value: Item }[] = [];
removeQueries(tree); removeQueries(tree);
console.log("Indexing items", name); console.log("Indexing items", name);
let coll = collectNodesOfType(tree, "ListItem"); const coll = collectNodesOfType(tree, "ListItem");
coll.forEach((n) => { coll.forEach((n) => {
if (!n.children) { if (!n.children) {
@ -38,9 +30,9 @@ export async function indexItems({ name, tree }: IndexTreeEvent) {
return; return;
} }
let textNodes: ParseTree[] = []; const textNodes: ParseTree[] = [];
let nested: string | undefined; let nested: string | undefined;
for (let child of n.children!.slice(1)) { for (const child of n.children!.slice(1)) {
if (child.type === "OrderedList" || child.type === "BulletList") { if (child.type === "OrderedList" || child.type === "BulletList") {
nested = renderToText(child); nested = renderToText(child);
break; break;
@ -48,8 +40,8 @@ export async function indexItems({ name, tree }: IndexTreeEvent) {
textNodes.push(child); textNodes.push(child);
} }
let itemText = textNodes.map(renderToText).join("").trim(); const itemText = textNodes.map(renderToText).join("").trim();
let item: Item = { const item: Item = {
name: itemText, name: itemText,
}; };
if (nested) { if (nested) {
@ -68,15 +60,15 @@ export async function indexItems({ name, tree }: IndexTreeEvent) {
}); });
}); });
console.log("Found", items.length, "item(s)"); console.log("Found", items.length, "item(s)");
await batchSet(name, items); await index.batchSet(name, items);
} }
export async function queryProvider({ export async function queryProvider({
query, query,
}: QueryProviderEvent): Promise<any[]> { }: QueryProviderEvent): Promise<any[]> {
let allItems: Item[] = []; const allItems: Item[] = [];
for (let { key, page, value } of await queryPrefix("it:")) { for (const { key, page, value } of await index.queryPrefix("it:")) {
let [, pos] = key.split(":"); const [, pos] = key.split(":");
allItems.push({ allItems.push({
...value, ...value,
page: page, page: page,

View File

@ -1,14 +1,6 @@
import { nodeAtPos } from "../../common/tree.ts"; import { nodeAtPos } from "$sb/lib/tree.ts";
import { import { editor, markdown, system } from "$sb/silverbullet-syscall/mod.ts";
filterBox, import { events } from "$sb/plugos-syscall/mod.ts";
flashNotification,
getCursor,
getText,
replaceRange,
} from "../../syscall/silverbullet-syscall/editor.ts";
import { parseMarkdown } from "../../syscall/silverbullet-syscall/markdown.ts";
import { dispatch as dispatchEvent } from "../../syscall/plugos-syscall/event.ts";
import { invokeFunction } from "../../syscall/silverbullet-syscall/system.ts";
type UnfurlOption = { type UnfurlOption = {
id: string; id: string;
@ -16,16 +8,16 @@ type UnfurlOption = {
}; };
export async function unfurlCommand() { export async function unfurlCommand() {
let mdTree = await parseMarkdown(await getText()); const mdTree = await markdown.parseMarkdown(await editor.getText());
let nakedUrlNode = nodeAtPos(mdTree, await getCursor()); const nakedUrlNode = nodeAtPos(mdTree, await editor.getCursor());
let url = nakedUrlNode!.children![0].text!; const url = nakedUrlNode!.children![0].text!;
console.log("Got URL to unfurl", url); console.log("Got URL to unfurl", url);
let optionResponses = await dispatchEvent("unfurl:options", url); const optionResponses = await events.dispatchEvent("unfurl:options", url);
let options: UnfurlOption[] = []; const options: UnfurlOption[] = [];
for (let resp of optionResponses) { for (const resp of optionResponses) {
options.push(...resp); options.push(...resp);
} }
let selectedUnfurl: any = await filterBox( const selectedUnfurl: any = await editor.filterBox(
"Unfurl", "Unfurl",
options, options,
"Select the unfurl strategy of your choice", "Select the unfurl strategy of your choice",
@ -34,19 +26,23 @@ export async function unfurlCommand() {
return; return;
} }
try { try {
let replacement = await invokeFunction( const replacement = await system.invokeFunction(
"server", "server",
"unfurlExec", "unfurlExec",
selectedUnfurl.id, selectedUnfurl.id,
url, url,
); );
await replaceRange(nakedUrlNode?.from!, nakedUrlNode?.to!, replacement); await editor.replaceRange(
nakedUrlNode?.from!,
nakedUrlNode?.to!,
replacement,
);
} catch (e: any) { } catch (e: any) {
await flashNotification(e.message, "error"); await editor.flashNotification(e.message, "error");
} }
} }
export async function titleUnfurlOptions(url: string): Promise<UnfurlOption[]> { export function titleUnfurlOptions(): UnfurlOption[] {
return [ return [
{ {
id: "title-unfurl", id: "title-unfurl",
@ -57,7 +53,7 @@ export async function titleUnfurlOptions(url: string): Promise<UnfurlOption[]> {
// Run on the server because plugs will likely rely on fetch for this // Run on the server because plugs will likely rely on fetch for this
export async function unfurlExec(id: string, url: string): Promise<string> { export async function unfurlExec(id: string, url: string): Promise<string> {
let replacement = await dispatchEvent(`unfurl:${id}`, url); const replacement = await events.dispatchEvent(`unfurl:${id}`, url);
if (replacement.length === 0) { if (replacement.length === 0) {
throw new Error("Unfurl failed"); throw new Error("Unfurl failed");
} else { } else {
@ -68,13 +64,13 @@ export async function unfurlExec(id: string, url: string): Promise<string> {
const titleRegex = /<title[^>]*>\s*([^<]+)\s*<\/title\s*>/i; const titleRegex = /<title[^>]*>\s*([^<]+)\s*<\/title\s*>/i;
export async function titleUnfurl(url: string): Promise<string> { export async function titleUnfurl(url: string): Promise<string> {
let response = await fetch(url); const response = await fetch(url);
if (response.status < 200 || response.status >= 300) { if (response.status < 200 || response.status >= 300) {
console.error("Unfurl failed", await response.text()); console.error("Unfurl failed", await response.text());
throw new Error(`Failed to fetch: ${await response.statusText}`); throw new Error(`Failed to fetch: ${await response.statusText}`);
} }
let body = await response.text(); const body = await response.text();
let match = titleRegex.exec(body); const match = titleRegex.exec(body);
if (match) { if (match) {
return `[${match[1]}](${url})`; return `[${match[1]}](${url})`;
} else { } else {

View File

@ -1,15 +1,6 @@
import type { ClickEvent } from "../../web/app_event.ts"; import type { ClickEvent } from "$sb/app_event.ts";
import { import { editor, markdown, system } from "$sb/silverbullet-syscall/mod.ts";
flashNotification, import { nodeAtPos, ParseTree } from "$sb/lib/tree.ts";
getCurrentPage,
getCursor,
getText,
navigate as navigateTo,
openUrl,
} from "../../syscall/silverbullet-syscall/editor.ts";
import { parseMarkdown } from "../../syscall/silverbullet-syscall/markdown.ts";
import { nodeAtPos, ParseTree } from "../../common/tree.ts";
import { invokeCommand } from "../../syscall/silverbullet-syscall/system.ts";
// Checks if the URL contains a protocol, if so keeps it, otherwise assumes an attachment // Checks if the URL contains a protocol, if so keeps it, otherwise assumes an attachment
function patchUrl(url: string): string { function patchUrl(url: string): string {
@ -35,21 +26,21 @@ async function actionClickOrActionEnter(mdTree: ParseTree | null) {
} }
} }
if (!pageLink) { if (!pageLink) {
pageLink = await getCurrentPage(); pageLink = await editor.getCurrentPage();
} }
await navigateTo(pageLink, pos); await editor.navigate(pageLink, pos);
break; break;
} }
case "URL": case "URL":
case "NakedURL": case "NakedURL":
await openUrl(patchUrl(mdTree.children![0].text!)); await editor.openUrl(patchUrl(mdTree.children![0].text!));
break; break;
case "Link": { case "Link": {
const url = patchUrl(mdTree.children![4].children![0].text!); const url = patchUrl(mdTree.children![4].children![0].text!);
if (url.length <= 1) { if (url.length <= 1) {
return flashNotification("Empty link, ignoring", "error"); return editor.flashNotification("Empty link, ignoring", "error");
} }
await openUrl(url); await editor.openUrl(url);
break; break;
} }
case "CommandLink": { case "CommandLink": {
@ -57,15 +48,15 @@ async function actionClickOrActionEnter(mdTree: ParseTree | null) {
.children![0].text!.substring(2, mdTree.children![0].text!.length - 2) .children![0].text!.substring(2, mdTree.children![0].text!.length - 2)
.trim(); .trim();
console.log("Got command link", command); console.log("Got command link", command);
await invokeCommand(command); await system.invokeCommand(command);
break; break;
} }
} }
} }
export async function linkNavigate() { export async function linkNavigate() {
const mdTree = await parseMarkdown(await getText()); const mdTree = await markdown.parseMarkdown(await editor.getText());
const newNode = nodeAtPos(mdTree, await getCursor()); const newNode = nodeAtPos(mdTree, await editor.getCursor());
await actionClickOrActionEnter(newNode); await actionClickOrActionEnter(newNode);
} }
@ -74,11 +65,11 @@ export async function clickNavigate(event: ClickEvent) {
if (event.ctrlKey || event.metaKey) { if (event.ctrlKey || event.metaKey) {
return; return;
} }
const mdTree = await parseMarkdown(await getText()); const mdTree = await markdown.parseMarkdown(await editor.getText());
const newNode = nodeAtPos(mdTree, event.pos); const newNode = nodeAtPos(mdTree, event.pos);
await actionClickOrActionEnter(newNode); await actionClickOrActionEnter(newNode);
} }
export async function navigateCommand(cmdDef: any) { export async function navigateCommand(cmdDef: any) {
await navigateTo(cmdDef.page); await editor.navigate(cmdDef.page);
} }

View File

@ -1,39 +1,26 @@
import type { IndexEvent, IndexTreeEvent } from "../../web/app_event.ts"; import type {
IndexEvent,
IndexTreeEvent,
QueryProviderEvent,
} from "$sb/app_event.ts";
import { import {
batchSet, editor,
clearPageIndex as clearPageIndexSyscall, index,
clearPageIndexForPage, markdown,
queryPrefix, space,
set, system,
} from "../../syscall/silverbullet-syscall/index.ts"; } from "$sb/silverbullet-syscall/mod.ts";
import { import { events, store } from "$sb/plugos-syscall/mod.ts";
flashNotification,
getCurrentPage,
getCursor,
getText,
matchBefore,
navigate,
prompt,
} from "../../syscall/silverbullet-syscall/editor.ts";
import { dispatch } from "../../syscall/plugos-syscall/event.ts";
import {
deletePage as deletePageSyscall,
listPages,
readPage,
writePage,
} from "../../syscall/silverbullet-syscall/space.ts";
import { invokeFunction } from "../../syscall/silverbullet-syscall/system.ts";
import { parseMarkdown } from "../../syscall/silverbullet-syscall/markdown.ts";
import { import {
addParentPointers, addParentPointers,
collectNodesMatching, collectNodesMatching,
ParseTree, ParseTree,
renderToText, renderToText,
replaceNodesMatching, replaceNodesMatching,
} from "../../common/tree.ts"; } from "$sb/lib/tree.ts";
import { applyQuery, QueryProviderEvent } from "../query/engine.ts"; import { applyQuery } from "$sb/lib/query.ts";
import { extractMeta } from "../query/data.ts"; import { extractMeta } from "../query/data.ts";
// Key space: // Key space:
@ -41,19 +28,19 @@ import { extractMeta } from "../query/data.ts";
// meta => metaJson // meta => metaJson
export async function indexLinks({ name, tree }: IndexTreeEvent) { export async function indexLinks({ name, tree }: IndexTreeEvent) {
let backLinks: { key: string; value: string }[] = []; const backLinks: { key: string; value: string }[] = [];
// [[Style Links]] // [[Style Links]]
console.log("Now indexing", name); console.log("Now indexing", name);
let pageMeta = extractMeta(tree); const pageMeta = extractMeta(tree);
if (Object.keys(pageMeta).length > 0) { if (Object.keys(pageMeta).length > 0) {
console.log("Extracted page meta data", pageMeta); console.log("Extracted page meta data", pageMeta);
// Don't index meta data starting with $ // Don't index meta data starting with $
for (let key in pageMeta) { for (const key in pageMeta) {
if (key.startsWith("$")) { if (key.startsWith("$")) {
delete pageMeta[key]; delete pageMeta[key];
} }
} }
await set(name, "meta:", pageMeta); await index.set(name, "meta:", pageMeta);
} }
collectNodesMatching(tree, (n) => n.type === "WikiLinkPage").forEach((n) => { collectNodesMatching(tree, (n) => n.type === "WikiLinkPage").forEach((n) => {
@ -67,18 +54,18 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) {
}); });
}); });
console.log("Found", backLinks.length, "wiki link(s)"); console.log("Found", backLinks.length, "wiki link(s)");
await batchSet(name, backLinks); await index.batchSet(name, backLinks);
} }
export async function pageQueryProvider({ export async function pageQueryProvider({
query, query,
}: QueryProviderEvent): Promise<any[]> { }: QueryProviderEvent): Promise<any[]> {
let allPages = await listPages(); let allPages = await space.listPages();
let allPageMap: Map<string, any> = new Map( const 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:")) { for (const { page, value } of await index.queryPrefix("meta:")) {
let p = allPageMap.get(page); const p = allPageMap.get(page);
if (p) { if (p) {
for (let [k, v] of Object.entries(value)) { for (let [k, v] of Object.entries(value)) {
p[k] = v; p[k] = v;
@ -93,8 +80,10 @@ export async function linkQueryProvider({
query, query,
pageName, pageName,
}: QueryProviderEvent): Promise<any[]> { }: QueryProviderEvent): Promise<any[]> {
let links: any[] = []; const links: any[] = [];
for (let { value: name, key } of await queryPrefix(`pl:${pageName}:`)) { for (
const { value: name, key } of await index.queryPrefix(`pl:${pageName}:`)
) {
const [, , pos] = key.split(":"); // Key: pl:page:pos const [, , pos] = key.split(":"); // Key: pl:page:pos
links.push({ name, pos }); links.push({ name, pos });
} }
@ -102,18 +91,18 @@ export async function linkQueryProvider({
} }
export async function deletePage() { export async function deletePage() {
let pageName = await getCurrentPage(); const pageName = await editor.getCurrentPage();
console.log("Navigating to index page"); console.log("Navigating to index page");
await navigate(""); await editor.navigate("");
console.log("Deleting page from space"); console.log("Deleting page from space");
await deletePageSyscall(pageName); await space.deletePage(pageName);
} }
export async function renamePage() { export async function renamePage() {
const oldName = await getCurrentPage(); const oldName = await editor.getCurrentPage();
const cursor = await getCursor(); const cursor = await editor.getCursor();
console.log("Old name is", oldName); console.log("Old name is", oldName);
const newName = await prompt(`Rename ${oldName} to:`, oldName); const newName = await editor.prompt(`Rename ${oldName} to:`, oldName);
if (!newName) { if (!newName) {
return; return;
} }
@ -123,45 +112,45 @@ export async function renamePage() {
} }
console.log("New name", newName); console.log("New name", newName);
let pagesToUpdate = await getBackLinks(oldName); const pagesToUpdate = await getBackLinks(oldName);
console.log("All pages containing backlinks", pagesToUpdate); console.log("All pages containing backlinks", pagesToUpdate);
let text = await getText(); const text = await editor.getText();
console.log("Writing new page to space"); console.log("Writing new page to space");
await writePage(newName, text); await space.writePage(newName, text);
console.log("Navigating to new page"); console.log("Navigating to new page");
await navigate(newName, cursor, true); await editor.navigate(newName, cursor, true);
console.log("Deleting page from space"); console.log("Deleting page from space");
await deletePageSyscall(oldName); await space.deletePage(oldName);
let pageToUpdateSet = new Set<string>(); const pageToUpdateSet = new Set<string>();
for (let pageToUpdate of pagesToUpdate) { for (const pageToUpdate of pagesToUpdate) {
pageToUpdateSet.add(pageToUpdate.page); pageToUpdateSet.add(pageToUpdate.page);
} }
for (let pageToUpdate of pageToUpdateSet) { for (const pageToUpdate of pageToUpdateSet) {
if (pageToUpdate === oldName) { if (pageToUpdate === oldName) {
continue; continue;
} }
console.log("Now going to update links in", pageToUpdate); console.log("Now going to update links in", pageToUpdate);
let { text } = await readPage(pageToUpdate); const text = await space.readPage(pageToUpdate);
// console.log("Received text", text); // console.log("Received text", text);
if (!text) { if (!text) {
// Page likely does not exist, but at least we can skip it // Page likely does not exist, but at least we can skip it
continue; continue;
} }
let mdTree = await parseMarkdown(text); const mdTree = await markdown.parseMarkdown(text);
addParentPointers(mdTree); addParentPointers(mdTree);
replaceNodesMatching(mdTree, (n): ParseTree | undefined | null => { replaceNodesMatching(mdTree, (n): ParseTree | undefined | null => {
if (n.type === "WikiLinkPage") { if (n.type === "WikiLinkPage") {
let pageName = n.children![0].text!; const pageName = n.children![0].text!;
if (pageName === oldName) { if (pageName === oldName) {
n.children![0].text = newName; n.children![0].text = newName;
return n; return n;
} }
// page name with @pos position // page name with @pos position
if (pageName.startsWith(`${oldName}@`)) { if (pageName.startsWith(`${oldName}@`)) {
let [, pos] = pageName.split("@"); const [, pos] = pageName.split("@");
n.children![0].text = `${newName}@${pos}`; n.children![0].text = `${newName}@${pos}`;
return n; return n;
} }
@ -169,10 +158,10 @@ export async function renamePage() {
return; return;
}); });
// let newText = text.replaceAll(`[[${oldName}]]`, `[[${newName}]]`); // let newText = text.replaceAll(`[[${oldName}]]`, `[[${newName}]]`);
let newText = renderToText(mdTree); const newText = renderToText(mdTree);
if (text !== newText) { if (text !== newText) {
console.log("Changes made, saving..."); console.log("Changes made, saving...");
await writePage(pageToUpdate, newText); await space.writePage(pageToUpdate, newText);
} }
} }
} }
@ -183,10 +172,10 @@ type BackLink = {
}; };
async function getBackLinks(pageName: string): Promise<BackLink[]> { async function getBackLinks(pageName: string): Promise<BackLink[]> {
let allBackLinks = await queryPrefix(`pl:${pageName}:`); const allBackLinks = await index.queryPrefix(`pl:${pageName}:`);
let pagesToUpdate: BackLink[] = []; const pagesToUpdate: BackLink[] = [];
for (let { key, value } of allBackLinks) { for (const { key, value } of allBackLinks) {
let keyParts = key.split(":"); const keyParts = key.split(":");
pagesToUpdate.push({ pagesToUpdate.push({
page: value, page: value,
pos: +keyParts[keyParts.length - 1], pos: +keyParts[keyParts.length - 1],
@ -196,18 +185,18 @@ async function getBackLinks(pageName: string): Promise<BackLink[]> {
} }
export async function reindexCommand() { export async function reindexCommand() {
await flashNotification("Reindexing..."); await editor.flashNotification("Reindexing...");
await invokeFunction("server", "reindexSpace"); await system.invokeFunction("server", "reindexSpace");
await flashNotification("Reindexing done"); await editor.flashNotification("Reindexing done");
} }
// Completion // Completion
export async function pageComplete() { export async function pageComplete() {
let prefix = await matchBefore("\\[\\[[^\\]@:]*"); const prefix = await editor.matchBefore("\\[\\[[^\\]@:]*");
if (!prefix) { if (!prefix) {
return null; return null;
} }
let allPages = await listPages(); const allPages = await space.listPages();
return { return {
from: prefix.from + 2, from: prefix.from + 2,
options: allPages.map((pageMeta) => ({ options: allPages.map((pageMeta) => ({
@ -220,14 +209,14 @@ export async function pageComplete() {
// Server functions // Server functions
export async function reindexSpace() { export async function reindexSpace() {
console.log("Clearing page index..."); console.log("Clearing page index...");
await clearPageIndexSyscall(); await index.clearPageIndex();
console.log("Listing all pages"); console.log("Listing all pages");
let pages = await listPages(); const pages = await space.listPages();
for (let { name } of pages) { for (const { name } of pages) {
console.log("Indexing", name); console.log("Indexing", name);
const { text } = await readPage(name); const text = await space.readPage(name);
let parsed = await parseMarkdown(text); const parsed = await markdown.parseMarkdown(text);
await dispatch("page:index", { await events.dispatchEvent("page:index", {
name, name,
tree: parsed, tree: parsed,
}); });
@ -237,12 +226,12 @@ export async function reindexSpace() {
export async function clearPageIndex(page: string) { export async function clearPageIndex(page: string) {
console.log("Clearing page index for page", page); console.log("Clearing page index for page", page);
await clearPageIndexForPage(page); await index.clearPageIndexForPage(page);
} }
export async function parseIndexTextRepublish({ name, text }: IndexEvent) { export async function parseIndexTextRepublish({ name, text }: IndexEvent) {
await dispatch("page:index", { await events.dispatchEvent("page:index", {
name, name,
tree: await parseMarkdown(text), tree: await markdown.parseMarkdown(text),
}); });
} }

View File

@ -1,30 +1,18 @@
import { dispatch } from "../../syscall/plugos-syscall/event.ts"; import { events } from "$sb/plugos-syscall/mod.ts";
import { Manifest } from "../../common/manifest.ts"; import type { Manifest } from "../../common/manifest.ts";
import { import { editor, space, system } from "$sb/silverbullet-syscall/mod.ts";
flashNotification,
save,
} from "../../syscall/silverbullet-syscall/editor.ts";
import {
deleteAttachment,
listPlugs,
writeAttachment,
} from "../../syscall/silverbullet-syscall/space.ts";
import {
invokeFunction,
reloadPlugs,
} from "../../syscall/silverbullet-syscall/system.ts";
import { readYamlPage } from "../lib/yaml_page.ts"; import { readYamlPage } from "$sb/lib/yaml_page.ts";
export async function updatePlugsCommand() { export async function updatePlugsCommand() {
await save(); await editor.save();
flashNotification("Updating plugs..."); await editor.flashNotification("Updating plugs...");
try { try {
await invokeFunction("server", "updatePlugs"); await system.invokeFunction("server", "updatePlugs");
flashNotification("And... done!"); await editor.flashNotification("And... done!");
await reloadPlugs(); system.reloadPlugs();
} catch (e: any) { } catch (e: any) {
flashNotification("Error updating plugs: " + e.message, "error"); editor.flashNotification("Error updating plugs: " + e.message, "error");
} }
} }
@ -42,18 +30,21 @@ export async function updatePlugs() {
throw new Error(`Error processing PLUGS: ${e.message}`); throw new Error(`Error processing PLUGS: ${e.message}`);
} }
console.log("Plug YAML", plugList); console.log("Plug YAML", plugList);
let allPlugNames: string[] = []; const allPlugNames: string[] = [];
for (let plugUri of plugList) { for (const plugUri of plugList) {
let [protocol, ...rest] = plugUri.split(":"); const [protocol, ...rest] = plugUri.split(":");
let manifests = await dispatch(`get-plug:${protocol}`, rest.join(":")); const manifests = await events.dispatchEvent(
`get-plug:${protocol}`,
rest.join(":"),
);
if (manifests.length === 0) { if (manifests.length === 0) {
console.error("Could not resolve plug", plugUri); console.error("Could not resolve plug", plugUri);
} }
// console.log("Got manifests", plugUri, protocol, manifests); // console.log("Got manifests", plugUri, protocol, manifests);
let manifest = manifests[0]; const manifest = manifests[0];
allPlugNames.push(manifest.name); allPlugNames.push(manifest.name);
// console.log("Writing", `_plug/${manifest.name}`); // console.log("Writing", `_plug/${manifest.name}`);
await writeAttachment( await space.writeAttachment(
`_plug/${manifest.name}.plug.json`, `_plug/${manifest.name}.plug.json`,
"string", "string",
JSON.stringify(manifest), JSON.stringify(manifest),
@ -61,34 +52,32 @@ export async function updatePlugs() {
} }
// And delete extra ones // And delete extra ones
for (let existingPlug of await listPlugs()) { for (const existingPlug of await space.listPlugs()) {
let plugName = existingPlug.substring( const plugName = existingPlug.substring(
"_plug/".length, "_plug/".length,
existingPlug.length - ".plug.json".length, existingPlug.length - ".plug.json".length,
); );
console.log("Considering", plugName); console.log("Considering", plugName);
if (!allPlugNames.includes(plugName)) { if (!allPlugNames.includes(plugName)) {
console.log("Removing plug", plugName); console.log("Removing plug", plugName);
await deleteAttachment(existingPlug); await space.deleteAttachment(existingPlug);
} }
} }
await reloadPlugs(); await system.reloadPlugs();
} }
export async function getPlugHTTPS(url: string): Promise<Manifest> { export async function getPlugHTTPS(url: string): Promise<Manifest> {
let fullUrl = `https:${url}`; const fullUrl = `https:${url}`;
console.log("Now fetching plug manifest from", fullUrl); console.log("Now fetching plug manifest from", fullUrl);
let req = await fetch(fullUrl); const req = await fetch(fullUrl);
if (req.status !== 200) { if (req.status !== 200) {
throw new Error(`Could not fetch plug manifest from ${fullUrl}`); throw new Error(`Could not fetch plug manifest from ${fullUrl}`);
} }
let json = await req.json(); return req.json();
return json;
} }
export async function getPlugGithub(identifier: string): Promise<Manifest> { export function getPlugGithub(identifier: string): Promise<Manifest> {
let [owner, repo, path] = identifier.split("/"); const [owner, repo, path] = identifier.split("/");
let [repoClean, branch] = repo.split("@"); let [repoClean, branch] = repo.split("@");
if (!branch) { if (!branch) {
branch = "main"; // or "master"? branch = "main"; // or "master"?

View File

@ -1,42 +1,36 @@
import { import { fulltext } from "$sb/plugos-syscall/mod.ts";
fullTextDelete, import { renderToText } from "$sb/lib/tree.ts";
fullTextIndex, import type { PageMeta } from "../../common/types.ts";
fullTextSearch, import { editor, index } from "$sb/silverbullet-syscall/mod.ts";
} from "../../syscall/plugos-syscall/fulltext.ts"; import { IndexTreeEvent, QueryProviderEvent } from "$sb/app_event.ts";
import { renderToText } from "../../common/tree.ts"; import { applyQuery, removeQueries } from "$sb/lib/query.ts";
import { PageMeta } from "../../common/types.ts";
import { queryPrefix } from "../../syscall/silverbullet-syscall/index.ts";
import { navigate, prompt } from "../../syscall/silverbullet-syscall/editor.ts";
import { IndexTreeEvent } from "../../web/app_event.ts";
import { applyQuery, QueryProviderEvent } from "../query/engine.ts";
import { removeQueries } from "../query/util.ts";
const searchPrefix = "🔍 "; const searchPrefix = "🔍 ";
export async function index(data: IndexTreeEvent) { export async function pageIndex(data: IndexTreeEvent) {
removeQueries(data.tree); removeQueries(data.tree);
let cleanText = renderToText(data.tree); const cleanText = renderToText(data.tree);
await fullTextIndex(data.name, cleanText); await fulltext.fullTextIndex(data.name, cleanText);
} }
export async function unindex(pageName: string) { export async function pageUnindex(pageName: string) {
await fullTextDelete(pageName); await fulltext.fullTextDelete(pageName);
} }
export async function queryProvider({ export async function queryProvider({
query, query,
}: QueryProviderEvent): Promise<any[]> { }: QueryProviderEvent): Promise<any[]> {
let phraseFilter = query.filter.find((f) => f.prop === "phrase"); const phraseFilter = query.filter.find((f) => f.prop === "phrase");
if (!phraseFilter) { if (!phraseFilter) {
throw Error("No 'phrase' filter specified, this is mandatory"); throw Error("No 'phrase' filter specified, this is mandatory");
} }
let results = await fullTextSearch(phraseFilter.value, 100); let results = await fulltext.fullTextSearch(phraseFilter.value, 100);
let allPageMap: Map<string, any> = new Map( const 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:")) { for (const { page, value } of await index.queryPrefix("meta:")) {
let p = allPageMap.get(page); const p = allPageMap.get(page);
if (p) { if (p) {
for (let [k, v] of Object.entries(value)) { for (let [k, v] of Object.entries(value)) {
p[k] = v; p[k] = v;
@ -52,17 +46,17 @@ export async function queryProvider({
} }
export async function searchCommand() { export async function searchCommand() {
let phrase = await prompt("Search for: "); const phrase = await prompt("Search for: ");
if (phrase) { if (phrase) {
await navigate(`${searchPrefix}${phrase}`); await editor.navigate(`${searchPrefix}${phrase}`);
} }
} }
export async function readPageSearch( export async function readPageSearch(
name: string, name: string,
): Promise<{ text: string; meta: PageMeta }> { ): Promise<{ text: string; meta: PageMeta }> {
let phrase = name.substring(searchPrefix.length); const phrase = name.substring(searchPrefix.length);
let results = await fullTextSearch(phrase, 100); const results = await fulltext.fullTextSearch(phrase, 100);
const text = `# Search results for "${phrase}"\n${ const text = `# Search results for "${phrase}"\n${
results results
.map((r: any) => `* [[${r.name}]] (score: ${r.rank})`) .map((r: any) => `* [[${r.name}]] (score: ${r.rank})`)
@ -79,7 +73,7 @@ export async function readPageSearch(
}; };
} }
export async function getPageMetaSearch(name: string): Promise<PageMeta> { export function getPageMetaSearch(name: string): PageMeta {
return { return {
name, name,
lastModified: 0, lastModified: 0,

View File

@ -1,8 +1,4 @@
import { import { editor, space } from "$sb/silverbullet-syscall/mod.ts";
flashNotification,
getText,
} from "../../syscall/silverbullet-syscall/editor.ts";
import { listPages } from "../../syscall/silverbullet-syscall/space.ts";
function countWords(str: string): number { function countWords(str: string): number {
const matches = str.match(/[\w\d\'-]+/gi); const matches = str.match(/[\w\d\'-]+/gi);
@ -15,11 +11,11 @@ function readingTime(wordCount: number): number {
} }
export async function statsCommand() { export async function statsCommand() {
const text = await getText(); const text = await editor.getText();
const allPages = await listPages(); const allPages = await space.listPages();
const wordCount = countWords(text); const wordCount = countWords(text);
const time = readingTime(wordCount); const time = readingTime(wordCount);
await flashNotification( await editor.flashNotification(
`${wordCount} words; ${time} minutes read; ${allPages.length} total pages in space.`, `${wordCount} words; ${time} minutes read; ${allPages.length} total pages in space.`,
); );
} }

View File

@ -1,35 +1,30 @@
import { collectNodesOfType } from "../../common/tree.ts"; import { collectNodesOfType } from "$sb/lib/tree.ts";
import { import { editor, index } from "$sb/silverbullet-syscall/mod.ts";
batchSet, import type { IndexTreeEvent, QueryProviderEvent } from "$sb/app_event.ts";
queryPrefix, import { applyQuery, removeQueries } from "$sb/lib/query.ts";
} from "../../syscall/silverbullet-syscall/index.ts";
import { matchBefore } from "../../syscall/silverbullet-syscall/editor.ts";
import type { IndexTreeEvent } from "../../web/app_event.ts";
import { applyQuery, QueryProviderEvent } from "../query/engine.ts";
import { removeQueries } from "../query/util.ts";
// Key space // Key space
// tag:TAG => true (for completion) // tag:TAG => true (for completion)
export async function indexTags({ name, tree }: IndexTreeEvent) { export async function indexTags({ name, tree }: IndexTreeEvent) {
removeQueries(tree); removeQueries(tree);
let allTags = new Set<string>(); const allTags = new Set<string>();
collectNodesOfType(tree, "Hashtag").forEach((n) => { collectNodesOfType(tree, "Hashtag").forEach((n) => {
allTags.add(n.children![0].text!); allTags.add(n.children![0].text!);
}); });
batchSet( await index.batchSet(
name, name,
[...allTags].map((t) => ({ key: `tag:${t}`, value: t })), [...allTags].map((t) => ({ key: `tag:${t}`, value: t })),
); );
} }
export async function tagComplete() { export async function tagComplete() {
let prefix = await matchBefore("#[^#\\s]+"); const prefix = await editor.matchBefore("#[^#\\s]+");
// console.log("Running tag complete", prefix); // console.log("Running tag complete", prefix);
if (!prefix) { if (!prefix) {
return null; return null;
} }
let allTags = await queryPrefix(`tag:${prefix.text}`); const allTags = await index.queryPrefix(`tag:${prefix.text}`);
return { return {
from: prefix.from, from: prefix.from,
options: allTags.map((tag) => ({ options: allTags.map((tag) => ({
@ -45,8 +40,8 @@ type Tag = {
}; };
export async function tagProvider({ query }: QueryProviderEvent) { export async function tagProvider({ query }: QueryProviderEvent) {
let allTags = new Map<string, number>(); const allTags = new Map<string, number>();
for (let { value } of await queryPrefix("tag:")) { for (const { value } of await index.queryPrefix("tag:")) {
let currentFreq = allTags.get(value); let currentFreq = allTags.get(value);
if (!currentFreq) { if (!currentFreq) {
currentFreq = 0; currentFreq = 0;

View File

@ -1,31 +1,16 @@
import { import { editor, markdown, space } from "$sb/silverbullet-syscall/mod.ts";
getPageMeta,
listPages,
readPage,
writePage,
} from "$sb/silverbullet-syscall/space.ts";
import {
filterBox,
getCurrentPage,
getCursor,
insertAtCursor,
moveCursor,
navigate,
prompt,
} from "../../syscall/silverbullet-syscall/editor.ts";
import { parseMarkdown } from "../../syscall/silverbullet-syscall/markdown.ts";
import { extractMeta } from "../query/data.ts"; import { extractMeta } from "../query/data.ts";
import { renderToText } from "../../common/tree.ts"; import { renderToText } from "$sb/lib/tree.ts";
import { niceDate } from "./dates.ts"; import { niceDate } from "$sb/lib/dates.ts";
import { readSettings } from "../lib/settings_page.ts"; import { readSettings } from "$sb/lib/settings_page.ts";
export async function instantiateTemplateCommand() { export async function instantiateTemplateCommand() {
const allPages = await listPages(); const allPages = await space.listPages();
const { pageTemplatePrefix } = await readSettings({ const { pageTemplatePrefix } = await readSettings({
pageTemplatePrefix: "template/page/", pageTemplatePrefix: "template/page/",
}); });
const selectedTemplate = await filterBox( const selectedTemplate = await editor.filterBox(
"Template", "Template",
allPages allPages
.filter((pageMeta) => pageMeta.name.startsWith(pageTemplatePrefix)) .filter((pageMeta) => pageMeta.name.startsWith(pageTemplatePrefix))
@ -41,40 +26,43 @@ export async function instantiateTemplateCommand() {
} }
console.log("Selected template", selectedTemplate); console.log("Selected template", selectedTemplate);
const { text } = await readPage( const text = await space.readPage(
`${pageTemplatePrefix}${selectedTemplate.name}`, `${pageTemplatePrefix}${selectedTemplate.name}`,
); );
const parseTree = await parseMarkdown(text); const parseTree = await markdown.parseMarkdown(text);
const additionalPageMeta = extractMeta(parseTree, [ const additionalPageMeta = extractMeta(parseTree, [
"$name", "$name",
"$disableDirectives", "$disableDirectives",
]); ]);
const pageName = await prompt("Name of new page", additionalPageMeta.$name); const pageName = await editor.prompt(
"Name of new page",
additionalPageMeta.$name,
);
if (!pageName) { if (!pageName) {
return; return;
} }
const pageText = replaceTemplateVars(renderToText(parseTree), pageName); const pageText = replaceTemplateVars(renderToText(parseTree), pageName);
await writePage(pageName, pageText); await space.writePage(pageName, pageText);
await navigate(pageName); await editor.navigate(pageName);
} }
export async function insertSnippet() { export async function insertSnippet() {
let allPages = await listPages(); const allPages = await space.listPages();
let { snippetPrefix } = await readSettings({ const { snippetPrefix } = await readSettings({
snippetPrefix: "snippet/", snippetPrefix: "snippet/",
}); });
let cursorPos = await getCursor(); const cursorPos = await editor.getCursor();
let page = await getCurrentPage(); const page = await editor.getCurrentPage();
let allSnippets = allPages const allSnippets = allPages
.filter((pageMeta) => pageMeta.name.startsWith(snippetPrefix)) .filter((pageMeta) => pageMeta.name.startsWith(snippetPrefix))
.map((pageMeta) => ({ .map((pageMeta) => ({
...pageMeta, ...pageMeta,
name: pageMeta.name.slice(snippetPrefix.length), name: pageMeta.name.slice(snippetPrefix.length),
})); }));
let selectedSnippet = await filterBox( const selectedSnippet = await editor.filterBox(
"Snippet", "Snippet",
allSnippets, allSnippets,
`Select the snippet to insert (listing any page starting with <tt>${snippetPrefix}</tt>)`, `Select the snippet to insert (listing any page starting with <tt>${snippetPrefix}</tt>)`,
@ -83,15 +71,15 @@ export async function insertSnippet() {
if (!selectedSnippet) { if (!selectedSnippet) {
return; return;
} }
let { text } = await readPage(`${snippetPrefix}${selectedSnippet.name}`);
const text = await space.readPage(`${snippetPrefix}${selectedSnippet.name}`);
let templateText = replaceTemplateVars(text, page); let templateText = replaceTemplateVars(text, page);
let carretPos = templateText.indexOf("|^|"); const carretPos = templateText.indexOf("|^|");
templateText = templateText.replace("|^|", ""); templateText = templateText.replace("|^|", "");
templateText = replaceTemplateVars(templateText, page); templateText = replaceTemplateVars(templateText, page);
await insertAtCursor(templateText); await editor.insertAtCursor(templateText);
if (carretPos !== -1) { if (carretPos !== -1) {
await moveCursor(cursorPos + carretPos); await editor.moveCursor(cursorPos + carretPos);
} }
} }
@ -101,18 +89,22 @@ export function replaceTemplateVars(s: string, pageName: string): string {
switch (v) { switch (v) {
case "today": case "today":
return niceDate(new Date()); return niceDate(new Date());
case "tomorrow": case "tomorrow": {
let tomorrow = new Date(); const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setDate(tomorrow.getDate() + 1);
return niceDate(tomorrow); return niceDate(tomorrow);
case "yesterday": }
let yesterday = new Date();
case "yesterday": {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1); yesterday.setDate(yesterday.getDate() - 1);
return niceDate(yesterday); return niceDate(yesterday);
case "lastWeek": }
let lastWeek = new Date(); case "lastWeek": {
const lastWeek = new Date();
lastWeek.setDate(lastWeek.getDate() - 7); lastWeek.setDate(lastWeek.getDate() - 7);
return niceDate(lastWeek); return niceDate(lastWeek);
}
case "page": case "page":
return pageName; return pageName;
} }
@ -121,55 +113,55 @@ export function replaceTemplateVars(s: string, pageName: string): string {
} }
export async function quickNoteCommand() { export async function quickNoteCommand() {
let { quickNotePrefix } = await readSettings({ const { quickNotePrefix } = await readSettings({
quickNotePrefix: "📥 ", quickNotePrefix: "📥 ",
}); });
let isoDate = new Date().toISOString(); const isoDate = new Date().toISOString();
let [date, time] = isoDate.split("T"); let [date, time] = isoDate.split("T");
time = time.split(".")[0]; time = time.split(".")[0];
let pageName = `${quickNotePrefix}${date} ${time}`; const pageName = `${quickNotePrefix}${date} ${time}`;
await navigate(pageName); await editor.navigate(pageName);
} }
export async function dailyNoteCommand() { export async function dailyNoteCommand() {
let { dailyNoteTemplate, dailyNotePrefix } = await readSettings({ const { dailyNoteTemplate, dailyNotePrefix } = await readSettings({
dailyNoteTemplate: "template/page/Daily Note", dailyNoteTemplate: "template/page/Daily Note",
dailyNotePrefix: "📅 ", dailyNotePrefix: "📅 ",
}); });
let dailyNoteTemplateText = ""; let dailyNoteTemplateText = "";
try { try {
let { text } = await readPage(dailyNoteTemplate); const text = await space.readPage(dailyNoteTemplate);
dailyNoteTemplateText = text; dailyNoteTemplateText = text;
} catch { } catch {
console.warn(`No daily note template found at ${dailyNoteTemplate}`); console.warn(`No daily note template found at ${dailyNoteTemplate}`);
} }
let date = niceDate(new Date()); const date = niceDate(new Date());
let pageName = `${dailyNotePrefix}${date}`; const pageName = `${dailyNotePrefix}${date}`;
if (dailyNoteTemplateText) { if (dailyNoteTemplateText) {
try { try {
await getPageMeta(pageName); await space.getPageMeta(pageName);
} catch { } catch {
// Doesn't exist, let's create // Doesn't exist, let's create
await writePage( await space.writePage(
pageName, pageName,
replaceTemplateVars(dailyNoteTemplateText, pageName), replaceTemplateVars(dailyNoteTemplateText, pageName),
); );
} }
await navigate(pageName); await editor.navigate(pageName);
} else { } else {
await navigate(pageName); await editor.navigate(pageName);
} }
} }
export async function insertTemplateText(cmdDef: any) { export async function insertTemplateText(cmdDef: any) {
let cursorPos = await getCursor(); const cursorPos = await editor.getCursor();
let page = await getCurrentPage(); const page = await editor.getCurrentPage();
let templateText: string = cmdDef.value; let templateText: string = cmdDef.value;
let carretPos = templateText.indexOf("|^|"); const carretPos = templateText.indexOf("|^|");
templateText = templateText.replace("|^|", ""); templateText = templateText.replace("|^|", "");
templateText = replaceTemplateVars(templateText, page); templateText = replaceTemplateVars(templateText, page);
await insertAtCursor(templateText); await editor.insertAtCursor(templateText);
if (carretPos !== -1) { if (carretPos !== -1) {
await moveCursor(cursorPos + carretPos); await editor.moveCursor(cursorPos + carretPos);
} }
} }

View File

@ -1,15 +1,8 @@
import { import { editor } from "$sb/silverbullet-syscall/mod.ts";
getSelection,
getText,
insertAtCursor,
moveCursor,
replaceRange,
setSelection,
} from "$sb/silverbullet-syscall/editor.ts";
export async function quoteSelection() { export async function quoteSelection() {
let text = await getText(); let text = await editor.getText();
const selection = await getSelection(); const selection = await editor.getSelection();
let from = selection.from; let from = selection.from;
while (from >= 0 && text[from] !== "\n") { while (from >= 0 && text[from] !== "\n") {
from--; from--;
@ -23,12 +16,12 @@ export async function quoteSelection() {
text = text.slice(from, selection.to); text = text.slice(from, selection.to);
text = `> ${text.replaceAll("\n", "\n> ")}`; text = `> ${text.replaceAll("\n", "\n> ")}`;
} }
await replaceRange(from, selection.to, text); await editor.replaceRange(from, selection.to, text);
} }
export async function listifySelection() { export async function listifySelection() {
let text = await getText(); let text = await editor.getText();
const selection = await getSelection(); const selection = await editor.getSelection();
let from = selection.from; let from = selection.from;
while (from >= 0 && text[from] !== "\n") { while (from >= 0 && text[from] !== "\n") {
from--; from--;
@ -36,12 +29,12 @@ export async function listifySelection() {
from++; from++;
text = text.slice(from, selection.to); text = text.slice(from, selection.to);
text = `* ${text.replaceAll(/\n(?!\n)/g, "\n* ")}`; text = `* ${text.replaceAll(/\n(?!\n)/g, "\n* ")}`;
await replaceRange(from, selection.to, text); await editor.replaceRange(from, selection.to, text);
} }
export async function numberListifySelection() { export async function numberListifySelection() {
let text = await getText(); let text = await editor.getText();
const selection = await getSelection(); const selection = await editor.getSelection();
let from = selection.from; let from = selection.from;
while (from >= 0 && text[from] !== "\n") { while (from >= 0 && text[from] !== "\n") {
from--; from--;
@ -55,12 +48,12 @@ export async function numberListifySelection() {
return `\n${counter}. `; return `\n${counter}. `;
}) })
}`; }`;
await replaceRange(from, selection.to, text); await editor.replaceRange(from, selection.to, text);
} }
export async function linkSelection() { export async function linkSelection() {
const text = await getText(); const text = await editor.getText();
const selection = await getSelection(); const selection = await editor.getSelection();
const textSelection = text.slice(selection.from, selection.to); const textSelection = text.slice(selection.from, selection.to);
let linkedText = `[]()`; let linkedText = `[]()`;
let pos = 1; let pos = 1;
@ -73,8 +66,8 @@ export async function linkSelection() {
pos = linkedText.length - 1; pos = linkedText.length - 1;
} }
} }
await replaceRange(selection.from, selection.to, linkedText); await editor.replaceRange(selection.from, selection.to, linkedText);
await moveCursor(selection.from + pos); await editor.moveCursor(selection.from + pos);
} }
export function wrapSelection(cmdDef: any) { export function wrapSelection(cmdDef: any) {
@ -82,17 +75,17 @@ export function wrapSelection(cmdDef: any) {
} }
async function insertMarker(marker: string) { async function insertMarker(marker: string) {
let text = await getText(); const text = await editor.getText();
const selection = await getSelection(); const selection = await editor.getSelection();
if (selection.from === selection.to) { if (selection.from === selection.to) {
// empty selection // empty selection
if (markerAt(selection.from)) { if (markerAt(selection.from)) {
// Already there, skipping ahead // Already there, skipping ahead
await moveCursor(selection.from + marker.length); await editor.moveCursor(selection.from + marker.length);
} else { } else {
// Not there, inserting // Not there, inserting
await insertAtCursor(marker + marker); await editor.insertAtCursor(marker + marker);
await moveCursor(selection.from + marker.length); await editor.moveCursor(selection.from + marker.length);
} }
} else { } else {
let from = selection.from; let from = selection.from;
@ -107,28 +100,28 @@ async function insertMarker(marker: string) {
if (!hasMarker) { if (!hasMarker) {
// Adding // Adding
await replaceRange( await editor.replaceRange(
selection.from, selection.from,
selection.to, selection.to,
marker + text.slice(selection.from, selection.to) + marker, marker + text.slice(selection.from, selection.to) + marker,
); );
await setSelection( await editor.setSelection(
selection.from + marker.length, selection.from + marker.length,
selection.to + marker.length, selection.to + marker.length,
); );
} else { } else {
// Removing // Removing
await replaceRange( await editor.replaceRange(
from, from,
to, to,
text.substring(from + marker.length, to - marker.length), text.substring(from + marker.length, to - marker.length),
); );
await setSelection(from, to - marker.length * 2); await editor.setSelection(from, to - marker.length * 2);
} }
} }
function markerAt(pos: number) { function markerAt(pos: number) {
for (var i = 0; i < marker.length; i++) { for (let i = 0; i < marker.length; i++) {
if (text[pos + i] !== marker[i]) { if (text[pos + i] !== marker[i]) {
return false; return false;
} }

View File

@ -1,8 +1,8 @@
import emojis from "./emoji.json" assert { type: "json" }; import emojis from "./emoji.json" assert { type: "json" };
import { matchBefore } from "$sb/silverbullet-syscall/editor.ts"; import { editor } from "$sb/silverbullet-syscall/mod.ts";
export async function emojiCompleter() { export async function emojiCompleter() {
const prefix = await matchBefore(":[\\w]+"); const prefix = await editor.matchBefore(":[\\w]+");
if (!prefix) { if (!prefix) {
return null; return null;
} }

View File

@ -1,6 +1,8 @@
name: markdown name: markdown
imports: imports:
- https://get.silverbullet.md/global.plug.json - https://get.silverbullet.md/global.plug.json
assets:
- styles.css
functions: functions:
toggle: toggle:
path: "./markdown.ts:togglePreview" path: "./markdown.ts:togglePreview"

View File

@ -1,13 +1,11 @@
import { hideLhs, hideRhs } from "../../syscall/silverbullet-syscall/editor.ts"; import { clientStore, editor, system } from "$sb/silverbullet-syscall/mod.ts";
import { invokeFunction } from "../../syscall/silverbullet-syscall/system.ts"; import { readSettings } from "$sb/lib/settings_page.ts";
import * as clientStore from "../../syscall/silverbullet-syscall/clientStore.ts";
import { readSettings } from "../lib/settings_page.ts";
export async function togglePreview() { export async function togglePreview() {
let currentValue = !!(await clientStore.get("enableMarkdownPreview")); const currentValue = !!(await clientStore.get("enableMarkdownPreview"));
await clientStore.set("enableMarkdownPreview", !currentValue); await clientStore.set("enableMarkdownPreview", !currentValue);
if (!currentValue) { if (!currentValue) {
await invokeFunction("client", "preview"); await system.invokeFunction("client", "preview");
} else { } else {
await hideMarkdownPreview(); await hideMarkdownPreview();
} }
@ -15,6 +13,6 @@ export async function togglePreview() {
async function hideMarkdownPreview() { async function hideMarkdownPreview() {
const setting = await readSettings({ previewOnRHS: true }); const setting = await readSettings({ previewOnRHS: true });
const hide = setting.previewOnRHS ? hideRhs : hideLhs; const hide = setting.previewOnRHS ? editor.hideRhs : editor.hideLhs;
await hide(); await hide();
} }

View File

@ -1,69 +1,10 @@
import MarkdownIt from "https://esm.sh/markdown-it@13.0.1"; import MarkdownIt from "https://esm.sh/markdown-it@13.0.1";
import {
getText,
showPanel,
} from "../../syscall/silverbullet-syscall/editor.ts";
import * as clientStore from "../../syscall/silverbullet-syscall/clientStore.ts";
import { cleanMarkdown } from "./util.ts";
const css = `
<style>
body {
font-family: georgia,times,serif;
font-size: 14pt;
max-width: 800px;
margin-left: auto;
margin-right: auto;
padding-left: 20px;
padding-right: 20px;
}
table {
width: 100%;
border-spacing: 0;
}
thead tr {
background-color: #333;
color: #eee;
}
th, td {
padding: 8px;
}
tbody tr:nth-of-type(even) {
background-color: #f3f3f3;
}
a[href] {
text-decoration: none;
}
blockquote {
border-left: 1px solid #333;
margin-left: 2px;
padding-left: 10px;
}
hr {
margin: 1em 0 1em 0;
text-align: center;
border-color: #777;
border-width: 0;
border-style: dotted;
}
hr:after {
content: "···";
letter-spacing: 1em;
}
</style>
`;
import taskLists from "https://esm.sh/markdown-it-task-lists@2.1.1"; import taskLists from "https://esm.sh/markdown-it-task-lists@2.1.1";
import { clientStore, editor } from "$sb/silverbullet-syscall/mod.ts";
import { asset } from "$sb/plugos-syscall/mod.ts";
import { cleanMarkdown } from "./util.ts";
const md = new MarkdownIt({ const md = new MarkdownIt({
linkify: true, linkify: true,
html: false, html: false,
@ -74,11 +15,14 @@ export async function updateMarkdownPreview() {
if (!(await clientStore.get("enableMarkdownPreview"))) { if (!(await clientStore.get("enableMarkdownPreview"))) {
return; return;
} }
let text = await getText(); const text = await editor.getText();
let cleanMd = await cleanMarkdown(text); const cleanMd = await cleanMarkdown(text);
await showPanel( const css = await asset.readAsset("styles.css");
await editor.showPanel(
"rhs", "rhs",
2, 2,
`<html><head>${css}</head><body>${md.render(cleanMd)}</body></html>`, `<html><head><style>${css}</style></head><body>${
md.render(cleanMd)
}</body></html>`,
); );
} }

51
plugs/markdown/styles.css Normal file
View File

@ -0,0 +1,51 @@
<style>
body {
font-family: georgia,times,serif;
font-size: 14pt;
max-width: 800px;
margin-left: auto;
margin-right: auto;
padding-left: 20px;
padding-right: 20px;
}
table {
width: 100%;
border-spacing: 0;
}
thead tr {
background-color: #333;
color: #eee;
}
th, td {
padding: 8px;
}
tbody tr:nth-of-type(even) {
background-color: #f3f3f3;
}
a[href] {
text-decoration: none;
}
blockquote {
border-left: 1px solid #333;
margin-left: 2px;
padding-left: 10px;
}
hr {
margin: 1em 0 1em 0;
text-align: center;
border-color: #777;
border-width: 0;
border-style: dotted;
}
hr:after {
content: "···";
letter-spacing: 1em;
}

View File

@ -2,8 +2,8 @@ import {
findNodeOfType, findNodeOfType,
renderToText, renderToText,
replaceNodesMatching, replaceNodesMatching,
} from "../../common/tree.ts"; } from "$sb/lib/tree.ts";
import { parseMarkdown } from "../../syscall/silverbullet-syscall/markdown.ts"; import { markdown } from "$sb/silverbullet-syscall/mod.ts";
export function encodePageUrl(name: string): string { export function encodePageUrl(name: string): string {
return name.replaceAll(" ", "_"); return name.replaceAll(" ", "_");
@ -13,7 +13,7 @@ export async function cleanMarkdown(
text: string, text: string,
validPages?: string[], validPages?: string[],
): Promise<string> { ): Promise<string> {
const mdTree = await parseMarkdown(text); const mdTree = await markdown.parseMarkdown(text);
replaceNodesMatching(mdTree, (n) => { replaceNodesMatching(mdTree, (n) => {
if (n.type === "WikiLink") { if (n.type === "WikiLink") {
const page = n.children![1].children![0].text!; const page = n.children![1].children![0].text!;

View File

@ -1,57 +1,57 @@
import { collectNodesOfType, findNodeOfType } from "../../common/tree.ts"; import { collectNodesOfType, findNodeOfType } from "$sb/lib/tree.ts";
import { getText, hideBhs, showBhs } from "$sb/silverbullet-syscall/editor.ts";
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
import { readPage, writePage } from "$sb/silverbullet-syscall/space.ts";
import { import {
invokeFunction, editor,
reloadPlugs, markdown,
} from "$sb/silverbullet-syscall/system.ts"; space,
system,
} from "$sb/silverbullet-syscall/mod.ts";
import { syscall } from "$sb/plugos-syscall/mod.ts";
import * as YAML from "yaml"; import * as YAML from "yaml";
import type { Manifest } from "../../common/manifest.ts"; import type { Manifest } from "../../common/manifest.ts";
export async function compileCommand() { export async function compileCommand() {
let text = await getText(); const text = await editor.getText();
try { try {
let manifest = await compileDefinition(text); const manifest = await compileDefinition(text);
await writePage( await space.writePage(
`_plug/${manifest.name}`, `_plug/${manifest.name}`,
JSON.stringify(manifest, null, 2), JSON.stringify(manifest, null, 2),
); );
console.log("Wrote this plug", manifest); console.log("Wrote this plug", manifest);
await hideBhs(); await editor.hidePanel("bhs");
await reloadPlugs(); system.reloadPlugs();
} catch (e: any) { } catch (e: any) {
await showBhs(e.message); await editor.showPanel("bhs", 1, e.message);
// console.error("Got this error from compiler", e.message); // console.error("Got this error from compiler", e.message);
} }
} }
export async function checkCommand() { export async function checkCommand() {
let text = await getText(); const text = await editor.getText();
try { try {
await compileDefinition(text); await compileDefinition(text);
await hideBhs(); await editor.hidePanel("bhs");
reloadPlugs(); system.reloadPlugs();
} catch (e: any) { } catch (e: any) {
await showBhs(e.message); await editor.showPanel("bhs", 1, e.message);
// console.error("Got this error from compiler", e.message); // console.error("Got this error from compiler", e.message);
} }
} }
async function compileDefinition(text: string): Promise<Manifest> { async function compileDefinition(text: string): Promise<Manifest> {
let tree = await parseMarkdown(text); const tree = await markdown.parseMarkdown(text);
let codeNodes = collectNodesOfType(tree, "FencedCode"); const codeNodes = collectNodesOfType(tree, "FencedCode");
let manifest: Manifest | undefined; let manifest: Manifest | undefined;
let code: string | undefined; let code: string | undefined;
let language = "js"; let language = "js";
for (let codeNode of codeNodes) { for (const codeNode of codeNodes) {
let codeInfo = findNodeOfType(codeNode, "CodeInfo")!.children![0].text!; const codeInfo = findNodeOfType(codeNode, "CodeInfo")!.children![0].text!;
let codeText = findNodeOfType(codeNode, "CodeText")!.children![0].text!; const codeText = findNodeOfType(codeNode, "CodeText")!.children![0].text!;
if (codeInfo === "yaml") { if (codeInfo === "yaml") {
manifest = YAML.parse(codeText); manifest = YAML.parse(codeText) as Manifest;
continue; continue;
} }
if (codeInfo === "typescript" || codeInfo === "ts") { if (codeInfo === "typescript" || codeInfo === "ts") {
@ -70,15 +70,19 @@ async function compileDefinition(text: string): Promise<Manifest> {
manifest.dependencies = manifest.dependencies || {}; manifest.dependencies = manifest.dependencies || {};
for (let [dep, depSpec] of Object.entries(manifest.dependencies)) { for (const [dep, depSpec] of Object.entries(manifest.dependencies)) {
let compiled = await invokeFunction("server", "compileModule", depSpec); const compiled = await system.invokeFunction(
"server",
"compileModule",
depSpec,
);
manifest.dependencies![dep] = compiled; manifest.dependencies![dep] = compiled;
} }
manifest.functions = manifest.functions || {}; manifest.functions = manifest.functions || {};
for (let [name, func] of Object.entries(manifest.functions)) { for (const [name, func] of Object.entries(manifest.functions)) {
let compiled = await invokeFunction( const compiled = await system.invokeFunction(
"server", "server",
"compileJS", "compileJS",
`file.${language}`, `file.${language}`,
@ -94,14 +98,14 @@ async function compileDefinition(text: string): Promise<Manifest> {
return manifest; return manifest;
} }
export async function compileJS( export function compileJS(
filename: string, filename: string,
code: string, code: string,
functionName: string, functionName: string,
excludeModules: string[], excludeModules: string[],
): Promise<string> { ): Promise<string> {
// console.log("Compiling JS", filename, excludeModules); // console.log("Compiling JS", filename, excludeModules);
return self.syscall( return syscall(
"esbuild.compile", "esbuild.compile",
filename, filename,
code, code,
@ -110,12 +114,12 @@ export async function compileJS(
); );
} }
export async function compileModule(moduleName: string): Promise<string> { export function compileModule(moduleName: string): Promise<string> {
return self.syscall("esbuild.compileModule", moduleName); return syscall("esbuild.compileModule", moduleName);
} }
export async function getPlugPlugMd(pageName: string): Promise<Manifest> { export async function getPlugPlugMd(pageName: string): Promise<Manifest> {
let { text } = await readPage(pageName); const text = await space.readPage(pageName);
console.log("Compiling", pageName); console.log("Compiling", pageName);
return compileDefinition(text); return compileDefinition(text);
} }

View File

@ -1,11 +1,11 @@
import { listEvents } from "$sb/plugos-syscall/event.ts"; import { events } from "$sb/plugos-syscall/mod.ts";
import { matchBefore } from "$sb/silverbullet-syscall/editor.ts"; import { editor } from "$sb/silverbullet-syscall/mod.ts";
export async function queryComplete() { export async function queryComplete() {
const prefix = await matchBefore("#query [\\w\\-_]*"); const prefix = await editor.matchBefore("#query [\\w\\-_]*");
if (prefix) { if (prefix) {
const allEvents = await listEvents(); const allEvents = await events.listEvents();
// console.log("All events", allEvents); // console.log("All events", allEvents);
return { return {

View File

@ -1,52 +1,48 @@
// Index key space: // Index key space:
// data:page@pos // data:page@pos
import type { IndexTreeEvent } from "../../web/app_event.ts"; import type { IndexTreeEvent, QueryProviderEvent } from "$sb/app_event.ts";
import { import { index } from "$sb/silverbullet-syscall/mod.ts";
batchSet,
queryPrefix,
} from "../../syscall/silverbullet-syscall/index.ts";
import { import {
addParentPointers, addParentPointers,
collectNodesOfType, collectNodesOfType,
findNodeOfType, findNodeOfType,
ParseTree, ParseTree,
replaceNodesMatching, replaceNodesMatching,
} from "../../common/tree.ts"; } from "$sb/lib/tree.ts";
import type { QueryProviderEvent } from "./engine.ts"; import { applyQuery, removeQueries } from "$sb/lib/query.ts";
import { applyQuery } from "./engine.ts";
import { removeQueries } from "./util.ts";
import * as YAML from "yaml"; import * as YAML from "yaml";
export async function indexData({ name, tree }: IndexTreeEvent) { export async function indexData({ name, tree }: IndexTreeEvent) {
let dataObjects: { key: string; value: Object }[] = []; const dataObjects: { key: string; value: any }[] = [];
removeQueries(tree); removeQueries(tree);
collectNodesOfType(tree, "FencedCode").forEach((t) => { collectNodesOfType(tree, "FencedCode").forEach((t) => {
let codeInfoNode = findNodeOfType(t, "CodeInfo"); const codeInfoNode = findNodeOfType(t, "CodeInfo");
if (!codeInfoNode) { if (!codeInfoNode) {
return; return;
} }
if (codeInfoNode.children![0].text !== "data") { if (codeInfoNode.children![0].text !== "data") {
return; return;
} }
let codeTextNode = findNodeOfType(t, "CodeText"); const codeTextNode = findNodeOfType(t, "CodeText");
if (!codeTextNode) { if (!codeTextNode) {
// Honestly, this shouldn't happen // Honestly, this shouldn't happen
return; return;
} }
let codeText = codeTextNode.children![0].text!; const codeText = codeTextNode.children![0].text!;
try { try {
const docs = codeText.split("---").map((d) => YAML.parse(d));
// We support multiple YAML documents in one block // We support multiple YAML documents in one block
for (let doc of parseAllDocuments(codeText)) { for (let i = 0; i < docs.length; i++) {
if (!doc.contents) { const doc = docs[i];
if (!doc) {
continue; continue;
} }
console.log(doc.contents.toJSON());
dataObjects.push({ dataObjects.push({
key: `data:${name}@${t.from! + doc.range[0]}`, key: `data:${name}@${i}`,
value: doc.contents.toJSON(), value: doc,
}); });
} }
// console.log("Parsed data", parsedData); // console.log("Parsed data", parsedData);
@ -56,7 +52,7 @@ export async function indexData({ name, tree }: IndexTreeEvent) {
} }
}); });
console.log("Found", dataObjects.length, "data objects"); console.log("Found", dataObjects.length, "data objects");
await batchSet(name, dataObjects); await index.batchSet(name, dataObjects);
} }
export function extractMeta( export function extractMeta(
@ -83,23 +79,23 @@ export function extractMeta(
if (t.type !== "FencedCode") { if (t.type !== "FencedCode") {
return; return;
} }
let codeInfoNode = findNodeOfType(t, "CodeInfo"); const codeInfoNode = findNodeOfType(t, "CodeInfo");
if (!codeInfoNode) { if (!codeInfoNode) {
return; return;
} }
if (codeInfoNode.children![0].text !== "meta") { if (codeInfoNode.children![0].text !== "meta") {
return; return;
} }
let codeTextNode = findNodeOfType(t, "CodeText"); const codeTextNode = findNodeOfType(t, "CodeText");
if (!codeTextNode) { if (!codeTextNode) {
// Honestly, this shouldn't happen // Honestly, this shouldn't happen
return; return;
} }
let codeText = codeTextNode.children![0].text!; const codeText = codeTextNode.children![0].text!;
data = YAML.parse(codeText); data = YAML.parse(codeText);
if (removeKeys.length > 0) { if (removeKeys.length > 0) {
let newData = { ...data }; const newData = { ...data };
for (let key of removeKeys) { for (const key of removeKeys) {
delete newData[key]; delete newData[key];
} }
codeTextNode.children![0].text = YAML.stringify(newData).trim(); codeTextNode.children![0].text = YAML.stringify(newData).trim();
@ -117,9 +113,9 @@ export function extractMeta(
export async function queryProvider({ export async function queryProvider({
query, query,
}: QueryProviderEvent): Promise<any[]> { }: QueryProviderEvent): Promise<any[]> {
let allData: any[] = []; const allData: any[] = [];
for (let { key, page, value } of await queryPrefix("data:")) { for (const { key, page, value } of await index.queryPrefix("data:")) {
let [, pos] = key.split("@"); const [, pos] = key.split("@");
allData.push({ allData.push({
...value, ...value,
page: page, page: page,

View File

@ -1,12 +1,12 @@
import { assertEquals } from "../../test_deps.ts"; import { assertEquals } from "../../test_deps.ts";
import { applyQuery } from "./engine.ts"; import { applyQuery } from "$sb/lib/query.ts";
import { parseQuery } from "./parser.ts"; import { parseQuery } from "./parser.ts";
Deno.test("Test parser", () => { Deno.test("Test parser", () => {
let parsedBasicQuery = parseQuery(`page`); const parsedBasicQuery = parseQuery(`page`);
assertEquals(parsedBasicQuery.table, "page"); assertEquals(parsedBasicQuery.table, "page");
let parsedQuery1 = parseQuery( const parsedQuery1 = parseQuery(
`task where completed = false and dueDate <= "{{today}}" order by dueDate desc limit 5`, `task where completed = false and dueDate <= "{{today}}" order by dueDate desc limit 5`,
); );
assertEquals(parsedQuery1.table, "task"); assertEquals(parsedQuery1.table, "task");
@ -25,7 +25,7 @@ Deno.test("Test parser", () => {
value: "{{today}}", value: "{{today}}",
}); });
let parsedQuery2 = parseQuery(`page where name =~ /interview\\/.*/"`); const parsedQuery2 = parseQuery(`page where name =~ /interview\\/.*/"`);
assertEquals(parsedQuery2.table, "page"); assertEquals(parsedQuery2.table, "page");
assertEquals(parsedQuery2.filter.length, 1); assertEquals(parsedQuery2.filter.length, 1);
assertEquals(parsedQuery2.filter[0], { assertEquals(parsedQuery2.filter[0], {
@ -34,7 +34,7 @@ Deno.test("Test parser", () => {
value: "interview\\/.*", value: "interview\\/.*",
}); });
let parsedQuery3 = parseQuery(`page where something != null`); const parsedQuery3 = parseQuery(`page where something != null`);
assertEquals(parsedQuery3.table, "page"); assertEquals(parsedQuery3.table, "page");
assertEquals(parsedQuery3.filter.length, 1); assertEquals(parsedQuery3.filter.length, 1);
assertEquals(parsedQuery3.filter[0], { assertEquals(parsedQuery3.filter[0], {
@ -77,7 +77,7 @@ Deno.test("Test parser", () => {
}); });
Deno.test("Test applyQuery", () => { Deno.test("Test applyQuery", () => {
let data: any[] = [ const data: any[] = [
{ name: "interview/My Interview", lastModified: 1 }, { name: "interview/My Interview", lastModified: 1 },
{ name: "interview/My Interview 2", lastModified: 2 }, { name: "interview/My Interview 2", lastModified: 2 },
{ name: "Pete", age: 38 }, { name: "Pete", age: 38 },
@ -132,7 +132,7 @@ Deno.test("Test applyQuery", () => {
}); });
Deno.test("Test applyQuery with multi value", () => { Deno.test("Test applyQuery with multi value", () => {
let data: any[] = [ const data: any[] = [
{ name: "Pete", children: ["John", "Angie"] }, { name: "Pete", children: ["John", "Angie"] },
{ name: "Angie", children: ["Angie"] }, { name: "Angie", children: ["Angie"] },
{ name: "Steve" }, { name: "Steve" },

View File

@ -1,15 +1,10 @@
import { collectNodesOfType, ParseTree } from "../../common/tree.ts"; import { collectNodesOfType, ParseTree } from "$sb/lib/tree.ts";
import Handlebars from "handlebars"; import Handlebars from "handlebars";
import * as YAML from "yaml"; import * as YAML from "yaml";
import { readPage } from "../../syscall/silverbullet-syscall/space.ts"; import { space } from "$sb/silverbullet-syscall/mod.ts";
import { niceDate } from "../core/dates.ts"; import { niceDate } from "$sb/lib/dates.ts";
import { ParsedQuery } from "./parser.ts"; import { ParsedQuery } from "$sb/lib/query.ts";
export type QueryProviderEvent = {
query: ParsedQuery;
pageName: string;
};
export function valueNodeToVal(valNode: ParseTree): any { export function valueNodeToVal(valNode: ParseTree): any {
switch (valNode.type) { switch (valNode.type) {
@ -21,124 +16,24 @@ export function valueNodeToVal(valNode: ParseTree): any {
return null; return null;
case "Name": case "Name":
return valNode.children![0].text!; return valNode.children![0].text!;
case "Regex": case "Regex": {
let val = valNode.children![0].text!; const val = valNode.children![0].text!;
return val.substring(1, val.length - 1); return val.substring(1, val.length - 1);
case "String": }
let stringVal = valNode.children![0].text!; case "String": {
const stringVal = valNode.children![0].text!;
return stringVal.substring(1, stringVal.length - 1); return stringVal.substring(1, stringVal.length - 1);
case "PageRef": }
let pageRefVal = valNode.children![0].text!; case "PageRef": {
const pageRefVal = valNode.children![0].text!;
return pageRefVal.substring(2, pageRefVal.length - 2); return pageRefVal.substring(2, pageRefVal.length - 2);
case "List": }
case "List": {
return collectNodesOfType(valNode, "Value").map((t) => return collectNodesOfType(valNode, "Value").map((t) =>
valueNodeToVal(t.children![0]) valueNodeToVal(t.children![0])
); );
}
}
export function applyQuery<T>(parsedQuery: ParsedQuery, records: T[]): T[] {
let resultRecords: any[] = [];
if (parsedQuery.filter.length === 0) {
resultRecords = records.slice();
} else {
recordLoop:
for (let 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 async function renderQuery( export async function renderQuery(
@ -175,9 +70,9 @@ export async function renderQuery(
return YAML.stringify(v).trim(); return YAML.stringify(v).trim();
} }
}); });
let { text: templateText } = await readPage(parsedQuery.render); let templateText = await space.readPage(parsedQuery.render);
templateText = `{{#each .}}\n${templateText}\n{{/each}}`; templateText = `{{#each .}}\n${templateText}\n{{/each}}`;
let template = Handlebars.compile(templateText, { noEscape: true }); const template = Handlebars.compile(templateText, { noEscape: true });
return template(data); return template(data);
} }

View File

@ -1,26 +1,22 @@
import { import { editor } from "$sb/silverbullet-syscall/mod.ts";
getCurrentPage,
reloadPage,
save,
} from "$sb/silverbullet-syscall/editor.ts";
import Handlebars from "handlebars"; import Handlebars from "handlebars";
import { readPage, writePage } from "$sb/silverbullet-syscall/space.ts"; import { markdown, space } from "$sb/silverbullet-syscall/mod.ts";
import { invokeFunction } from "$sb/silverbullet-syscall/system.ts"; import { invokeFunction } from "$sb/silverbullet-syscall/system.ts";
import { renderQuery } from "./engine.ts"; import { renderQuery } from "./engine.ts";
import { parseQuery } from "./parser.ts"; import { parseQuery } from "./parser.ts";
import { replaceTemplateVars } from "../core/template.ts"; import { replaceTemplateVars } from "../core/template.ts";
import { jsonToMDTable, queryRegex } from "./util.ts"; import { jsonToMDTable } from "./util.ts";
import { dispatch } from "$sb/plugos-syscall/event.ts"; import { queryRegex } from "$sb/lib/query.ts";
import { replaceAsync } from "../lib/util.ts"; import { events } from "$sb/plugos-syscall/mod.ts";
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts"; import { replaceAsync } from "$sb/lib/util.ts";
import { nodeAtPos, renderToText } from "../../common/tree.ts"; import { nodeAtPos, renderToText } from "$sb/lib/tree.ts";
import { extractMeta } from "./data.ts"; import { extractMeta } from "./data.ts";
export async function updateMaterializedQueriesCommand() { export async function updateMaterializedQueriesCommand() {
const currentPage = await getCurrentPage(); const currentPage = await editor.getCurrentPage();
await save(); await editor.save();
if ( if (
await invokeFunction( await invokeFunction(
"server", "server",
@ -29,7 +25,7 @@ export async function updateMaterializedQueriesCommand() {
) )
) { ) {
console.log("Going reload the page"); console.log("Going reload the page");
await reloadPage(); await editor.reloadPage();
} }
} }
@ -43,13 +39,13 @@ function updateTemplateInstantiations(
return replaceAsync( return replaceAsync(
text, text,
templateInstRegex, templateInstRegex,
async (fullMatch, startInst, type, template, args, body, endInst) => { async (fullMatch, startInst, type, template, args, _body, endInst) => {
args = args.trim(); args = args.trim();
let parsedArgs = {}; let parsedArgs = {};
if (args) { if (args) {
try { try {
parsedArgs = JSON.parse(args); parsedArgs = JSON.parse(args);
} catch (e) { } catch {
console.error("Failed to parse template instantiation args", args); console.error("Failed to parse template instantiation args", args);
return fullMatch; return fullMatch;
} }
@ -57,21 +53,21 @@ function updateTemplateInstantiations(
let templateText = ""; let templateText = "";
if (template.startsWith("http://") || template.startsWith("https://")) { if (template.startsWith("http://") || template.startsWith("https://")) {
try { try {
let req = await fetch(template); const req = await fetch(template);
templateText = await req.text(); templateText = await req.text();
} catch (e: any) { } catch (e: any) {
templateText = `ERROR: ${e.message}`; templateText = `ERROR: ${e.message}`;
} }
} else { } else {
templateText = (await readPage(template)).text; templateText = await space.readPage(template);
} }
let newBody = templateText; let newBody = templateText;
// if it's a template injection (not a literal "include") // if it's a template injection (not a literal "include")
if (type === "use" || type === "use-verbose") { if (type === "use" || type === "use-verbose") {
let tree = await parseMarkdown(templateText); const tree = await markdown.parseMarkdown(templateText);
extractMeta(tree, ["$disableDirectives"]); extractMeta(tree, ["$disableDirectives"]);
templateText = renderToText(tree); templateText = renderToText(tree);
let templateFn = Handlebars.compile( const templateFn = Handlebars.compile(
replaceTemplateVars(templateText, pageName), replaceTemplateVars(templateText, pageName),
{ noEscape: true }, { noEscape: true },
); );
@ -87,11 +83,11 @@ function cleanTemplateInstantiations(text: string): Promise<string> {
text, text,
templateInstRegex, templateInstRegex,
( (
fullMatch, _fullMatch,
startInst, startInst,
type, type,
template, _template,
args, _args,
body, body,
endInst, endInst,
): Promise<string> => { ): Promise<string> => {
@ -99,9 +95,9 @@ function cleanTemplateInstantiations(text: string): Promise<string> {
body = body.replaceAll( body = body.replaceAll(
queryRegex, queryRegex,
( (
fullMatch: string, _fullMatch: string,
startQuery: string, _startQuery: string,
query: string, _query: string,
body: string, body: string,
) => { ) => {
return body.trim(); return body.trim();
@ -120,7 +116,7 @@ export async function updateMaterializedQueriesOnPage(
// console.log("Updating queries"); // console.log("Updating queries");
let text = ""; let text = "";
try { try {
text = (await readPage(pageName)).text; text = await space.readPage(pageName);
} catch { } catch {
console.warn( console.warn(
"Could not read page", "Could not read page",
@ -130,8 +126,8 @@ export async function updateMaterializedQueriesOnPage(
return false; return false;
} }
let newText = await updateTemplateInstantiations(text, pageName); let newText = await updateTemplateInstantiations(text, pageName);
let tree = await parseMarkdown(newText); const tree = await markdown.parseMarkdown(newText);
let metaData = extractMeta(tree, ["$disableDirectives"]); const metaData = extractMeta(tree, ["$disableDirectives"]);
if (metaData.$disableDirectives) { if (metaData.$disableDirectives) {
console.log("Directives disabled, skipping"); console.log("Directives disabled, skipping");
return false; return false;
@ -141,18 +137,18 @@ export async function updateMaterializedQueriesOnPage(
newText = await replaceAsync( newText = await replaceAsync(
newText, newText,
queryRegex, queryRegex,
async (fullMatch, startQuery, query, body, endQuery, index) => { async (fullMatch, startQuery, query, _body, endQuery, index) => {
let currentNode = nodeAtPos(tree, index + 1); const currentNode = nodeAtPos(tree, index + 1);
if (currentNode?.type !== "CommentBlock") { if (currentNode?.type !== "CommentBlock") {
// If not a comment block, it's likely a code block, ignore // If not a comment block, it's likely a code block, ignore
return fullMatch; return fullMatch;
} }
let parsedQuery = parseQuery(replaceTemplateVars(query, pageName)); const parsedQuery = parseQuery(replaceTemplateVars(query, pageName));
// console.log("Parsed query", parsedQuery); // console.log("Parsed query", parsedQuery);
// Let's dispatch an event and see what happens // Let's dispatch an event and see what happens
let results = await dispatch( const results = await events.dispatchEvent(
`query:${parsedQuery.table}`, `query:${parsedQuery.table}`,
{ query: parsedQuery, pageName: pageName }, { query: parsedQuery, pageName: pageName },
10 * 1000, 10 * 1000,
@ -161,7 +157,7 @@ export async function updateMaterializedQueriesOnPage(
return `${startQuery}\n${endQuery}`; return `${startQuery}\n${endQuery}`;
} else if (results.length === 1) { } else if (results.length === 1) {
if (parsedQuery.render) { if (parsedQuery.render) {
let rendered = await renderQuery(parsedQuery, results[0]); const rendered = await renderQuery(parsedQuery, results[0]);
return `${startQuery}\n${rendered.trim()}\n${endQuery}`; return `${startQuery}\n${rendered.trim()}\n${endQuery}`;
} else { } else {
return `${startQuery}\n${jsonToMDTable(results[0])}\n${endQuery}`; return `${startQuery}\n${jsonToMDTable(results[0])}\n${endQuery}`;
@ -174,7 +170,7 @@ export async function updateMaterializedQueriesOnPage(
); );
newText = await cleanTemplateInstantiations(newText); newText = await cleanTemplateInstantiations(newText);
if (text !== newText) { if (text !== newText) {
await writePage(pageName, newText); await space.writePage(pageName, newText);
return true; return true;
} }
return false; return false;

View File

@ -2,35 +2,20 @@ import {
collectNodesOfType, collectNodesOfType,
findNodeOfType, findNodeOfType,
replaceNodesMatching, replaceNodesMatching,
} from "../../common/tree.ts"; } from "$sb/lib/tree.ts";
import { lezerToParseTree } from "../../common/parse_tree.ts"; import { lezerToParseTree } from "../../common/parse_tree.ts";
import { valueNodeToVal } from "./engine.ts"; import { valueNodeToVal } from "./engine.ts";
// @ts-ignore auto generated // @ts-ignore auto generated
import { parser } from "./parse-query.js"; import { parser } from "./parse-query.js";
import { ParsedQuery, QueryFilter } from "$sb/lib/query.ts";
export type Filter = {
op: string;
prop: string;
value: any;
};
export type ParsedQuery = {
table: string;
orderBy?: string;
orderDesc?: boolean;
limit?: number;
filter: Filter[];
select?: string[];
render?: string;
};
export function parseQuery(query: string): ParsedQuery { export function parseQuery(query: string): ParsedQuery {
let n = lezerToParseTree(query, parser.parse(query).topNode); const n = lezerToParseTree(query, parser.parse(query).topNode);
// Clean the tree a bit // Clean the tree a bit
replaceNodesMatching(n, (n) => { replaceNodesMatching(n, (n) => {
if (!n.type) { if (!n.type) {
let trimmed = n.text!.trim(); const trimmed = n.text!.trim();
if (!trimmed) { if (!trimmed) {
return null; return null;
} }
@ -39,50 +24,47 @@ export function parseQuery(query: string): ParsedQuery {
}); });
// console.log("Parsed", JSON.stringify(n, null, 2)); // console.log("Parsed", JSON.stringify(n, null, 2));
let queryNode = n.children![0]; const queryNode = n.children![0];
let parsedQuery: ParsedQuery = { const parsedQuery: ParsedQuery = {
table: queryNode.children![0].children![0].text!, table: queryNode.children![0].children![0].text!,
filter: [], filter: [],
}; };
let orderByNode = findNodeOfType(queryNode, "OrderClause"); const orderByNode = findNodeOfType(queryNode, "OrderClause");
if (orderByNode) { if (orderByNode) {
let nameNode = findNodeOfType(orderByNode, "Name"); const nameNode = findNodeOfType(orderByNode, "Name");
parsedQuery.orderBy = nameNode!.children![0].text!; parsedQuery.orderBy = nameNode!.children![0].text!;
let orderNode = findNodeOfType(orderByNode, "Order"); const orderNode = findNodeOfType(orderByNode, "Order");
parsedQuery.orderDesc = orderNode parsedQuery.orderDesc = orderNode
? orderNode.children![0].text! === "desc" ? orderNode.children![0].text! === "desc"
: false; : false;
} }
let limitNode = findNodeOfType(queryNode, "LimitClause"); const limitNode = findNodeOfType(queryNode, "LimitClause");
if (limitNode) { if (limitNode) {
let nameNode = findNodeOfType(limitNode, "Number"); const nameNode = findNodeOfType(limitNode, "Number");
parsedQuery.limit = valueNodeToVal(nameNode!); parsedQuery.limit = valueNodeToVal(nameNode!);
} }
let filterNodes = collectNodesOfType(queryNode, "FilterExpr"); const filterNodes = collectNodesOfType(queryNode, "FilterExpr");
for (let filterNode of filterNodes) { for (const filterNode of filterNodes) {
let val: any = undefined; let val: any = undefined;
let valNode = filterNode.children![2].children![0]; const valNode = filterNode.children![2].children![0];
val = valueNodeToVal(valNode); val = valueNodeToVal(valNode);
let f: Filter = { const f: QueryFilter = {
prop: filterNode.children![0].children![0].text!, prop: filterNode.children![0].children![0].text!,
op: filterNode.children![1].text!, op: filterNode.children![1].text!,
value: val, value: val,
}; };
parsedQuery.filter.push(f); parsedQuery.filter.push(f);
} }
let selectNode = findNodeOfType(queryNode, "SelectClause"); const selectNode = findNodeOfType(queryNode, "SelectClause");
if (selectNode) { if (selectNode) {
// console.log("Select node", JSON.stringify(selectNode));
parsedQuery.select = []; parsedQuery.select = [];
collectNodesOfType(selectNode, "Name").forEach((t) => { collectNodesOfType(selectNode, "Name").forEach((t) => {
parsedQuery.select!.push(t.children![0].text!); parsedQuery.select!.push(t.children![0].text!);
}); });
// let nameNode = findNodeOfType(selectNode, "Number");
// parsedQuery.limit = +nameNode!.children![0].text!;
} }
let renderNode = findNodeOfType(queryNode, "RenderClause"); const renderNode = findNodeOfType(queryNode, "RenderClause");
if (renderNode) { if (renderNode) {
let renderNameNode = findNodeOfType(renderNode, "PageRef"); let renderNameNode = findNodeOfType(renderNode, "PageRef");
if (!renderNameNode) { if (!renderNameNode) {
@ -91,6 +73,5 @@ export function parseQuery(query: string): ParsedQuery {
parsedQuery.render = valueNodeToVal(renderNameNode!); parsedQuery.render = valueNodeToVal(renderNameNode!);
} }
// console.log(JSON.stringify(queryNode, null, 2));
return parsedQuery; return parsedQuery;
} }

View File

@ -1,58 +1,10 @@
import {
addParentPointers,
collectNodesMatching,
ParseTree,
renderToText,
} from "../../common/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 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;
});
}
const maxWidth = 70; const maxWidth = 70;
// Nicely format an array of JSON objects as a Markdown table // Nicely format an array of JSON objects as a Markdown table
export function jsonToMDTable( export function jsonToMDTable(
jsonArray: any[], jsonArray: any[],
valueTransformer: (k: string, v: any) => string = (k, v) => "" + v, valueTransformer: (k: string, v: any) => string = (k, v) => "" + v,
): string { ): string {
let fieldWidths = new Map<string, number>(); const fieldWidths = new Map<string, number>();
for (let entry of jsonArray) { for (let entry of jsonArray) {
for (let k of Object.keys(entry)) { for (let k of Object.keys(entry)) {
let fieldWidth = fieldWidths.get(k); let fieldWidth = fieldWidths.get(k);
@ -70,8 +22,8 @@ export function jsonToMDTable(
fullWidth += v + 1; fullWidth += v + 1;
} }
let headerList = [...fieldWidths.keys()]; const headerList = [...fieldWidths.keys()];
let lines = []; const lines = [];
lines.push( lines.push(
"|" + "|" +
headerList headerList

View File

@ -1,14 +1,17 @@
import type { ClickEvent, IndexTreeEvent } from "../../web/app_event.ts"; import type {
ClickEvent,
IndexTreeEvent,
QueryProviderEvent,
} from "$sb/app_event.ts";
import { batchSet, queryPrefix } from "$sb/silverbullet-syscall/index.ts";
import { readPage, writePage } from "$sb/silverbullet-syscall/space.ts";
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
import { import {
dispatch, editor,
filterBox, index,
getCursor, markdown,
getText, space,
} from "$sb/silverbullet-syscall/editor.ts"; } from "$sb/silverbullet-syscall/mod.ts";
import { events } from "$sb/plugos-syscall/mod.ts";
import { import {
addParentPointers, addParentPointers,
collectNodesMatching, collectNodesMatching,
@ -18,10 +21,9 @@ import {
ParseTree, ParseTree,
renderToText, renderToText,
replaceNodesMatching, replaceNodesMatching,
} from "../../common/tree.ts"; } from "$sb/lib/tree.ts";
import { removeQueries } from "../query/util.ts"; import { applyQuery, removeQueries } from "$sb/lib/query.ts";
import { applyQuery, QueryProviderEvent } from "../query/engine.ts"; import { niceDate } from "$sb/lib/dates.ts";
import { niceDate } from "../core/dates.ts";
export type Task = { export type Task = {
name: string; name: string;
@ -40,11 +42,11 @@ function getDeadline(deadlineNode: ParseTree): string {
export async function indexTasks({ name, tree }: IndexTreeEvent) { export async function indexTasks({ name, tree }: IndexTreeEvent) {
// console.log("Indexing tasks"); // console.log("Indexing tasks");
let tasks: { key: string; value: Task }[] = []; const tasks: { key: string; value: Task }[] = [];
removeQueries(tree); removeQueries(tree);
collectNodesOfType(tree, "Task").forEach((n) => { collectNodesOfType(tree, "Task").forEach((n) => {
let complete = n.children![0].children![0].text! !== "[ ]"; const complete = n.children![0].children![0].text! !== "[ ]";
let task: Task = { const task: Task = {
name: "", name: "",
done: complete, done: complete,
}; };
@ -67,8 +69,8 @@ export async function indexTasks({ name, tree }: IndexTreeEvent) {
task.name = n.children!.slice(1).map(renderToText).join("").trim(); task.name = n.children!.slice(1).map(renderToText).join("").trim();
let taskIndex = n.parent!.children!.indexOf(n); const taskIndex = n.parent!.children!.indexOf(n);
let nestedItems = n.parent!.children!.slice(taskIndex + 1); const nestedItems = n.parent!.children!.slice(taskIndex + 1);
if (nestedItems.length > 0) { if (nestedItems.length > 0) {
task.nested = nestedItems.map(renderToText).join("").trim(); task.nested = nestedItems.map(renderToText).join("").trim();
} }
@ -80,10 +82,10 @@ export async function indexTasks({ name, tree }: IndexTreeEvent) {
}); });
console.log("Found", tasks.length, "task(s)"); console.log("Found", tasks.length, "task(s)");
await batchSet(name, tasks); await index.batchSet(name, tasks);
} }
export async function taskToggle(event: ClickEvent) { export function taskToggle(event: ClickEvent) {
return taskToggleAtPos(event.pos); return taskToggleAtPos(event.pos);
} }
@ -92,7 +94,7 @@ async function toggleTaskMarker(node: ParseTree, moveToPos: number) {
if (node.children![0].text === "[x]" || node.children![0].text === "[X]") { if (node.children![0].text === "[x]" || node.children![0].text === "[X]") {
changeTo = "[ ]"; changeTo = "[ ]";
} }
await dispatch({ await editor.dispatch({
changes: { changes: {
from: node.from, from: node.from,
to: node.to, to: node.to,
@ -103,19 +105,19 @@ async function toggleTaskMarker(node: ParseTree, moveToPos: number) {
}, },
}); });
let parentWikiLinks = collectNodesMatching( const parentWikiLinks = collectNodesMatching(
node.parent!, node.parent!,
(n) => n.type === "WikiLinkPage", (n) => n.type === "WikiLinkPage",
); );
for (let wikiLink of parentWikiLinks) { for (const wikiLink of parentWikiLinks) {
let ref = wikiLink.children![0].text!; const ref = wikiLink.children![0].text!;
if (ref.includes("@")) { if (ref.includes("@")) {
let [page, pos] = ref.split("@"); const [page, pos] = ref.split("@");
let text = (await readPage(page)).text; let text = (await space.readPage(page));
let referenceMdTree = await parseMarkdown(text); const referenceMdTree = await markdown.parseMarkdown(text);
// Adding +1 to immediately hit the task marker // Adding +1 to immediately hit the task marker
let taskMarkerNode = nodeAtPos(referenceMdTree, +pos + 1); const taskMarkerNode = nodeAtPos(referenceMdTree, +pos + 1);
if (!taskMarkerNode || taskMarkerNode.type !== "TaskMarker") { if (!taskMarkerNode || taskMarkerNode.type !== "TaskMarker") {
console.error( console.error(
@ -127,44 +129,44 @@ async function toggleTaskMarker(node: ParseTree, moveToPos: number) {
taskMarkerNode.children![0].text = changeTo; taskMarkerNode.children![0].text = changeTo;
text = renderToText(referenceMdTree); text = renderToText(referenceMdTree);
console.log("Updated reference paged text", text); console.log("Updated reference paged text", text);
await writePage(page, text); await space.writePage(page, text);
} }
} }
} }
export async function taskToggleAtPos(pos: number) { export async function taskToggleAtPos(pos: number) {
let text = await getText(); const text = await editor.getText();
let mdTree = await parseMarkdown(text); const mdTree = await markdown.parseMarkdown(text);
addParentPointers(mdTree); addParentPointers(mdTree);
let node = nodeAtPos(mdTree, pos); const node = nodeAtPos(mdTree, pos);
if (node && node.type === "TaskMarker") { if (node && node.type === "TaskMarker") {
await toggleTaskMarker(node, pos); await toggleTaskMarker(node, pos);
} }
} }
export async function taskToggleCommand() { export async function taskToggleCommand() {
let text = await getText(); const text = await editor.getText();
let pos = await getCursor(); const pos = await editor.getCursor();
let tree = await parseMarkdown(text); const tree = await markdown.parseMarkdown(text);
addParentPointers(tree); addParentPointers(tree);
let node = nodeAtPos(tree, pos); const node = nodeAtPos(tree, pos);
// We kwow node.type === Task (due to the task context) // We kwow node.type === Task (due to the task context)
let taskMarker = findNodeOfType(node!, "TaskMarker"); const taskMarker = findNodeOfType(node!, "TaskMarker");
await toggleTaskMarker(taskMarker!, pos); await toggleTaskMarker(taskMarker!, pos);
} }
export async function postponeCommand() { export async function postponeCommand() {
let text = await getText(); const text = await editor.getText();
let pos = await getCursor(); const pos = await editor.getCursor();
let tree = await parseMarkdown(text); const tree = await markdown.parseMarkdown(text);
addParentPointers(tree); addParentPointers(tree);
let node = nodeAtPos(tree, pos)!; const node = nodeAtPos(tree, pos)!;
// We kwow node.type === DeadlineDate (due to the task context) // We kwow node.type === DeadlineDate (due to the task context)
let date = getDeadline(node); const date = getDeadline(node);
let option = await filterBox( const option = await editor.filterBox(
"Postpone for...", "Postpone for...",
[ [
{ name: "a day", orderId: 1 }, { name: "a day", orderId: 1 },
@ -188,7 +190,7 @@ export async function postponeCommand() {
d.setDate(d.getDate() + ((7 - d.getDay() + 1) % 7 || 7)); d.setDate(d.getDate() + ((7 - d.getDay() + 1) % 7 || 7));
break; break;
} }
await dispatch({ await editor.dispatch({
changes: { changes: {
from: node.from, from: node.from,
to: node.to, to: node.to,
@ -204,8 +206,8 @@ export async function postponeCommand() {
export async function queryProvider({ export async function queryProvider({
query, query,
}: QueryProviderEvent): Promise<Task[]> { }: QueryProviderEvent): Promise<Task[]> {
let allTasks: Task[] = []; const allTasks: Task[] = [];
for (let { key, page, value } of await queryPrefix("task:")) { for (let { key, page, value } of await index.queryPrefix("task:")) {
let [, pos] = key.split(":"); let [, pos] = key.split(":");
allTasks.push({ allTasks.push({
...value, ...value,

View File

@ -50,7 +50,7 @@ export class PlugSpacePrimitives implements SpacePrimitives {
name: string, name: string,
encoding: FileEncoding, encoding: FileEncoding,
): Promise<{ data: FileData; meta: FileMeta }> { ): Promise<{ data: FileData; meta: FileMeta }> {
let result = this.performOperation("readFile", name); const result = this.performOperation("readFile", name);
if (result) { if (result) {
return result; return result;
} }

View File

@ -8,55 +8,55 @@ import {
export default (space: Space): SysCallMapping => { export default (space: Space): SysCallMapping => {
return { return {
"space.listPages": async (): Promise<PageMeta[]> => { "space.listPages": (): PageMeta[] => {
return [...space.listPages()]; return [...space.listPages()];
}, },
"space.readPage": async ( "space.readPage": async (
ctx, _ctx,
name: string, name: string,
): Promise<{ text: string; meta: PageMeta }> => { ): Promise<string> => {
return space.readPage(name); return (await space.readPage(name)).text;
}, },
"space.getPageMeta": async (ctx, name: string): Promise<PageMeta> => { "space.getPageMeta": (_ctx, name: string): Promise<PageMeta> => {
return space.getPageMeta(name); return space.getPageMeta(name);
}, },
"space.writePage": async ( "space.writePage": (
ctx, _ctx,
name: string, name: string,
text: string, text: string,
): Promise<PageMeta> => { ): Promise<PageMeta> => {
return space.writePage(name, text); return space.writePage(name, text);
}, },
"space.deletePage": async (ctx, name: string) => { "space.deletePage": (_ctx, name: string) => {
return space.deletePage(name); return space.deletePage(name);
}, },
"space.listPlugs": async (): Promise<string[]> => { "space.listPlugs": (): Promise<string[]> => {
return await space.listPlugs(); return space.listPlugs();
}, },
"space.listAttachments": async (ctx): Promise<AttachmentMeta[]> => { "space.listAttachments": async (): Promise<AttachmentMeta[]> => {
return await space.fetchAttachmentList(); return await space.fetchAttachmentList();
}, },
"space.readAttachment": async ( "space.readAttachment": async (
ctx, _ctx,
name: string, name: string,
): Promise<{ data: FileData; meta: AttachmentMeta }> => { ): Promise<FileData> => {
return await space.readAttachment(name, "dataurl"); return (await space.readAttachment(name, "dataurl")).data;
}, },
"space.getAttachmentMeta": async ( "space.getAttachmentMeta": async (
ctx, _ctx,
name: string, name: string,
): Promise<AttachmentMeta> => { ): Promise<AttachmentMeta> => {
return await space.getAttachmentMeta(name); return await space.getAttachmentMeta(name);
}, },
"space.writeAttachment": async ( "space.writeAttachment": async (
ctx, _ctx,
name: string, name: string,
encoding: FileEncoding, encoding: FileEncoding,
data: string, data: string,
): Promise<AttachmentMeta> => { ): Promise<AttachmentMeta> => {
return await space.writeAttachment(name, encoding, data); return await space.writeAttachment(name, encoding, data);
}, },
"space.deleteAttachment": async (ctx, name: string) => { "space.deleteAttachment": async (_ctx, name: string) => {
await space.deleteAttachment(name); await space.deleteAttachment(name);
}, },
}; };

View File

@ -1,22 +0,0 @@
import { base64Decode } from "../../plugos/asset_bundle/base64.ts";
import type { FileMeta } from "./fs.ts";
import { syscall } from "./syscall.ts";
export async function readAsset(
name: string,
encoding: "utf8" | "dataurl" = "utf8",
): Promise<{ text: string; meta: FileMeta }> {
const { data, meta } = await syscall("asset.readAsset", name);
switch (encoding) {
case "utf8":
return {
text: new TextDecoder().decode(base64Decode(data)),
meta,
};
case "dataurl":
return {
text: "data:application/octet-stream," + data,
meta,
};
}
}

View File

@ -48,7 +48,7 @@ import { eventSyscalls } from "../plugos/syscalls/event.ts";
import sandboxSyscalls from "../plugos/syscalls/sandbox.ts"; import sandboxSyscalls from "../plugos/syscalls/sandbox.ts";
import { System } from "../plugos/system.ts"; import { System } from "../plugos/system.ts";
import { AppEvent, ClickEvent } from "./app_event.ts"; import { AppEvent, ClickEvent } from "../plug-api/app_event.ts";
import { CommandPalette } from "./components/command_palette.tsx"; import { CommandPalette } from "./components/command_palette.tsx";
import { FilterList } from "./components/filter.tsx"; import { FilterList } from "./components/filter.tsx";
import { PageNavigator } from "./components/page_navigator.tsx"; import { PageNavigator } from "./components/page_navigator.tsx";

View File

@ -54,7 +54,7 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
"editor.reloadPage": async () => { "editor.reloadPage": async () => {
await editor.reloadPage(); await editor.reloadPage();
}, },
"editor.openUrl": async (ctx, url: string) => { "editor.openUrl": (_ctx, url: string) => {
let win = window.open(url, "_blank"); let win = window.open(url, "_blank");
if (win) { if (win) {
win.focus(); win.focus();
@ -95,25 +95,6 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
id: id as any, id: id as any,
}); });
}, },
// Deprecated in favor of using "hidePanel" and "showPanel"
"editor.showRhs": (ctx, html: string, script: string, flex: number) => {
syscalls["editor.showPanel"](ctx, "rhs", flex, html, script);
},
"editor.hideRhs": (ctx) => {
syscalls["editor.hidePanel"](ctx, "rhs");
},
"editor.showLhs": (ctx, html: string, script: string, flex: number) => {
syscalls["editor.showPanel"](ctx, "lhs", flex, html, script);
},
"editor.hideLhs": (ctx) => {
syscalls["editor.hidePanel"](ctx, "lhs");
},
"editor.showBhs": (ctx, html: string, script: string, flex: number) => {
syscalls["editor.showPanel"](ctx, "bhs", flex, html, script);
},
"editor.hideBhs": (ctx) => {
syscalls["editor.hidePanel"](ctx, "bhs");
},
"editor.insertAtPos": (ctx, text: string, pos: number) => { "editor.insertAtPos": (ctx, text: string, pos: number) => {
editor.editorView!.dispatch({ editor.editorView!.dispatch({
changes: { changes: {

View File

@ -14,8 +14,8 @@ export function spaceSyscalls(editor: Editor): SysCallMapping {
"space.readPage": async ( "space.readPage": async (
_ctx, _ctx,
name: string, name: string,
): Promise<{ text: string; meta: PageMeta }> => { ): Promise<string> => {
return await editor.space.readPage(name); return (await editor.space.readPage(name)).text;
}, },
"space.getPageMeta": async (_ctx, name: string): Promise<PageMeta> => { "space.getPageMeta": async (_ctx, name: string): Promise<PageMeta> => {
return await editor.space.getPageMeta(name); return await editor.space.getPageMeta(name);
@ -46,8 +46,8 @@ export function spaceSyscalls(editor: Editor): SysCallMapping {
"space.readAttachment": async ( "space.readAttachment": async (
_ctx, _ctx,
name: string, name: string,
): Promise<{ data: FileData; meta: AttachmentMeta }> => { ): Promise<FileData> => {
return await editor.space.readAttachment(name, "dataurl"); return (await editor.space.readAttachment(name, "dataurl")).data;
}, },
"space.getAttachmentMeta": async ( "space.getAttachmentMeta": async (
_ctx, _ctx,

View File

@ -4,7 +4,7 @@ import { CommandDef } from "../hooks/command.ts";
export function systemSyscalls(editor: Editor): SysCallMapping { export function systemSyscalls(editor: Editor): SysCallMapping {
return { return {
"system.invokeFunction": async ( "system.invokeFunction": (
ctx, ctx,
env: string, env: string,
name: string, name: string,
@ -20,23 +20,20 @@ export function systemSyscalls(editor: Editor): SysCallMapping {
return editor.space.invokeFunction(ctx.plug, env, name, args); return editor.space.invokeFunction(ctx.plug, env, name, args);
}, },
"system.invokeCommand": async (ctx, name: string) => { "system.invokeCommand": (ctx, name: string) => {
return editor.runCommandByName(name); return editor.runCommandByName(name);
}, },
"system.listCommands": async ( "system.listCommands": (): { [key: string]: CommandDef } => {
ctx, const allCommands: { [key: string]: CommandDef } = {};
): Promise<{ [key: string]: CommandDef }> => {
let allCommands: { [key: string]: CommandDef } = {};
for (let [cmd, def] of editor.commandHook.editorCommands) { for (let [cmd, def] of editor.commandHook.editorCommands) {
allCommands[cmd] = def.command; allCommands[cmd] = def.command;
} }
return allCommands; return allCommands;
}, },
"system.reloadPlugs": async () => { "system.reloadPlugs": () => {
return editor.reloadPlugs(); return editor.reloadPlugs();
}, },
"sandbox.getServerLogs": (ctx) => {
"sandbox.getServerLogs": async (ctx) => {
return editor.space.proxySyscall(ctx.plug, "sandbox.getLogs", []); return editor.space.proxySyscall(ctx.plug, "sandbox.getLogs", []);
}, },
}; };

View File

@ -3,7 +3,16 @@ release.
--- ---
## Deno release ## 0.1.2
- Breaking plugs API change: `readPage`, `readAttachment`, `readFile` now return
the read data object directly, without it being wrapped with a text object.
- Also some syscalls have been renamed, e.g. store-related one now all have a
`store` prefix, such as `get` become `storeGet`.
---
## 0.1.0 First Deno release
- The entire repo has been migrated to [Deno](https://deno.land) - The entire repo has been migrated to [Deno](https://deno.land)
- This may temporarily break some things. - This may temporarily break some things.