@silverbulletmd/silverbullet 2.10.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 +6 -6
- package/client/asset_bundle/bundle.ts +0 -1
- package/client/config.ts +343 -0
- package/client/data/kv_primitives.ts +2 -2
- package/client/markdown_parser/constants.ts +5 -1
- package/client/plugos/hooks/command.ts +8 -6
- package/client/plugos/hooks/event.ts +60 -24
- package/client/plugos/hooks/mq.ts +0 -3
- package/client/plugos/hooks/slash_command.ts +3 -20
- package/client/plugos/hooks/syscall.ts +0 -4
- package/client/plugos/manifest_cache.ts +2 -34
- package/client/plugos/plug.ts +0 -3
- package/client/plugos/plug_compile.ts +3 -13
- package/client/plugos/sandboxes/sandbox.ts +2 -2
- package/client/plugos/sandboxes/worker_sandbox.ts +19 -9
- package/client/plugos/syscalls/capture.ts +104 -0
- package/client/plugos/syscalls/editor.ts +197 -56
- package/client/plugos/syscalls/event.ts +12 -9
- 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 +138 -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 +70 -4
- package/client/plugos/syscalls/system.ts +39 -0
- package/client/plugos/system.ts +9 -7
- package/client/plugos/types.ts +6 -6
- package/client/plugos/worker_runtime.ts +0 -2
- package/client/space_lua/aggregates.ts +6 -22
- 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 +2 -7
- package/client/space_lua/parse.ts +0 -17
- package/client/space_lua/quarantine.ts +127 -0
- package/client/space_lua/query_collection.ts +18 -51
- 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 +64 -77
- 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 +18 -23
- package/client/space_lua/stdlib/space_lua.ts +1 -2
- package/client/space_lua/stdlib/string_pack.ts +4 -7
- 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 +3 -4
- package/package.json +52 -41
- 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 +1 -9
- 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 +5 -44
- package/plug-api/syscall.ts +1 -5
- package/plug-api/syscalls/config.ts +2 -2
- package/plug-api/syscalls/editor.ts +75 -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 +31 -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 +20 -4
- package/plug-api/types/datastore.ts +11 -2
- package/plug-api/types/index.ts +10 -0
- package/plug-api/types/manifest.ts +7 -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 +285 -0
- package/plug-api/ui/tree_types.ts +67 -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
|
@@ -69,16 +69,25 @@ import {
|
|
|
69
69
|
type Ref,
|
|
70
70
|
} from "@silverbulletmd/silverbullet/lib/ref";
|
|
71
71
|
import type {
|
|
72
|
+
DocumentCapability,
|
|
73
|
+
DocumentCapabilityDescriptor,
|
|
72
74
|
FilterOption,
|
|
73
75
|
NotificationAction,
|
|
74
76
|
NotificationType,
|
|
77
|
+
PanelMode,
|
|
75
78
|
UploadFile,
|
|
76
79
|
} from "@silverbulletmd/silverbullet/type/client";
|
|
77
80
|
import type { VimConfig } from "@silverbulletmd/silverbullet/type/config";
|
|
78
81
|
import type { PageMeta } from "@silverbulletmd/silverbullet/type/index";
|
|
79
82
|
import { updateBakedSections } from "../../baked_sections/bake.ts";
|
|
80
83
|
import type { Client } from "../../client.ts";
|
|
84
|
+
import { copyToClipboard } from "../../clipboard.ts";
|
|
81
85
|
import { refreshLintEffect } from "../../codemirror/lint.ts";
|
|
86
|
+
import { resolveDocumentCapability } from "../../document_editor_resolver.ts";
|
|
87
|
+
import { isMobileDevice, isNarrowScreen } from "../../lib/mobile.ts";
|
|
88
|
+
import { browserMediaCapabilities } from "../../media.ts";
|
|
89
|
+
import { hide as hideNavigatorSlot } from "../../navigator/navigator.ts";
|
|
90
|
+
import type { PanelSlot } from "../../types/ui.ts";
|
|
82
91
|
import { getVimModule } from "../../vim_loader.ts";
|
|
83
92
|
import type { SysCallMapping } from "../system.ts";
|
|
84
93
|
|
|
@@ -137,6 +146,68 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
137
146
|
"Returns page metadata ordered from most to least recently opened.",
|
|
138
147
|
returns: [{ type: "PageMeta[]", description: "Recently opened pages." }],
|
|
139
148
|
},
|
|
149
|
+
"editor.getLastOpenedMap": {
|
|
150
|
+
callback: (): Record<string, number> => {
|
|
151
|
+
const out: Record<string, number> = {};
|
|
152
|
+
for (const pageMeta of client.ui.viewState.allPages) {
|
|
153
|
+
if (pageMeta.lastOpened) out[pageMeta.name] = pageMeta.lastOpened;
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
},
|
|
157
|
+
description: `Returns a map of page name to the time it was last opened (epoch milliseconds), for the pages that have ever been opened on this client. This lives outside the object index.`,
|
|
158
|
+
returns: [
|
|
159
|
+
{
|
|
160
|
+
type: "Record<string, number>",
|
|
161
|
+
description: "Page name to last-opened timestamp.",
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
"editor.getViewableExtensions": {
|
|
166
|
+
callback: (): string[] => {
|
|
167
|
+
return [
|
|
168
|
+
...new Set(
|
|
169
|
+
Array.from(
|
|
170
|
+
client.clientSystem.documentEditorHook.documentEditors.values(),
|
|
171
|
+
).flatMap(({ extensions }) => extensions),
|
|
172
|
+
),
|
|
173
|
+
];
|
|
174
|
+
},
|
|
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.`,
|
|
176
|
+
returns: [
|
|
177
|
+
{
|
|
178
|
+
type: "string[]",
|
|
179
|
+
description: "Extensions with a registered document editor.",
|
|
180
|
+
},
|
|
181
|
+
],
|
|
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
|
+
},
|
|
140
211
|
"editor.getText": {
|
|
141
212
|
callback: () => {
|
|
142
213
|
return client.editorView.state.sliceDoc();
|
|
@@ -353,9 +424,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
353
424
|
},
|
|
354
425
|
],
|
|
355
426
|
},
|
|
356
|
-
// Re-evaluate every baked section on the current page and rewrite each body
|
|
357
|
-
// with its latest output (same as the "Baked Sections: Update" command),
|
|
358
|
-
// exposed for programmatic use.
|
|
359
427
|
"editor.updateBakedSections": {
|
|
360
428
|
callback: (): Promise<void> => {
|
|
361
429
|
return updateBakedSections(client);
|
|
@@ -430,6 +498,29 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
430
498
|
},
|
|
431
499
|
],
|
|
432
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
|
+
},
|
|
433
524
|
"editor.uploadFile": {
|
|
434
525
|
callback: (
|
|
435
526
|
_ctx,
|
|
@@ -447,6 +538,7 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
447
538
|
}
|
|
448
539
|
|
|
449
540
|
input.onchange = () => {
|
|
541
|
+
input.remove();
|
|
450
542
|
const file = input.files?.item(0);
|
|
451
543
|
if (!file) {
|
|
452
544
|
reject(new Error("No file found"));
|
|
@@ -473,11 +565,21 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
473
565
|
input.onabort = (e) => {
|
|
474
566
|
reject(e);
|
|
475
567
|
};
|
|
568
|
+
input.oncancel = () => {
|
|
569
|
+
input.remove();
|
|
570
|
+
};
|
|
476
571
|
|
|
477
572
|
input.style.display = "none";
|
|
478
573
|
document.body.appendChild(input);
|
|
479
|
-
|
|
480
|
-
|
|
574
|
+
if (isMobileDevice()) {
|
|
575
|
+
try {
|
|
576
|
+
input.showPicker();
|
|
577
|
+
} catch {
|
|
578
|
+
input.click();
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
input.click();
|
|
582
|
+
}
|
|
481
583
|
});
|
|
482
584
|
},
|
|
483
585
|
description:
|
|
@@ -594,13 +696,20 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
594
696
|
],
|
|
595
697
|
},
|
|
596
698
|
"editor.showPanel": {
|
|
597
|
-
callback: (
|
|
699
|
+
callback: async (
|
|
598
700
|
_ctx,
|
|
599
701
|
id: string,
|
|
600
|
-
mode:
|
|
702
|
+
mode: PanelMode,
|
|
601
703
|
html: HTMLElement | HTMLElement[] | string,
|
|
602
704
|
script: string,
|
|
603
705
|
) => {
|
|
706
|
+
// The other half of the shared-slot arbitration in editor_ui.tsx: a
|
|
707
|
+
// plug opening one of these slots closes the navigator's occupant.
|
|
708
|
+
if (id === "modal" || id === "bhs") {
|
|
709
|
+
await hideNavigatorSlot(id, undefined, {
|
|
710
|
+
restoreDisplaced: false,
|
|
711
|
+
});
|
|
712
|
+
}
|
|
604
713
|
client.ui.viewDispatch({
|
|
605
714
|
type: "show-panel",
|
|
606
715
|
id: id as any,
|
|
@@ -620,7 +729,7 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
620
729
|
},
|
|
621
730
|
{
|
|
622
731
|
name: "mode",
|
|
623
|
-
type: "number",
|
|
732
|
+
type: "number | string",
|
|
624
733
|
description: "The panel display mode or size.",
|
|
625
734
|
},
|
|
626
735
|
{
|
|
@@ -641,8 +750,33 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
641
750
|
},
|
|
642
751
|
description: "Returns focus to the main editor.",
|
|
643
752
|
},
|
|
753
|
+
"editor.getFocusedPanelSlot": {
|
|
754
|
+
callback: (): PanelSlot | undefined => {
|
|
755
|
+
const active = document.activeElement;
|
|
756
|
+
if (!(active instanceof HTMLIFrameElement)) return undefined;
|
|
757
|
+
const slot = active.dataset.slot;
|
|
758
|
+
return slot === "lhs" ||
|
|
759
|
+
slot === "rhs" ||
|
|
760
|
+
slot === "bhs" ||
|
|
761
|
+
slot === "modal"
|
|
762
|
+
? slot
|
|
763
|
+
: undefined;
|
|
764
|
+
},
|
|
765
|
+
description:
|
|
766
|
+
"Returns the slot of the panel (keyed or legacy) whose iframe currently holds focus, or undefined if none does.",
|
|
767
|
+
returns: [
|
|
768
|
+
{
|
|
769
|
+
type: '"lhs" | "rhs" | "bhs" | "modal" | undefined',
|
|
770
|
+
description:
|
|
771
|
+
"The focused panel's slot, or undefined if no panel iframe has focus.",
|
|
772
|
+
},
|
|
773
|
+
],
|
|
774
|
+
},
|
|
644
775
|
"editor.hidePanel": {
|
|
645
|
-
callback: (_ctx, id: string) => {
|
|
776
|
+
callback: async (_ctx, id: string) => {
|
|
777
|
+
// The navigator owns this slot whenever it is the thing showing
|
|
778
|
+
// there, so "hide the panel at this location" has to reach it too.
|
|
779
|
+
await hideNavigatorSlot(id);
|
|
646
780
|
client.ui.viewDispatch({
|
|
647
781
|
type: "hide-panel",
|
|
648
782
|
id: id as any,
|
|
@@ -664,14 +798,19 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
664
798
|
"editor.showProgress": {
|
|
665
799
|
callback: (
|
|
666
800
|
_ctx,
|
|
801
|
+
progressType: "sync" | "index",
|
|
667
802
|
progressPercentage?: number,
|
|
668
|
-
progressType?: "sync" | "index",
|
|
669
803
|
) => {
|
|
670
|
-
client.ui.showProgress(
|
|
804
|
+
client.ui.showProgress(progressType, progressPercentage);
|
|
671
805
|
},
|
|
672
806
|
description:
|
|
673
807
|
"Shows, updates, or hides a sync or indexing progress indicator.",
|
|
674
808
|
parameters: [
|
|
809
|
+
{
|
|
810
|
+
name: "progressType",
|
|
811
|
+
type: "sync | index",
|
|
812
|
+
description: "The operation represented by the indicator.",
|
|
813
|
+
},
|
|
675
814
|
{
|
|
676
815
|
name: "progressPercentage",
|
|
677
816
|
type: "number",
|
|
@@ -679,12 +818,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
679
818
|
"Completion percentage, or undefined to hide the indicator.",
|
|
680
819
|
optional: true,
|
|
681
820
|
},
|
|
682
|
-
{
|
|
683
|
-
name: "progressType",
|
|
684
|
-
type: "sync | index",
|
|
685
|
-
description: "The operation represented by the indicator.",
|
|
686
|
-
optional: true,
|
|
687
|
-
},
|
|
688
821
|
],
|
|
689
822
|
},
|
|
690
823
|
"editor.insertAtPos": {
|
|
@@ -828,11 +961,8 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
828
961
|
},
|
|
829
962
|
"editor.moveCursorToLine": {
|
|
830
963
|
callback: (_ctx, line: number, column = 1, center = false) => {
|
|
831
|
-
// CodeMirror already keeps information about lines
|
|
832
964
|
const cmLine = client.editorView.state.doc.line(line);
|
|
833
|
-
// How much to move inside the line, column number starts from 1
|
|
834
965
|
const offset = Math.max(0, Math.min(cmLine.length, column - 1));
|
|
835
|
-
// Just reuse the implementation above
|
|
836
966
|
const moveCursor = syscalls["editor.moveCursor"];
|
|
837
967
|
const moveCursorCallback =
|
|
838
968
|
typeof moveCursor === "function" ? moveCursor : moveCursor.callback;
|
|
@@ -1068,44 +1198,30 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1068
1198
|
throw new Error("Vim module not loaded.");
|
|
1069
1199
|
}
|
|
1070
1200
|
const { Vim } = vimMod;
|
|
1071
|
-
// Override the default "o" binding to be more intelligent and follow the markdown editor's behavior
|
|
1072
1201
|
Vim.mapCommand("o", "action", "newline-continue-markup", {}, {});
|
|
1073
1202
|
Vim.mapCommand("O", "action", "back-newline-continue-markup", {}, {});
|
|
1074
1203
|
Vim.unmap("<C-q>", undefined as any);
|
|
1075
1204
|
Vim.defineAction("newline-continue-markup", (cm) => {
|
|
1076
|
-
// Append at end of line
|
|
1077
1205
|
Vim.handleKey(cm, "A", "+input");
|
|
1078
|
-
// Insert newline continuing markup where appropriate
|
|
1079
1206
|
insertNewlineContinueMarkup(client.editorView) ||
|
|
1080
1207
|
insertNewlineAndIndent(client.editorView);
|
|
1081
1208
|
});
|
|
1082
1209
|
Vim.defineAction("back-newline-continue-markup", (cm) => {
|
|
1083
|
-
// Determine current line
|
|
1084
1210
|
const pos = client.editorView.state.selection.main.from;
|
|
1085
1211
|
const line = client.editorView.state.doc.lineAt(pos).number;
|
|
1086
1212
|
if (line === 1) {
|
|
1087
|
-
// We're on the top line
|
|
1088
|
-
// Go to 0:0
|
|
1089
1213
|
Vim.handleKey(cm, "0", "+input");
|
|
1090
|
-
// Insert a newline
|
|
1091
1214
|
insertNewline(client.editorView);
|
|
1092
|
-
// Go up to the new line
|
|
1093
1215
|
Vim.handleKey(cm, "k", "+input");
|
|
1094
|
-
// Into insert mode
|
|
1095
1216
|
Vim.handleKey(cm, "i", "+input");
|
|
1096
1217
|
} else {
|
|
1097
|
-
// We're elsewhere in the document
|
|
1098
|
-
// Go up
|
|
1099
1218
|
Vim.handleKey(cm, "k", "+input");
|
|
1100
|
-
// Append mode at the end of the line
|
|
1101
1219
|
Vim.handleKey(cm, "A", "+input");
|
|
1102
|
-
// Insert a newline using the continue markup thing
|
|
1103
1220
|
insertNewlineContinueMarkup(client.editorView) ||
|
|
1104
1221
|
insertNewlineAndIndent(client.editorView);
|
|
1105
1222
|
}
|
|
1106
1223
|
});
|
|
1107
1224
|
|
|
1108
|
-
// Load the config if any
|
|
1109
1225
|
const config = client.config.get<VimConfig>("vim", {});
|
|
1110
1226
|
if (config) {
|
|
1111
1227
|
config.unmap?.forEach((binding) => {
|
|
@@ -1143,9 +1259,9 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1143
1259
|
},
|
|
1144
1260
|
"editor.openPageNavigator": {
|
|
1145
1261
|
callback: (_ctx, mode: "page" | "meta" | "document" | "all" = "page") => {
|
|
1146
|
-
client.startPageNavigate(mode);
|
|
1262
|
+
void client.startPageNavigate(mode);
|
|
1147
1263
|
},
|
|
1148
|
-
description:
|
|
1264
|
+
description: `Opens the page picker in the requested browsing mode. Each mode maps to a segment of the \`std.pages\` navigator view.`,
|
|
1149
1265
|
parameters: [
|
|
1150
1266
|
{
|
|
1151
1267
|
name: "mode",
|
|
@@ -1161,6 +1277,30 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1161
1277
|
},
|
|
1162
1278
|
description: "Opens the command palette.",
|
|
1163
1279
|
},
|
|
1280
|
+
"editor.openNavigator": {
|
|
1281
|
+
callback: (
|
|
1282
|
+
_ctx,
|
|
1283
|
+
name: string,
|
|
1284
|
+
opts?: {
|
|
1285
|
+
segment?: string;
|
|
1286
|
+
phrase?: string;
|
|
1287
|
+
dropdown?: unknown;
|
|
1288
|
+
focus?: boolean;
|
|
1289
|
+
},
|
|
1290
|
+
) => client.openNavigatorView(name, opts),
|
|
1291
|
+
description: `Opens a navigator view, returning whether it opened. False means the view isn't there to open -- typically because it's defined in Space Lua that hasn't been indexed yet -- so a caller can fall back to something else.`,
|
|
1292
|
+
parameters: [
|
|
1293
|
+
{ name: "name", type: "string", description: "The view's name." },
|
|
1294
|
+
{
|
|
1295
|
+
name: "opts",
|
|
1296
|
+
type: "table",
|
|
1297
|
+
description:
|
|
1298
|
+
"Optional `segment` (segment label), `phrase`, `dropdown` (dropdown value to select), and `focus` (`false` opens without taking focus).",
|
|
1299
|
+
optional: true,
|
|
1300
|
+
},
|
|
1301
|
+
],
|
|
1302
|
+
returns: [{ type: "boolean", description: "Whether a view opened." }],
|
|
1303
|
+
},
|
|
1164
1304
|
"editor.deleteLine": {
|
|
1165
1305
|
callback: () => {
|
|
1166
1306
|
deleteLine(client.editorView);
|
|
@@ -1222,7 +1362,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1222
1362
|
},
|
|
1223
1363
|
description: "Moves the current line or selected lines downward.",
|
|
1224
1364
|
},
|
|
1225
|
-
// Folding
|
|
1226
1365
|
"editor.fold": {
|
|
1227
1366
|
callback: () => {
|
|
1228
1367
|
foldCode(client.editorView);
|
|
@@ -1265,7 +1404,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1265
1404
|
},
|
|
1266
1405
|
description: "Redoes the most recently undone editor change.",
|
|
1267
1406
|
},
|
|
1268
|
-
// Cursor motion syscalls
|
|
1269
1407
|
"editor.cursorCharLeft": {
|
|
1270
1408
|
callback: () => cursorCharLeft(client.editorView),
|
|
1271
1409
|
description:
|
|
@@ -1310,7 +1448,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1310
1448
|
callback: () => cursorDocEnd(client.editorView),
|
|
1311
1449
|
description: "Moves the cursor to the end of the document.",
|
|
1312
1450
|
},
|
|
1313
|
-
// Cursor motions that also navigate the completion popup if it's open
|
|
1314
1451
|
"editor.cursorLineUp": {
|
|
1315
1452
|
callback: () => {
|
|
1316
1453
|
const view = client.editorView;
|
|
@@ -1347,7 +1484,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1347
1484
|
description:
|
|
1348
1485
|
"Moves completion selection down one page when open, otherwise moves the cursor down one viewport page.",
|
|
1349
1486
|
},
|
|
1350
|
-
// Selection-extending motions
|
|
1351
1487
|
"editor.selectCharLeft": {
|
|
1352
1488
|
callback: () => selectCharLeft(client.editorView),
|
|
1353
1489
|
description:
|
|
@@ -1411,7 +1547,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1411
1547
|
callback: () => selectPageDown(client.editorView),
|
|
1412
1548
|
description: "Extends the selection downward by one viewport page.",
|
|
1413
1549
|
},
|
|
1414
|
-
// Delete
|
|
1415
1550
|
"editor.deleteCharBackward": {
|
|
1416
1551
|
callback: () => deleteCharBackward(client.editorView),
|
|
1417
1552
|
description: "Deletes the selection or the character before the cursor.",
|
|
@@ -1444,7 +1579,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1444
1579
|
callback: () => transposeChars(client.editorView),
|
|
1445
1580
|
description: "Transposes the characters around the cursor.",
|
|
1446
1581
|
},
|
|
1447
|
-
// Enter: accept completion if popup is open, else newline-and-indent
|
|
1448
1582
|
"editor.insertNewline": {
|
|
1449
1583
|
callback: () => {
|
|
1450
1584
|
const view = client.editorView;
|
|
@@ -1454,7 +1588,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1454
1588
|
description:
|
|
1455
1589
|
"Accepts the active completion, or inserts a newline with appropriate indentation.",
|
|
1456
1590
|
},
|
|
1457
|
-
// Completion popup control
|
|
1458
1591
|
"editor.acceptCompletion": {
|
|
1459
1592
|
callback: () => acceptCompletion(client.editorView),
|
|
1460
1593
|
description:
|
|
@@ -1483,20 +1616,14 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1483
1616
|
"editor.copyToClipboard": {
|
|
1484
1617
|
callback: async (_ctx, data: string | Blob) => {
|
|
1485
1618
|
try {
|
|
1486
|
-
|
|
1487
|
-
await navigator.clipboard.writeText(data);
|
|
1488
|
-
} else {
|
|
1489
|
-
await navigator.clipboard.write([
|
|
1490
|
-
new ClipboardItem({ [data.type]: data }),
|
|
1491
|
-
]);
|
|
1492
|
-
}
|
|
1619
|
+
await copyToClipboard(data);
|
|
1493
1620
|
} catch (e) {
|
|
1494
1621
|
console.error(e);
|
|
1495
1622
|
client.ui.flashNotification(`Could not copy to clipboard: ${e}`);
|
|
1496
1623
|
}
|
|
1497
1624
|
},
|
|
1498
1625
|
description:
|
|
1499
|
-
"Copies text or binary Blob data to the system clipboard.
|
|
1626
|
+
"Copies text or binary Blob data to the system clipboard. Binary clipboard access requires HTTPS or localhost; text copying also works over HTTP when supported by the browser.",
|
|
1500
1627
|
parameters: [
|
|
1501
1628
|
{
|
|
1502
1629
|
name: "data",
|
|
@@ -1510,7 +1637,7 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1510
1637
|
callback: (_ctx, type: string, data: any) => {
|
|
1511
1638
|
if (!client.contentManager.isDocumentEditor()) return;
|
|
1512
1639
|
|
|
1513
|
-
client.contentManager.documentEditor.sendPublicMessage({
|
|
1640
|
+
client.contentManager.documentEditor.sendPublicMessage?.({
|
|
1514
1641
|
type,
|
|
1515
1642
|
data,
|
|
1516
1643
|
});
|
|
@@ -1529,10 +1656,7 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1529
1656
|
},
|
|
1530
1657
|
|
|
1531
1658
|
"editor.isMobile": {
|
|
1532
|
-
callback: () =>
|
|
1533
|
-
const mouseDetected = globalThis.matchMedia("(pointer:fine)").matches;
|
|
1534
|
-
return !mouseDetected;
|
|
1535
|
-
},
|
|
1659
|
+
callback: () => isMobileDevice(),
|
|
1536
1660
|
description:
|
|
1537
1661
|
"Checks whether the current device lacks a fine pointer and should be treated as mobile.",
|
|
1538
1662
|
returns: [
|
|
@@ -1543,6 +1667,23 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1543
1667
|
},
|
|
1544
1668
|
],
|
|
1545
1669
|
},
|
|
1670
|
+
|
|
1671
|
+
// Deliberately separate from `editor.isMobile`: that one answers "what
|
|
1672
|
+
// kind of pointer is this", which is the right question for touch
|
|
1673
|
+
// affordances and the wrong one for layout. A narrow window on a desktop
|
|
1674
|
+
// gets the narrow layout with a mouse attached; a tablet in landscape gets
|
|
1675
|
+
// the wide one without.
|
|
1676
|
+
"editor.isNarrowScreen": {
|
|
1677
|
+
callback: () => isNarrowScreen(),
|
|
1678
|
+
description:
|
|
1679
|
+
"Checks whether the client is currently laid out for a narrow screen, i.e. below the breakpoint where sidebar panels become full-width drawers.",
|
|
1680
|
+
returns: [
|
|
1681
|
+
{
|
|
1682
|
+
type: "boolean",
|
|
1683
|
+
description: "Whether the narrow-screen layout is in effect.",
|
|
1684
|
+
},
|
|
1685
|
+
],
|
|
1686
|
+
},
|
|
1546
1687
|
};
|
|
1547
1688
|
|
|
1548
1689
|
return syscalls;
|
|
@@ -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,15 +43,13 @@ export function eventSyscalls(
|
|
|
38
43
|
*/
|
|
39
44
|
"event.listen": {
|
|
40
45
|
callback: (_ctx, def: EventSubscription) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
49
|
-
);
|
|
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);
|
|
50
53
|
},
|
|
51
54
|
description: "Registers a Space Lua listener on the event bus.",
|
|
52
55
|
parameters: [
|
|
@@ -22,7 +22,6 @@ export function sandboxFetchSyscalls(client: Client): SysCallMapping {
|
|
|
22
22
|
options: ProxyFetchRequest = {},
|
|
23
23
|
): Promise<ProxyFetchResponse> => {
|
|
24
24
|
console.warn("Deprecated: use net.proxyFetch() instead");
|
|
25
|
-
// JSONify any non-serializable body
|
|
26
25
|
if (
|
|
27
26
|
options?.body &&
|
|
28
27
|
typeof options.body !== "string" &&
|
|
@@ -44,7 +43,6 @@ export function sandboxFetchSyscalls(client: Client): SysCallMapping {
|
|
|
44
43
|
buildProxyUrl(client, url),
|
|
45
44
|
fetchOptions,
|
|
46
45
|
);
|
|
47
|
-
// Do sensible things with the body based on the content type
|
|
48
46
|
// Read as ArrayBuffer first to safely handle empty responses (e.g.
|
|
49
47
|
// PUT/DELETE returning 204 with Content-Type: application/json).
|
|
50
48
|
// resp.arrayBuffer() never throws on an empty body, whereas
|
|
@@ -100,7 +98,6 @@ export function sandboxFetchSyscalls(client: Client): SysCallMapping {
|
|
|
100
98
|
url: string,
|
|
101
99
|
options?: ProxyFetchRequest64,
|
|
102
100
|
): Promise<ProxyFetchResponse64> => {
|
|
103
|
-
// console.log("Got sandbox fetch ", url, op);
|
|
104
101
|
const fetchOptions = options
|
|
105
102
|
? {
|
|
106
103
|
method: options.method,
|
|
@@ -147,7 +144,6 @@ export function sandboxFetchSyscalls(client: Client): SysCallMapping {
|
|
|
147
144
|
|
|
148
145
|
function buildProxyUrl(client: Client, url: string) {
|
|
149
146
|
url = url.replace(/^https?:\/\//, "");
|
|
150
|
-
// Strip off the /.fs and replace with /.proxy
|
|
151
147
|
return (
|
|
152
148
|
client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length) +
|
|
153
149
|
"/.proxy/" +
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { resolveFeatherIcons } from "../../lib/feather_icons.ts";
|
|
2
|
+
import type { SysCallMapping } from "../system.ts";
|
|
3
|
+
|
|
4
|
+
export function iconSyscalls(): SysCallMapping {
|
|
5
|
+
return {
|
|
6
|
+
"icon.feather": {
|
|
7
|
+
callback: (_ctx, name: string): string | null => {
|
|
8
|
+
if (!name) return null;
|
|
9
|
+
return resolveFeatherIcons([name])[name] ?? null;
|
|
10
|
+
},
|
|
11
|
+
description:
|
|
12
|
+
"Renders a Feather icon to SVG markup by name, or nil when the name isn't a Feather icon. The markup carries no color of its own (`currentColor`) and a default 24x24 size, both of which CSS at the injection site can override.",
|
|
13
|
+
parameters: [
|
|
14
|
+
{
|
|
15
|
+
name: "name",
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "A Feather icon name, e.g. `trash-2`.",
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
returns: [
|
|
21
|
+
{
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "The icon's SVG markup, or nil when unknown.",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
examples: [{ code: 'local lock = icon.feather("lock")' }],
|
|
27
|
+
},
|
|
28
|
+
"icon.resolveFeather": {
|
|
29
|
+
callback: (_ctx, names: string[]): Record<string, string> => {
|
|
30
|
+
return resolveFeatherIcons(names ?? []);
|
|
31
|
+
},
|
|
32
|
+
description:
|
|
33
|
+
"Renders a batch of Feather icons to SVG markup in one round trip, so panels can show icons without bundling the icon set themselves. Unknown names are omitted from the result. The markup carries no color of its own (`currentColor`) and a default 24x24 size, both of which CSS at the injection site can override.",
|
|
34
|
+
parameters: [
|
|
35
|
+
{
|
|
36
|
+
name: "names",
|
|
37
|
+
type: "table",
|
|
38
|
+
description: "Feather icon names, e.g. `trash-2`.",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
returns: [
|
|
42
|
+
{ type: "table", description: "Map of icon name to SVG markup." },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|