@silverbulletmd/silverbullet 2.5.3 → 2.7.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.
Files changed (96) hide show
  1. package/README.md +4 -6
  2. package/client/asset_bundle/bundle.ts +3 -9
  3. package/client/data/datastore.ts +4 -5
  4. package/client/markdown_parser/constants.ts +4 -2
  5. package/client/plugos/hooks/code_widget.ts +3 -5
  6. package/client/plugos/hooks/command.ts +54 -24
  7. package/client/plugos/hooks/document_editor.ts +10 -12
  8. package/client/plugos/hooks/event.ts +33 -36
  9. package/client/plugos/hooks/mq.ts +17 -17
  10. package/client/plugos/hooks/plug_namespace.ts +3 -5
  11. package/client/plugos/hooks/slash_command.ts +15 -28
  12. package/client/plugos/hooks/syscall.ts +3 -3
  13. package/client/plugos/manifest_cache.ts +22 -15
  14. package/client/plugos/plug.ts +7 -7
  15. package/client/plugos/plug_compile.ts +119 -67
  16. package/client/plugos/protocol.ts +28 -28
  17. package/client/plugos/proxy_fetch.ts +7 -6
  18. package/client/plugos/sandboxes/worker_sandbox.ts +26 -15
  19. package/client/plugos/syscalls/asset.ts +1 -3
  20. package/client/plugos/syscalls/code_widget.ts +1 -3
  21. package/client/plugos/syscalls/config.ts +10 -7
  22. package/client/plugos/syscalls/datastore.ts +1 -1
  23. package/client/plugos/syscalls/editor.ts +199 -60
  24. package/client/plugos/syscalls/event.ts +9 -12
  25. package/client/plugos/syscalls/fetch.ts +30 -22
  26. package/client/plugos/syscalls/index.ts +11 -2
  27. package/client/plugos/syscalls/jsonschema.ts +54 -32
  28. package/client/plugos/syscalls/language.ts +9 -5
  29. package/client/plugos/syscalls/markdown.ts +29 -7
  30. package/client/plugos/syscalls/mq.ts +3 -11
  31. package/client/plugos/syscalls/service_registry.ts +1 -4
  32. package/client/plugos/syscalls/shell.ts +2 -5
  33. package/client/plugos/syscalls/sync.ts +69 -60
  34. package/client/plugos/syscalls/system.ts +15 -3
  35. package/client/plugos/system.ts +18 -20
  36. package/client/plugos/types.ts +25 -0
  37. package/client/plugos/util.ts +20 -0
  38. package/client/plugos/worker_runtime.ts +4 -3
  39. package/client/space_lua/aggregates.ts +633 -60
  40. package/client/space_lua/ast.ts +21 -9
  41. package/client/space_lua/ast_narrow.ts +4 -2
  42. package/client/space_lua/eval.ts +842 -536
  43. package/client/space_lua/labels.ts +6 -11
  44. package/client/space_lua/numeric.ts +5 -8
  45. package/client/space_lua/parse.ts +290 -169
  46. package/client/space_lua/query_collection.ts +213 -149
  47. package/client/space_lua/render_lua_markdown.ts +369 -0
  48. package/client/space_lua/rp.ts +5 -4
  49. package/client/space_lua/runtime.ts +245 -142
  50. package/client/space_lua/sliq_null.ts +6 -0
  51. package/client/space_lua/stdlib/format.ts +34 -20
  52. package/client/space_lua/stdlib/js.ts +3 -7
  53. package/client/space_lua/stdlib/load.ts +1 -3
  54. package/client/space_lua/stdlib/math.ts +15 -14
  55. package/client/space_lua/stdlib/net.ts +25 -15
  56. package/client/space_lua/stdlib/os.ts +76 -85
  57. package/client/space_lua/stdlib/pattern.ts +28 -35
  58. package/client/space_lua/stdlib/prng.ts +15 -12
  59. package/client/space_lua/stdlib/space_lua.ts +16 -17
  60. package/client/space_lua/stdlib/string.ts +7 -17
  61. package/client/space_lua/stdlib/string_pack.ts +23 -19
  62. package/client/space_lua/stdlib/table.ts +5 -9
  63. package/client/space_lua/stdlib.ts +20 -30
  64. package/client/space_lua/tonumber.ts +79 -40
  65. package/client/space_lua/util.ts +14 -10
  66. package/dist/plug-compile.js +87 -42
  67. package/package.json +24 -22
  68. package/plug-api/constants.ts +20 -0
  69. package/plug-api/lib/async.ts +19 -6
  70. package/plug-api/lib/crypto.ts +5 -6
  71. package/plug-api/lib/dates.ts +15 -7
  72. package/plug-api/lib/json.ts +10 -4
  73. package/plug-api/lib/ref.ts +27 -20
  74. package/plug-api/lib/resolve.ts +7 -11
  75. package/plug-api/lib/shortcut.ts +16 -0
  76. package/plug-api/lib/tags.ts +13 -4
  77. package/plug-api/lib/transclusion.ts +6 -17
  78. package/plug-api/lib/tree.ts +146 -43
  79. package/plug-api/lib/yaml.ts +25 -15
  80. package/plug-api/syscalls/asset.ts +1 -1
  81. package/plug-api/syscalls/config.ts +38 -4
  82. package/plug-api/syscalls/editor.ts +169 -14
  83. package/plug-api/syscalls/index.ts +18 -0
  84. package/plug-api/syscalls/jsonschema.ts +1 -3
  85. package/plug-api/syscalls/lua.ts +3 -9
  86. package/plug-api/syscalls/mq.ts +1 -4
  87. package/plug-api/syscalls/shell.ts +4 -1
  88. package/plug-api/syscalls/space.ts +3 -10
  89. package/plug-api/syscalls/system.ts +16 -4
  90. package/plug-api/syscalls/yaml.ts +2 -6
  91. package/plug-api/types/client.ts +16 -1
  92. package/plug-api/types/event.ts +6 -4
  93. package/plug-api/types/manifest.ts +71 -9
  94. package/plugs/builtin_plugs.ts +3 -3
  95. package/client/plugos/sandboxes/web_worker_sandbox.ts +0 -17
  96. package/dist/worker_runtime_bundle.js +0 -233
@@ -8,3 +8,23 @@ export const wrongSpacePathError: Error = new Error(
8
8
  );
9
9
  export const pingTimeout: number = 2000;
10
10
  export const pingInterval: number = 5000;
11
+
12
+ /**
13
+ * Determines if an error thrown by fetch() is a network-level error
14
+ * (as opposed to a server-side or application error).
15
+ *
16
+ * Browser-specific error messages when there is no internet connection:
17
+ * - Firefox: "NetworkError when attempting to fetch resource"
18
+ * - Safari (service worker): "FetchEvent.respondWith received an error: TypeError: Load failed"
19
+ * - Safari (no service worker): "Load failed"
20
+ * - Chrome: "Failed to fetch"
21
+ */
22
+ export function isNetworkError(e: any): boolean {
23
+ const msg = (e?.message || "").toLowerCase();
24
+ return (
25
+ msg.includes("fetch") ||
26
+ msg.includes("load failed") ||
27
+ msg.includes("networkerror") ||
28
+ msg.includes("unavailable")
29
+ );
30
+ }
@@ -1,6 +1,9 @@
1
- export function throttle(func: () => void, limit: number): () => void {
1
+ export function throttle(
2
+ func: () => void,
3
+ limit: number,
4
+ ): (() => void) & { flush(): void } {
2
5
  let timer: any = null;
3
- return () => {
6
+ const throttled = () => {
4
7
  if (!timer) {
5
8
  timer = setTimeout(() => {
6
9
  func();
@@ -8,6 +11,15 @@ export function throttle(func: () => void, limit: number): () => void {
8
11
  }, limit);
9
12
  }
10
13
  };
14
+ // Immediately execute any pending call and cancel the timer
15
+ throttled.flush = () => {
16
+ if (timer) {
17
+ clearTimeout(timer);
18
+ timer = null;
19
+ func();
20
+ }
21
+ };
22
+ return throttled;
11
23
  }
12
24
 
13
25
  export function throttleImmediately(
@@ -38,7 +50,7 @@ export function timeout(ms: number): Promise<never> {
38
50
  return new Promise((_resolve, reject) =>
39
51
  setTimeout(() => {
40
52
  reject(new Error("timeout"));
41
- }, ms)
53
+ }, ms),
42
54
  );
43
55
  }
44
56
 
@@ -58,7 +70,7 @@ export class PromiseQueue {
58
70
  return new Promise((resolve, reject) => {
59
71
  this.queue.push({ fn, resolve, reject });
60
72
  if (!this.processing) {
61
- this.process();
73
+ void this.process();
62
74
  }
63
75
  });
64
76
  }
@@ -79,7 +91,7 @@ export class PromiseQueue {
79
91
  reject(error);
80
92
  }
81
93
 
82
- this.process(); // Continue processing the next promise in the queue
94
+ void this.process(); // Continue processing the next promise in the queue
83
95
  }
84
96
  }
85
97
 
@@ -105,7 +117,8 @@ export async function batchRequests<I, O>(
105
117
  const batchResults = await Promise.all(batches.map(fn));
106
118
  // Flatten the results
107
119
  for (const batchResult of batchResults) {
108
- if (Array.isArray(batchResult)) { // If fn returns an array, collect them
120
+ if (Array.isArray(batchResult)) {
121
+ // If fn returns an array, collect them
109
122
  results.push(...batchResult);
110
123
  }
111
124
  }
@@ -42,9 +42,8 @@ export async function hashSHA256(
42
42
  ): Promise<string> {
43
43
  // Transform the string into an ArrayBuffer
44
44
  const encoder = new TextEncoder();
45
- const data: Uint8Array = typeof message === "string"
46
- ? encoder.encode(message)
47
- : message;
45
+ const data: Uint8Array =
46
+ typeof message === "string" ? encoder.encode(message) : message;
48
47
 
49
48
  // Generate the hash
50
49
  const hashBuffer = await globalThis.crypto.subtle.digest(
@@ -53,9 +52,9 @@ export async function hashSHA256(
53
52
  );
54
53
 
55
54
  // Transform the hash into a hex string
56
- return Array.from(new Uint8Array(hashBuffer)).map((b) =>
57
- b.toString(16).padStart(2, "0")
58
- ).join("");
55
+ return Array.from(new Uint8Array(hashBuffer))
56
+ .map((b) => b.toString(16).padStart(2, "0"))
57
+ .join("");
59
58
  }
60
59
 
61
60
  /**
@@ -3,11 +3,19 @@ export function niceDate(d: Date): string {
3
3
  }
4
4
 
5
5
  export function localDateString(d: Date): string {
6
- return d.getFullYear() +
7
- "-" + String(d.getMonth() + 1).padStart(2, "0") +
8
- "-" + String(d.getDate()).padStart(2, "0") +
9
- "T" + String(d.getHours()).padStart(2, "0") +
10
- ":" + String(d.getMinutes()).padStart(2, "0") +
11
- ":" + String(d.getSeconds()).padStart(2, "0") +
12
- "." + String(d.getMilliseconds()).padStart(3, "0");
6
+ return (
7
+ d.getFullYear() +
8
+ "-" +
9
+ String(d.getMonth() + 1).padStart(2, "0") +
10
+ "-" +
11
+ String(d.getDate()).padStart(2, "0") +
12
+ "T" +
13
+ String(d.getHours()).padStart(2, "0") +
14
+ ":" +
15
+ String(d.getMinutes()).padStart(2, "0") +
16
+ ":" +
17
+ String(d.getSeconds()).padStart(2, "0") +
18
+ "." +
19
+ String(d.getMilliseconds()).padStart(3, "0")
20
+ );
13
21
  }
@@ -52,11 +52,17 @@ export function deepEqual(a: any, b: any): boolean {
52
52
  export function cleanStringDate(d: Date): string {
53
53
  // If no significant time, return a date string only
54
54
  if (
55
- d.getUTCHours() === 0 && d.getUTCMinutes() === 0 && d.getUTCSeconds() === 0
55
+ d.getUTCHours() === 0 &&
56
+ d.getUTCMinutes() === 0 &&
57
+ d.getUTCSeconds() === 0
56
58
  ) {
57
- return d.getUTCFullYear() + "-" +
58
- String(d.getUTCMonth() + 1).padStart(2, "0") + "-" +
59
- String(d.getUTCDate()).padStart(2, "0");
59
+ return (
60
+ d.getUTCFullYear() +
61
+ "-" +
62
+ String(d.getUTCMonth() + 1).padStart(2, "0") +
63
+ "-" +
64
+ String(d.getUTCDate()).padStart(2, "0")
65
+ );
60
66
  } else {
61
67
  return d.toISOString();
62
68
  }
@@ -23,7 +23,8 @@ export type Ref = {
23
23
  details?:
24
24
  | { type: "position"; pos: number }
25
25
  | { type: "linecolumn"; line: number; column: number }
26
- | { type: "header"; header: string };
26
+ | { type: "header"; header: string }
27
+ | { type: "anchor"; name: string };
27
28
  };
28
29
 
29
30
  /**
@@ -77,8 +78,9 @@ export function isValidName(name: string): boolean {
77
78
  // If the name, parses as a link and doesn't provide any other info we can be
78
79
  // sure it was only parsed as a path and that the path then conforms to all
79
80
  // the requirements
80
- return !!ref && !ref.details && !ref.meta && name !== "" &&
81
- encodeRef(ref) === name;
81
+ return (
82
+ !!ref && !ref.details && !ref.meta && name !== "" && encodeRef(ref) === name
83
+ );
82
84
  }
83
85
 
84
86
  /**
@@ -95,7 +97,7 @@ export function isValidPath(path: string): path is Path {
95
97
  * TO THE INNER WORKINGS OF SILVERBULLET AND CHANGES COULD INTRODUCE MAJOR BUGS
96
98
  */
97
99
  const refRegex =
98
- /^(?<meta>\^)?(?<path>(?!.*\.[a-zA-Z0-9]+\.md$)(?!\/?(\.|\^))(?!.*(?:\/|^)\.{1,2}(?:\/|$)|.*\/{2})(?!.*(?:\]\]|\[\[))[^@#|<>]*)(@(?<pos>\d+)|@[Ll](?<line>\d+)(?:[Cc](?<col>\d+))?|#\s*(?<header>.*))?$/;
100
+ /^(?<meta>\^)?(?<path>(?!.*\.[a-zA-Z0-9]+\.md$)(?!\/?(\.|\^))(?!.*(?:\/|^)\.{1,2}(?:\/|$)|.*\/{2})(?!.*(?:\]\]|\[\[))[^@#|<>$]*)(@(?<pos>\d+)|@[Ll](?<line>\d+)(?:[Cc](?<col>\d+))?|#\s*(?<header>.*)|\$(?<anchor>[A-Za-z_][A-Za-z0-9_/:-]*))?$/;
99
101
 
100
102
  /**
101
103
  * Parses a reference string into a ref object.
@@ -131,6 +133,8 @@ export function parseToRef(stringRef: string): Ref | null {
131
133
  type: "header",
132
134
  header: groups.header,
133
135
  };
136
+ } else if (groups.anchor !== undefined) {
137
+ ref.details = { type: "anchor", name: groups.anchor };
134
138
  }
135
139
 
136
140
  return ref;
@@ -157,6 +161,8 @@ export function encodeRef(ref: Ref): string {
157
161
  stringRef += `@${ref.details.pos}`;
158
162
  } else if (ref.details?.type === "header") {
159
163
  stringRef += `#${ref.details.header}`;
164
+ } else if (ref.details?.type === "anchor") {
165
+ stringRef += `$${ref.details.name}`;
160
166
  }
161
167
 
162
168
  return stringRef;
@@ -190,6 +196,8 @@ export function getOffsetFromRef(
190
196
  case "header": {
191
197
  return getOffsetFromHeader(parseTree, ref.details.header);
192
198
  }
199
+ case "anchor":
200
+ return -1;
193
201
  }
194
202
  }
195
203
 
@@ -206,23 +214,22 @@ export function getOffsetFromHeader(
206
214
  parseTree: ParseTree,
207
215
  header: string,
208
216
  ): number {
209
- const node = findNodeMatching(
210
- parseTree,
211
- (subTree) => {
212
- if (!subTree.type || !subTree.type.startsWith("ATXHeading")) {
213
- return false;
214
- }
215
-
216
- const mark = findNodeOfType(subTree, "HeaderMark");
217
- if (!mark || mark.from === undefined || mark.to === undefined) {
218
- return false;
219
- }
220
-
221
- return renderToText(subTree)
217
+ const node = findNodeMatching(parseTree, (subTree) => {
218
+ if (!subTree.type || !subTree.type.startsWith("ATXHeading")) {
219
+ return false;
220
+ }
221
+
222
+ const mark = findNodeOfType(subTree, "HeaderMark");
223
+ if (!mark || mark.from === undefined || mark.to === undefined) {
224
+ return false;
225
+ }
226
+
227
+ return (
228
+ renderToText(subTree)
222
229
  .slice(mark.to - mark.from)
223
- .trimStart() === header.trim();
224
- },
225
- );
230
+ .trimStart() === header.trim()
231
+ );
232
+ });
226
233
 
227
234
  if (!node) {
228
235
  return -1;
@@ -4,9 +4,11 @@ import type { Path } from "@silverbulletmd/silverbullet/lib/ref";
4
4
  * Determines wether a url points into the world wide web or to the local SB instance
5
5
  */
6
6
  export function isLocalURL(url: string): boolean {
7
- return !url.includes("://") &&
7
+ return (
8
+ !url.includes("://") &&
8
9
  !url.startsWith("mailto:") &&
9
- !url.startsWith("tel:");
10
+ !url.startsWith("tel:")
11
+ );
10
12
  }
11
13
 
12
14
  /**
@@ -22,10 +24,7 @@ export function fileName(name: string | Path): string | Path {
22
24
  return name.split("/").pop()!;
23
25
  }
24
26
 
25
- const builtinPrefixes = [
26
- "tag:",
27
- "search:",
28
- ];
27
+ const builtinPrefixes = ["tag:", "search:"];
29
28
 
30
29
  /**
31
30
  * Builtin pages are pages which SB should automatically consider as existing
@@ -51,11 +50,8 @@ export function resolveMarkdownLink(
51
50
  if (relative.startsWith("/")) {
52
51
  return relative.slice(1);
53
52
  } else {
54
- const splitAbsolute = absolute
55
- .split("/")
56
- .slice(0, -1);
57
- const splitRelative = relative
58
- .split("/");
53
+ const splitAbsolute = absolute.split("/").slice(0, -1);
54
+ const splitRelative = relative.split("/");
59
55
 
60
56
  while (splitRelative && splitRelative[0] === "..") {
61
57
  splitAbsolute.pop();
@@ -0,0 +1,16 @@
1
+ export const isMacLike = typeof navigator !== "undefined" &&
2
+ /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
3
+
4
+ export function prettifyShortcut(shortcut: string): string {
5
+ if (!isMacLike) return shortcut;
6
+ const pretty = shortcut
7
+ .replace(/Mod-/g, "⌘")
8
+ .replace(/Cmd-/g, "⌘")
9
+ .replace(/Ctrl-/g, "⌃")
10
+ .replace(/Alt-/g, "⌥")
11
+ .replace(/Shift-/g, "⇧");
12
+ return pretty.replace(
13
+ /([⌘⌃⌥⇧])([a-z])$/,
14
+ (_, mod, key) => mod + key.toUpperCase(),
15
+ );
16
+ }
@@ -1,15 +1,24 @@
1
+ /** Check if a tag marks a page as a meta page (exact "meta" or "meta/..." subtag) */
2
+ export function isMetaTag(tag: string): boolean {
3
+ return tag === "meta" || tag.startsWith("meta/");
4
+ }
5
+
1
6
  /** Extract the name from hashtag text, removing # prefix and <angle brackets> if necessary */
2
7
  export function extractHashtag(text: string): string {
3
- if (text[0] !== "#") { // you shouldn't call this function at all
8
+ if (text[0] !== "#") {
9
+ // you shouldn't call this function at all
4
10
  console.error("extractHashtag called on already clean string", text);
5
11
  return text;
6
12
  } else if (text[1] === "<") {
7
- if (text.slice(-1) !== ">") { // this is malformed: #<name but maybe we're trying to autocomplete
13
+ if (text.slice(-1) !== ">") {
14
+ // this is malformed: #<name but maybe we're trying to autocomplete
8
15
  return text.slice(2);
9
- } else { // this is correct #<name>
16
+ } else {
17
+ // this is correct #<name>
10
18
  return text.slice(2, -1);
11
19
  }
12
- } else { // this is just #name
20
+ } else {
21
+ // this is just #name
13
22
  return text.slice(1);
14
23
  }
15
24
  }
@@ -1,7 +1,3 @@
1
- import {
2
- isLocalURL,
3
- resolveMarkdownLink,
4
- } from "@silverbulletmd/silverbullet/lib/resolve";
5
1
  import {
6
2
  mdLinkRegex,
7
3
  wikiLinkRegex,
@@ -34,9 +30,10 @@ export type ContentDimensions = {
34
30
  * Parse an alias, possibly containing dimensions into an object
35
31
  * @example "alias", "alias|100", "alias|100x200", "100", "100x200"
36
32
  */
37
- export function parseDimensionFromAlias(
38
- text: string,
39
- ): { alias: string; dimension?: ContentDimensions } {
33
+ export function parseDimensionFromAlias(text: string): {
34
+ alias: string;
35
+ dimension?: ContentDimensions;
36
+ } {
40
37
  let alias: string;
41
38
  let dim: ContentDimensions | undefined;
42
39
  if (text.includes("|")) {
@@ -71,10 +68,8 @@ export function parseDimensionFromAlias(
71
68
  * Parses a transclusion of the type `![[]]` or `![]()`
72
69
  * @param text
73
70
  */
74
- export function parseTransclusion(
75
- text: string,
76
- ): Transclusion | null {
77
- let url, alias ;
71
+ export function parseTransclusion(text: string): Transclusion | null {
72
+ let url, alias;
78
73
  let linktype: LinkType = "markdownlink";
79
74
  // TODO: Take in the tree and use tree nodes to get url and alias (Applies to all regex uses)
80
75
  mdLinkRegex.lastIndex = 0;
@@ -83,12 +78,6 @@ export function parseTransclusion(
83
78
  if ((match = mdLinkRegex.exec(text)) && match.groups) {
84
79
  ({ url, title: alias } = match.groups);
85
80
 
86
- if (isLocalURL(url)) {
87
- url = resolveMarkdownLink(
88
- client.currentName(),
89
- decodeURI(url),
90
- );
91
- }
92
81
  linktype = "markdownlink";
93
82
  } else if ((match = wikiLinkRegex.exec(text)) && match.groups) {
94
83
  ({ stringRef: url, alias } = match.groups);