Massive restructure of plugin API
This commit is contained in:
parent
982623fc38
commit
7d28b53b75
@ -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";
|
||||||
|
@ -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 {
|
||||||
|
@ -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"
|
||||||
|
@ -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
168
plug-api/lib/query.ts
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
@ -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
|
@ -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.
|
@ -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```");
|
||||||
}
|
}
|
15
plug-api/plugos-syscall/asset.ts
Normal file
15
plug-api/plugos-syscall/asset.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
8
plug-api/plugos-syscall/mod.ts
Normal file
8
plug-api/plugos-syscall/mod.ts
Normal 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";
|
@ -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
|
|
@ -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);
|
7
plug-api/silverbullet-syscall/mod.ts
Normal file
7
plug-api/silverbullet-syscall/mod.ts
Normal 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";
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
@ -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) => ({
|
||||||
|
@ -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",
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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"?
|
||||||
|
@ -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,
|
||||||
|
@ -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.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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
51
plugs/markdown/styles.css
Normal 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;
|
||||||
|
}
|
@ -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!;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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" },
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -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";
|
||||||
|
@ -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: {
|
||||||
|
@ -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,
|
||||||
|
@ -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", []);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user