@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
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
LuaCollectionQuery,
|
|
4
|
-
LuaQueryCollection,
|
|
5
|
-
} from "../../space_lua/query_collection.ts";
|
|
1
|
+
import type { ObjectValue } from "@silverbulletmd/silverbullet/type/index";
|
|
2
|
+
import type { Client } from "../../client.ts";
|
|
6
3
|
|
|
7
4
|
import {
|
|
8
5
|
type ObjectIndex,
|
|
9
6
|
ObjectValidationError,
|
|
10
7
|
} from "../../data/object_index.ts";
|
|
11
|
-
import type {
|
|
12
|
-
|
|
8
|
+
import type {
|
|
9
|
+
LuaCollectionQuery,
|
|
10
|
+
LuaQueryCollection,
|
|
11
|
+
} from "../../space_lua/query_collection.ts";
|
|
13
12
|
import type { LuaTable } from "../../space_lua/runtime.ts";
|
|
13
|
+
import type { SysCallMapping } from "../system.ts";
|
|
14
14
|
import { describeSchemas, tagSchema } from "./schema_introspection.ts";
|
|
15
15
|
|
|
16
16
|
export function indexSyscalls(
|
|
@@ -18,7 +18,6 @@ export function indexSyscalls(
|
|
|
18
18
|
client: Client,
|
|
19
19
|
): SysCallMapping {
|
|
20
20
|
return {
|
|
21
|
-
// Query collection API
|
|
22
21
|
"index.tag": {
|
|
23
22
|
callback: (_ctx, tagName: string): LuaQueryCollection => {
|
|
24
23
|
return objectIndex.objectsWithTag(tagName);
|
|
@@ -63,10 +62,12 @@ export function indexSyscalls(
|
|
|
63
62
|
description: "Returns all indexed links as a query collection.",
|
|
64
63
|
},
|
|
65
64
|
"index.relations": {
|
|
66
|
-
callback: (): LuaQueryCollection => {
|
|
67
|
-
return objectIndex.
|
|
65
|
+
callback: (_ctx, kind?: string): LuaQueryCollection => {
|
|
66
|
+
return objectIndex.relations(kind);
|
|
68
67
|
},
|
|
69
|
-
description:
|
|
68
|
+
description:
|
|
69
|
+
"Returns all indexed relations, optionally filtered by kind, as a query collection.",
|
|
70
|
+
signatures: ["index.relations(kind?)"],
|
|
70
71
|
},
|
|
71
72
|
"index.contentPages": {
|
|
72
73
|
callback: (_ctx, tagName?: string): LuaQueryCollection => {
|
|
@@ -128,7 +129,6 @@ export function indexSyscalls(
|
|
|
128
129
|
},
|
|
129
130
|
description: "Returns all indexed tag objects as a query collection.",
|
|
130
131
|
},
|
|
131
|
-
// Schema introspection: indexed object-type / tag schemas
|
|
132
132
|
"index.describeSchema": {
|
|
133
133
|
callback: (): Record<string, unknown> => {
|
|
134
134
|
return describeSchemas(client.config.get(["tags"], {}));
|
|
@@ -144,6 +144,13 @@ export function indexSyscalls(
|
|
|
144
144
|
"Returns the raw JSON Schema for a tag, or nil when none is declared.",
|
|
145
145
|
signatures: ["index.tagSchema(tagName)"],
|
|
146
146
|
},
|
|
147
|
+
"index.isAvailable": {
|
|
148
|
+
callback: (): Promise<boolean> => objectIndex.isIndexAvailable(),
|
|
149
|
+
description: `Whether a full indexing pass has ever completed for this space. False on a fresh client, and for as long as the first index takes on a large one: every object query answers with whatever has been indexed so far, which is nothing to begin with. Code that must work in that window reads the space directly instead.`,
|
|
150
|
+
returns: [
|
|
151
|
+
{ type: "boolean", description: "Whether the index can be trusted." },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
147
154
|
"index.aspiringPages": {
|
|
148
155
|
callback: (): LuaQueryCollection => {
|
|
149
156
|
return objectIndex.aspiringPages();
|
|
@@ -151,7 +158,13 @@ export function indexSyscalls(
|
|
|
151
158
|
description:
|
|
152
159
|
"Returns linked but not yet created pages as a query collection.",
|
|
153
160
|
},
|
|
154
|
-
|
|
161
|
+
"index.ambiguousLinks": {
|
|
162
|
+
callback: (): LuaQueryCollection => {
|
|
163
|
+
return objectIndex.ambiguousLinks();
|
|
164
|
+
},
|
|
165
|
+
description:
|
|
166
|
+
"Returns links whose page name matches more than one page as a query collection.",
|
|
167
|
+
},
|
|
155
168
|
"index.aggregates": {
|
|
156
169
|
callback: (): LuaQueryCollection => {
|
|
157
170
|
return objectIndex.aggregates();
|
|
@@ -246,6 +259,13 @@ export function indexSyscalls(
|
|
|
246
259
|
signatures: ["index.queryLuaObjects(tag, query, scopedVariables?)"],
|
|
247
260
|
},
|
|
248
261
|
|
|
262
|
+
"index.clearFileIndex": {
|
|
263
|
+
callback: (_ctx, path: string): Promise<void> =>
|
|
264
|
+
objectIndex.clearFileIndex(path),
|
|
265
|
+
description:
|
|
266
|
+
"Drops every indexed object belonging to a file, so a re-index replaces them rather than merging on top.",
|
|
267
|
+
signatures: ["index.clearFileIndex(path)"],
|
|
268
|
+
},
|
|
249
269
|
"index.deleteObject": {
|
|
250
270
|
callback: (
|
|
251
271
|
_ctx,
|
|
@@ -2,7 +2,6 @@ import type { SysCallMapping } from "../system.ts";
|
|
|
2
2
|
import { type OutputUnit, Validator, format } from "@cfworker/json-schema";
|
|
3
3
|
import { stripFunctions } from "../util.ts";
|
|
4
4
|
|
|
5
|
-
// Register custom formats
|
|
6
5
|
format.email = (data: string) => data.includes("@");
|
|
7
6
|
format["page-ref"] = (data: string) =>
|
|
8
7
|
data.startsWith("[[") && data.endsWith("]]");
|
|
@@ -14,7 +14,6 @@ export function mqSyscalls(mq: DataStoreMQ): SysCallMapping {
|
|
|
14
14
|
"mq.subscribe": {
|
|
15
15
|
callback: (_ctx, def: MQListenerSpec) => {
|
|
16
16
|
def.autoAck = def.autoAck !== false;
|
|
17
|
-
// console.log("Registering Lua event listener: ", def.name);
|
|
18
17
|
client.config.insert(["mqSubscriptions", def.queue], def);
|
|
19
18
|
},
|
|
20
19
|
description: "Subscribes a Space Lua callback to a message queue.",
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineView,
|
|
3
|
+
focusPanel,
|
|
4
|
+
moveByRename,
|
|
5
|
+
openView,
|
|
6
|
+
pickView,
|
|
7
|
+
} from "../../navigator/navigator.ts";
|
|
8
|
+
import { newView } from "../../navigator/view_value.ts";
|
|
9
|
+
import type { SysCallMapping } from "../system.ts";
|
|
10
|
+
|
|
11
|
+
export function navigatorSyscalls(): SysCallMapping {
|
|
12
|
+
const viewSyscalls: SysCallMapping = {
|
|
13
|
+
"view.open": {
|
|
14
|
+
callback: (_ctx, name: string, opts?: any) => {
|
|
15
|
+
return openView(name, opts);
|
|
16
|
+
},
|
|
17
|
+
description: "Opens a navigator view by name.",
|
|
18
|
+
parameters: [
|
|
19
|
+
{ name: "name", type: "string", description: "View name." },
|
|
20
|
+
{
|
|
21
|
+
name: "opts",
|
|
22
|
+
type: "table",
|
|
23
|
+
description:
|
|
24
|
+
"Optional segment, phrase, dropdown, focus and quiet flags.",
|
|
25
|
+
optional: true,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
returns: [
|
|
29
|
+
{ type: "boolean", description: "Whether the view actually opened." },
|
|
30
|
+
],
|
|
31
|
+
examples: [{ code: 'view.open("std.spaceTree")' }],
|
|
32
|
+
},
|
|
33
|
+
"view.focus": {
|
|
34
|
+
callback: (_ctx, slot?: string) => focusPanel(slot),
|
|
35
|
+
description:
|
|
36
|
+
"Returns focus to an open navigator panel's input, keeping its current selection.",
|
|
37
|
+
parameters: [
|
|
38
|
+
{
|
|
39
|
+
name: "slot",
|
|
40
|
+
type: "string",
|
|
41
|
+
description:
|
|
42
|
+
"Which panel: modal, lhs, rhs or bhs. Defaults to any open one.",
|
|
43
|
+
optional: true,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
returns: [
|
|
47
|
+
{ type: "boolean", description: "Whether a panel was there to focus." },
|
|
48
|
+
],
|
|
49
|
+
examples: [{ code: 'view.focus("rhs")' }],
|
|
50
|
+
},
|
|
51
|
+
"view.moveByRename": {
|
|
52
|
+
callback: (_ctx, obj: any, newName: string) => {
|
|
53
|
+
return moveByRename(obj, newName);
|
|
54
|
+
},
|
|
55
|
+
description:
|
|
56
|
+
"Renames the page, document or folder an object stands for -- the default `onMove` for space-backed views.",
|
|
57
|
+
parameters: [
|
|
58
|
+
{ name: "obj", type: "table", description: "The row's object." },
|
|
59
|
+
{ name: "newName", type: "string", description: "New name." },
|
|
60
|
+
],
|
|
61
|
+
examples: [{ code: "onMove = view.moveByRename" }],
|
|
62
|
+
},
|
|
63
|
+
"lua:view.new": {
|
|
64
|
+
callback: (_ctx, spec: any) => newView(spec),
|
|
65
|
+
description:
|
|
66
|
+
"Creates a reusable navigator view value without registering or evaluating it.",
|
|
67
|
+
parameters: [
|
|
68
|
+
{
|
|
69
|
+
name: "spec",
|
|
70
|
+
type: "table",
|
|
71
|
+
description: "View content and behavior; see the Navigator API docs.",
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
returns: [{ type: "table", description: "The reusable view value." }],
|
|
75
|
+
examples: [
|
|
76
|
+
{
|
|
77
|
+
code: "local projects = view.new { source = function() return {} end }",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
"lua:view.define": {
|
|
82
|
+
callback: (_ctx, spec: any) => {
|
|
83
|
+
return defineView(spec);
|
|
84
|
+
},
|
|
85
|
+
description:
|
|
86
|
+
"Registers a navigator view, and optionally a command that opens it. Rejects a bad spec at definition time.",
|
|
87
|
+
parameters: [
|
|
88
|
+
{
|
|
89
|
+
name: "spec",
|
|
90
|
+
type: "table",
|
|
91
|
+
description: "View definition; see the Navigator API docs.",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
examples: [
|
|
95
|
+
{
|
|
96
|
+
code: 'view.define {\n name = "myView",\n source = function() return {} end,\n onSelect = function(obj) editor.navigate(obj.ref) end,\n}',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
"lua:view.pick": {
|
|
101
|
+
callback: (_ctx, spec: any) => {
|
|
102
|
+
return pickView(spec);
|
|
103
|
+
},
|
|
104
|
+
description:
|
|
105
|
+
"Opens a one-shot picker over the supplied source and returns the object picked (or nil).",
|
|
106
|
+
parameters: [
|
|
107
|
+
{
|
|
108
|
+
name: "spec",
|
|
109
|
+
type: "table",
|
|
110
|
+
description:
|
|
111
|
+
"Picker definition; a view.define spec minus its name and chrome.",
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
returns: [{ description: "The picked object, or nil." }],
|
|
115
|
+
examples: [
|
|
116
|
+
{
|
|
117
|
+
code: 'local picked = view.pick { source = function() return { { name = "One" } } end }',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
...viewSyscalls,
|
|
125
|
+
...aliasNamespace(viewSyscalls, "view", "navigator"),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function aliasNamespace(
|
|
130
|
+
mapping: SysCallMapping,
|
|
131
|
+
from: string,
|
|
132
|
+
to: string,
|
|
133
|
+
): SysCallMapping {
|
|
134
|
+
const aliased: SysCallMapping = {};
|
|
135
|
+
for (const [name, definition] of Object.entries(mapping)) {
|
|
136
|
+
const isLuaNative = name.startsWith("lua:");
|
|
137
|
+
const cleanName = isLuaNative ? name.slice("lua:".length) : name;
|
|
138
|
+
if (!cleanName.startsWith(`${from}.`)) continue;
|
|
139
|
+
const aliasedName = `${to}.${cleanName.slice(from.length + 1)}`;
|
|
140
|
+
aliased[isLuaNative ? `lua:${aliasedName}` : aliasedName] = definition;
|
|
141
|
+
}
|
|
142
|
+
return aliased;
|
|
143
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { SysCallMapping } from "../system.ts";
|
|
2
|
+
import { rank, type RankOptions } from "../../../plug-api/lib/fuzzy.ts";
|
|
3
|
+
|
|
4
|
+
export function searchSyscalls(): SysCallMapping {
|
|
5
|
+
return {
|
|
6
|
+
"search.rank": {
|
|
7
|
+
callback: (
|
|
8
|
+
_ctx,
|
|
9
|
+
objects: Record<string, any>[],
|
|
10
|
+
phrase: string,
|
|
11
|
+
options?: {
|
|
12
|
+
fields?: Record<
|
|
13
|
+
string,
|
|
14
|
+
number | { weight: number; segments?: boolean }
|
|
15
|
+
>;
|
|
16
|
+
},
|
|
17
|
+
) => rank(objects, phrase, options as RankOptions<any>),
|
|
18
|
+
description:
|
|
19
|
+
"Fuzzy-ranks objects against a phrase, best match first. The same ranker the navigator's own filtering uses.",
|
|
20
|
+
parameters: [
|
|
21
|
+
{ name: "objects", type: "table", description: "Objects to rank." },
|
|
22
|
+
{
|
|
23
|
+
name: "phrase",
|
|
24
|
+
type: "string",
|
|
25
|
+
description:
|
|
26
|
+
"Phrase to match against. An empty phrase returns every object, in the order given.",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "options",
|
|
30
|
+
type: "table",
|
|
31
|
+
description:
|
|
32
|
+
"Ranking options. `fields` maps a field name to its weight, or to `{ weight, segments = true }` to also score the parts of a `/`-separated value.",
|
|
33
|
+
optional: true,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
returns: [
|
|
37
|
+
{
|
|
38
|
+
type: "table",
|
|
39
|
+
description: "The matching objects, best match first.",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -1,15 +1,120 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
encodePageURI,
|
|
3
|
+
type Path,
|
|
4
|
+
parseToRef,
|
|
5
|
+
type Ref,
|
|
6
|
+
} from "@silverbulletmd/silverbullet/lib/ref";
|
|
7
|
+
import { fileName } from "@silverbulletmd/silverbullet/lib/resolve";
|
|
8
|
+
import {
|
|
9
|
+
BasenameIndex,
|
|
10
|
+
type PathLookup,
|
|
11
|
+
resolvePath,
|
|
12
|
+
} from "@silverbulletmd/silverbullet/lib/resolve_path";
|
|
5
13
|
import type {
|
|
6
14
|
DocumentMeta,
|
|
7
15
|
FileMeta,
|
|
8
16
|
PageMeta,
|
|
9
17
|
} from "@silverbulletmd/silverbullet/type/index";
|
|
18
|
+
import type {
|
|
19
|
+
FileRevisions,
|
|
20
|
+
GitSyncSnapshot,
|
|
21
|
+
GitConflicts,
|
|
22
|
+
GitConflictAction,
|
|
23
|
+
SpaceLog,
|
|
24
|
+
} from "@silverbulletmd/silverbullet/type/revisions";
|
|
25
|
+
import type { Client } from "../../client.ts";
|
|
26
|
+
import { fsEndpoint } from "../../spaces/constants.ts";
|
|
27
|
+
import type { SysCallMapping } from "../system.ts";
|
|
28
|
+
|
|
29
|
+
function revisionsUrl(client: Client, suffix: string): string {
|
|
30
|
+
return `${client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length)}/.revisions/${suffix}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function fetchRevisionsJson(
|
|
34
|
+
client: Client,
|
|
35
|
+
suffix: string,
|
|
36
|
+
body?: unknown,
|
|
37
|
+
): Promise<any> {
|
|
38
|
+
const resp = await client.httpSpacePrimitives.authenticatedFetch(
|
|
39
|
+
revisionsUrl(client, suffix),
|
|
40
|
+
{
|
|
41
|
+
method: body === undefined ? "GET" : "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
Accept: "application/json",
|
|
44
|
+
...(body === undefined ? {} : { "Content-Type": "application/json" }),
|
|
45
|
+
},
|
|
46
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
if (resp.status === 404) {
|
|
50
|
+
throw Object.assign(
|
|
51
|
+
new Error("Revisions are not available for this space"),
|
|
52
|
+
{ status: resp.status },
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (!resp.ok) {
|
|
56
|
+
throw Object.assign(new Error(`Revisions request failed: ${resp.status}`), {
|
|
57
|
+
status: resp.status,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return resp.json();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The index resolution syscalls answer from. The client's own index is only
|
|
65
|
+
* trustworthy once the file list has loaded; before that, ask the space
|
|
66
|
+
* directly rather than reporting everything as missing.
|
|
67
|
+
*/
|
|
68
|
+
async function resolutionIndex(client: Client): Promise<BasenameIndex> {
|
|
69
|
+
if (client.clientSystem.knownFilesLoaded) {
|
|
70
|
+
return client.clientSystem.allKnownFiles;
|
|
71
|
+
}
|
|
72
|
+
const listing = new BasenameIndex();
|
|
73
|
+
listing.rebuild(
|
|
74
|
+
(await client.space.spacePrimitives.fetchFileList()).map((f) => f.name),
|
|
75
|
+
);
|
|
76
|
+
return listing;
|
|
77
|
+
}
|
|
10
78
|
|
|
11
79
|
export function spaceReadSyscalls(client: Client): SysCallMapping {
|
|
12
80
|
return {
|
|
81
|
+
"space.getGitSyncStatus": {
|
|
82
|
+
callback: (): Promise<GitSyncSnapshot> =>
|
|
83
|
+
fetchRevisionsJson(client, "_sync"),
|
|
84
|
+
description: "Reads Git sync status and its last successful check.",
|
|
85
|
+
},
|
|
86
|
+
"space.getGitConflictVersion": {
|
|
87
|
+
callback: async (
|
|
88
|
+
_ctx,
|
|
89
|
+
id: string,
|
|
90
|
+
generation: string,
|
|
91
|
+
side: "local" | "remote",
|
|
92
|
+
): Promise<Uint8Array> => {
|
|
93
|
+
if (side !== "local" && side !== "remote")
|
|
94
|
+
throw new Error("Unknown conflict side");
|
|
95
|
+
const response = await client.httpSpacePrimitives.authenticatedFetch(
|
|
96
|
+
revisionsUrl(
|
|
97
|
+
client,
|
|
98
|
+
`_conflicts/${encodeURIComponent(id)}/${side}?generation=${encodeURIComponent(generation)}`,
|
|
99
|
+
),
|
|
100
|
+
{ method: "GET" },
|
|
101
|
+
);
|
|
102
|
+
if (!response.ok)
|
|
103
|
+
throw Object.assign(
|
|
104
|
+
new Error("Could not read this conflict version"),
|
|
105
|
+
{ status: response.status },
|
|
106
|
+
);
|
|
107
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
108
|
+
},
|
|
109
|
+
description: "Downloads one original side of an unresolved Git conflict.",
|
|
110
|
+
signatures: ["space.getGitConflictVersion(id, generation, side)"],
|
|
111
|
+
},
|
|
112
|
+
"space.getGitConflicts": {
|
|
113
|
+
callback: (): Promise<GitConflicts> =>
|
|
114
|
+
fetchRevisionsJson(client, "_conflicts"),
|
|
115
|
+
description:
|
|
116
|
+
"Lists unresolved Git conflicts with saved content versions.",
|
|
117
|
+
},
|
|
13
118
|
"space.listPages": {
|
|
14
119
|
callback: (): Promise<PageMeta[]> => client.space.fetchPageList(),
|
|
15
120
|
description: "Lists all pages in the space.",
|
|
@@ -46,7 +151,7 @@ export function spaceReadSyscalls(client: Client): SysCallMapping {
|
|
|
46
151
|
},
|
|
47
152
|
"space.pageExists": {
|
|
48
153
|
callback: (_ctx, name: string): boolean =>
|
|
49
|
-
|
|
154
|
+
resolvePath(`${name}.md`, "", client.clientSystem.allKnownFiles).exists,
|
|
50
155
|
description: "Checks whether a page exists in the space.",
|
|
51
156
|
signatures: ["space.pageExists(name)"],
|
|
52
157
|
},
|
|
@@ -98,7 +203,6 @@ export function spaceReadSyscalls(client: Client): SysCallMapping {
|
|
|
98
203
|
deprecated: "Use space.getDocumentMeta instead.",
|
|
99
204
|
signatures: ["space.getAttachmentMeta(name)"],
|
|
100
205
|
},
|
|
101
|
-
// FS
|
|
102
206
|
"space.listFiles": {
|
|
103
207
|
callback: (): Promise<FileMeta[]> =>
|
|
104
208
|
client.space.spacePrimitives.fetchFileList(),
|
|
@@ -125,6 +229,33 @@ export function spaceReadSyscalls(client: Client): SysCallMapping {
|
|
|
125
229
|
description: "Reads an arbitrary space file together with its metadata.",
|
|
126
230
|
signatures: ["space.readFileWithMeta(name)"],
|
|
127
231
|
},
|
|
232
|
+
"space.collidingBasenames": {
|
|
233
|
+
callback: async (): Promise<Record<string, Path[]>> =>
|
|
234
|
+
(await resolutionIndex(client)).collidingBuckets(),
|
|
235
|
+
description:
|
|
236
|
+
"Returns every basename carried by more than one file, with the files carrying it.",
|
|
237
|
+
signatures: ["space.collidingBasenames()"],
|
|
238
|
+
},
|
|
239
|
+
"space.lookupPaths": {
|
|
240
|
+
callback: async (
|
|
241
|
+
_ctx,
|
|
242
|
+
paths: string[],
|
|
243
|
+
): Promise<Record<string, PathLookup>> => {
|
|
244
|
+
const index = await resolutionIndex(client);
|
|
245
|
+
|
|
246
|
+
const result: Record<string, PathLookup> = {};
|
|
247
|
+
for (const path of paths) {
|
|
248
|
+
result[path] = {
|
|
249
|
+
exact: index.has(path),
|
|
250
|
+
candidates: index.candidates(fileName(path)),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
},
|
|
255
|
+
description:
|
|
256
|
+
"Looks up, for each path, whether it exists exactly and which files share its basename.",
|
|
257
|
+
signatures: ["space.lookupPaths(paths)"],
|
|
258
|
+
},
|
|
128
259
|
"space.fileExists": {
|
|
129
260
|
callback: async (_ctx, name: string): Promise<boolean> => {
|
|
130
261
|
// If a full sync has successfully completed (so we know what files exist)
|
|
@@ -137,7 +268,6 @@ export function spaceReadSyscalls(client: Client): SysCallMapping {
|
|
|
137
268
|
}
|
|
138
269
|
try {
|
|
139
270
|
await client.space.spacePrimitives.getFileMeta(name);
|
|
140
|
-
// If this returned the file exists
|
|
141
271
|
return true;
|
|
142
272
|
} catch {
|
|
143
273
|
// Assumption: any error means the file does not exist
|
|
@@ -147,11 +277,103 @@ export function spaceReadSyscalls(client: Client): SysCallMapping {
|
|
|
147
277
|
description: "Checks whether an arbitrary file exists in the space.",
|
|
148
278
|
signatures: ["space.fileExists(name)"],
|
|
149
279
|
},
|
|
280
|
+
"space.listRevisions": {
|
|
281
|
+
callback: (_ctx, path: string, before?: string): Promise<FileRevisions> =>
|
|
282
|
+
fetchRevisionsJson(
|
|
283
|
+
client,
|
|
284
|
+
`${encodePageURI(path)}${before ? `?before=${before}` : ""}`,
|
|
285
|
+
),
|
|
286
|
+
description: "Lists the revision history of a file.",
|
|
287
|
+
signatures: ["space.listRevisions(path, before?)"],
|
|
288
|
+
},
|
|
289
|
+
"space.getRevision": {
|
|
290
|
+
callback: async (
|
|
291
|
+
_ctx,
|
|
292
|
+
path: string,
|
|
293
|
+
rev: string,
|
|
294
|
+
parent?: boolean,
|
|
295
|
+
): Promise<string> => {
|
|
296
|
+
const resp = await client.httpSpacePrimitives.authenticatedFetch(
|
|
297
|
+
revisionsUrl(
|
|
298
|
+
client,
|
|
299
|
+
`${encodePageURI(path)}?rev=${rev}${parent ? "&parent=1" : ""}`,
|
|
300
|
+
),
|
|
301
|
+
{ method: "GET" },
|
|
302
|
+
);
|
|
303
|
+
if (!resp.ok) {
|
|
304
|
+
throw Object.assign(
|
|
305
|
+
new Error(`Could not load revision: ${resp.status}`),
|
|
306
|
+
{ status: resp.status },
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
return resp.text();
|
|
310
|
+
},
|
|
311
|
+
description:
|
|
312
|
+
"Reads the text of a file as it was at a given revision, or at that revision's parent.",
|
|
313
|
+
signatures: ["space.getRevision(path, rev, parent?)"],
|
|
314
|
+
},
|
|
315
|
+
"space.getRevisionDiff": {
|
|
316
|
+
callback: async (_ctx, path: string, rev?: string): Promise<string> => {
|
|
317
|
+
const resp = await client.httpSpacePrimitives.authenticatedFetch(
|
|
318
|
+
revisionsUrl(
|
|
319
|
+
client,
|
|
320
|
+
`${encodePageURI(path)}?${rev ? `rev=${rev}&` : ""}format=diff`,
|
|
321
|
+
),
|
|
322
|
+
{ method: "GET" },
|
|
323
|
+
);
|
|
324
|
+
if (!resp.ok) {
|
|
325
|
+
throw Object.assign(
|
|
326
|
+
new Error(`Could not load revision diff: ${resp.status}`),
|
|
327
|
+
{ status: resp.status },
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
return resp.text();
|
|
331
|
+
},
|
|
332
|
+
description:
|
|
333
|
+
"Reads a unified diff of a revision's own change (vs its parent), or of the uncommitted change when no revision is given.",
|
|
334
|
+
signatures: ["space.getRevisionDiff(path, rev?)"],
|
|
335
|
+
},
|
|
336
|
+
"space.getSpaceLog": {
|
|
337
|
+
callback: (_ctx, before?: string, q?: string): Promise<SpaceLog> => {
|
|
338
|
+
const params: string[] = [];
|
|
339
|
+
if (before) params.push(`before=${before}`);
|
|
340
|
+
if (q) params.push(`q=${encodeURIComponent(q)}`);
|
|
341
|
+
return fetchRevisionsJson(
|
|
342
|
+
client,
|
|
343
|
+
params.length ? `?${params.join("&")}` : "",
|
|
344
|
+
);
|
|
345
|
+
},
|
|
346
|
+
description: "Lists the space-wide commit log.",
|
|
347
|
+
signatures: ["space.getSpaceLog(before?, q?)"],
|
|
348
|
+
},
|
|
150
349
|
};
|
|
151
350
|
}
|
|
152
351
|
|
|
153
352
|
export function spaceWriteSyscalls(editor: Client): SysCallMapping {
|
|
154
353
|
return {
|
|
354
|
+
"space.syncGitNow": {
|
|
355
|
+
callback: () => fetchRevisionsJson(editor, "_sync", {}),
|
|
356
|
+
description: "Requests sync on the existing Git connection.",
|
|
357
|
+
},
|
|
358
|
+
"space.resolveGitConflict": {
|
|
359
|
+
callback: (
|
|
360
|
+
_ctx,
|
|
361
|
+
id: string,
|
|
362
|
+
generation: string,
|
|
363
|
+
contentRevision: string,
|
|
364
|
+
action: GitConflictAction,
|
|
365
|
+
): Promise<GitConflicts> =>
|
|
366
|
+
fetchRevisionsJson(editor, `_conflicts/${encodeURIComponent(id)}`, {
|
|
367
|
+
generation,
|
|
368
|
+
contentRevision,
|
|
369
|
+
action,
|
|
370
|
+
}),
|
|
371
|
+
description:
|
|
372
|
+
"Resolves a Git conflict if the merge and saved content still match.",
|
|
373
|
+
signatures: [
|
|
374
|
+
"space.resolveGitConflict(id, generation, contentRevision, action)",
|
|
375
|
+
],
|
|
376
|
+
},
|
|
155
377
|
"space.writePage": {
|
|
156
378
|
callback: (_ctx, name: string, text: string): Promise<PageMeta> =>
|
|
157
379
|
editor.space.writePage(name, text),
|
|
@@ -191,5 +413,26 @@ export function spaceWriteSyscalls(editor: Client): SysCallMapping {
|
|
|
191
413
|
description: "Deletes an arbitrary file from the space.",
|
|
192
414
|
signatures: ["space.deleteFile(name)"],
|
|
193
415
|
},
|
|
416
|
+
"space.createRevisionSnapshot": {
|
|
417
|
+
callback: async (): Promise<boolean> => {
|
|
418
|
+
const resp = await editor.httpSpacePrimitives.authenticatedFetch(
|
|
419
|
+
revisionsUrl(editor, ""),
|
|
420
|
+
{ method: "POST", headers: { Accept: "application/json" } },
|
|
421
|
+
);
|
|
422
|
+
if (resp.status === 404) {
|
|
423
|
+
throw new Error("Revisions are not available for this space");
|
|
424
|
+
}
|
|
425
|
+
if (resp.status === 409) {
|
|
426
|
+
throw new Error("Revisions are not managed for this space");
|
|
427
|
+
}
|
|
428
|
+
if (!resp.ok) {
|
|
429
|
+
throw new Error(`Snapshot request failed: ${resp.status}`);
|
|
430
|
+
}
|
|
431
|
+
return (await resp.json()).committed === true;
|
|
432
|
+
},
|
|
433
|
+
description:
|
|
434
|
+
"Commits everything outstanding as a revision now, rather than waiting for the automatic commit. False if there was nothing to commit.",
|
|
435
|
+
signatures: ["space.createRevisionSnapshot()"],
|
|
436
|
+
},
|
|
194
437
|
};
|
|
195
438
|
}
|