@silverbulletmd/silverbullet 2.8.1 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/client/plugos/syscalls/editor.ts +15 -4
- package/client/plugos/syscalls/index.ts +5 -2
- package/client/plugos/syscalls/lua.ts +25 -2
- package/client/space_lua/parse.ts +12 -12
- package/client/space_lua/pretty_print.ts +391 -0
- package/client/space_lua/query_collection.ts +6 -6
- package/client/space_lua/stdlib/load.ts +2 -2
- package/client/space_lua/stdlib/space_lua.ts +62 -2
- package/client/space_lua/stdlib.ts +2 -2
- package/dist/plug-compile.js +1 -1
- package/package.json +10 -2
- package/plug-api/syscalls/editor.ts +7 -2
- package/plug-api/syscalls/index.ts +7 -0
- package/plug-api/syscalls/lua.ts +21 -1
- package/plug-api/types/config.ts +1 -1
- package/plug-api/ui/cx.ts +6 -0
- package/plug-api/ui/index.ts +19 -0
- package/plug-api/ui/panel_styles.ts +32 -0
- package/plugs/builtin_plugs.ts +1 -0
package/README.md
CHANGED
|
@@ -5,18 +5,22 @@
|
|
|
5
5
|
[](https://deepwiki.com/silverbulletmd/silverbullet)
|
|
6
6
|
|
|
7
7
|
# SilverBullet
|
|
8
|
-
SilverBullet is a Programmable, Private, Browser-based, Open Source, Self Hosted, Personal Knowledge
|
|
8
|
+
SilverBullet is a Programmable, Private, Browser-based, Open Source, Self Hosted, Personal Knowledge Database — a fancy term for a "notes app on steroids".
|
|
9
9
|
|
|
10
10
|
_Yowza!_ That surely is a lot of adjectives to describe a browser-based Markdown editor programmable with Lua.
|
|
11
11
|
|
|
12
12
|
Let’s get more specific.
|
|
13
13
|
|
|
14
|
+
SilverBullet combines a clean live-preview editor with wiki-style linking, a built-in database and query language, and a fully integrated Space Lua scripting environment, turning your notes into a programmable system that grows with you.
|
|
15
|
+
|
|
14
16
|
In SilverBullet you keep your content as a collection of Markdown Pages (called a Space). You navigate your space using the Page Picker like a traditional notes app, or through Links like a wiki (except they are bi-directional).
|
|
15
17
|
|
|
16
|
-
If you are the **writer** type, you’ll appreciate SilverBullet as a clean Markdown editor with Live Preview. If you have more of an **outliner** personality, SilverBullet has Outlining tools for you. Productivity freak? Have a look at Tasks. More of a **database** person? You will appreciate Objects and Queries.
|
|
18
|
+
If you are the **writer** type, you’ll appreciate SilverBullet as a clean Markdown editor with Live Preview. If you have more of an **outliner** personality, SilverBullet has Outlining tools for you. Productivity freak? Have a look at Tasks. More of a **database** person? You will appreciate Objects and Queries (SLIQ).
|
|
17
19
|
|
|
18
20
|
And if you are comfortable **programming** a little bit — now we’re really talking. You will love _dynamically generating content_ with Space Lua (SilverBullet’s Lua dialect), or to use it to create custom Commands, Page Templates or Widgets.
|
|
19
21
|
|
|
22
|
+
You were told there’s no such thing as a silver bullet. You were told wrong.
|
|
23
|
+
|
|
20
24
|
[Much more detail can be found on silverbullet.md](https://silverbullet.md)
|
|
21
25
|
|
|
22
26
|
## Installing SilverBullet
|
|
@@ -219,10 +219,12 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
219
219
|
);
|
|
220
220
|
} else if (
|
|
221
221
|
ref.details &&
|
|
222
|
-
!["position", "linecolumn", "header"].includes(
|
|
222
|
+
!["position", "linecolumn", "header", "anchor"].includes(
|
|
223
|
+
ref.details.type,
|
|
224
|
+
)
|
|
223
225
|
) {
|
|
224
226
|
throw new Error(
|
|
225
|
-
"ref.details.type has to be 'position', 'linecolumn' or '
|
|
227
|
+
"ref.details.type has to be 'position', 'linecolumn', 'header' or 'anchor'",
|
|
226
228
|
);
|
|
227
229
|
}
|
|
228
230
|
|
|
@@ -244,6 +246,11 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
244
246
|
throw new Error(
|
|
245
247
|
"ref.details.line and ref.details.column has to be of type `number`",
|
|
246
248
|
);
|
|
249
|
+
} else if (
|
|
250
|
+
ref.details?.type === "anchor" &&
|
|
251
|
+
typeof ref.details.name !== "string"
|
|
252
|
+
) {
|
|
253
|
+
throw new Error("ref.details.name has to be of type `string`");
|
|
247
254
|
}
|
|
248
255
|
|
|
249
256
|
await client.navigate(ref, replaceState, newWindow);
|
|
@@ -535,8 +542,12 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
535
542
|
): Promise<string | undefined> => {
|
|
536
543
|
return client.ui.prompt(message, defaultValue);
|
|
537
544
|
},
|
|
538
|
-
"editor.confirm": (
|
|
539
|
-
|
|
545
|
+
"editor.confirm": (
|
|
546
|
+
_ctx,
|
|
547
|
+
message: string,
|
|
548
|
+
options?: { destructive?: boolean },
|
|
549
|
+
): Promise<boolean> => {
|
|
550
|
+
return client.ui.confirm(message, options);
|
|
540
551
|
},
|
|
541
552
|
"editor.alert": (_ctx, message: string) => {
|
|
542
553
|
alert(message);
|
|
@@ -37,8 +37,11 @@ export function indexSyscalls(
|
|
|
37
37
|
"index.links": (): LuaQueryCollection => {
|
|
38
38
|
return objectIndex.objectsWithTag("link");
|
|
39
39
|
},
|
|
40
|
-
"index.
|
|
41
|
-
return objectIndex.
|
|
40
|
+
"index.relations": (): LuaQueryCollection => {
|
|
41
|
+
return objectIndex.objectsWithTag("relation");
|
|
42
|
+
},
|
|
43
|
+
"index.contentPages": (_ctx, tagName?: string): LuaQueryCollection => {
|
|
44
|
+
return objectIndex.contentPages(tagName);
|
|
42
45
|
},
|
|
43
46
|
"index.metaPages": (): LuaQueryCollection => {
|
|
44
47
|
return objectIndex.metaPages();
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { SysCallMapping } from "../system.ts";
|
|
2
2
|
import { evalExpression } from "../../space_lua/eval.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { parseBlock, parseExpressionString } from "../../space_lua/parse.ts";
|
|
4
|
+
import {
|
|
5
|
+
type PrintOptions,
|
|
6
|
+
prettyPrintBlock,
|
|
7
|
+
prettyPrintExpression,
|
|
8
|
+
} from "../../space_lua/pretty_print.ts";
|
|
4
9
|
import {
|
|
5
10
|
LuaStackFrame,
|
|
6
11
|
luaToString,
|
|
@@ -13,12 +18,30 @@ import type { ClientSystem } from "../../client_system.ts";
|
|
|
13
18
|
|
|
14
19
|
export function luaSyscalls(clientSystem: ClientSystem): SysCallMapping {
|
|
15
20
|
return {
|
|
21
|
+
"lua.parseBlock": (_ctx, code: string): LuaBlock => {
|
|
22
|
+
return parseBlock(code);
|
|
23
|
+
},
|
|
24
|
+
// Deprecated alias for `lua.parseBlock`, kept for backwards compatibility.
|
|
16
25
|
"lua.parse": (_ctx, code: string): LuaBlock => {
|
|
17
|
-
return
|
|
26
|
+
return parseBlock(code);
|
|
18
27
|
},
|
|
19
28
|
"lua.parseExpression": (_ctx, expression: string): LuaExpression => {
|
|
20
29
|
return parseExpressionString(expression);
|
|
21
30
|
},
|
|
31
|
+
"lua.prettyPrintBlock": (
|
|
32
|
+
_ctx,
|
|
33
|
+
block: LuaBlock,
|
|
34
|
+
opts?: PrintOptions,
|
|
35
|
+
): string => {
|
|
36
|
+
return prettyPrintBlock(block, opts);
|
|
37
|
+
},
|
|
38
|
+
"lua.prettyPrintExpression": (
|
|
39
|
+
_ctx,
|
|
40
|
+
expression: LuaExpression,
|
|
41
|
+
opts?: PrintOptions,
|
|
42
|
+
): string => {
|
|
43
|
+
return prettyPrintExpression(expression, opts);
|
|
44
|
+
},
|
|
22
45
|
/**
|
|
23
46
|
* Evaluates a Lua expression and returns the result as a JavaScript value
|
|
24
47
|
* @param _ctx
|
|
@@ -67,7 +67,7 @@ function parseChunk(t: ParseTree, ctx: ASTCtx): LuaBlock {
|
|
|
67
67
|
if (t.type !== "Chunk") {
|
|
68
68
|
throw new Error(`Expected Chunk, got ${t.type}`);
|
|
69
69
|
}
|
|
70
|
-
return
|
|
70
|
+
return parseBlockNode(t.children![0], ctx);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
function hasCloseLocal(names: LuaAttName[] | undefined): boolean {
|
|
@@ -636,7 +636,7 @@ function exprCapturesNames(e: LuaExpression, names: Set<string>): boolean {
|
|
|
636
636
|
}
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
-
function
|
|
639
|
+
function parseBlockNode(t: ParseTree, ctx: ASTCtx): LuaBlock {
|
|
640
640
|
if (t.type !== "Block") {
|
|
641
641
|
throw new Error(`Expected Block, got ${t.type}`);
|
|
642
642
|
}
|
|
@@ -846,20 +846,20 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
|
|
|
846
846
|
ctx: context(t, ctx),
|
|
847
847
|
};
|
|
848
848
|
case "Scope":
|
|
849
|
-
return
|
|
849
|
+
return parseBlockNode(t.children![1], ctx);
|
|
850
850
|
case ";":
|
|
851
851
|
return { type: "Semicolon", ctx: context(t, ctx) };
|
|
852
852
|
case "WhileStatement":
|
|
853
853
|
return {
|
|
854
854
|
type: "While",
|
|
855
855
|
condition: parseExpression(t.children![1], ctx),
|
|
856
|
-
block:
|
|
856
|
+
block: parseBlockNode(t.children![3], ctx),
|
|
857
857
|
ctx: context(t, ctx),
|
|
858
858
|
};
|
|
859
859
|
case "RepeatStatement":
|
|
860
860
|
return {
|
|
861
861
|
type: "Repeat",
|
|
862
|
-
block:
|
|
862
|
+
block: parseBlockNode(t.children![1], ctx),
|
|
863
863
|
condition: parseExpression(t.children![3], ctx),
|
|
864
864
|
ctx: context(t, ctx),
|
|
865
865
|
};
|
|
@@ -880,12 +880,12 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
|
|
|
880
880
|
if (token === "if" || token === "elseif") {
|
|
881
881
|
conditions.push({
|
|
882
882
|
condition: parseExpression(t.children![i + 1], ctx),
|
|
883
|
-
block:
|
|
883
|
+
block: parseBlockNode(t.children![i + 3], ctx),
|
|
884
884
|
from: child.from,
|
|
885
885
|
to: child.to,
|
|
886
886
|
});
|
|
887
887
|
} else if (token === "else") {
|
|
888
|
-
elseBlock =
|
|
888
|
+
elseBlock = parseBlockNode(t.children![i + 1], ctx);
|
|
889
889
|
} else if (token === "end") {
|
|
890
890
|
break;
|
|
891
891
|
} else {
|
|
@@ -903,7 +903,7 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
|
|
|
903
903
|
if (t.children![1].type === "ForNumeric") {
|
|
904
904
|
const forNumeric = t.children![1];
|
|
905
905
|
const name = forNumeric.children![0].children![0].text!;
|
|
906
|
-
const block =
|
|
906
|
+
const block = parseBlockNode(t.children![3], ctx);
|
|
907
907
|
const node: LuaStatement = {
|
|
908
908
|
type: "For",
|
|
909
909
|
name,
|
|
@@ -923,7 +923,7 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
|
|
|
923
923
|
}
|
|
924
924
|
const forGeneric = t.children![1];
|
|
925
925
|
const names = parseNameList(forGeneric.children![0]);
|
|
926
|
-
const block =
|
|
926
|
+
const block = parseBlockNode(t.children![3], ctx);
|
|
927
927
|
const node: LuaStatement = {
|
|
928
928
|
type: "ForIn",
|
|
929
929
|
names,
|
|
@@ -1512,7 +1512,7 @@ function parseFunctionBody(t: ParseTree, ctx: ASTCtx): LuaFunctionBody {
|
|
|
1512
1512
|
(c) => c.type && ["Name", "Ellipsis"].includes(c.type),
|
|
1513
1513
|
)
|
|
1514
1514
|
.map((c) => c.children![0].text!),
|
|
1515
|
-
block:
|
|
1515
|
+
block: parseBlockNode(t.children![3], ctx),
|
|
1516
1516
|
ctx: context(t, ctx),
|
|
1517
1517
|
};
|
|
1518
1518
|
}
|
|
@@ -1686,7 +1686,7 @@ export function stripLuaComments(s: string): string {
|
|
|
1686
1686
|
return result;
|
|
1687
1687
|
}
|
|
1688
1688
|
|
|
1689
|
-
export function
|
|
1689
|
+
export function parseBlock(s: string, ctx: ASTCtx = {}): LuaBlock {
|
|
1690
1690
|
try {
|
|
1691
1691
|
const t = parseToAST(stripLuaComments(s));
|
|
1692
1692
|
// console.log("Clean tree", JSON.stringify(t, null, 2));
|
|
@@ -1743,6 +1743,6 @@ function luaUnexpectedSymbolMessage(src: string, from: number): string {
|
|
|
1743
1743
|
* Helper function to parse a Lua expression string
|
|
1744
1744
|
*/
|
|
1745
1745
|
export function parseExpressionString(expr: string): LuaExpression {
|
|
1746
|
-
const parsedLua =
|
|
1746
|
+
const parsedLua = parseBlock(`_(${expr})`) as LuaBlock;
|
|
1747
1747
|
return (parsedLua.statements[0] as LuaFunctionCallStatement).call.args[0];
|
|
1748
1748
|
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LuaBlock,
|
|
3
|
+
LuaExpression,
|
|
4
|
+
LuaOrderBy,
|
|
5
|
+
LuaQueryClause,
|
|
6
|
+
LuaStatement,
|
|
7
|
+
LuaTableField,
|
|
8
|
+
} from "./ast.ts";
|
|
9
|
+
import { luaFormatNumber } from "./runtime.ts";
|
|
10
|
+
|
|
11
|
+
export type PrintOptions = {
|
|
12
|
+
indentWidth?: number;
|
|
13
|
+
quote?: "double" | "single";
|
|
14
|
+
trailingComma?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type ResolvedOptions = {
|
|
18
|
+
indentWidth: number;
|
|
19
|
+
quote: "double" | "single";
|
|
20
|
+
trailingComma: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function resolveOptions(opts?: PrintOptions): ResolvedOptions {
|
|
24
|
+
return {
|
|
25
|
+
indentWidth: opts?.indentWidth ?? 2,
|
|
26
|
+
quote: opts?.quote ?? "double",
|
|
27
|
+
trailingComma: opts?.trailingComma ?? true,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const RESERVED = new Set([
|
|
32
|
+
"and", "break", "do", "else", "elseif", "end", "false", "for", "function",
|
|
33
|
+
"goto", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then",
|
|
34
|
+
"true", "until", "while",
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
function isLuaIdentifier(s: string): boolean {
|
|
38
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(s) && !RESERVED.has(s);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const BINARY_PREC: Record<string, number> = {
|
|
42
|
+
"or": 1,
|
|
43
|
+
"and": 2,
|
|
44
|
+
"<": 3, ">": 3, "<=": 3, ">=": 3, "~=": 3, "==": 3,
|
|
45
|
+
"|": 4,
|
|
46
|
+
"~": 5, // binary bitwise xor
|
|
47
|
+
"&": 6,
|
|
48
|
+
"<<": 7, ">>": 7,
|
|
49
|
+
"..": 8,
|
|
50
|
+
"+": 9, "-": 9,
|
|
51
|
+
"*": 10, "/": 10, "//": 10, "%": 10,
|
|
52
|
+
"^": 12,
|
|
53
|
+
};
|
|
54
|
+
const UNARY_PREC = 11;
|
|
55
|
+
const RIGHT_ASSOC = new Set(["..", "^"]);
|
|
56
|
+
const ATOM_PREC = 99;
|
|
57
|
+
|
|
58
|
+
function exprPrec(e: LuaExpression): number {
|
|
59
|
+
if (e.type === "Binary") return BINARY_PREC[e.operator] ?? ATOM_PREC;
|
|
60
|
+
if (e.type === "Unary") return UNARY_PREC;
|
|
61
|
+
return ATOM_PREC;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class Printer {
|
|
65
|
+
constructor(private opts: ResolvedOptions) { }
|
|
66
|
+
|
|
67
|
+
private indent(depth: number): string {
|
|
68
|
+
return " ".repeat(depth * this.opts.indentWidth);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private quoteString(s: string): string {
|
|
72
|
+
const q = this.opts.quote === "double" ? '"' : "'";
|
|
73
|
+
let out = q;
|
|
74
|
+
for (const ch of s) {
|
|
75
|
+
switch (ch) {
|
|
76
|
+
case "\\":
|
|
77
|
+
out += "\\\\";
|
|
78
|
+
break;
|
|
79
|
+
case "\n":
|
|
80
|
+
out += "\\n";
|
|
81
|
+
break;
|
|
82
|
+
case "\r":
|
|
83
|
+
out += "\\r";
|
|
84
|
+
break;
|
|
85
|
+
case "\t":
|
|
86
|
+
out += "\\t";
|
|
87
|
+
break;
|
|
88
|
+
case "\0":
|
|
89
|
+
out += "\\0";
|
|
90
|
+
break;
|
|
91
|
+
default:
|
|
92
|
+
out += ch === q ? `\\${ch}` : ch;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return out + q;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
expression(e: LuaExpression, depth = 0): string {
|
|
99
|
+
switch (e.type) {
|
|
100
|
+
case "Nil":
|
|
101
|
+
return "nil";
|
|
102
|
+
case "Boolean":
|
|
103
|
+
return e.value ? "true" : "false";
|
|
104
|
+
case "Number":
|
|
105
|
+
return luaFormatNumber(e.value, e.numericType);
|
|
106
|
+
case "String":
|
|
107
|
+
return this.quoteString(e.value);
|
|
108
|
+
case "Variable":
|
|
109
|
+
return e.name;
|
|
110
|
+
case "PropertyAccess":
|
|
111
|
+
return `${this.expression(e.object, depth)}.${e.property}`;
|
|
112
|
+
case "TableAccess":
|
|
113
|
+
return `${this.expression(e.object, depth)}[${this.expression(e.key, depth)}]`;
|
|
114
|
+
case "Parenthesized":
|
|
115
|
+
return `(${this.expression(e.expression, depth)})`;
|
|
116
|
+
case "Binary": {
|
|
117
|
+
const p = BINARY_PREC[e.operator] ?? ATOM_PREC;
|
|
118
|
+
const rightAssoc = RIGHT_ASSOC.has(e.operator);
|
|
119
|
+
const left = this.operand(e.left, p, false, rightAssoc, depth);
|
|
120
|
+
const right = this.operand(e.right, p, true, rightAssoc, depth);
|
|
121
|
+
return `${left} ${e.operator} ${right}`;
|
|
122
|
+
}
|
|
123
|
+
case "Unary": {
|
|
124
|
+
const arg = this.unaryOperand(e.argument, depth);
|
|
125
|
+
return /^[a-z]+$/.test(e.operator)
|
|
126
|
+
? `${e.operator} ${arg}`
|
|
127
|
+
: `${e.operator}${arg}`;
|
|
128
|
+
}
|
|
129
|
+
case "TableConstructor":
|
|
130
|
+
return this.tableConstructor(e.fields, depth);
|
|
131
|
+
case "FunctionDefinition":
|
|
132
|
+
return "function" +
|
|
133
|
+
this.functionRest(e.body.parameters, e.body.block, depth);
|
|
134
|
+
case "FunctionCall":
|
|
135
|
+
return this.functionCall(e, depth);
|
|
136
|
+
case "Query": {
|
|
137
|
+
const lines = e.clauses.map((c) =>
|
|
138
|
+
this.indent(depth + 1) + this.queryClause(c, depth + 1)
|
|
139
|
+
);
|
|
140
|
+
return `query[[\n${lines.join("\n")}\n${this.indent(depth)}]]`;
|
|
141
|
+
}
|
|
142
|
+
case "FilteredCall":
|
|
143
|
+
return `${this.expression(e.call, depth)} filter ${this.expression(e.filter, depth)
|
|
144
|
+
}`;
|
|
145
|
+
case "AggregateCall":
|
|
146
|
+
return `${this.expression(e.call, depth)} order by ${this.orderByList(e.orderBy, depth)
|
|
147
|
+
}`;
|
|
148
|
+
default:
|
|
149
|
+
throw new Error(
|
|
150
|
+
`pretty_print: unsupported expression type: ${(e as any).type}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private operand(
|
|
156
|
+
e: LuaExpression,
|
|
157
|
+
parentPrec: number,
|
|
158
|
+
isRight: boolean,
|
|
159
|
+
parentRightAssoc: boolean,
|
|
160
|
+
depth: number,
|
|
161
|
+
): string {
|
|
162
|
+
const cp = exprPrec(e);
|
|
163
|
+
let parens = false;
|
|
164
|
+
if (cp < parentPrec) {
|
|
165
|
+
parens = true;
|
|
166
|
+
} else if (cp === parentPrec) {
|
|
167
|
+
// left-assoc parent: the right child of equal precedence needs parens.
|
|
168
|
+
// right-assoc parent: the left child of equal precedence needs parens.
|
|
169
|
+
parens = parentRightAssoc ? !isRight : isRight;
|
|
170
|
+
}
|
|
171
|
+
const s = this.expression(e, depth);
|
|
172
|
+
return parens ? `(${s})` : s;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private unaryOperand(e: LuaExpression, depth: number): string {
|
|
176
|
+
const s = this.expression(e, depth);
|
|
177
|
+
return exprPrec(e) < UNARY_PREC ? `(${s})` : s;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private field(f: LuaTableField, depth: number): string {
|
|
181
|
+
switch (f.type) {
|
|
182
|
+
case "PropField":
|
|
183
|
+
return isLuaIdentifier(f.key)
|
|
184
|
+
? `${f.key} = ${this.expression(f.value, depth)}`
|
|
185
|
+
: `[${this.quoteString(f.key)}] = ${this.expression(f.value, depth)}`;
|
|
186
|
+
case "DynamicField":
|
|
187
|
+
return `[${this.expression(f.key, depth)}] = ${this.expression(f.value, depth)}`;
|
|
188
|
+
case "ExpressionField":
|
|
189
|
+
return this.expression(f.value, depth);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private tableConstructor(fields: LuaTableField[], depth: number): string {
|
|
194
|
+
if (fields.length === 0) return "{}";
|
|
195
|
+
if (fields.length === 1) return `{${this.field(fields[0], depth)}}`;
|
|
196
|
+
const lines = fields.map((f) =>
|
|
197
|
+
this.indent(depth + 1) + this.field(f, depth + 1)
|
|
198
|
+
);
|
|
199
|
+
const tail = this.opts.trailingComma ? "," : "";
|
|
200
|
+
return `{\n${lines.join(",\n")}${tail}\n${this.indent(depth)}}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// returns the part of a function after the `function` keyword (+ optional
|
|
204
|
+
// name): "(params) end" or "(params)\n <body>\nend"
|
|
205
|
+
private functionRest(
|
|
206
|
+
params: string[],
|
|
207
|
+
body: LuaBlock,
|
|
208
|
+
depth: number,
|
|
209
|
+
): string {
|
|
210
|
+
const head = `(${params.join(", ")})`;
|
|
211
|
+
const rendered = this.block(body, depth + 1);
|
|
212
|
+
if (!rendered) return `${head} end`;
|
|
213
|
+
return `${head}\n${rendered}\n${this.indent(depth)}end`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private functionCall(
|
|
217
|
+
e: Extract<LuaExpression, { type: "FunctionCall" }>,
|
|
218
|
+
depth: number,
|
|
219
|
+
): string {
|
|
220
|
+
let base = this.expression(e.prefix, depth);
|
|
221
|
+
if (e.name) base += `:${e.name}`;
|
|
222
|
+
const args = e.args;
|
|
223
|
+
const orderBy = e.orderBy
|
|
224
|
+
? ` order by ${this.orderByList(e.orderBy, depth)}`
|
|
225
|
+
: "";
|
|
226
|
+
if (
|
|
227
|
+
!e.orderBy && args.length === 1 &&
|
|
228
|
+
(args[0].type === "TableConstructor" || args[0].type === "String")
|
|
229
|
+
) {
|
|
230
|
+
return `${base} ${this.expression(args[0], depth)}`;
|
|
231
|
+
}
|
|
232
|
+
return `${base}(${args.map((a) => this.expression(a, depth)).join(", ")})${orderBy}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private fieldList(fields: LuaTableField[], depth: number): string {
|
|
236
|
+
return fields.map((f) => this.field(f, depth)).join(", ");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private orderByList(orders: LuaOrderBy[], depth: number): string {
|
|
240
|
+
return orders
|
|
241
|
+
.map((o) => {
|
|
242
|
+
let s = this.expression(o.expression, depth);
|
|
243
|
+
if (o.direction === "desc") s += " desc";
|
|
244
|
+
if (o.nulls) s += ` nulls ${o.nulls}`;
|
|
245
|
+
return s;
|
|
246
|
+
})
|
|
247
|
+
.join(", ");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private queryClause(c: LuaQueryClause, depth: number): string {
|
|
251
|
+
switch (c.type) {
|
|
252
|
+
case "From":
|
|
253
|
+
return `from ${this.fieldList(c.fields, depth)}`;
|
|
254
|
+
case "Where":
|
|
255
|
+
return `where ${this.expression(c.expression, depth)}`;
|
|
256
|
+
case "Limit":
|
|
257
|
+
return `limit ${this.expression(c.limit, depth)}` +
|
|
258
|
+
(c.offset ? ` offset ${this.expression(c.offset, depth)}` : "");
|
|
259
|
+
case "Offset":
|
|
260
|
+
return `offset ${this.expression(c.offset, depth)}`;
|
|
261
|
+
case "OrderBy":
|
|
262
|
+
return `order by ${this.orderByList(c.orderBy, depth)}`;
|
|
263
|
+
case "Select":
|
|
264
|
+
return `select ${this.fieldList(c.fields, depth)}`;
|
|
265
|
+
case "GroupBy":
|
|
266
|
+
return `group by ${this.fieldList(c.fields, depth)}`;
|
|
267
|
+
case "Having":
|
|
268
|
+
return `having ${this.expression(c.expression, depth)}`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
block(b: LuaBlock, depth: number): string {
|
|
273
|
+
const stmts = b.statements.filter((s) => s.type !== "Semicolon");
|
|
274
|
+
const isDef = (s: LuaStatement) =>
|
|
275
|
+
s.type === "Function" || s.type === "LocalFunction";
|
|
276
|
+
let out = "";
|
|
277
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
278
|
+
if (i > 0) {
|
|
279
|
+
out += "\n";
|
|
280
|
+
// blank line separating function definitions from neighbours
|
|
281
|
+
if (isDef(stmts[i - 1]) || isDef(stmts[i])) out += "\n";
|
|
282
|
+
}
|
|
283
|
+
out += this.statement(stmts[i], depth);
|
|
284
|
+
}
|
|
285
|
+
return out;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private statement(s: LuaStatement, depth: number): string {
|
|
289
|
+
const ind = this.indent(depth);
|
|
290
|
+
switch (s.type) {
|
|
291
|
+
case "Local": {
|
|
292
|
+
const names = s.names
|
|
293
|
+
.map((n) =>
|
|
294
|
+
n.name +
|
|
295
|
+
(n.attribute
|
|
296
|
+
? ` <${n.attribute}>`
|
|
297
|
+
: (n.attributes?.length ? ` <${n.attributes[0]}>` : ""))
|
|
298
|
+
)
|
|
299
|
+
.join(", ");
|
|
300
|
+
const exprs = s.expressions?.length
|
|
301
|
+
? ` = ${s.expressions.map((e) => this.expression(e, depth)).join(", ")}`
|
|
302
|
+
: "";
|
|
303
|
+
return `${ind}local ${names}${exprs}`;
|
|
304
|
+
}
|
|
305
|
+
case "Assignment":
|
|
306
|
+
return `${ind}${s.variables.map((v) => this.expression(v, depth)).join(", ")
|
|
307
|
+
} = ${s.expressions.map((e) => this.expression(e, depth)).join(", ")}`;
|
|
308
|
+
case "Return":
|
|
309
|
+
return s.expressions.length
|
|
310
|
+
? `${ind}return ${s.expressions.map((e) => this.expression(e, depth)).join(", ")
|
|
311
|
+
}`
|
|
312
|
+
: `${ind}return`;
|
|
313
|
+
case "FunctionCallStatement":
|
|
314
|
+
return ind + this.expression(s.call, depth);
|
|
315
|
+
case "Break":
|
|
316
|
+
return `${ind}break`;
|
|
317
|
+
case "Goto":
|
|
318
|
+
return `${ind}goto ${s.name}`;
|
|
319
|
+
case "Label":
|
|
320
|
+
return `${ind}::${s.name}::`;
|
|
321
|
+
case "Block": {
|
|
322
|
+
const body = this.block(s, depth + 1);
|
|
323
|
+
return body ? `${ind}do\n${body}\n${ind}end` : `${ind}do end`;
|
|
324
|
+
}
|
|
325
|
+
case "If": {
|
|
326
|
+
let out = "";
|
|
327
|
+
s.conditions.forEach((c, i) => {
|
|
328
|
+
const kw = i === 0 ? "if" : "elseif";
|
|
329
|
+
const head = i === 0 ? `${ind}${kw} ` : `\n${ind}${kw} `;
|
|
330
|
+
const body = this.block(c.block, depth + 1);
|
|
331
|
+
out += `${head}${this.expression(c.condition, depth)} then` +
|
|
332
|
+
(body ? `\n${body}` : "");
|
|
333
|
+
});
|
|
334
|
+
if (s.elseBlock) {
|
|
335
|
+
const body = this.block(s.elseBlock, depth + 1);
|
|
336
|
+
out += `\n${ind}else${body ? `\n${body}` : ""}`;
|
|
337
|
+
}
|
|
338
|
+
out += `\n${ind}end`;
|
|
339
|
+
return out;
|
|
340
|
+
}
|
|
341
|
+
case "While": {
|
|
342
|
+
const body = this.block(s.block, depth + 1);
|
|
343
|
+
return `${ind}while ${this.expression(s.condition, depth)} do` +
|
|
344
|
+
(body ? `\n${body}` : "") + `\n${ind}end`;
|
|
345
|
+
}
|
|
346
|
+
case "Repeat": {
|
|
347
|
+
const body = this.block(s.block, depth + 1);
|
|
348
|
+
return `${ind}repeat` + (body ? `\n${body}` : "") +
|
|
349
|
+
`\n${ind}until ${this.expression(s.condition, depth)}`;
|
|
350
|
+
}
|
|
351
|
+
case "For": {
|
|
352
|
+
const step = s.step ? `, ${this.expression(s.step, depth)}` : "";
|
|
353
|
+
const body = this.block(s.block, depth + 1);
|
|
354
|
+
return `${ind}for ${s.name} = ${this.expression(s.start, depth)}, ${this.expression(s.end, depth)
|
|
355
|
+
}${step} do${body ? `\n${body}` : ""}\n${ind}end`;
|
|
356
|
+
}
|
|
357
|
+
case "ForIn": {
|
|
358
|
+
const body = this.block(s.block, depth + 1);
|
|
359
|
+
return `${ind}for ${s.names.join(", ")} in ${s.expressions.map((e) => this.expression(e, depth)).join(", ")
|
|
360
|
+
} do${body ? `\n${body}` : ""}\n${ind}end`;
|
|
361
|
+
}
|
|
362
|
+
case "Function": {
|
|
363
|
+
const name = s.name.propNames.join(".") +
|
|
364
|
+
(s.name.colonName ? `:${s.name.colonName}` : "");
|
|
365
|
+
return `${ind}function ${name}` +
|
|
366
|
+
this.functionRest(s.body.parameters, s.body.block, depth);
|
|
367
|
+
}
|
|
368
|
+
case "LocalFunction":
|
|
369
|
+
return `${ind}local function ${s.name}` +
|
|
370
|
+
this.functionRest(s.body.parameters, s.body.block, depth);
|
|
371
|
+
default:
|
|
372
|
+
throw new Error(
|
|
373
|
+
`pretty_print: unsupported statement type: ${(s as any).type}`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function prettyPrintExpression(
|
|
380
|
+
expr: LuaExpression,
|
|
381
|
+
opts?: PrintOptions,
|
|
382
|
+
): string {
|
|
383
|
+
return new Printer(resolveOptions(opts)).expression(expr, 0);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function prettyPrintBlock(
|
|
387
|
+
block: LuaBlock,
|
|
388
|
+
opts?: PrintOptions,
|
|
389
|
+
): string {
|
|
390
|
+
return new Printer(resolveOptions(opts)).block(block, 0);
|
|
391
|
+
}
|
|
@@ -193,7 +193,7 @@ export interface LuaQueryCollection {
|
|
|
193
193
|
* Implements a query collection for a regular JavaScript array
|
|
194
194
|
*/
|
|
195
195
|
export class ArrayQueryCollection<T> implements LuaQueryCollection {
|
|
196
|
-
constructor(private readonly array: T[]) {}
|
|
196
|
+
constructor(private readonly array: T[]) { }
|
|
197
197
|
query(
|
|
198
198
|
query: LuaCollectionQuery,
|
|
199
199
|
env: LuaEnv,
|
|
@@ -842,7 +842,7 @@ export async function applyQuery(
|
|
|
842
842
|
|
|
843
843
|
const mkEnv = grouped
|
|
844
844
|
? (ov: string | undefined, item: any, e: LuaEnv, s: LuaStackFrame) =>
|
|
845
|
-
|
|
845
|
+
buildGroupItemEnv(ov, groupByNames, item, e, s)
|
|
846
846
|
: buildItemEnvLocal;
|
|
847
847
|
|
|
848
848
|
let selectResults: any[] | undefined;
|
|
@@ -915,9 +915,7 @@ export async function applyQuery(
|
|
|
915
915
|
for (let i = 0; i < violated.length; i++) {
|
|
916
916
|
if (violated[i]) {
|
|
917
917
|
throw new LuaRuntimeError(
|
|
918
|
-
`order by #${
|
|
919
|
-
i + 1
|
|
920
|
-
}: 'using' comparator violates strict weak ordering`,
|
|
918
|
+
`order by #${i + 1}: 'using' comparator violates strict weak ordering`,
|
|
921
919
|
sf,
|
|
922
920
|
);
|
|
923
921
|
}
|
|
@@ -1004,6 +1002,8 @@ export async function queryLua<T = any>(
|
|
|
1004
1002
|
for await (let { key, value } of kv.query({ prefix })) {
|
|
1005
1003
|
if (enricher) {
|
|
1006
1004
|
value = enricher(key, value);
|
|
1005
|
+
// Enrichers may return undefined to filter a row out
|
|
1006
|
+
if (value === undefined) continue;
|
|
1007
1007
|
}
|
|
1008
1008
|
results.push(value);
|
|
1009
1009
|
}
|
|
@@ -1056,7 +1056,7 @@ export class DataStoreQueryCollection implements LuaQueryCollection {
|
|
|
1056
1056
|
constructor(
|
|
1057
1057
|
private readonly dataStore: DataStore,
|
|
1058
1058
|
readonly prefix: string[],
|
|
1059
|
-
) {}
|
|
1059
|
+
) { }
|
|
1060
1060
|
query(
|
|
1061
1061
|
query: LuaCollectionQuery,
|
|
1062
1062
|
env: LuaEnv,
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type LuaStackFrame,
|
|
6
6
|
type LuaValue,
|
|
7
7
|
} from "../runtime.ts";
|
|
8
|
-
import {
|
|
8
|
+
import { parseBlock } from "../parse.ts";
|
|
9
9
|
import { evalStatement } from "../eval.ts";
|
|
10
10
|
|
|
11
11
|
// Returns a function (callable chunk) or (nil, "error message") pair.
|
|
@@ -13,7 +13,7 @@ export function luaLoad(code: LuaValue, sf: LuaStackFrame): LuaValue {
|
|
|
13
13
|
const s = typeof code === "string" ? code : String(code);
|
|
14
14
|
|
|
15
15
|
try {
|
|
16
|
-
const block =
|
|
16
|
+
const block = parseBlock(s, sf.astCtx || {});
|
|
17
17
|
const globalEnvMaybe = sf.threadLocal.get("_GLOBAL");
|
|
18
18
|
|
|
19
19
|
// Be vocal when no _GLOBAL is set
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { parseExpressionString } from "../parse.ts";
|
|
2
|
-
import type { LuaExpression } from "../ast.ts";
|
|
1
|
+
import { parseBlock, parseExpressionString } from "../parse.ts";
|
|
2
|
+
import type { LuaBlock, LuaExpression } from "../ast.ts";
|
|
3
3
|
import { evalExpression } from "../eval.ts";
|
|
4
|
+
import {
|
|
5
|
+
type PrintOptions,
|
|
6
|
+
prettyPrintBlock,
|
|
7
|
+
prettyPrintExpression,
|
|
8
|
+
} from "../pretty_print.ts";
|
|
4
9
|
import {
|
|
5
10
|
jsToLuaValue,
|
|
6
11
|
LuaBuiltinFunction,
|
|
@@ -101,6 +106,25 @@ export async function interpolateLuaString(
|
|
|
101
106
|
return result;
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Converts an optional Lua options table into a `PrintOptions` object,
|
|
111
|
+
* keeping only the recognised keys with the expected types.
|
|
112
|
+
*/
|
|
113
|
+
function toPrintOptions(
|
|
114
|
+
sf: LuaStackFrame,
|
|
115
|
+
opts?: LuaTable,
|
|
116
|
+
): PrintOptions | undefined {
|
|
117
|
+
if (!opts) return undefined;
|
|
118
|
+
const js = luaValueToJS(opts, sf) as Record<string, unknown>;
|
|
119
|
+
const result: PrintOptions = {};
|
|
120
|
+
if (typeof js.indentWidth === "number") result.indentWidth = js.indentWidth;
|
|
121
|
+
if (js.quote === "double" || js.quote === "single") result.quote = js.quote;
|
|
122
|
+
if (typeof js.trailingComma === "boolean") {
|
|
123
|
+
result.trailingComma = js.trailingComma;
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
104
128
|
export const spaceluaApi = new LuaTable({
|
|
105
129
|
/**
|
|
106
130
|
* Parses a lua expression and returns the parsed expression.
|
|
@@ -112,6 +136,42 @@ export const spaceluaApi = new LuaTable({
|
|
|
112
136
|
parseExpression: new LuaBuiltinFunction((_sf, luaExpression: string) => {
|
|
113
137
|
return parseExpressionString(luaExpression);
|
|
114
138
|
}),
|
|
139
|
+
/**
|
|
140
|
+
* Parses a lua chunk (block) and returns the parsed AST block.
|
|
141
|
+
*
|
|
142
|
+
* @param sf - The current space_lua state.
|
|
143
|
+
* @param code - The lua code to parse.
|
|
144
|
+
* @returns The parsed block.
|
|
145
|
+
*/
|
|
146
|
+
parseBlock: new LuaBuiltinFunction((_sf, code: string): LuaBlock => {
|
|
147
|
+
return parseBlock(code);
|
|
148
|
+
}),
|
|
149
|
+
/**
|
|
150
|
+
* Pretty-prints a parsed lua block AST back to formatted source.
|
|
151
|
+
*
|
|
152
|
+
* @param sf - The current space_lua state.
|
|
153
|
+
* @param block - The parsed lua block.
|
|
154
|
+
* @param opts - Optional formatting options.
|
|
155
|
+
* @returns The formatted lua source.
|
|
156
|
+
*/
|
|
157
|
+
prettyPrintBlock: new LuaBuiltinFunction(
|
|
158
|
+
(sf, block: LuaBlock, opts?: LuaTable): string => {
|
|
159
|
+
return prettyPrintBlock(block, toPrintOptions(sf, opts));
|
|
160
|
+
},
|
|
161
|
+
),
|
|
162
|
+
/**
|
|
163
|
+
* Pretty-prints a parsed lua expression AST back to formatted source.
|
|
164
|
+
*
|
|
165
|
+
* @param sf - The current space_lua state.
|
|
166
|
+
* @param expr - The parsed lua expression.
|
|
167
|
+
* @param opts - Optional formatting options.
|
|
168
|
+
* @returns The formatted lua source.
|
|
169
|
+
*/
|
|
170
|
+
prettyPrintExpression: new LuaBuiltinFunction(
|
|
171
|
+
(sf, expr: LuaExpression, opts?: LuaTable): string => {
|
|
172
|
+
return prettyPrintExpression(expr, toPrintOptions(sf, opts));
|
|
173
|
+
},
|
|
174
|
+
),
|
|
115
175
|
/**
|
|
116
176
|
* Evaluates a parsed lua expression and returns the result.
|
|
117
177
|
*
|
|
@@ -26,7 +26,7 @@ import { osApi } from "./stdlib/os.ts";
|
|
|
26
26
|
import { jsApi } from "./stdlib/js.ts";
|
|
27
27
|
import { spaceluaApi } from "./stdlib/space_lua.ts";
|
|
28
28
|
import { mathApi } from "./stdlib/math.ts";
|
|
29
|
-
import {
|
|
29
|
+
import { parseBlock } from "./parse.ts";
|
|
30
30
|
import { evalStatement } from "./eval.ts";
|
|
31
31
|
import { encodingApi } from "./stdlib/encoding.ts";
|
|
32
32
|
import { luaToNumberDetailed } from "./tonumber.ts";
|
|
@@ -350,7 +350,7 @@ const dofileFunction = new LuaBuiltinFunction(async (sf, filename: string) => {
|
|
|
350
350
|
)) as Uint8Array;
|
|
351
351
|
const code = new TextDecoder().decode(file);
|
|
352
352
|
try {
|
|
353
|
-
const parsedExpr =
|
|
353
|
+
const parsedExpr = parseBlock(code);
|
|
354
354
|
const env = new LuaEnv(global);
|
|
355
355
|
await evalStatement(parsedExpr, env, sf.withCtx(parsedExpr.ctx));
|
|
356
356
|
} catch (e: any) {
|
package/dist/plug-compile.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silverbulletmd/silverbullet",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A self-hosted, web-based note taking app",
|
|
6
6
|
"publishConfig": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"./syscall": "./plug-api/syscall.ts",
|
|
11
11
|
"./syscalls": "./plug-api/syscalls.ts",
|
|
12
12
|
"./constants": "./plug-api/constants.ts",
|
|
13
|
+
"./ui": "./plug-api/ui/index.ts",
|
|
13
14
|
"./lib/json": "./plug-api/lib/json.ts",
|
|
14
15
|
"./lib/tree": "./plug-api/lib/tree.ts",
|
|
15
16
|
"./lib/ref": "./plug-api/lib/ref.ts",
|
|
@@ -61,9 +62,13 @@
|
|
|
61
62
|
"fmt": "biome format --write .",
|
|
62
63
|
"fmt:check": "biome format .",
|
|
63
64
|
"bench": "vitest bench",
|
|
65
|
+
"index-stats": "tsx plugs/index/index_stats.ts",
|
|
64
66
|
"test:e2e": "npx playwright test",
|
|
65
67
|
"test:e2e:headed": "npx playwright test --headed"
|
|
66
68
|
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"preact": "^10.25.0"
|
|
71
|
+
},
|
|
67
72
|
"dependencies": {
|
|
68
73
|
"@cfworker/json-schema": "^4.1.1",
|
|
69
74
|
"@codemirror/autocomplete": "6.20.1",
|
|
@@ -91,10 +96,11 @@
|
|
|
91
96
|
"@replit/codemirror-vim": "6.3.0",
|
|
92
97
|
"commander": "^14.0.3",
|
|
93
98
|
"crelt": "1.0.6",
|
|
99
|
+
"d3-force": "^3.0.0",
|
|
94
100
|
"esbuild": "^0.27.3",
|
|
95
101
|
"fast-diff": "1.3.0",
|
|
102
|
+
"force-graph": "^1.49.0",
|
|
96
103
|
"fast-glob": "^3.3.3",
|
|
97
|
-
"fuse.js": "7.1.0",
|
|
98
104
|
"gitignore-parser": "0.0.2",
|
|
99
105
|
"idb": "8.0.3",
|
|
100
106
|
"js-yaml": "^4.1.1",
|
|
@@ -110,11 +116,13 @@
|
|
|
110
116
|
"@biomejs/biome": "2.4.6",
|
|
111
117
|
"@playwright/test": "^1.58.2",
|
|
112
118
|
"@preact/preset-vite": "^2.10.3",
|
|
119
|
+
"@types/d3-force": "^3.0.10",
|
|
113
120
|
"@types/gitignore-parser": "^0.0.3",
|
|
114
121
|
"@types/js-yaml": "^4.0.9",
|
|
115
122
|
"@types/node": "^22.0.0",
|
|
116
123
|
"@types/turndown": "^5.0.6",
|
|
117
124
|
"fake-indexeddb": "6.0.1",
|
|
125
|
+
"preact-render-to-string": "^6.7.0",
|
|
118
126
|
"tsx": "^4.19.0",
|
|
119
127
|
"typescript": "^5.9.3",
|
|
120
128
|
"vite": "^7.3.1",
|
|
@@ -374,10 +374,15 @@ export function prompt(
|
|
|
374
374
|
/**
|
|
375
375
|
* Prompt the user for confirmation
|
|
376
376
|
* @param message the message to show in the confirmation dialog
|
|
377
|
+
* @param options optional settings; set `destructive: true` to style the
|
|
378
|
+
* confirm button as a destructive (danger) action
|
|
377
379
|
* @returns
|
|
378
380
|
*/
|
|
379
|
-
export function confirm(
|
|
380
|
-
|
|
381
|
+
export function confirm(
|
|
382
|
+
message: string,
|
|
383
|
+
options?: { destructive?: boolean },
|
|
384
|
+
): Promise<boolean> {
|
|
385
|
+
return syscall("editor.confirm", message, options);
|
|
381
386
|
}
|
|
382
387
|
|
|
383
388
|
/**
|
|
@@ -78,6 +78,13 @@ export function links(): Promise<LuaQueryCollection> {
|
|
|
78
78
|
return syscall("index.links");
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Returns all relation objects as a query collection.
|
|
83
|
+
*/
|
|
84
|
+
export function relations(): Promise<LuaQueryCollection> {
|
|
85
|
+
return syscall("index.relations");
|
|
86
|
+
}
|
|
87
|
+
|
|
81
88
|
/**
|
|
82
89
|
* Returns all tasks (optionally filtered by an additional tag) as a query collection.
|
|
83
90
|
*/
|
package/plug-api/syscalls/lua.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { syscall } from "../syscall.ts";
|
|
2
2
|
import type { LuaBlock, LuaExpression } from "../../client/space_lua/ast.ts";
|
|
3
|
+
import type { PrintOptions } from "../../client/space_lua/pretty_print.ts";
|
|
3
4
|
|
|
5
|
+
export function parseBlock(code: string): Promise<LuaBlock> {
|
|
6
|
+
return syscall("lua.parseBlock", code);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** @deprecated use {@link parseBlock} instead */
|
|
4
10
|
export function parse(code: string): Promise<LuaBlock> {
|
|
5
|
-
return syscall("lua.
|
|
11
|
+
return syscall("lua.parseBlock", code);
|
|
6
12
|
}
|
|
7
13
|
|
|
8
14
|
export function parseExpression(expression: string): Promise<LuaExpression> {
|
|
@@ -12,3 +18,17 @@ export function parseExpression(expression: string): Promise<LuaExpression> {
|
|
|
12
18
|
export function evalExpression(expression: string): Promise<any> {
|
|
13
19
|
return syscall("lua.evalExpression", expression);
|
|
14
20
|
}
|
|
21
|
+
|
|
22
|
+
export function prettyPrintBlock(
|
|
23
|
+
block: LuaBlock,
|
|
24
|
+
opts?: PrintOptions,
|
|
25
|
+
): Promise<string> {
|
|
26
|
+
return syscall("lua.prettyPrintBlock", block, opts);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function prettyPrintExpression(
|
|
30
|
+
expression: LuaExpression,
|
|
31
|
+
opts?: PrintOptions,
|
|
32
|
+
): Promise<string> {
|
|
33
|
+
return syscall("lua.prettyPrintExpression", expression, opts);
|
|
34
|
+
}
|
package/plug-api/types/config.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type QueryCollationConfig = {
|
|
|
7
7
|
options?: object;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
type vimMode = "normal" | "insert" | "visual";
|
|
10
|
+
type vimMode = "normal" | "insert" | "visual" | "operator";
|
|
11
11
|
|
|
12
12
|
export type VimConfig = {
|
|
13
13
|
unmap?: (string | { key: string; mode?: vimMode })[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { Button } from "./button.tsx";
|
|
2
|
+
export type { ButtonProps, ButtonVariant } from "./button.tsx";
|
|
3
|
+
export { Input } from "./input.tsx";
|
|
4
|
+
export type { InputProps } from "./input.tsx";
|
|
5
|
+
export { Select } from "./select.tsx";
|
|
6
|
+
export type { SelectProps } from "./select.tsx";
|
|
7
|
+
export { Checkbox } from "./checkbox.tsx";
|
|
8
|
+
export type { CheckboxProps } from "./checkbox.tsx";
|
|
9
|
+
export { Tabs } from "./tabs.tsx";
|
|
10
|
+
export type { TabItem, TabsProps } from "./tabs.tsx";
|
|
11
|
+
export { Alert } from "./alert.tsx";
|
|
12
|
+
export type { AlertProps, AlertVariant } from "./alert.tsx";
|
|
13
|
+
export { Badge } from "./badge.tsx";
|
|
14
|
+
export type { BadgeProps } from "./badge.tsx";
|
|
15
|
+
export { Progress } from "./progress.tsx";
|
|
16
|
+
export type { ProgressProps } from "./progress.tsx";
|
|
17
|
+
export { cx } from "./cx.ts";
|
|
18
|
+
export { panelStyles } from "./panel_styles.ts";
|
|
19
|
+
export type { PanelStylesOptions } from "./panel_styles.ts";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getUiOption } from "../syscalls/editor.ts";
|
|
2
|
+
|
|
3
|
+
export type PanelStylesOptions = {
|
|
4
|
+
/** Include the shared component stylesheet link (default true). */
|
|
5
|
+
components?: boolean;
|
|
6
|
+
/** Include the user's space-style CSS (default true). */
|
|
7
|
+
spaceStyles?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build an HTML preamble that styles a plug panel/widget iframe like the rest
|
|
12
|
+
* of SilverBullet: the shared component stylesheet plus the user's space-style
|
|
13
|
+
* CSS. Prepend the result to the HTML you pass to `editor.showPanel`.
|
|
14
|
+
*
|
|
15
|
+
* Order: components first (base), then space styles (so user theming wins),
|
|
16
|
+
* leaving any plug-specific CSS you append afterwards highest-precedence.
|
|
17
|
+
*/
|
|
18
|
+
export async function panelStyles(
|
|
19
|
+
{ components = true, spaceStyles = true }: PanelStylesOptions = {},
|
|
20
|
+
): Promise<string> {
|
|
21
|
+
let out = "";
|
|
22
|
+
if (components) {
|
|
23
|
+
out += `<link rel="stylesheet" href=".client/components.css">`;
|
|
24
|
+
}
|
|
25
|
+
if (spaceStyles) {
|
|
26
|
+
const custom = await getUiOption("customStyles");
|
|
27
|
+
if (typeof custom === "string" && custom) {
|
|
28
|
+
out += custom;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|