@silverbulletmd/silverbullet 2.10.0 → 2.11.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 +4 -4
- package/client/asset_bundle/bundle.ts +0 -1
- package/client/config.ts +343 -0
- package/client/markdown_parser/constants.ts +3 -1
- package/client/plugos/hooks/command.ts +0 -5
- package/client/plugos/hooks/event.ts +0 -6
- package/client/plugos/hooks/mq.ts +0 -3
- package/client/plugos/hooks/slash_command.ts +1 -7
- package/client/plugos/hooks/syscall.ts +0 -4
- package/client/plugos/manifest_cache.ts +0 -32
- package/client/plugos/plug.ts +0 -3
- package/client/plugos/plug_compile.ts +3 -13
- package/client/plugos/sandboxes/worker_sandbox.ts +19 -9
- package/client/plugos/syscalls/editor.ts +128 -53
- package/client/plugos/syscalls/event.ts +0 -2
- package/client/plugos/syscalls/fetch.ts +0 -4
- package/client/plugos/syscalls/icon.ts +46 -0
- package/client/plugos/syscalls/index.ts +33 -13
- package/client/plugos/syscalls/jsonschema.ts +0 -1
- package/client/plugos/syscalls/mq.ts +0 -1
- package/client/plugos/syscalls/navigator.ts +143 -0
- package/client/plugos/syscalls/search.ts +44 -0
- package/client/plugos/syscalls/shell.ts +0 -1
- package/client/plugos/syscalls/space.ts +250 -7
- package/client/plugos/syscalls/sync.ts +56 -4
- package/client/plugos/syscalls/system.ts +38 -0
- package/client/plugos/system.ts +7 -5
- package/client/plugos/worker_runtime.ts +0 -2
- package/client/space_lua/aggregates.ts +0 -16
- package/client/space_lua/ast.ts +0 -5
- package/client/space_lua/ast_narrow.ts +0 -5
- package/client/space_lua/budget.ts +113 -0
- package/client/space_lua/budget_ui.ts +49 -0
- package/client/space_lua/eval.ts +113 -88
- package/client/space_lua/labels.ts +0 -4
- package/client/space_lua/numeric.ts +0 -5
- package/client/space_lua/parse.ts +0 -17
- package/client/space_lua/quarantine.ts +127 -0
- package/client/space_lua/query_collection.ts +3 -47
- package/client/space_lua/query_env.ts +0 -2
- package/client/space_lua/render_lua_markdown.ts +2 -7
- package/client/space_lua/render_widget.ts +18 -1
- package/client/space_lua/runtime.ts +50 -70
- package/client/space_lua/stdlib/format.ts +0 -12
- package/client/space_lua/stdlib/js.ts +0 -2
- package/client/space_lua/stdlib/load.ts +0 -1
- package/client/space_lua/stdlib/math.ts +0 -7
- package/client/space_lua/stdlib/net.ts +0 -4
- package/client/space_lua/stdlib/os.ts +0 -10
- package/client/space_lua/stdlib/pattern.ts +10 -15
- package/client/space_lua/stdlib/space_lua.ts +1 -2
- package/client/space_lua/stdlib/string_pack.ts +0 -3
- package/client/space_lua/stdlib/table.ts +0 -5
- package/client/space_lua/stdlib.ts +18 -31
- package/client/space_lua/tonumber.ts +0 -9
- package/dist/plug-compile.js +2 -3
- package/package.json +13 -3
- package/plug-api/lib/async.ts +1 -6
- package/plug-api/lib/collation.ts +22 -0
- package/plug-api/lib/crypto.ts +16 -15
- package/plug-api/lib/dates.ts +33 -0
- package/plug-api/lib/fuzzy.ts +282 -0
- package/plug-api/lib/json.ts +0 -6
- package/plug-api/lib/limited_map.ts +0 -2
- package/plug-api/lib/link_write.ts +27 -0
- package/plug-api/lib/ref.ts +28 -5
- package/plug-api/lib/resolve.ts +0 -1
- package/plug-api/lib/resolve_path.ts +290 -0
- package/plug-api/lib/shortcut.ts +15 -0
- package/plug-api/lib/tags.ts +0 -3
- package/plug-api/lib/transclusion.ts +28 -1
- package/plug-api/lib/tree.ts +0 -5
- package/plug-api/lib/yaml.ts +3 -42
- package/plug-api/syscall.ts +1 -5
- package/plug-api/syscalls/config.ts +2 -2
- package/plug-api/syscalls/editor.ts +59 -213
- package/plug-api/syscalls/icon.ts +18 -0
- package/plug-api/syscalls/index.ts +22 -4
- package/plug-api/syscalls/search.ts +11 -0
- package/plug-api/syscalls/space.ts +122 -5
- package/plug-api/syscalls/sync.ts +23 -2
- package/plug-api/syscalls/system.ts +46 -0
- package/plug-api/syscalls.ts +2 -0
- package/plug-api/system_mock.ts +13 -0
- package/plug-api/types/client.ts +4 -3
- package/plug-api/types/datastore.ts +11 -2
- package/plug-api/types/index.ts +10 -0
- package/plug-api/types/profile.ts +13 -0
- package/plug-api/types/revisions.ts +67 -0
- package/plug-api/ui/description.ts +64 -0
- package/plug-api/ui/hover.ts +69 -0
- package/plug-api/ui/index.ts +58 -2
- package/plug-api/ui/scroll.ts +30 -0
- package/plug-api/ui/tree_model.ts +263 -0
- package/plug-api/ui/tree_types.ts +66 -0
- package/plug-api/ui/use_fit_collapse.ts +67 -0
- package/plugs/builtin_plugs.ts +0 -1
- package/plugs/index/types.ts +9 -0
- package/plug-api/lib/memory_cache.ts +0 -21
- /package/plug-api/{ui → lib}/panel_styles.ts +0 -0
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ Run the server in development with `cargo run`. A **debug** build serves the cli
|
|
|
83
83
|
|
|
84
84
|
```shell
|
|
85
85
|
npm run build
|
|
86
|
-
cargo run <PATH-TO-YOUR-
|
|
86
|
+
cargo run <PATH-TO-YOUR-DATA-FOLDER>
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
**When you change only the client** (TypeScript in `client/`): you do **not** need to restart the server. Rebuild just the client bundle and reload the page in your browser — the debug server serves the freshly-built bundle straight from disk:
|
|
@@ -96,7 +96,7 @@ To build a self-contained **release** binary (with the client bundle embedded),
|
|
|
96
96
|
|
|
97
97
|
```shell
|
|
98
98
|
make
|
|
99
|
-
./target/release/silverbullet <PATH-TO-YOUR-
|
|
99
|
+
./target/release/silverbullet <PATH-TO-YOUR-DATA-FOLDER>
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
### Useful development tasks
|
|
@@ -115,7 +115,7 @@ make bench
|
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
### Build a docker container
|
|
118
|
-
Note, you do not need Node.js nor
|
|
118
|
+
Note, you do not need Node.js nor Rust locally installed for this to work:
|
|
119
119
|
|
|
120
120
|
```shell
|
|
121
121
|
docker build -t silverbullet .
|
|
@@ -123,5 +123,5 @@ docker build -t silverbullet .
|
|
|
123
123
|
|
|
124
124
|
To run:
|
|
125
125
|
```shell
|
|
126
|
-
docker run -p 3000:3000 -v <PATH-TO-YOUR-
|
|
126
|
+
docker run -p 3000:3000 -v <PATH-TO-YOUR-DATA-FOLDER>:/data silverbullet
|
|
127
127
|
```
|
package/client/config.ts
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { type OutputUnit, Validator, format } from "@cfworker/json-schema";
|
|
2
|
+
import { stripFunctions } from "./plugos/util.ts";
|
|
3
|
+
|
|
4
|
+
format.email = (data: string) => data.includes("@");
|
|
5
|
+
format["page-ref"] = (data: string) =>
|
|
6
|
+
data.startsWith("[[") && data.endsWith("]]");
|
|
7
|
+
|
|
8
|
+
function cfwFormatErrors(errors: OutputUnit[]): string {
|
|
9
|
+
// Filter out "properties" wrapper errors, keep only the specific leaf errors
|
|
10
|
+
const leafErrors = errors.filter((e) => e.keyword !== "properties");
|
|
11
|
+
const errorsToUse = leafErrors.length > 0 ? leafErrors : errors;
|
|
12
|
+
|
|
13
|
+
return errorsToUse
|
|
14
|
+
.map((e) => {
|
|
15
|
+
// Convert instanceLocation from "#/foo/bar" to "foo.bar"
|
|
16
|
+
const path =
|
|
17
|
+
e.instanceLocation === "#"
|
|
18
|
+
? ""
|
|
19
|
+
: e.instanceLocation.slice(2).replaceAll("/", ".");
|
|
20
|
+
return path ? `${path}: ${e.error}` : e.error;
|
|
21
|
+
})
|
|
22
|
+
.join(", ");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validates that a value looks like a valid JSON schema.
|
|
27
|
+
* Uses a lightweight structural check rather than full meta-schema validation.
|
|
28
|
+
*/
|
|
29
|
+
function isValidJsonSchema(schema: any): { valid: boolean; error?: string } {
|
|
30
|
+
if (schema === null || schema === undefined) {
|
|
31
|
+
return { valid: false, error: "schema must not be null or undefined" };
|
|
32
|
+
}
|
|
33
|
+
if (typeof schema === "boolean") {
|
|
34
|
+
return { valid: true };
|
|
35
|
+
}
|
|
36
|
+
if (typeof schema !== "object" || Array.isArray(schema)) {
|
|
37
|
+
return { valid: false, error: "schema must be an object or boolean" };
|
|
38
|
+
}
|
|
39
|
+
if (schema.type !== undefined) {
|
|
40
|
+
const validTypes = [
|
|
41
|
+
"string",
|
|
42
|
+
"number",
|
|
43
|
+
"integer",
|
|
44
|
+
"boolean",
|
|
45
|
+
"object",
|
|
46
|
+
"array",
|
|
47
|
+
"null",
|
|
48
|
+
];
|
|
49
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
50
|
+
for (const t of types) {
|
|
51
|
+
if (!validTypes.includes(t)) {
|
|
52
|
+
return {
|
|
53
|
+
valid: false,
|
|
54
|
+
error: `schema.type must be one of ${validTypes.join(", ")}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { valid: true };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Configuration management (config.* APIs) for the client
|
|
64
|
+
*/
|
|
65
|
+
export type CategoryDefinition = {
|
|
66
|
+
name: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
priority?: number;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export class Config {
|
|
72
|
+
public schemas: Record<string, any> = {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Registered UI categories for the configuration manager. Categories appear
|
|
79
|
+
* in the UI in descending `priority` (higher first); categories referenced
|
|
80
|
+
* by a schema's `ui.category` but never registered fall to the bottom in
|
|
81
|
+
* alphabetical order.
|
|
82
|
+
*/
|
|
83
|
+
public categories: Record<string, CategoryDefinition> = {};
|
|
84
|
+
|
|
85
|
+
constructor(public values: Record<string, any> = {}) {}
|
|
86
|
+
|
|
87
|
+
public clear() {
|
|
88
|
+
this.schemas = {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {},
|
|
91
|
+
};
|
|
92
|
+
this.values = {};
|
|
93
|
+
this.categories = {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Defines (or updates) a UI category for the configuration manager. The
|
|
98
|
+
* category's `name` is the same string used in a schema field's
|
|
99
|
+
* `ui.category`.
|
|
100
|
+
*/
|
|
101
|
+
defineCategory(definition: CategoryDefinition): void {
|
|
102
|
+
if (typeof definition?.name !== "string" || !definition.name) {
|
|
103
|
+
throw new Error("defineCategory requires a non-empty name");
|
|
104
|
+
}
|
|
105
|
+
this.categories[definition.name] = { ...definition };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Defines a JSON schema for a configuration key
|
|
110
|
+
* @param key The configuration key to define a schema for
|
|
111
|
+
* @param schema The JSON schema to validate against
|
|
112
|
+
*/
|
|
113
|
+
define(key: string | string[], schema: any): void {
|
|
114
|
+
const result = isValidJsonSchema(schema);
|
|
115
|
+
if (!result.valid) {
|
|
116
|
+
throw new Error(`Invalid schema for key ${key}: ${result.error}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (typeof key === "string") {
|
|
120
|
+
key = key.split(".");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let current = this.schemas;
|
|
124
|
+
for (let i = 0; i < key.length - 1; i++) {
|
|
125
|
+
const part = key[i];
|
|
126
|
+
if (!current.properties[part]) {
|
|
127
|
+
current.properties[part] = {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
current = current.properties[part];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const finalKey = key[key.length - 1];
|
|
136
|
+
current.properties[finalKey] = schema;
|
|
137
|
+
|
|
138
|
+
this.applySchemaDefaults(key, schema);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Recursively applies default values from a schema definition.
|
|
143
|
+
* Only sets a value if none exists at that path.
|
|
144
|
+
*/
|
|
145
|
+
private applySchemaDefaults(path: string[], schema: any): void {
|
|
146
|
+
if ("default" in schema) {
|
|
147
|
+
if (!this.has(path)) {
|
|
148
|
+
this.set(path, structuredClone(schema.default));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (schema.properties) {
|
|
152
|
+
for (const [propKey, propSchema] of Object.entries(schema.properties)) {
|
|
153
|
+
this.applySchemaDefaults([...path, propKey], propSchema as any);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Gets a value from the config
|
|
160
|
+
* @param path The path to get, supports dot notation (e.g. "foo.bar.baz")
|
|
161
|
+
* @param defaultValue The default value to return if the path doesn't exist
|
|
162
|
+
* @returns The value at the path, or the default value
|
|
163
|
+
*/
|
|
164
|
+
get<T>(path: string | string[], defaultValue: T): T {
|
|
165
|
+
if (typeof path === "string") {
|
|
166
|
+
path = path.split(".");
|
|
167
|
+
}
|
|
168
|
+
const resolved = resolvePath(this.values, path);
|
|
169
|
+
if (!resolved) {
|
|
170
|
+
return defaultValue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return (resolved.obj[resolved.key] ?? defaultValue) as T;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Sets a value in the config
|
|
178
|
+
* @param path The path to set, supports dot notation (e.g. "foo.bar.baz")
|
|
179
|
+
* @param value The value to set
|
|
180
|
+
*/
|
|
181
|
+
set<T>(path: string, value: T): void;
|
|
182
|
+
/**
|
|
183
|
+
* Sets a value in the config
|
|
184
|
+
* @param path The path to set (one path element per array element)
|
|
185
|
+
* @param value The value to set
|
|
186
|
+
*/
|
|
187
|
+
set<T>(path: string[], value: T): void;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Sets multiple values in the config
|
|
191
|
+
* @param values An object containing key-value pairs to set
|
|
192
|
+
*/
|
|
193
|
+
set(values: Record<string, any>): void;
|
|
194
|
+
|
|
195
|
+
set<T>(
|
|
196
|
+
keyOrValues: string | string[] | Record<string, any>,
|
|
197
|
+
value?: T,
|
|
198
|
+
): void {
|
|
199
|
+
if (typeof keyOrValues === "string") {
|
|
200
|
+
keyOrValues = keyOrValues.split(".");
|
|
201
|
+
}
|
|
202
|
+
if (Array.isArray(keyOrValues)) {
|
|
203
|
+
const key = keyOrValues as string[];
|
|
204
|
+
|
|
205
|
+
const resolved = resolvePath(this.values, key, true);
|
|
206
|
+
if (resolved) {
|
|
207
|
+
resolved.obj[resolved.key] = value;
|
|
208
|
+
} else {
|
|
209
|
+
throw new Error(`Invalid key ${key}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Re-apply nested schema defaults so partial object writes don't wipe
|
|
213
|
+
// out defaults for nested properties the user didn't specify.
|
|
214
|
+
const schema = this.getSchemaAtPath(key);
|
|
215
|
+
if (schema) {
|
|
216
|
+
this.applySchemaDefaults(key, schema);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.validatePath(key);
|
|
220
|
+
} else {
|
|
221
|
+
for (const [key, val] of Object.entries(keyOrValues)) {
|
|
222
|
+
this.set(key, val);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
insert<T>(key: string | string[], value?: T): void {
|
|
228
|
+
if (typeof key === "string") {
|
|
229
|
+
key = key.split(".");
|
|
230
|
+
}
|
|
231
|
+
const resolved = resolvePath(this.values, key, true);
|
|
232
|
+
if (resolved) {
|
|
233
|
+
if (!Array.isArray(resolved.obj[resolved.key])) {
|
|
234
|
+
resolved.obj[resolved.key] = [];
|
|
235
|
+
}
|
|
236
|
+
resolved.obj[resolved.key].push(value);
|
|
237
|
+
} else {
|
|
238
|
+
throw new Error(`Invalid key ${key}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.validatePath(key);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Checks if a path exists in the config
|
|
246
|
+
* @param path The path to check, supports dot notation (e.g. "foo.bar.baz")
|
|
247
|
+
* @returns True if the path exists, false otherwise
|
|
248
|
+
*/
|
|
249
|
+
has(path: string | string[]): boolean {
|
|
250
|
+
if (typeof path === "string") {
|
|
251
|
+
path = path.split(".");
|
|
252
|
+
}
|
|
253
|
+
const resolved = resolvePath(this.values, path);
|
|
254
|
+
if (!resolved) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return resolved.key in resolved.obj;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Lists all configuration keys in the config
|
|
263
|
+
* @returns An array of keys
|
|
264
|
+
*/
|
|
265
|
+
keys(): string[] {
|
|
266
|
+
return Object.keys(this.values);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Validates a specific path against its schema
|
|
271
|
+
*/
|
|
272
|
+
private validatePath(path: string[]): void {
|
|
273
|
+
for (let i = path.length; i > 0; i--) {
|
|
274
|
+
const schemaPath = path.slice(0, i);
|
|
275
|
+
const schema = this.getSchemaAtPath(schemaPath);
|
|
276
|
+
if (schema) {
|
|
277
|
+
const valueAtPath = this.get(schemaPath, undefined);
|
|
278
|
+
if (valueAtPath !== undefined) {
|
|
279
|
+
const validator = new Validator(schema, "7");
|
|
280
|
+
const result = validator.validate(stripFunctions(valueAtPath));
|
|
281
|
+
if (!result.valid) {
|
|
282
|
+
const errorText = cfwFormatErrors(result.errors);
|
|
283
|
+
throw new Error(
|
|
284
|
+
`Validation error for ${schemaPath.join(".")}:> ${errorText}`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
break; // Only validate the most specific schema
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Gets the schema at a specific path
|
|
295
|
+
*/
|
|
296
|
+
private getSchemaAtPath(path: string[]): any | null {
|
|
297
|
+
let current = this.schemas;
|
|
298
|
+
for (const part of path) {
|
|
299
|
+
if (!current.properties || !current.properties[part]) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
current = current.properties[part];
|
|
303
|
+
}
|
|
304
|
+
return current.type ? current : null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Resolves a configuration path to the containing object and final key
|
|
310
|
+
* @param path The path to resolve (e.g. ["foo", "bar", "baz"])
|
|
311
|
+
* @param create Whether to create objects along the path if they don't exist
|
|
312
|
+
* @returns The containing object and the final key, or null if the path cannot be resolved
|
|
313
|
+
*/
|
|
314
|
+
function resolvePath(
|
|
315
|
+
inObject: Record<string, any>,
|
|
316
|
+
path: string[],
|
|
317
|
+
create = false,
|
|
318
|
+
): { obj: any; key: string } | null {
|
|
319
|
+
path = [...path];
|
|
320
|
+
|
|
321
|
+
const lastKey = path.pop()!;
|
|
322
|
+
let current = inObject;
|
|
323
|
+
|
|
324
|
+
for (const part of path) {
|
|
325
|
+
if (current[part] === undefined) {
|
|
326
|
+
if (create) {
|
|
327
|
+
current[part] = {};
|
|
328
|
+
} else {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
} else if (typeof current[part] !== "object" || current[part] === null) {
|
|
332
|
+
if (create) {
|
|
333
|
+
current[part] = {};
|
|
334
|
+
} else {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
current = current[part];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return { obj: current, key: lastKey };
|
|
343
|
+
}
|
|
@@ -3,7 +3,9 @@ export const wikiLinkRegex =
|
|
|
3
3
|
export const mdLinkRegex =
|
|
4
4
|
/!?\[(?<title>[^\]\\]*(?:\\.[^\]\\]*)*)\]\((?<url>.+)\)/g;
|
|
5
5
|
export const tagRegex =
|
|
6
|
-
/#(?:(?:\d*[^\d\s!@#$%^&*(),.?":{}|<>\\][^\s!@#$%^&*(),.?":{}|<>\\]*)|(?:<[^>\n]+>))/;
|
|
6
|
+
/#(?:(?:\d*[^\d\s!@#$%^&*(),.?":{}|<>\\';\/–—][^\s!@#$%^&*(),.?":{}|<>\\]*)|(?:<[^>\n]+>))(?<!-)/;
|
|
7
|
+
export const atMentionRegex =
|
|
8
|
+
/@(?:\d*[^\d\s!@#$%^&*(),.?":{}|<>\\\/][^\s!@#$%^&*(),.?":{}|<>\\\/]*(?:\.[^\s!@#$%^&*(),.?":{}|<>\\\/]+)*)/;
|
|
7
9
|
export const nakedUrlRegex =
|
|
8
10
|
/(^https?:\/\/([-a-zA-Z0-9@:%_+~#=]|(?:[.](?!(\s|$)))){1,256})(([-a-zA-Z0-9(@:%_+~#?&=/]|(?:[.,:;)](?!(\s|$))))*)/;
|
|
9
11
|
export const frontmatterQuotesRegex = /["'].*["']/g;
|
|
@@ -39,7 +39,6 @@ export class CommandHook
|
|
|
39
39
|
cmd: Command,
|
|
40
40
|
) {
|
|
41
41
|
if (commands.has(name)) {
|
|
42
|
-
// Existing command, let's do some inline patching
|
|
43
42
|
const existingCommand = commands.get(name)!;
|
|
44
43
|
const command: Command = {
|
|
45
44
|
...existingCommand,
|
|
@@ -68,9 +67,7 @@ export class CommandHook
|
|
|
68
67
|
}
|
|
69
68
|
commands.set(name, cmd);
|
|
70
69
|
}
|
|
71
|
-
// Add commands from plugs
|
|
72
70
|
if (!this.system) {
|
|
73
|
-
// Not initialized yet
|
|
74
71
|
return commands;
|
|
75
72
|
}
|
|
76
73
|
for (const plug of this.system.loadedPlugs.values()) {
|
|
@@ -82,7 +79,6 @@ export class CommandHook
|
|
|
82
79
|
}
|
|
83
80
|
const cmd = functionDef.command;
|
|
84
81
|
if (cmd.requireMode === "rw" && this.readOnly) {
|
|
85
|
-
// Bit hacky, but don't expose commands that require write mode in read-only mode
|
|
86
82
|
continue;
|
|
87
83
|
}
|
|
88
84
|
this.mergeCommand(commands, cmd.name, {
|
|
@@ -112,7 +108,6 @@ export class CommandHook
|
|
|
112
108
|
this.throttledBuildAllCommandsAndEmit();
|
|
113
109
|
},
|
|
114
110
|
});
|
|
115
|
-
// On next tick
|
|
116
111
|
setTimeout(() => {
|
|
117
112
|
this.throttledBuildAllCommandsAndEmit();
|
|
118
113
|
});
|
|
@@ -79,9 +79,7 @@ export class EventHook implements EventHookI {
|
|
|
79
79
|
event === eventName ||
|
|
80
80
|
eventNameToRegex(event).test(eventName)
|
|
81
81
|
) {
|
|
82
|
-
// Only dispatch functions that can run in this environment
|
|
83
82
|
if (plug.canInvoke(name)) {
|
|
84
|
-
// Queue the promise
|
|
85
83
|
promises.push(
|
|
86
84
|
(async () => {
|
|
87
85
|
try {
|
|
@@ -101,11 +99,9 @@ export class EventHook implements EventHookI {
|
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
101
|
|
|
104
|
-
// Local listeners
|
|
105
102
|
for (const [name, localListeners] of this.localListeners) {
|
|
106
103
|
if (eventNameToRegex(name).test(eventName)) {
|
|
107
104
|
for (const localListener of localListeners) {
|
|
108
|
-
// Queue the promise
|
|
109
105
|
promises.push(
|
|
110
106
|
(async () => {
|
|
111
107
|
return await Promise.resolve(localListener(...args));
|
|
@@ -115,7 +111,6 @@ export class EventHook implements EventHookI {
|
|
|
115
111
|
}
|
|
116
112
|
}
|
|
117
113
|
|
|
118
|
-
// Space Lua listeners
|
|
119
114
|
if (this.config) {
|
|
120
115
|
const configListeners: Record<string, Function[]> = this.config.get(
|
|
121
116
|
"eventListeners",
|
|
@@ -140,7 +135,6 @@ export class EventHook implements EventHookI {
|
|
|
140
135
|
}
|
|
141
136
|
}
|
|
142
137
|
|
|
143
|
-
// Wait for all promises to resolve
|
|
144
138
|
return (await Promise.allSettled(promises))
|
|
145
139
|
.filter((result) => {
|
|
146
140
|
if (result.status === "rejected") {
|
|
@@ -48,7 +48,6 @@ export class MQHook implements Hook<MQHookT> {
|
|
|
48
48
|
|
|
49
49
|
reloadQueues() {
|
|
50
50
|
this.stop();
|
|
51
|
-
// Plug based subscriptions
|
|
52
51
|
for (const plug of this.system.loadedPlugs.values()) {
|
|
53
52
|
if (!plug.manifest) {
|
|
54
53
|
continue;
|
|
@@ -96,14 +95,12 @@ export class MQHook implements Hook<MQHookT> {
|
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
|
-
// Space Lua based subscriptions
|
|
100
98
|
const configListeners: Record<string, MQListenerSpec[]> = this.config.get(
|
|
101
99
|
"mqSubscriptions",
|
|
102
100
|
{},
|
|
103
101
|
);
|
|
104
102
|
for (const [queue, listeners] of Object.entries(configListeners)) {
|
|
105
103
|
for (const listener of listeners) {
|
|
106
|
-
// console.log("Subscribing to", queue, listener);
|
|
107
104
|
this.subscriptions.push(
|
|
108
105
|
this.mq.subscribe(
|
|
109
106
|
queue,
|
|
@@ -46,7 +46,6 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
// Iterate over script defined slash commands
|
|
50
49
|
for (const command of Object.values(
|
|
51
50
|
this.client.config.get<Record<string, SlashCommand>>("slashCommands", {}),
|
|
52
51
|
)) {
|
|
@@ -70,13 +69,12 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
|
|
70
69
|
// the user is referring to preceding text, not what comes after.
|
|
71
70
|
const currentNode = syntaxTree(ctx.state).resolveInner(ctx.pos, -1);
|
|
72
71
|
if (
|
|
73
|
-
currentNode.type.name === "
|
|
72
|
+
currentNode.type.name === "CommentMarkerBlock" ||
|
|
74
73
|
currentNode.type.name === "Link"
|
|
75
74
|
) {
|
|
76
75
|
return null;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
// Check if the slash command is available in the current context
|
|
80
78
|
const parentNodes = this.client.extractParentNodes(ctx.state, currentNode);
|
|
81
79
|
for (const def of this.slashCommands) {
|
|
82
80
|
if (
|
|
@@ -99,7 +97,6 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
|
|
99
97
|
detail: def.description,
|
|
100
98
|
boost: def.priority,
|
|
101
99
|
apply: () => {
|
|
102
|
-
// Delete slash command part
|
|
103
100
|
this.client.editorView.dispatch({
|
|
104
101
|
changes: {
|
|
105
102
|
from: prefix!.from + prefixText.indexOf("/"),
|
|
@@ -107,7 +104,6 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
|
|
107
104
|
insert: "",
|
|
108
105
|
},
|
|
109
106
|
});
|
|
110
|
-
// Replace with whatever the completion is
|
|
111
107
|
safeRun(async () => {
|
|
112
108
|
await def.run!();
|
|
113
109
|
this.client.focus();
|
|
@@ -126,7 +122,6 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
|
|
126
122
|
detail: slashCompletion.detail,
|
|
127
123
|
boost: slashCompletion.order && -slashCompletion.order,
|
|
128
124
|
apply: () => {
|
|
129
|
-
// Delete slash command part
|
|
130
125
|
this.client.editorView.dispatch({
|
|
131
126
|
changes: {
|
|
132
127
|
from: prefix!.from + prefixText.indexOf("/"),
|
|
@@ -134,7 +129,6 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
|
|
134
129
|
insert: "",
|
|
135
130
|
},
|
|
136
131
|
});
|
|
137
|
-
// Replace with whatever the completion is
|
|
138
132
|
safeRun(async () => {
|
|
139
133
|
await this.client.clientSystem.system.invokeFunction(
|
|
140
134
|
slashCompletion.invoke,
|
|
@@ -13,7 +13,6 @@ export class SyscallHook implements Hook<SyscallHookT> {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
registerSyscalls(system: System<SyscallHookT>) {
|
|
16
|
-
// Register syscalls from all loaded plugs
|
|
17
16
|
for (const plug of system.loadedPlugs.values()) {
|
|
18
17
|
const syscalls: SysCallMapping = {};
|
|
19
18
|
|
|
@@ -30,7 +29,6 @@ export class SyscallHook implements Hook<SyscallHookT> {
|
|
|
30
29
|
? syscallDefinition
|
|
31
30
|
: syscallDefinition.name;
|
|
32
31
|
|
|
33
|
-
// Add the syscall to our mapping
|
|
34
32
|
const callback = (ctx: SyscallContext, ...args: any[]) =>
|
|
35
33
|
system.syscall(ctx, "system.invokeFunction", [
|
|
36
34
|
`${plug.manifest!.name}.${name}`,
|
|
@@ -46,7 +44,6 @@ export class SyscallHook implements Hook<SyscallHookT> {
|
|
|
46
44
|
};
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
// Register the syscalls with no required permissions
|
|
50
47
|
system.registerSyscalls([], syscalls);
|
|
51
48
|
}
|
|
52
49
|
}
|
|
@@ -68,7 +65,6 @@ export class SyscallHook implements Hook<SyscallHookT> {
|
|
|
68
65
|
continue;
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
// Validate syscall name format (should be namespaced)
|
|
72
68
|
if (!syscallName.includes(".")) {
|
|
73
69
|
errors.push(
|
|
74
70
|
`Function ${name} has invalid syscall name "${syscallName}" - must be in format "namespace.name"`,
|
|
@@ -23,7 +23,6 @@ export class KVPrimitivesManifestCache<T> implements ManifestCache<T> {
|
|
|
23
23
|
): Promise<Manifest<T>> {
|
|
24
24
|
const [cached] = await this.kv.batchGet([[this.manifestPrefix, cacheKey]]);
|
|
25
25
|
if (cached && cached.hash === cacheHash) {
|
|
26
|
-
// console.log("Using KV cached manifest for", plug.name);
|
|
27
26
|
return cached.manifest;
|
|
28
27
|
}
|
|
29
28
|
await plug.sandbox.init();
|
|
@@ -41,34 +40,3 @@ export class KVPrimitivesManifestCache<T> implements ManifestCache<T> {
|
|
|
41
40
|
return manifest;
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
|
-
|
|
45
|
-
export class InMemoryManifestCache<T> implements ManifestCache<T> {
|
|
46
|
-
private cache = new Map<
|
|
47
|
-
string,
|
|
48
|
-
{
|
|
49
|
-
manifest: Manifest<T>;
|
|
50
|
-
hash: number;
|
|
51
|
-
}
|
|
52
|
-
>();
|
|
53
|
-
|
|
54
|
-
async getManifest(
|
|
55
|
-
plug: Plug<T>,
|
|
56
|
-
cacheKey: string,
|
|
57
|
-
cacheHash: number,
|
|
58
|
-
): Promise<Manifest<T>> {
|
|
59
|
-
const cached = this.cache.get(cacheKey);
|
|
60
|
-
if (cached && cached.hash === cacheHash) {
|
|
61
|
-
// console.log("Using memory cached manifest for", plug.name);
|
|
62
|
-
return cached.manifest;
|
|
63
|
-
}
|
|
64
|
-
await plug.sandbox.init();
|
|
65
|
-
const manifest = plug.sandbox.manifest!;
|
|
66
|
-
|
|
67
|
-
// Deliverately removing the assets from the manifest to preserve space, will be re-added upon load of actual worker
|
|
68
|
-
this.cache.set(cacheKey, {
|
|
69
|
-
manifest: { ...manifest, assets: undefined },
|
|
70
|
-
hash: cacheHash,
|
|
71
|
-
});
|
|
72
|
-
return manifest;
|
|
73
|
-
}
|
|
74
|
-
}
|
package/client/plugos/plug.ts
CHANGED
|
@@ -31,7 +31,6 @@ export class Plug<HookT> {
|
|
|
31
31
|
const plug = new Plug(system, sandboxFactory);
|
|
32
32
|
plug.path = path;
|
|
33
33
|
|
|
34
|
-
// Retrieve the manifest, which may either come from a cache or be loaded from the worker
|
|
35
34
|
plug.manifest = await system.options.manifestCache!.getManifest(
|
|
36
35
|
plug,
|
|
37
36
|
path,
|
|
@@ -62,14 +61,12 @@ export class Plug<HookT> {
|
|
|
62
61
|
|
|
63
62
|
// Invoke a function
|
|
64
63
|
async invoke(name: string, args: any[]): Promise<any> {
|
|
65
|
-
// Before we access the manifest
|
|
66
64
|
const funDef = this.manifest!.functions[name];
|
|
67
65
|
if (!funDef) {
|
|
68
66
|
throw new Error(`Function ${name} not found in manifest`);
|
|
69
67
|
}
|
|
70
68
|
const sandbox = this.sandbox!;
|
|
71
69
|
if (funDef.redirect) {
|
|
72
|
-
// Function redirect, look up
|
|
73
70
|
let plug: Plug<HookT> | undefined = this;
|
|
74
71
|
if (funDef.redirect.indexOf(".") !== -1) {
|
|
75
72
|
const [plugName, functionName] = funDef.redirect.split(".");
|