@silverbulletmd/silverbullet 2.11.0 → 2.12.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 +2 -2
- package/client/data/kv_primitives.ts +2 -2
- package/client/markdown_parser/constants.ts +3 -1
- package/client/plugos/hooks/command.ts +8 -1
- package/client/plugos/hooks/event.ts +60 -18
- package/client/plugos/hooks/slash_command.ts +2 -13
- package/client/plugos/manifest_cache.ts +2 -2
- package/client/plugos/sandboxes/sandbox.ts +2 -2
- package/client/plugos/syscalls/capture.ts +104 -0
- package/client/plugos/syscalls/editor.ts +71 -5
- package/client/plugos/syscalls/event.ts +12 -7
- package/client/plugos/syscalls/navigator.ts +4 -9
- package/client/plugos/syscalls/sync.ts +14 -0
- package/client/plugos/syscalls/system.ts +1 -0
- package/client/plugos/system.ts +2 -2
- package/client/plugos/types.ts +6 -6
- package/client/space_lua/aggregates.ts +6 -6
- package/client/space_lua/numeric.ts +2 -2
- package/client/space_lua/query_collection.ts +15 -4
- package/client/space_lua/runtime.ts +14 -7
- package/client/space_lua/stdlib/pattern.ts +8 -8
- package/client/space_lua/stdlib/string_pack.ts +4 -4
- package/dist/plug-compile.js +2 -2
- package/package.json +41 -40
- package/plug-api/lib/resolve.ts +1 -8
- package/plug-api/lib/yaml.ts +2 -2
- package/plug-api/syscalls/editor.ts +18 -2
- package/plug-api/syscalls/sync.ts +8 -0
- package/plug-api/types/client.ts +16 -1
- package/plug-api/types/manifest.ts +7 -0
- package/plug-api/ui/tree_model.ts +24 -2
- package/plug-api/ui/tree_types.ts +1 -0
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ You were told there’s no such thing as a silver bullet. You were told wrong.
|
|
|
24
24
|
[Much more detail can be found on silverbullet.md](https://silverbullet.md)
|
|
25
25
|
|
|
26
26
|
## Installing SilverBullet
|
|
27
|
-
Check out the [instructions](https://silverbullet.md/Install).
|
|
27
|
+
Check out the [instructions](https://docs.silverbullet.md/Install).
|
|
28
28
|
|
|
29
29
|
## Developing SilverBullet
|
|
30
30
|
SilverBullet's frontend ("client") is written in [TypeScript](https://www.typescriptlang.org/) and built on top of the excellent [CodeMirror 6](https://codemirror.net/) editor component. Additional UI is built using [Preact](https://preactjs.com). [ESBuild](https://esbuild.github.io) is used to build the frontend.
|
|
@@ -46,7 +46,7 @@ The server backend is written in [Rust](https://www.rust-lang.org/) (a Cargo wor
|
|
|
46
46
|
* `syscalls/`: TypeScript wrappers around syscalls
|
|
47
47
|
* `types/`: Various (client) types that can be references from plugs
|
|
48
48
|
* `scripts/`: Useful scripts
|
|
49
|
-
* `docs/`: documentation (
|
|
49
|
+
* `docs/`: documentation (published as docs.silverbullet.md and bundled as Desktop's Help)
|
|
50
50
|
|
|
51
51
|
### Requirements
|
|
52
52
|
* [Node.js](https://nodejs.org/) 24+ and npm 10+: Used to build the frontend (client) and plugs
|
|
@@ -4,7 +4,7 @@ export type KvQueryOptions = {
|
|
|
4
4
|
prefix?: KvKey;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
export
|
|
7
|
+
export type KvPrimitives = {
|
|
8
8
|
batchGet(keys: KvKey[]): Promise<(any | undefined)[]>;
|
|
9
9
|
|
|
10
10
|
batchSet(entries: KV[]): Promise<void>;
|
|
@@ -22,4 +22,4 @@ export interface KvPrimitives {
|
|
|
22
22
|
clear(): Promise<void>;
|
|
23
23
|
|
|
24
24
|
close(): void;
|
|
25
|
-
}
|
|
25
|
+
};
|
|
@@ -4,8 +4,10 @@ export const mdLinkRegex =
|
|
|
4
4
|
/!?\[(?<title>[^\]\\]*(?:\\.[^\]\\]*)*)\]\((?<url>.+)\)/g;
|
|
5
5
|
export const tagRegex =
|
|
6
6
|
/#(?:(?:\d*[^\d\s!@#$%^&*(),.?":{}|<>\\';\/–—][^\s!@#$%^&*(),.?":{}|<>\\]*)|(?:<[^>\n]+>))(?<!-)/;
|
|
7
|
+
// Dots and apostrophes may only join name segments, so `@pete.` and `@Chris'`
|
|
8
|
+
// end before them; a possessive `'s` never joins, so `@Foo's` is `@Foo`.
|
|
7
9
|
export const atMentionRegex =
|
|
8
|
-
/@(?:\d*[^\d\s!@#$%^&*(),.?":{}|<>\\\/][^\s!@#$%^&*(),.?":{}|<>\\\/]*(
|
|
10
|
+
/@(?:\d*[^\d\s!@#$%^&*(),.?":{}|<>\\\/'’][^\s!@#$%^&*(),.?":{}|<>\\\/'’]*(?:(?:\.|['’](?,.?":{}|<>\\\/'’])))[^\s!@#$%^&*(),.?":{}|<>\\\/'’]+)*)/;
|
|
9
11
|
export const nakedUrlRegex =
|
|
10
12
|
/(^https?:\/\/([-a-zA-Z0-9@:%_+~#=]|(?:[.](?!(\s|$)))){1,256})(([-a-zA-Z0-9(@:%_+~#?&=/]|(?:[.,:;)](?!(\s|$))))*)/;
|
|
11
13
|
export const frontmatterQuotesRegex = /["'].*["']/g;
|
|
@@ -19,6 +19,7 @@ export class CommandHook
|
|
|
19
19
|
constructor(
|
|
20
20
|
private readOnly: boolean,
|
|
21
21
|
private additionalCommands: Map<string, Command>,
|
|
22
|
+
private disableServiceWorker = false,
|
|
22
23
|
) {
|
|
23
24
|
super();
|
|
24
25
|
}
|
|
@@ -65,6 +66,9 @@ export class CommandHook
|
|
|
65
66
|
if (cmd.requireMode === "rw" && this.readOnly) {
|
|
66
67
|
continue;
|
|
67
68
|
}
|
|
69
|
+
if (cmd.requireServiceWorker && this.disableServiceWorker) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
68
72
|
commands.set(name, cmd);
|
|
69
73
|
}
|
|
70
74
|
if (!this.system) {
|
|
@@ -81,6 +85,9 @@ export class CommandHook
|
|
|
81
85
|
if (cmd.requireMode === "rw" && this.readOnly) {
|
|
82
86
|
continue;
|
|
83
87
|
}
|
|
88
|
+
if (cmd.requireServiceWorker && this.disableServiceWorker) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
84
91
|
this.mergeCommand(commands, cmd.name, {
|
|
85
92
|
...cmd,
|
|
86
93
|
run: (args?: string[]) => {
|
|
@@ -98,7 +105,7 @@ export class CommandHook
|
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
public buildAllCommandsAndEmit() {
|
|
101
|
-
this.emit("commandsUpdated", this.buildAllCommands());
|
|
108
|
+
void this.emit("commandsUpdated", this.buildAllCommands());
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
apply(system: System<CommandHookT>): void {
|
|
@@ -3,6 +3,10 @@ import type { System } from "../system.ts";
|
|
|
3
3
|
import type { EventHookI } from "../eventhook.ts";
|
|
4
4
|
import type { EventHookT } from "@silverbulletmd/silverbullet/type/manifest";
|
|
5
5
|
import type { Config } from "../../config.ts";
|
|
6
|
+
import type { Ref } from "@silverbulletmd/silverbullet/lib/ref";
|
|
7
|
+
import { listenerDefinition, type DefinedListener } from "../syscalls/event.ts";
|
|
8
|
+
|
|
9
|
+
export type EventResultWithSource = { value: any; definition: Ref | null };
|
|
6
10
|
|
|
7
11
|
// System events:
|
|
8
12
|
// - plug:load (plugName: string)
|
|
@@ -66,10 +70,33 @@ export class EventHook implements EventHookI {
|
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
async dispatchEvent(eventName: string, ...args: any[]): Promise<any[]> {
|
|
73
|
+
return (await this.dispatch(eventName, args, false)).map(
|
|
74
|
+
({ value }) => value,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async dispatchEventStrict(eventName: string, ...args: any[]): Promise<any[]> {
|
|
79
|
+
return (await this.dispatch(eventName, args, true)).map(
|
|
80
|
+
({ value }) => value,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async dispatchEventWithSources(
|
|
85
|
+
eventName: string,
|
|
86
|
+
...args: any[]
|
|
87
|
+
): Promise<EventResultWithSource[]> {
|
|
88
|
+
return this.dispatch(eventName, args, false);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async dispatch(
|
|
92
|
+
eventName: string,
|
|
93
|
+
args: any[],
|
|
94
|
+
strict: boolean,
|
|
95
|
+
): Promise<EventResultWithSource[]> {
|
|
69
96
|
if (!this.system) {
|
|
70
97
|
throw new Error("Event hook is not initialized");
|
|
71
98
|
}
|
|
72
|
-
const promises: Promise<any
|
|
99
|
+
const promises: { promise: Promise<any>; definition: Ref | null }[] = [];
|
|
73
100
|
for (const plug of this.system.loadedPlugs.values()) {
|
|
74
101
|
const manifest = plug.manifest;
|
|
75
102
|
for (const [name, functionDef] of Object.entries(manifest!.functions)) {
|
|
@@ -80,8 +107,9 @@ export class EventHook implements EventHookI {
|
|
|
80
107
|
eventNameToRegex(event).test(eventName)
|
|
81
108
|
) {
|
|
82
109
|
if (plug.canInvoke(name)) {
|
|
83
|
-
promises.push(
|
|
84
|
-
|
|
110
|
+
promises.push({
|
|
111
|
+
definition: null,
|
|
112
|
+
promise: (async () => {
|
|
85
113
|
try {
|
|
86
114
|
return await plug.invoke(name, args);
|
|
87
115
|
} catch (e: any) {
|
|
@@ -91,7 +119,7 @@ export class EventHook implements EventHookI {
|
|
|
91
119
|
throw e;
|
|
92
120
|
}
|
|
93
121
|
})(),
|
|
94
|
-
);
|
|
122
|
+
});
|
|
95
123
|
}
|
|
96
124
|
}
|
|
97
125
|
}
|
|
@@ -102,25 +130,25 @@ export class EventHook implements EventHookI {
|
|
|
102
130
|
for (const [name, localListeners] of this.localListeners) {
|
|
103
131
|
if (eventNameToRegex(name).test(eventName)) {
|
|
104
132
|
for (const localListener of localListeners) {
|
|
105
|
-
promises.push(
|
|
106
|
-
|
|
133
|
+
promises.push({
|
|
134
|
+
definition: null,
|
|
135
|
+
promise: (async () => {
|
|
107
136
|
return await Promise.resolve(localListener(...args));
|
|
108
137
|
})(),
|
|
109
|
-
);
|
|
138
|
+
});
|
|
110
139
|
}
|
|
111
140
|
}
|
|
112
141
|
}
|
|
113
142
|
|
|
114
143
|
if (this.config) {
|
|
115
|
-
const configListeners: Record<string,
|
|
116
|
-
"eventListeners",
|
|
117
|
-
{},
|
|
118
|
-
);
|
|
144
|
+
const configListeners: Record<string, DefinedListener[]> =
|
|
145
|
+
this.config.get("eventListeners", {});
|
|
119
146
|
for (const [name, listeners] of Object.entries(configListeners)) {
|
|
120
147
|
if (eventNameToRegex(name).test(eventName)) {
|
|
121
148
|
for (const listener of listeners) {
|
|
122
|
-
promises.push(
|
|
123
|
-
|
|
149
|
+
promises.push({
|
|
150
|
+
definition: listener[listenerDefinition] ?? null,
|
|
151
|
+
promise: (async () => {
|
|
124
152
|
return await Promise.resolve(
|
|
125
153
|
listener({
|
|
126
154
|
name: eventName,
|
|
@@ -129,14 +157,25 @@ export class EventHook implements EventHookI {
|
|
|
129
157
|
}),
|
|
130
158
|
);
|
|
131
159
|
})(),
|
|
132
|
-
);
|
|
160
|
+
});
|
|
133
161
|
}
|
|
134
162
|
}
|
|
135
163
|
}
|
|
136
164
|
}
|
|
137
165
|
|
|
138
|
-
|
|
139
|
-
.
|
|
166
|
+
const settled = await Promise.allSettled(
|
|
167
|
+
promises.map(({ promise }) => promise),
|
|
168
|
+
);
|
|
169
|
+
if (strict) {
|
|
170
|
+
const failure = settled.find((result) => result.status === "rejected");
|
|
171
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
172
|
+
}
|
|
173
|
+
return settled
|
|
174
|
+
.map((result, index) => ({
|
|
175
|
+
result,
|
|
176
|
+
definition: promises[index].definition,
|
|
177
|
+
}))
|
|
178
|
+
.filter(({ result }) => {
|
|
140
179
|
if (result.status === "rejected") {
|
|
141
180
|
console.error(
|
|
142
181
|
"Error while dispatching event",
|
|
@@ -147,8 +186,11 @@ export class EventHook implements EventHookI {
|
|
|
147
186
|
}
|
|
148
187
|
return result.status === "fulfilled";
|
|
149
188
|
})
|
|
150
|
-
.map((result) =>
|
|
151
|
-
|
|
189
|
+
.map(({ result, definition }) => ({
|
|
190
|
+
value: result.status === "fulfilled" ? result.value : undefined,
|
|
191
|
+
definition,
|
|
192
|
+
}))
|
|
193
|
+
.filter(({ value }) => value != null); // This keeps non-null/undefined results
|
|
152
194
|
}
|
|
153
195
|
|
|
154
196
|
apply(system: System<EventHookT>): void {
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
SlashCompletionOption,
|
|
15
15
|
SlashCompletions,
|
|
16
16
|
} from "@silverbulletmd/silverbullet/type/client";
|
|
17
|
+
import { matchesNodeContexts } from "../../lib/node_contexts.ts";
|
|
17
18
|
|
|
18
19
|
const slashCommandRegexp = /([^\w:]|^)\/[\w#-]*/;
|
|
19
20
|
|
|
@@ -77,19 +78,7 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
|
|
77
78
|
|
|
78
79
|
const parentNodes = this.client.extractParentNodes(ctx.state, currentNode);
|
|
79
80
|
for (const def of this.slashCommands) {
|
|
80
|
-
if (
|
|
81
|
-
def.onlyContexts &&
|
|
82
|
-
!def.onlyContexts.some((context) =>
|
|
83
|
-
parentNodes.some((node) => node.startsWith(context)),
|
|
84
|
-
)
|
|
85
|
-
) {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (
|
|
89
|
-
def.exceptContexts?.some((context) =>
|
|
90
|
-
parentNodes.some((node) => node.startsWith(context)),
|
|
91
|
-
)
|
|
92
|
-
) {
|
|
81
|
+
if (!matchesNodeContexts(def, parentNodes)) {
|
|
93
82
|
continue;
|
|
94
83
|
}
|
|
95
84
|
options.push({
|
|
@@ -2,13 +2,13 @@ import type { KvPrimitives } from "../data/kv_primitives.ts";
|
|
|
2
2
|
import type { Plug } from "./plug.ts";
|
|
3
3
|
import type { Manifest } from "./types.ts";
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export type ManifestCache<T> = {
|
|
6
6
|
getManifest(
|
|
7
7
|
plug: Plug<T>,
|
|
8
8
|
cacheKey: string,
|
|
9
9
|
cacheHash: number,
|
|
10
10
|
): Promise<Manifest<T>>;
|
|
11
|
-
}
|
|
11
|
+
};
|
|
12
12
|
|
|
13
13
|
export class KVPrimitivesManifestCache<T> implements ManifestCache<T> {
|
|
14
14
|
constructor(
|
|
@@ -3,7 +3,7 @@ import type { Manifest } from "../types.ts";
|
|
|
3
3
|
|
|
4
4
|
export type SandboxFactory<HookT> = (plug: Plug<HookT>) => Sandbox<HookT>;
|
|
5
5
|
|
|
6
|
-
export
|
|
6
|
+
export type Sandbox<HookT> = {
|
|
7
7
|
manifest?: Manifest<HookT>;
|
|
8
8
|
|
|
9
9
|
init(): Promise<void>;
|
|
@@ -11,4 +11,4 @@ export interface Sandbox<HookT> {
|
|
|
11
11
|
invoke(name: string, args: any[]): Promise<any>;
|
|
12
12
|
|
|
13
13
|
stop(): void;
|
|
14
|
-
}
|
|
14
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
maximumDocumentSize,
|
|
3
|
+
notFoundError,
|
|
4
|
+
} from "@silverbulletmd/silverbullet/constants";
|
|
5
|
+
import { isValidPath } from "@silverbulletmd/silverbullet/lib/ref";
|
|
6
|
+
import type { CaptureInvocationContext } from "../../capture/invocation.ts";
|
|
7
|
+
import type { Client } from "../../client.ts";
|
|
8
|
+
import type { SysCallMapping } from "../system.ts";
|
|
9
|
+
|
|
10
|
+
function sameBytes(a: Uint8Array, b: Uint8Array): boolean {
|
|
11
|
+
return a.length === b.length && a.every((byte, index) => byte === b[index]);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function captureSyscalls(
|
|
15
|
+
context: () => CaptureInvocationContext | undefined,
|
|
16
|
+
client: Client,
|
|
17
|
+
): SysCallMapping {
|
|
18
|
+
const active = (): CaptureInvocationContext => {
|
|
19
|
+
const current = context();
|
|
20
|
+
if (!current) throw new Error("No active capture action");
|
|
21
|
+
return current;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
"capture.readFile": {
|
|
26
|
+
callback: async (_ctx, handle: string): Promise<Uint8Array> =>
|
|
27
|
+
new Uint8Array(await (await active().readFile(handle)).arrayBuffer()),
|
|
28
|
+
description:
|
|
29
|
+
"Reads a staged file belonging to the current capture action.",
|
|
30
|
+
parameters: [
|
|
31
|
+
{
|
|
32
|
+
name: "handle",
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "File handle from the capture.",
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
returns: [{ type: "byteArray", description: "File bytes." }],
|
|
38
|
+
examples: [
|
|
39
|
+
{ code: "local bytes = capture.readFile(data.files[1].handle)" },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
"capture.saveFile": {
|
|
43
|
+
callback: async (
|
|
44
|
+
_ctx,
|
|
45
|
+
handle: string,
|
|
46
|
+
path: string,
|
|
47
|
+
): Promise<boolean> => {
|
|
48
|
+
if (!isValidPath(path)) throw new Error("Invalid capture file path");
|
|
49
|
+
const blob = await active().readFile(handle);
|
|
50
|
+
const limit = client.config.get<number>(
|
|
51
|
+
"maximumDocumentSize",
|
|
52
|
+
maximumDocumentSize,
|
|
53
|
+
);
|
|
54
|
+
if (typeof limit !== "number" || blob.size > limit * 1024 * 1024) {
|
|
55
|
+
throw new Error("Shared file is too large");
|
|
56
|
+
}
|
|
57
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
58
|
+
let existing = false;
|
|
59
|
+
try {
|
|
60
|
+
await client.space.getDocumentMeta(path);
|
|
61
|
+
existing = true;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (
|
|
64
|
+
!(error instanceof Error && error.message === notFoundError.message)
|
|
65
|
+
)
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
if (existing) {
|
|
69
|
+
const saved = await client.space.readDocument(path);
|
|
70
|
+
if (!sameBytes(saved.data, bytes))
|
|
71
|
+
throw new Error(`Shared file conflict: ${path}`);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
await client.space.writeDocument(path, bytes);
|
|
75
|
+
const saved = await client.space.readDocument(path);
|
|
76
|
+
if (!sameBytes(saved.data, bytes))
|
|
77
|
+
throw new Error(`Shared file verification failed: ${path}`);
|
|
78
|
+
return true;
|
|
79
|
+
},
|
|
80
|
+
description:
|
|
81
|
+
"Saves a staged file without overwriting different bytes. Returns true when newly created and false when identical content already exists.",
|
|
82
|
+
parameters: [
|
|
83
|
+
{
|
|
84
|
+
name: "handle",
|
|
85
|
+
type: "string",
|
|
86
|
+
description: "File handle from the capture.",
|
|
87
|
+
},
|
|
88
|
+
{ name: "path", type: "string", description: "New document path." },
|
|
89
|
+
],
|
|
90
|
+
returns: [
|
|
91
|
+
{
|
|
92
|
+
type: "boolean",
|
|
93
|
+
description:
|
|
94
|
+
"true when the file was created, false when identical bytes already exist.",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
examples: [
|
|
98
|
+
{
|
|
99
|
+
code: 'capture.saveFile(data.files[1].handle, "Inbox/" .. data.files[1].name)',
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { copyToClipboard } from "../../clipboard.ts";
|
|
2
1
|
import {
|
|
3
2
|
acceptCompletion,
|
|
4
3
|
closeCompletion,
|
|
@@ -70,6 +69,8 @@ import {
|
|
|
70
69
|
type Ref,
|
|
71
70
|
} from "@silverbulletmd/silverbullet/lib/ref";
|
|
72
71
|
import type {
|
|
72
|
+
DocumentCapability,
|
|
73
|
+
DocumentCapabilityDescriptor,
|
|
73
74
|
FilterOption,
|
|
74
75
|
NotificationAction,
|
|
75
76
|
NotificationType,
|
|
@@ -80,8 +81,11 @@ import type { VimConfig } from "@silverbulletmd/silverbullet/type/config";
|
|
|
80
81
|
import type { PageMeta } from "@silverbulletmd/silverbullet/type/index";
|
|
81
82
|
import { updateBakedSections } from "../../baked_sections/bake.ts";
|
|
82
83
|
import type { Client } from "../../client.ts";
|
|
84
|
+
import { copyToClipboard } from "../../clipboard.ts";
|
|
83
85
|
import { refreshLintEffect } from "../../codemirror/lint.ts";
|
|
86
|
+
import { resolveDocumentCapability } from "../../document_editor_resolver.ts";
|
|
84
87
|
import { isMobileDevice, isNarrowScreen } from "../../lib/mobile.ts";
|
|
88
|
+
import { browserMediaCapabilities } from "../../media.ts";
|
|
85
89
|
import { hide as hideNavigatorSlot } from "../../navigator/navigator.ts";
|
|
86
90
|
import type { PanelSlot } from "../../types/ui.ts";
|
|
87
91
|
import { getVimModule } from "../../vim_loader.ts";
|
|
@@ -168,7 +172,7 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
168
172
|
),
|
|
169
173
|
];
|
|
170
174
|
},
|
|
171
|
-
description: `Returns the file extensions
|
|
175
|
+
description: `Returns the file extensions claimed by explicitly registered document editors. Extensions carry no leading dot. Built-in text and media fallbacks are not included.`,
|
|
172
176
|
returns: [
|
|
173
177
|
{
|
|
174
178
|
type: "string[]",
|
|
@@ -176,6 +180,34 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
176
180
|
},
|
|
177
181
|
],
|
|
178
182
|
},
|
|
183
|
+
"editor.getDocumentCapabilities": {
|
|
184
|
+
callback: (
|
|
185
|
+
_ctx,
|
|
186
|
+
documents: DocumentCapabilityDescriptor[],
|
|
187
|
+
): DocumentCapability[] =>
|
|
188
|
+
documents.map((document) =>
|
|
189
|
+
resolveDocumentCapability(
|
|
190
|
+
document,
|
|
191
|
+
client.clientSystem.documentEditorHook.documentEditors,
|
|
192
|
+
browserMediaCapabilities,
|
|
193
|
+
),
|
|
194
|
+
),
|
|
195
|
+
description:
|
|
196
|
+
"Resolves the available editor capability for each document from metadata only, in input order.",
|
|
197
|
+
parameters: [
|
|
198
|
+
{
|
|
199
|
+
name: "documents",
|
|
200
|
+
type: "DocumentCapabilityDescriptor[]",
|
|
201
|
+
description: "Document metadata to resolve without reading bodies.",
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
returns: [
|
|
205
|
+
{
|
|
206
|
+
type: "DocumentCapability[]",
|
|
207
|
+
description: "Capabilities in the same order as the input.",
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
},
|
|
179
211
|
"editor.getText": {
|
|
180
212
|
callback: () => {
|
|
181
213
|
return client.editorView.state.sliceDoc();
|
|
@@ -466,6 +498,29 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
466
498
|
},
|
|
467
499
|
],
|
|
468
500
|
},
|
|
501
|
+
"editor.downloadSpaceFile": {
|
|
502
|
+
callback: async (_ctx, name: string) => {
|
|
503
|
+
const { data, meta } =
|
|
504
|
+
await client.space.spacePrimitives.readFile(name);
|
|
505
|
+
const url = URL.createObjectURL(
|
|
506
|
+
new Blob([new Uint8Array(data)], {
|
|
507
|
+
type: meta.contentType || "application/octet-stream",
|
|
508
|
+
}),
|
|
509
|
+
);
|
|
510
|
+
try {
|
|
511
|
+
const link = document.createElement("a");
|
|
512
|
+
link.href = url;
|
|
513
|
+
link.download = name.split("/").at(-1)!;
|
|
514
|
+
link.click();
|
|
515
|
+
} finally {
|
|
516
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
description: "Downloads a space file using its current contents.",
|
|
520
|
+
parameters: [
|
|
521
|
+
{ name: "name", type: "string", description: "Space file path." },
|
|
522
|
+
],
|
|
523
|
+
},
|
|
469
524
|
"editor.uploadFile": {
|
|
470
525
|
callback: (
|
|
471
526
|
_ctx,
|
|
@@ -483,6 +538,7 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
483
538
|
}
|
|
484
539
|
|
|
485
540
|
input.onchange = () => {
|
|
541
|
+
input.remove();
|
|
486
542
|
const file = input.files?.item(0);
|
|
487
543
|
if (!file) {
|
|
488
544
|
reject(new Error("No file found"));
|
|
@@ -509,11 +565,21 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
509
565
|
input.onabort = (e) => {
|
|
510
566
|
reject(e);
|
|
511
567
|
};
|
|
568
|
+
input.oncancel = () => {
|
|
569
|
+
input.remove();
|
|
570
|
+
};
|
|
512
571
|
|
|
513
572
|
input.style.display = "none";
|
|
514
573
|
document.body.appendChild(input);
|
|
515
|
-
|
|
516
|
-
|
|
574
|
+
if (isMobileDevice()) {
|
|
575
|
+
try {
|
|
576
|
+
input.showPicker();
|
|
577
|
+
} catch {
|
|
578
|
+
input.click();
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
input.click();
|
|
582
|
+
}
|
|
517
583
|
});
|
|
518
584
|
},
|
|
519
585
|
description:
|
|
@@ -1571,7 +1637,7 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1571
1637
|
callback: (_ctx, type: string, data: any) => {
|
|
1572
1638
|
if (!client.contentManager.isDocumentEditor()) return;
|
|
1573
1639
|
|
|
1574
|
-
client.contentManager.documentEditor.sendPublicMessage({
|
|
1640
|
+
client.contentManager.documentEditor.sendPublicMessage?.({
|
|
1575
1641
|
type,
|
|
1576
1642
|
data,
|
|
1577
1643
|
});
|
|
@@ -3,6 +3,11 @@ import type { EventHookI } from "../eventhook.ts";
|
|
|
3
3
|
import type { EventSubscription } from "@silverbulletmd/silverbullet/type/event";
|
|
4
4
|
import { LuaStackFrame, luaValueToJS } from "../../space_lua/runtime.ts";
|
|
5
5
|
import type { Client } from "../../client.ts";
|
|
6
|
+
import { luaDefinitionRef } from "../../space_lua.ts";
|
|
7
|
+
import type { Ref } from "@silverbulletmd/silverbullet/lib/ref";
|
|
8
|
+
|
|
9
|
+
export const listenerDefinition = Symbol("listenerDefinition");
|
|
10
|
+
export type DefinedListener = Function & { [listenerDefinition]?: Ref | null };
|
|
6
11
|
|
|
7
12
|
export function eventSyscalls(
|
|
8
13
|
eventHook: EventHookI,
|
|
@@ -38,13 +43,13 @@ export function eventSyscalls(
|
|
|
38
43
|
*/
|
|
39
44
|
"event.listen": {
|
|
40
45
|
callback: (_ctx, def: EventSubscription) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
);
|
|
46
|
+
const definition = luaDefinitionRef(def.run);
|
|
47
|
+
const listener = async (...args: any[]) => {
|
|
48
|
+
const val = await def.run(...args);
|
|
49
|
+
return luaValueToJS(val, LuaStackFrame.lostFrame);
|
|
50
|
+
};
|
|
51
|
+
(listener as DefinedListener)[listenerDefinition] = definition;
|
|
52
|
+
client.config.insert(["eventListeners", def.name], listener);
|
|
48
53
|
},
|
|
49
54
|
description: "Registers a Space Lua listener on the event bus.",
|
|
50
55
|
parameters: [
|
|
@@ -60,23 +60,18 @@ export function navigatorSyscalls(): SysCallMapping {
|
|
|
60
60
|
],
|
|
61
61
|
examples: [{ code: "onMove = view.moveByRename" }],
|
|
62
62
|
},
|
|
63
|
-
"lua:
|
|
63
|
+
"lua:widget.newLive": {
|
|
64
64
|
callback: (_ctx, spec: any) => newView(spec),
|
|
65
65
|
description:
|
|
66
|
-
"
|
|
66
|
+
"Internal: builds a live widget for `widget.new` when its spec has `source` or `content`. Call `widget.new` instead.",
|
|
67
67
|
parameters: [
|
|
68
68
|
{
|
|
69
69
|
name: "spec",
|
|
70
70
|
type: "table",
|
|
71
|
-
description: "
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
returns: [{ type: "table", description: "The reusable view value." }],
|
|
75
|
-
examples: [
|
|
76
|
-
{
|
|
77
|
-
code: "local projects = view.new { source = function() return {} end }",
|
|
71
|
+
description: "A `widget.new` spec with `source` or `content`.",
|
|
78
72
|
},
|
|
79
73
|
],
|
|
74
|
+
returns: [{ type: "table", description: "The live widget value." }],
|
|
80
75
|
},
|
|
81
76
|
"lua:view.define": {
|
|
82
77
|
callback: (_ctx, spec: any) => {
|
|
@@ -65,6 +65,20 @@ export function syncSyscalls(client: Client): SysCallMapping {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
return {
|
|
68
|
+
"sync.isEnabled": {
|
|
69
|
+
callback: (): boolean => {
|
|
70
|
+
return !client.bootConfig.disableServiceWorker;
|
|
71
|
+
},
|
|
72
|
+
description:
|
|
73
|
+
"Whether client Sync is enabled. False when the service worker is off.",
|
|
74
|
+
returns: [
|
|
75
|
+
{
|
|
76
|
+
type: "boolean",
|
|
77
|
+
description:
|
|
78
|
+
"False when SB_DISABLE_SERVICE_WORKER is set or the browser has no service worker.",
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
68
82
|
"sync.hasInitialSyncCompleted": {
|
|
69
83
|
callback: (): boolean => {
|
|
70
84
|
return client.fullSyncCompleted;
|
package/client/plugos/system.ts
CHANGED
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
} from "../../plugs/builtin_plugs.ts";
|
|
10
10
|
import type { LuaFunctionDocumentation } from "../../plug-api/types/index.ts";
|
|
11
11
|
|
|
12
|
-
export
|
|
12
|
+
export type SysCallMapping = {
|
|
13
13
|
[key: string]: SyscallSignature | SyscallDefinition;
|
|
14
|
-
}
|
|
14
|
+
};
|
|
15
15
|
|
|
16
16
|
export type SystemEvents<HookT> = {
|
|
17
17
|
plugLoaded: (plug: Plug<HookT>) => void | Promise<void>;
|
package/client/plugos/types.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { AssetJson } from "../asset_bundle/bundle.ts";
|
|
|
4
4
|
/** The generic top level of a plug manifest file.
|
|
5
5
|
* Defines plug metadata and functions.
|
|
6
6
|
*/
|
|
7
|
-
export
|
|
7
|
+
export type Manifest<HookT> = {
|
|
8
8
|
/** The plug's name. Typically this is the name of the manifest file, without the file extension. */
|
|
9
9
|
name: string;
|
|
10
10
|
|
|
@@ -42,7 +42,7 @@ export interface Manifest<HookT> {
|
|
|
42
42
|
* which runs esbuild in bundle mode on `in` and writes the result to `out`.
|
|
43
43
|
*/
|
|
44
44
|
build?: BuildStep[];
|
|
45
|
-
}
|
|
45
|
+
};
|
|
46
46
|
|
|
47
47
|
/** A pre-asset-bundling transformation, e.g. bundling a TypeScript entry point
|
|
48
48
|
* into a single JS file that is then loaded from plug assets.
|
|
@@ -52,14 +52,14 @@ export interface Manifest<HookT> {
|
|
|
52
52
|
* - `sass`: compile an SCSS/SASS file at `in` to CSS at `out`.
|
|
53
53
|
* - `copy`: copy `in` verbatim to `out`.
|
|
54
54
|
*/
|
|
55
|
-
export
|
|
55
|
+
export type BuildStep = {
|
|
56
56
|
/** Transformation type. Defaults to "esbuild" when omitted. */
|
|
57
57
|
type?: "esbuild" | "sass" | "copy";
|
|
58
58
|
/** Input path relative to the manifest. */
|
|
59
59
|
in: string;
|
|
60
60
|
/** Output path relative to the manifest. */
|
|
61
61
|
out: string;
|
|
62
|
-
}
|
|
62
|
+
};
|
|
63
63
|
|
|
64
64
|
/** Associates hooks with a function. This is the generic base structure, that identifies the function. Hooks are defined by the type parameter. */
|
|
65
65
|
export type FunctionDef<HookT> = {
|
|
@@ -78,8 +78,8 @@ export type FunctionDef<HookT> = {
|
|
|
78
78
|
env?: string;
|
|
79
79
|
} & HookT;
|
|
80
80
|
|
|
81
|
-
export
|
|
81
|
+
export type Hook<HookT> = {
|
|
82
82
|
validateManifest(manifest: Manifest<HookT>): string[];
|
|
83
83
|
|
|
84
84
|
apply(system: System<HookT>): void;
|
|
85
|
-
}
|
|
85
|
+
};
|
|
@@ -24,13 +24,13 @@ import type { Config } from "../config.ts";
|
|
|
24
24
|
import { coerceToNumber, isTaggedFloat } from "./numeric.ts";
|
|
25
25
|
import YAML from "js-yaml";
|
|
26
26
|
|
|
27
|
-
export
|
|
27
|
+
export type AggregateSpec = {
|
|
28
28
|
name: string;
|
|
29
29
|
description?: string;
|
|
30
30
|
initialize: LuaValue; // ILuaFunction
|
|
31
31
|
iterate: LuaValue; // ILuaFunction
|
|
32
32
|
finish?: LuaValue; // ILuaFunction | undefined
|
|
33
|
-
}
|
|
33
|
+
};
|
|
34
34
|
|
|
35
35
|
// Helper to build an ILuaFunction from a plain function. Equivalent to
|
|
36
36
|
// LuaBuiltinFunction but without referencing the class.
|
|
@@ -54,11 +54,11 @@ function unboxValue(value: LuaValue): LuaValue {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// Welford's online algorithm (for variance and standard deviation)
|
|
57
|
-
|
|
57
|
+
type WelfordState = {
|
|
58
58
|
n: number;
|
|
59
59
|
mean: number;
|
|
60
60
|
m2: number;
|
|
61
|
-
}
|
|
61
|
+
};
|
|
62
62
|
|
|
63
63
|
function welfordInit(): WelfordState {
|
|
64
64
|
return { n: 0, mean: 0, m2: 0 };
|
|
@@ -119,11 +119,11 @@ type QuantileMethod =
|
|
|
119
119
|
| "nearest"
|
|
120
120
|
| "midpoint";
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
type QuantileState = {
|
|
123
123
|
values: number[];
|
|
124
124
|
q: number;
|
|
125
125
|
method: QuantileMethod;
|
|
126
|
-
}
|
|
126
|
+
};
|
|
127
127
|
|
|
128
128
|
const quantileNameDefaults: Record<string, QuantileMethod> = {
|
|
129
129
|
percentile_cont: "linear",
|
|
@@ -2,10 +2,10 @@ import type { NumericType } from "./ast.ts";
|
|
|
2
2
|
import { luaToNumberDetailed } from "./tonumber.ts";
|
|
3
3
|
import { luaTypeName } from "./runtime.ts";
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export type LuaTaggedFloat = {
|
|
6
6
|
readonly value: number;
|
|
7
7
|
readonly isFloat: true;
|
|
8
|
-
}
|
|
8
|
+
};
|
|
9
9
|
|
|
10
10
|
const FLOAT_POS_ZERO: LuaTaggedFloat = { value: 0, isFloat: true };
|
|
11
11
|
const FLOAT_NEG_ZERO: LuaTaggedFloat = { value: -0, isFloat: true };
|
|
@@ -162,14 +162,14 @@ export type LuaCollectionQuery = {
|
|
|
162
162
|
having?: LuaExpression;
|
|
163
163
|
};
|
|
164
164
|
|
|
165
|
-
export
|
|
165
|
+
export type LuaQueryCollection = {
|
|
166
166
|
query(
|
|
167
167
|
query: LuaCollectionQuery,
|
|
168
168
|
env: LuaEnv,
|
|
169
169
|
sf: LuaStackFrame,
|
|
170
170
|
config?: Config,
|
|
171
171
|
): Promise<any[]>;
|
|
172
|
-
}
|
|
172
|
+
};
|
|
173
173
|
|
|
174
174
|
export class ArrayQueryCollection<T> implements LuaQueryCollection {
|
|
175
175
|
constructor(private readonly array: T[]) {}
|
|
@@ -648,6 +648,18 @@ async function evalSelectExpression(
|
|
|
648
648
|
return result;
|
|
649
649
|
}
|
|
650
650
|
|
|
651
|
+
/** Whether `value` passes the query's `where`, as `applyQuery` filters it. */
|
|
652
|
+
export async function matchesWhere(
|
|
653
|
+
value: any,
|
|
654
|
+
query: LuaCollectionQuery,
|
|
655
|
+
env: LuaEnv,
|
|
656
|
+
sf: LuaStackFrame,
|
|
657
|
+
): Promise<boolean> {
|
|
658
|
+
if (!query.where) return true;
|
|
659
|
+
const itemEnv = buildItemEnvLocal(query.objectVariable, value, env, sf);
|
|
660
|
+
return !!(await evalExpression(query.where, itemEnv, sf));
|
|
661
|
+
}
|
|
662
|
+
|
|
651
663
|
export async function applyQuery(
|
|
652
664
|
results: any[],
|
|
653
665
|
query: LuaCollectionQuery,
|
|
@@ -659,8 +671,7 @@ export async function applyQuery(
|
|
|
659
671
|
if (query.where) {
|
|
660
672
|
const filteredResults = [];
|
|
661
673
|
for (const value of results) {
|
|
662
|
-
|
|
663
|
-
if (await evalExpression(query.where, itemEnv, sf)) {
|
|
674
|
+
if (await matchesWhere(value, query, env, sf)) {
|
|
664
675
|
filteredResults.push(value);
|
|
665
676
|
}
|
|
666
677
|
}
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
} from "../../plug-api/types/index.ts";
|
|
13
13
|
|
|
14
14
|
let boundaryBudgetFactory: (() => LuaBudget) | undefined;
|
|
15
|
+
export const luaFunctionInfo = Symbol("luaFunctionInfo");
|
|
15
16
|
|
|
16
17
|
export function setBoundaryBudgetFactory(
|
|
17
18
|
f: (() => LuaBudget) | undefined,
|
|
@@ -39,27 +40,27 @@ export type LuaFunctionDefinition<Callback> = {
|
|
|
39
40
|
callback: Callback;
|
|
40
41
|
} & LuaFunctionDefinitionMetadata;
|
|
41
42
|
|
|
42
|
-
export
|
|
43
|
+
export type ILuaFunction = {
|
|
43
44
|
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue;
|
|
44
45
|
|
|
45
46
|
asString(): string;
|
|
46
47
|
|
|
47
48
|
info?: LuaFunctionInfo;
|
|
48
|
-
}
|
|
49
|
+
};
|
|
49
50
|
|
|
50
|
-
export
|
|
51
|
+
export type ILuaSettable = {
|
|
51
52
|
set(
|
|
52
53
|
key: LuaValue,
|
|
53
54
|
value: LuaValue,
|
|
54
55
|
sf?: LuaStackFrame,
|
|
55
56
|
numType?: NumericType,
|
|
56
57
|
): void | Promise<void>;
|
|
57
|
-
}
|
|
58
|
+
};
|
|
58
59
|
|
|
59
|
-
export
|
|
60
|
+
export type ILuaGettable = {
|
|
60
61
|
get(key: LuaValue, sf?: LuaStackFrame): LuaValue | Promise<LuaValue> | null;
|
|
61
62
|
getNumericType?(key: LuaValue): NumericType | undefined;
|
|
62
|
-
}
|
|
63
|
+
};
|
|
63
64
|
|
|
64
65
|
export function isILuaFunction(v: unknown): v is ILuaFunction {
|
|
65
66
|
return !!v && typeof (v as any).call === "function";
|
|
@@ -1825,7 +1826,7 @@ export function luaValueToJS(value: any, sf: LuaStackFrame): any {
|
|
|
1825
1826
|
value instanceof LuaFunction ||
|
|
1826
1827
|
value instanceof LuaBuiltinFunction
|
|
1827
1828
|
) {
|
|
1828
|
-
|
|
1829
|
+
const fn = (...args: any[]) => {
|
|
1829
1830
|
// A converted closure captures one frame forever; without a fresh
|
|
1830
1831
|
// thread state per call, every invocation would share one budget.
|
|
1831
1832
|
const boundaryBudget = boundaryBudgetFactory?.();
|
|
@@ -1868,6 +1869,12 @@ export function luaValueToJS(value: any, sf: LuaStackFrame): any {
|
|
|
1868
1869
|
throw e;
|
|
1869
1870
|
}
|
|
1870
1871
|
};
|
|
1872
|
+
if (value instanceof LuaFunction) {
|
|
1873
|
+
(fn as typeof fn & { [luaFunctionInfo]: LuaFunctionInfo })[
|
|
1874
|
+
luaFunctionInfo
|
|
1875
|
+
] = value.info;
|
|
1876
|
+
}
|
|
1877
|
+
return fn;
|
|
1871
1878
|
}
|
|
1872
1879
|
if (isTaggedFloat(value)) {
|
|
1873
1880
|
return value.value;
|
|
@@ -32,12 +32,12 @@ const MAX_MATCH_DEPTH = 200;
|
|
|
32
32
|
const CAP_UNFINISHED = -1;
|
|
33
33
|
const CAP_POSITION = -2;
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
type Capture = {
|
|
36
36
|
init: number;
|
|
37
37
|
len: number;
|
|
38
|
-
}
|
|
38
|
+
};
|
|
39
39
|
|
|
40
|
-
export
|
|
40
|
+
export type MatchState = {
|
|
41
41
|
src: string;
|
|
42
42
|
s: Uint16Array;
|
|
43
43
|
slen: number;
|
|
@@ -46,7 +46,7 @@ export interface MatchState {
|
|
|
46
46
|
level: number;
|
|
47
47
|
capture: Capture[]; // pre-allocated, length `MAX_CAPTURES`
|
|
48
48
|
matchdepth: number;
|
|
49
|
-
}
|
|
49
|
+
};
|
|
50
50
|
|
|
51
51
|
function toCodeUnits(s: string): Uint16Array {
|
|
52
52
|
const len = s.length;
|
|
@@ -434,11 +434,11 @@ function noSpecials(p: string): boolean {
|
|
|
434
434
|
|
|
435
435
|
export type CaptureResult = { s: string } | { position: number };
|
|
436
436
|
|
|
437
|
-
|
|
437
|
+
type RawCapture = {
|
|
438
438
|
kind: 0 | 1; // 0 = string slice, 1 = position
|
|
439
439
|
start: number;
|
|
440
440
|
len: number; // kind = 0: substring length; kind = 1: 1-based position
|
|
441
|
-
}
|
|
441
|
+
};
|
|
442
442
|
|
|
443
443
|
function getOneRawCapture(
|
|
444
444
|
ms: MatchState,
|
|
@@ -622,13 +622,13 @@ function expandReplacementString(
|
|
|
622
622
|
return parts.join("");
|
|
623
623
|
}
|
|
624
624
|
|
|
625
|
-
export
|
|
625
|
+
export type GsubCallbacks = {
|
|
626
626
|
replString?: string;
|
|
627
627
|
replFunction?: (
|
|
628
628
|
...captures: CaptureResult[]
|
|
629
629
|
) => Promise<string | null | undefined> | string | null | undefined;
|
|
630
630
|
replTable?: (key: string) => string | null | undefined;
|
|
631
|
-
}
|
|
631
|
+
};
|
|
632
632
|
|
|
633
633
|
export async function patternGsub(
|
|
634
634
|
s: string,
|
|
@@ -28,16 +28,16 @@ type KOption =
|
|
|
28
28
|
| "paddalign"
|
|
29
29
|
| "nop";
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
type ParsedOption = {
|
|
32
32
|
opt: KOption;
|
|
33
33
|
size: number; // byte width
|
|
34
34
|
ntoalign: number; // padding bytes before this field
|
|
35
|
-
}
|
|
35
|
+
};
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
type Header = {
|
|
38
38
|
islittle: boolean;
|
|
39
39
|
maxalign: number;
|
|
40
|
-
}
|
|
40
|
+
};
|
|
41
41
|
|
|
42
42
|
function makeHeader(): Header {
|
|
43
43
|
return { islittle: NATIVE_LITTLE, maxalign: NATIVE_MAXALIGN };
|
package/dist/plug-compile.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// version.ts
|
|
7
|
-
var version = "2.
|
|
7
|
+
var version = "2.12.0";
|
|
8
8
|
|
|
9
9
|
// client/plugos/plug_compile.ts
|
|
10
10
|
import * as path from "node:path";
|
|
@@ -1330,7 +1330,7 @@ var workerRuntimePlugin = {
|
|
|
1330
1330
|
namespace: "worker-runtime"
|
|
1331
1331
|
}));
|
|
1332
1332
|
build2.onLoad({ filter: /.*/, namespace: "worker-runtime" }, () => ({
|
|
1333
|
-
contents: '// plug-api/lib/crypto.ts\nfunction base64Decode(s) {\n const binString = atob(s);\n const len = binString.length;\n const bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) {\n bytes[i] = binString.charCodeAt(i);\n }\n return bytes;\n}\nfunction base64Encode(buffer) {\n if (typeof buffer === "string") {\n buffer = new TextEncoder().encode(buffer);\n }\n let binary = "";\n const len = buffer.byteLength;\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(buffer[i]);\n }\n return btoa(binary);\n}\nvar fixedCounter = new Uint8Array(16);\n\n// client/lib/logger.ts\nvar Logger = class {\n constructor(prefix = "", maxCaptureSize = 1e3) {\n this.prefix = prefix;\n this.maxCaptureSize = maxCaptureSize;\n this.prefix = prefix;\n this.originalConsole = {\n log: console.log.bind(console),\n info: console.info.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n debug: console.debug.bind(console)\n };\n this.patchConsole();\n }\n originalConsole;\n logBuffer = [];\n patchConsole() {\n const createPatchedMethod = (level) => {\n return (...args) => {\n const prefixedArgs = this.prefix ? [this.prefix, ...args] : args;\n this.originalConsole[level](...prefixedArgs);\n this.captureLog(level, args);\n };\n };\n console.log = createPatchedMethod("log");\n console.info = createPatchedMethod("info");\n console.warn = createPatchedMethod("warn");\n console.error = createPatchedMethod("error");\n console.debug = createPatchedMethod("debug");\n }\n captureLog(level, args) {\n const entry = {\n level,\n timestamp: Date.now(),\n message: args.map((arg) => {\n if (typeof arg === "string") {\n return arg;\n }\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n }).join(" ")\n };\n this.logBuffer.push(entry);\n if (this.logBuffer.length > this.maxCaptureSize) {\n this.logBuffer.shift();\n }\n }\n /**\n * Posts all buffered logs to a server endpoint\n */\n async postToServer(logEndpoint, source) {\n const logs = this.logBuffer;\n if (logs.length > 0) {\n const logCopy = [...this.logBuffer];\n this.logBuffer = [];\n try {\n const resp = await fetch(logEndpoint, {\n method: "POST",\n headers: {\n "Content-Type": "application/json"\n },\n body: JSON.stringify(logCopy.map((entry) => ({ ...entry, source })))\n });\n if (!resp.ok) {\n throw new Error("Failed to post logs to server");\n }\n } catch (e) {\n console.warn("Could not post logs to server", e.message);\n this.logBuffer.unshift(...logCopy);\n }\n }\n }\n};\nvar globalLogger;\nfunction initLogger(prefix = "") {\n globalLogger = new Logger(prefix);\n return globalLogger;\n}\n\n// client/plugos/worker_runtime.ts\nvar workerPostMessage = (_msg) => {\n throw new Error("Not initialized yet");\n};\nvar runningAsWebWorker = typeof window === "undefined" && // @ts-expect-error: globalThis\ntypeof globalThis.WebSocketPair === "undefined";\nvar pendingRequests = /* @__PURE__ */ new Map();\nvar syscallReqId = 0;\nif (runningAsWebWorker) {\n globalThis.syscall = async (name, ...args) => {\n return await new Promise((resolve, reject) => {\n syscallReqId++;\n pendingRequests.set(syscallReqId, { resolve, reject });\n workerPostMessage({\n type: "sys",\n id: syscallReqId,\n name,\n args\n });\n });\n };\n}\nfunction setupMessageListener(functionMapping, manifest, postMessageFn) {\n if (!runningAsWebWorker) {\n return;\n }\n workerPostMessage = postMessageFn;\n self.addEventListener("message", (event) => {\n (async () => {\n const data = event.data;\n switch (data.type) {\n case "inv":\n {\n const fn = functionMapping[data.name];\n if (!fn) {\n throw new Error(`Function not loaded: ${data.name}`);\n }\n try {\n const result = await Promise.resolve(fn(...data.args || []));\n workerPostMessage({\n type: "invr",\n id: data.id,\n result\n });\n } catch (e) {\n console.error(\n "An exception was thrown as a result of invoking function",\n data.name,\n "error:",\n e.message\n );\n workerPostMessage({\n type: "invr",\n id: data.id,\n error: e.message\n });\n }\n }\n break;\n case "sysr":\n {\n const syscallId = data.id;\n const lookup = pendingRequests.get(syscallId);\n if (!lookup) {\n throw Error("Invalid request id");\n }\n pendingRequests.delete(syscallId);\n if (data.error) {\n lookup.reject(new Error(data.error));\n } else {\n lookup.resolve(data.result);\n }\n }\n break;\n }\n })().catch(console.error);\n });\n workerPostMessage({\n type: "manifest",\n manifest\n });\n initLogger(`[${manifest.name} plug]`);\n}\nasync function sandboxFetch(reqInfo, options) {\n if (typeof reqInfo !== "string") {\n const body = new Uint8Array(await reqInfo.arrayBuffer());\n const encodedBody = body.length > 0 ? base64Encode(body) : void 0;\n options = {\n method: reqInfo.method,\n headers: Object.fromEntries(reqInfo.headers.entries()),\n base64Body: encodedBody\n };\n reqInfo = reqInfo.url;\n }\n return syscall("sandboxFetch.fetch", reqInfo, options);\n}\nglobalThis.nativeFetch = globalThis.fetch;\nfunction monkeyPatchFetch() {\n globalThis.fetch = async (reqInfo, init) => {\n const encodedBody = init?.body ? base64Encode(\n new Uint8Array(await new Response(init.body).arrayBuffer())\n ) : void 0;\n const r = await sandboxFetch(\n reqInfo,\n init && {\n method: init.method,\n headers: init.headers,\n base64Body: encodedBody\n }\n );\n return new Response(\n r.base64Body ? base64Decode(r.base64Body) : null,\n {\n status: r.status,\n headers: r.headers\n }\n );\n };\n}\nif (runningAsWebWorker) {\n monkeyPatchFetch();\n}\nexport {\n monkeyPatchFetch,\n sandboxFetch,\n setupMessageListener\n};\n',
|
|
1333
|
+
contents: '// plug-api/lib/crypto.ts\nfunction base64Decode(s) {\n const binString = atob(s);\n const len = binString.length;\n const bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) {\n bytes[i] = binString.charCodeAt(i);\n }\n return bytes;\n}\nfunction base64Encode(buffer) {\n if (typeof buffer === "string") {\n buffer = new TextEncoder().encode(buffer);\n }\n let binary = "";\n const len = buffer.byteLength;\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(buffer[i]);\n }\n return btoa(binary);\n}\nvar fixedCounter = new Uint8Array(16);\n\n// client/lib/logger.ts\nvar Logger = class {\n constructor(prefix = "", maxCaptureSize = 1e3) {\n this.prefix = prefix;\n this.maxCaptureSize = maxCaptureSize;\n this.prefix = prefix;\n this.originalConsole = {\n log: console.log.bind(console),\n info: console.info.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n debug: console.debug.bind(console)\n };\n this.patchConsole();\n }\n prefix;\n maxCaptureSize;\n originalConsole;\n logBuffer = [];\n patchConsole() {\n const createPatchedMethod = (level) => {\n return (...args) => {\n const prefixedArgs = this.prefix ? [this.prefix, ...args] : args;\n this.originalConsole[level](...prefixedArgs);\n this.captureLog(level, args);\n };\n };\n console.log = createPatchedMethod("log");\n console.info = createPatchedMethod("info");\n console.warn = createPatchedMethod("warn");\n console.error = createPatchedMethod("error");\n console.debug = createPatchedMethod("debug");\n }\n captureLog(level, args) {\n const entry = {\n level,\n timestamp: Date.now(),\n message: args.map((arg) => {\n if (typeof arg === "string") {\n return arg;\n }\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n }).join(" ")\n };\n this.logBuffer.push(entry);\n if (this.logBuffer.length > this.maxCaptureSize) {\n this.logBuffer.shift();\n }\n }\n /**\n * Posts all buffered logs to a server endpoint\n */\n async postToServer(logEndpoint, source) {\n const logs = this.logBuffer;\n if (logs.length > 0) {\n const logCopy = [...this.logBuffer];\n this.logBuffer = [];\n try {\n const resp = await fetch(logEndpoint, {\n method: "POST",\n headers: {\n "Content-Type": "application/json"\n },\n body: JSON.stringify(logCopy.map((entry) => ({ ...entry, source })))\n });\n if (!resp.ok) {\n throw new Error("Failed to post logs to server");\n }\n } catch (e) {\n console.warn("Could not post logs to server", e.message);\n this.logBuffer.unshift(...logCopy);\n }\n }\n }\n};\nvar globalLogger;\nfunction initLogger(prefix = "") {\n globalLogger = new Logger(prefix);\n return globalLogger;\n}\n\n// client/plugos/worker_runtime.ts\nvar workerPostMessage = (_msg) => {\n throw new Error("Not initialized yet");\n};\nvar runningAsWebWorker = typeof window === "undefined" && // @ts-expect-error: globalThis\ntypeof globalThis.WebSocketPair === "undefined";\nvar pendingRequests = /* @__PURE__ */ new Map();\nvar syscallReqId = 0;\nif (runningAsWebWorker) {\n globalThis.syscall = async (name, ...args) => {\n return await new Promise((resolve, reject) => {\n syscallReqId++;\n pendingRequests.set(syscallReqId, { resolve, reject });\n workerPostMessage({\n type: "sys",\n id: syscallReqId,\n name,\n args\n });\n });\n };\n}\nfunction setupMessageListener(functionMapping, manifest, postMessageFn) {\n if (!runningAsWebWorker) {\n return;\n }\n workerPostMessage = postMessageFn;\n self.addEventListener("message", (event) => {\n (async () => {\n const data = event.data;\n switch (data.type) {\n case "inv":\n {\n const fn = functionMapping[data.name];\n if (!fn) {\n throw new Error(`Function not loaded: ${data.name}`);\n }\n try {\n const result = await Promise.resolve(fn(...data.args || []));\n workerPostMessage({\n type: "invr",\n id: data.id,\n result\n });\n } catch (e) {\n console.error(\n "An exception was thrown as a result of invoking function",\n data.name,\n "error:",\n e.message\n );\n workerPostMessage({\n type: "invr",\n id: data.id,\n error: e.message\n });\n }\n }\n break;\n case "sysr":\n {\n const syscallId = data.id;\n const lookup = pendingRequests.get(syscallId);\n if (!lookup) {\n throw Error("Invalid request id");\n }\n pendingRequests.delete(syscallId);\n if (data.error) {\n lookup.reject(new Error(data.error));\n } else {\n lookup.resolve(data.result);\n }\n }\n break;\n }\n })().catch(console.error);\n });\n workerPostMessage({\n type: "manifest",\n manifest\n });\n initLogger(`[${manifest.name} plug]`);\n}\nasync function sandboxFetch(reqInfo, options) {\n if (typeof reqInfo !== "string") {\n const body = new Uint8Array(await reqInfo.arrayBuffer());\n const encodedBody = body.length > 0 ? base64Encode(body) : void 0;\n options = {\n method: reqInfo.method,\n headers: Object.fromEntries(reqInfo.headers.entries()),\n base64Body: encodedBody\n };\n reqInfo = reqInfo.url;\n }\n return syscall("sandboxFetch.fetch", reqInfo, options);\n}\nglobalThis.nativeFetch = globalThis.fetch;\nfunction monkeyPatchFetch() {\n globalThis.fetch = async (reqInfo, init) => {\n const encodedBody = init?.body ? base64Encode(\n new Uint8Array(await new Response(init.body).arrayBuffer())\n ) : void 0;\n const r = await sandboxFetch(\n reqInfo,\n init && {\n method: init.method,\n headers: init.headers,\n base64Body: encodedBody\n }\n );\n return new Response(\n r.base64Body ? base64Decode(r.base64Body) : null,\n {\n status: r.status,\n headers: r.headers\n }\n );\n };\n}\nif (runningAsWebWorker) {\n monkeyPatchFetch();\n}\nexport {\n monkeyPatchFetch,\n sandboxFetch,\n setupMessageListener\n};\n',
|
|
1334
1334
|
loader: "js"
|
|
1335
1335
|
}));
|
|
1336
1336
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silverbulletmd/silverbullet",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A self-hosted, web-based note taking app",
|
|
6
6
|
"publishConfig": {
|
|
@@ -80,63 +80,64 @@
|
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
82
|
"@cfworker/json-schema": "^4.1.1",
|
|
83
|
-
"@codemirror/autocomplete": "6.20.
|
|
84
|
-
"@codemirror/commands": "6.
|
|
83
|
+
"@codemirror/autocomplete": "6.20.3",
|
|
84
|
+
"@codemirror/commands": "6.11.1",
|
|
85
85
|
"@codemirror/lang-css": "6.3.1",
|
|
86
|
-
"@codemirror/lang-html": "6.4.
|
|
86
|
+
"@codemirror/lang-html": "6.4.12",
|
|
87
87
|
"@codemirror/lang-javascript": "6.2.5",
|
|
88
|
-
"@codemirror/lang-markdown": "6.5.
|
|
89
|
-
"@codemirror/language": "6.12.
|
|
90
|
-
"@codemirror/legacy-modes": "6.5.
|
|
91
|
-
"@codemirror/lint": "6.9.
|
|
92
|
-
"@codemirror/search": "6.
|
|
93
|
-
"@codemirror/state": "6.6
|
|
94
|
-
"@codemirror/view": "6.
|
|
95
|
-
"@joplin/turndown-plugin-gfm": "1.0.
|
|
96
|
-
"@lezer/common": "1.5.
|
|
97
|
-
"@lezer/css": "1.3.
|
|
98
|
-
"@lezer/highlight": "1.2.
|
|
88
|
+
"@codemirror/lang-markdown": "6.5.2",
|
|
89
|
+
"@codemirror/language": "6.12.4",
|
|
90
|
+
"@codemirror/legacy-modes": "6.5.4",
|
|
91
|
+
"@codemirror/lint": "6.9.7",
|
|
92
|
+
"@codemirror/search": "6.7.2",
|
|
93
|
+
"@codemirror/state": "6.7.6",
|
|
94
|
+
"@codemirror/view": "6.43.13",
|
|
95
|
+
"@joplin/turndown-plugin-gfm": "1.0.68",
|
|
96
|
+
"@lezer/common": "1.5.2",
|
|
97
|
+
"@lezer/css": "1.3.8",
|
|
98
|
+
"@lezer/highlight": "1.2.4",
|
|
99
99
|
"@lezer/html": "1.3.13",
|
|
100
|
-
"@lezer/javascript": "1.5.
|
|
101
|
-
"@lezer/lr": "1.4.
|
|
102
|
-
"@lezer/markdown": "1.
|
|
100
|
+
"@lezer/javascript": "1.5.5",
|
|
101
|
+
"@lezer/lr": "1.4.10",
|
|
102
|
+
"@lezer/markdown": "1.7.2",
|
|
103
103
|
"@msgpack/msgpack": "3.1.3",
|
|
104
|
-
"@noble/hashes": "2.0
|
|
104
|
+
"@noble/hashes": "2.4.0",
|
|
105
105
|
"@replit/codemirror-lang-nix": "6.0.1",
|
|
106
|
-
"@replit/codemirror-vim": "6.
|
|
106
|
+
"@replit/codemirror-vim": "6.4.0",
|
|
107
107
|
"@types/js-yaml": "^4.0.9",
|
|
108
|
-
"commander": "^
|
|
109
|
-
"crelt": "1.0.
|
|
108
|
+
"commander": "^15.0.0",
|
|
109
|
+
"crelt": "1.0.7",
|
|
110
110
|
"d3-force": "^3.0.0",
|
|
111
|
-
"esbuild": "^0.
|
|
111
|
+
"esbuild": "^0.28.2",
|
|
112
112
|
"fast-diff": "1.3.0",
|
|
113
|
-
"force-graph": "^1.49.0",
|
|
114
113
|
"fast-glob": "^3.3.3",
|
|
114
|
+
"force-graph": "^1.51.4",
|
|
115
115
|
"gitignore-parser": "0.0.2",
|
|
116
116
|
"idb": "8.0.3",
|
|
117
|
-
"js-yaml": "^4.
|
|
117
|
+
"js-yaml": "^4.3.2",
|
|
118
118
|
"mime": "4.1.0",
|
|
119
|
-
"preact": "10.
|
|
120
|
-
"preact-feather": "4.
|
|
121
|
-
"react-icons": "5.
|
|
122
|
-
"sass": "^1.
|
|
123
|
-
"style-mod": "4.1.
|
|
124
|
-
"turndown": "7.2.
|
|
119
|
+
"preact": "10.29.8",
|
|
120
|
+
"preact-feather": "4.3.0",
|
|
121
|
+
"react-icons": "5.7.0",
|
|
122
|
+
"sass": "^1.105.0",
|
|
123
|
+
"style-mod": "4.1.4",
|
|
124
|
+
"turndown": "7.2.4"
|
|
125
125
|
},
|
|
126
126
|
"devDependencies": {
|
|
127
|
-
"@biomejs/biome": "2.
|
|
128
|
-
"@playwright/test": "
|
|
129
|
-
"@preact/preset-vite": "^2.10.
|
|
127
|
+
"@biomejs/biome": "2.5.14",
|
|
128
|
+
"@playwright/test": "1.58.2",
|
|
129
|
+
"@preact/preset-vite": "^2.10.6",
|
|
130
130
|
"@types/d3-force": "^3.0.10",
|
|
131
131
|
"@types/gitignore-parser": "^0.0.3",
|
|
132
|
-
"@types/node": "^
|
|
132
|
+
"@types/node": "^26.6.2",
|
|
133
133
|
"@types/turndown": "^5.0.6",
|
|
134
|
-
"
|
|
134
|
+
"@typescript/native": "npm:typescript@^7.0.2",
|
|
135
|
+
"fake-indexeddb": "6.2.5",
|
|
135
136
|
"preact-render-to-string": "^6.7.0",
|
|
136
|
-
"tsx": "^4.
|
|
137
|
-
"typescript": "
|
|
138
|
-
"vite": "^
|
|
139
|
-
"vitest": "^4.
|
|
137
|
+
"tsx": "^4.23.15",
|
|
138
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
139
|
+
"vite": "^8.3.1",
|
|
140
|
+
"vitest": "^4.1.11"
|
|
140
141
|
},
|
|
141
142
|
"engines": {
|
|
142
143
|
"node": ">=24.13.0",
|
package/plug-api/lib/resolve.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import type { Path } from "@silverbulletmd/silverbullet/lib/ref";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Determines wether a url points into the world wide web or to the local SB instance
|
|
5
|
-
*/
|
|
6
3
|
export function isLocalURL(url: string): boolean {
|
|
7
|
-
return (
|
|
8
|
-
!url.includes("://") &&
|
|
9
|
-
!url.startsWith("mailto:") &&
|
|
10
|
-
!url.startsWith("tel:")
|
|
11
|
-
);
|
|
4
|
+
return !/^[a-z][a-z0-9+.-]*:/i.test(url);
|
|
12
5
|
}
|
|
13
6
|
|
|
14
7
|
/**
|
package/plug-api/lib/yaml.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type YamlPatch = {
|
|
2
2
|
op: "set-key" | "delete-key";
|
|
3
3
|
path: string; // Top-level key names only
|
|
4
4
|
value?: any; // Required for set-key, not used for delete-key
|
|
5
|
-
}
|
|
5
|
+
};
|
|
6
6
|
|
|
7
7
|
function serializeToYamlScalar(
|
|
8
8
|
value: string | number | boolean | null,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
DocumentCapability,
|
|
3
|
+
DocumentCapabilityDescriptor,
|
|
2
4
|
FilterOption,
|
|
3
5
|
NotificationType,
|
|
4
6
|
UploadFile,
|
|
@@ -38,13 +40,23 @@ export function getLastOpenedMap(): Promise<Record<string, number>> {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
|
-
* Returns the file extensions (without a leading dot)
|
|
42
|
-
*
|
|
43
|
+
* Returns the file extensions (without a leading dot) claimed by explicitly
|
|
44
|
+
* registered document editors.
|
|
43
45
|
*/
|
|
44
46
|
export function getViewableExtensions(): Promise<string[]> {
|
|
45
47
|
return syscall("editor.getViewableExtensions");
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Resolves the editor capability for a batch of documents without reading
|
|
52
|
+
* their bodies.
|
|
53
|
+
*/
|
|
54
|
+
export function getDocumentCapabilities(
|
|
55
|
+
documents: DocumentCapabilityDescriptor[],
|
|
56
|
+
): Promise<DocumentCapability[]> {
|
|
57
|
+
return syscall("editor.getDocumentCapabilities", documents);
|
|
58
|
+
}
|
|
59
|
+
|
|
48
60
|
export function getCurrentEditor(): Promise<string> {
|
|
49
61
|
return syscall("editor.getCurrentEditor");
|
|
50
62
|
}
|
|
@@ -187,6 +199,10 @@ export function downloadFile(filename: string, dataUrl: string): Promise<void> {
|
|
|
187
199
|
return syscall("editor.downloadFile", filename, dataUrl);
|
|
188
200
|
}
|
|
189
201
|
|
|
202
|
+
export function downloadSpaceFile(name: string): Promise<void> {
|
|
203
|
+
return syscall("editor.downloadSpaceFile", name);
|
|
204
|
+
}
|
|
205
|
+
|
|
190
206
|
export function uploadFile(
|
|
191
207
|
accept?: string,
|
|
192
208
|
capture?: string,
|
|
@@ -5,6 +5,14 @@ import { syscall } from "../syscall.ts";
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Whether client Sync is enabled. False when the service worker is off
|
|
10
|
+
* (`SB_DISABLE_SERVICE_WORKER` / `bootConfig.disableServiceWorker`).
|
|
11
|
+
*/
|
|
12
|
+
export function isEnabled(): Promise<boolean> {
|
|
13
|
+
return syscall("sync.isEnabled");
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
/**
|
|
9
17
|
* Checks if an initial sync has completed
|
|
10
18
|
*/
|
package/plug-api/types/client.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { ParseTree } from "@silverbulletmd/silverbullet/lib/tree";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
DocumentMeta,
|
|
4
|
+
PageMeta,
|
|
5
|
+
} from "@silverbulletmd/silverbullet/type/index";
|
|
3
6
|
|
|
4
7
|
export type FilterOption = {
|
|
5
8
|
name: string;
|
|
@@ -51,6 +54,17 @@ export type DocumentEditorContent = {
|
|
|
51
54
|
html: string;
|
|
52
55
|
};
|
|
53
56
|
|
|
57
|
+
export type DocumentCapabilityDescriptor = Pick<
|
|
58
|
+
DocumentMeta,
|
|
59
|
+
"name" | "extension" | "contentType" | "size"
|
|
60
|
+
>;
|
|
61
|
+
|
|
62
|
+
export type DocumentCapability = {
|
|
63
|
+
kind: "plug" | "text" | "media" | "external";
|
|
64
|
+
editor?: string;
|
|
65
|
+
reason?: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
54
68
|
export type LintDiagnostic = {
|
|
55
69
|
from: number;
|
|
56
70
|
to: number;
|
|
@@ -95,6 +109,7 @@ export type AppEvent =
|
|
|
95
109
|
| "editor:unfold"
|
|
96
110
|
| "plugs:loaded"
|
|
97
111
|
| "cron:secondPassed"
|
|
112
|
+
// Deprecated: dock a view at "page-top"/"page-bottom" with view.define instead.
|
|
98
113
|
| "hooks:renderTopWidgets"
|
|
99
114
|
| "hooks:renderBottomWidgets";
|
|
100
115
|
|
|
@@ -77,6 +77,13 @@ export type CommandDef = {
|
|
|
77
77
|
hide?: boolean;
|
|
78
78
|
requireMode?: "rw" | "ro";
|
|
79
79
|
requireEditor?: "any" | "page" | "notpage" | string;
|
|
80
|
+
/**
|
|
81
|
+
* When true, the command is omitted from the palette, menus, and
|
|
82
|
+
* `buildAllCommands()` while `bootConfig.disableServiceWorker` is set
|
|
83
|
+
* (`SB_DISABLE_SERVICE_WORKER`). Same space-wide drop as `requireMode: rw`
|
|
84
|
+
* in a read-only space.
|
|
85
|
+
*/
|
|
86
|
+
requireServiceWorker?: boolean;
|
|
80
87
|
|
|
81
88
|
// When true, this binding is NOT registered while vim mode is active,
|
|
82
89
|
// so the key falls through to vim.
|
|
@@ -245,12 +245,34 @@ export function computeTreeDisplay(
|
|
|
245
245
|
state: { expanded: Set<string>; expandAll: boolean },
|
|
246
246
|
scores?: Map<string, number>,
|
|
247
247
|
): TreeDisplay {
|
|
248
|
+
return expandTreeDisplay(
|
|
249
|
+
buildDisplayTree(rows, separator, foldersFirst, scores),
|
|
250
|
+
state,
|
|
251
|
+
scores !== undefined,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The tree half of `computeTreeDisplay`: kept apart so expanding a folder
|
|
256
|
+
* reuses the same nodes instead of rebuilding them all. */
|
|
257
|
+
export function buildDisplayTree(
|
|
258
|
+
rows: Row[],
|
|
259
|
+
separator: string,
|
|
260
|
+
foldersFirst: boolean,
|
|
261
|
+
scores?: Map<string, number>,
|
|
262
|
+
): TreeNode {
|
|
248
263
|
const built = buildTree(rows, separator, foldersFirst);
|
|
249
|
-
|
|
264
|
+
return scores ? pruneTree(built, scores) : built;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function expandTreeDisplay(
|
|
268
|
+
tree: TreeNode,
|
|
269
|
+
state: { expanded: Set<string>; expandAll: boolean },
|
|
270
|
+
filtering: boolean,
|
|
271
|
+
): TreeDisplay {
|
|
250
272
|
// Filtering force-expands the pruned tree either way, so the two readings
|
|
251
273
|
// converge there and neither set is consulted -- no double bookkeeping.
|
|
252
274
|
let effectiveExpanded: Set<string>;
|
|
253
|
-
if (
|
|
275
|
+
if (filtering) {
|
|
254
276
|
effectiveExpanded = allFolderPaths(tree);
|
|
255
277
|
} else if (state.expandAll) {
|
|
256
278
|
effectiveExpanded = allFolderPaths(tree);
|
|
@@ -12,6 +12,7 @@ export type Decoration = {
|
|
|
12
12
|
export type Row = {
|
|
13
13
|
obj: Record<string, any>;
|
|
14
14
|
primary: string;
|
|
15
|
+
cells?: unknown[];
|
|
15
16
|
/**
|
|
16
17
|
* What a *tree* row shows in place of its last path segment. For a view
|
|
17
18
|
* whose hierarchy is synthesized rather than read off a name, the path is
|