@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.
Files changed (100) hide show
  1. package/README.md +4 -4
  2. package/client/asset_bundle/bundle.ts +0 -1
  3. package/client/config.ts +343 -0
  4. package/client/markdown_parser/constants.ts +3 -1
  5. package/client/plugos/hooks/command.ts +0 -5
  6. package/client/plugos/hooks/event.ts +0 -6
  7. package/client/plugos/hooks/mq.ts +0 -3
  8. package/client/plugos/hooks/slash_command.ts +1 -7
  9. package/client/plugos/hooks/syscall.ts +0 -4
  10. package/client/plugos/manifest_cache.ts +0 -32
  11. package/client/plugos/plug.ts +0 -3
  12. package/client/plugos/plug_compile.ts +3 -13
  13. package/client/plugos/sandboxes/worker_sandbox.ts +19 -9
  14. package/client/plugos/syscalls/editor.ts +128 -53
  15. package/client/plugos/syscalls/event.ts +0 -2
  16. package/client/plugos/syscalls/fetch.ts +0 -4
  17. package/client/plugos/syscalls/icon.ts +46 -0
  18. package/client/plugos/syscalls/index.ts +33 -13
  19. package/client/plugos/syscalls/jsonschema.ts +0 -1
  20. package/client/plugos/syscalls/mq.ts +0 -1
  21. package/client/plugos/syscalls/navigator.ts +143 -0
  22. package/client/plugos/syscalls/search.ts +44 -0
  23. package/client/plugos/syscalls/shell.ts +0 -1
  24. package/client/plugos/syscalls/space.ts +250 -7
  25. package/client/plugos/syscalls/sync.ts +56 -4
  26. package/client/plugos/syscalls/system.ts +38 -0
  27. package/client/plugos/system.ts +7 -5
  28. package/client/plugos/worker_runtime.ts +0 -2
  29. package/client/space_lua/aggregates.ts +0 -16
  30. package/client/space_lua/ast.ts +0 -5
  31. package/client/space_lua/ast_narrow.ts +0 -5
  32. package/client/space_lua/budget.ts +113 -0
  33. package/client/space_lua/budget_ui.ts +49 -0
  34. package/client/space_lua/eval.ts +113 -88
  35. package/client/space_lua/labels.ts +0 -4
  36. package/client/space_lua/numeric.ts +0 -5
  37. package/client/space_lua/parse.ts +0 -17
  38. package/client/space_lua/quarantine.ts +127 -0
  39. package/client/space_lua/query_collection.ts +3 -47
  40. package/client/space_lua/query_env.ts +0 -2
  41. package/client/space_lua/render_lua_markdown.ts +2 -7
  42. package/client/space_lua/render_widget.ts +18 -1
  43. package/client/space_lua/runtime.ts +50 -70
  44. package/client/space_lua/stdlib/format.ts +0 -12
  45. package/client/space_lua/stdlib/js.ts +0 -2
  46. package/client/space_lua/stdlib/load.ts +0 -1
  47. package/client/space_lua/stdlib/math.ts +0 -7
  48. package/client/space_lua/stdlib/net.ts +0 -4
  49. package/client/space_lua/stdlib/os.ts +0 -10
  50. package/client/space_lua/stdlib/pattern.ts +10 -15
  51. package/client/space_lua/stdlib/space_lua.ts +1 -2
  52. package/client/space_lua/stdlib/string_pack.ts +0 -3
  53. package/client/space_lua/stdlib/table.ts +0 -5
  54. package/client/space_lua/stdlib.ts +18 -31
  55. package/client/space_lua/tonumber.ts +0 -9
  56. package/dist/plug-compile.js +2 -3
  57. package/package.json +13 -3
  58. package/plug-api/lib/async.ts +1 -6
  59. package/plug-api/lib/collation.ts +22 -0
  60. package/plug-api/lib/crypto.ts +16 -15
  61. package/plug-api/lib/dates.ts +33 -0
  62. package/plug-api/lib/fuzzy.ts +282 -0
  63. package/plug-api/lib/json.ts +0 -6
  64. package/plug-api/lib/limited_map.ts +0 -2
  65. package/plug-api/lib/link_write.ts +27 -0
  66. package/plug-api/lib/ref.ts +28 -5
  67. package/plug-api/lib/resolve.ts +0 -1
  68. package/plug-api/lib/resolve_path.ts +290 -0
  69. package/plug-api/lib/shortcut.ts +15 -0
  70. package/plug-api/lib/tags.ts +0 -3
  71. package/plug-api/lib/transclusion.ts +28 -1
  72. package/plug-api/lib/tree.ts +0 -5
  73. package/plug-api/lib/yaml.ts +3 -42
  74. package/plug-api/syscall.ts +1 -5
  75. package/plug-api/syscalls/config.ts +2 -2
  76. package/plug-api/syscalls/editor.ts +59 -213
  77. package/plug-api/syscalls/icon.ts +18 -0
  78. package/plug-api/syscalls/index.ts +22 -4
  79. package/plug-api/syscalls/search.ts +11 -0
  80. package/plug-api/syscalls/space.ts +122 -5
  81. package/plug-api/syscalls/sync.ts +23 -2
  82. package/plug-api/syscalls/system.ts +46 -0
  83. package/plug-api/syscalls.ts +2 -0
  84. package/plug-api/system_mock.ts +13 -0
  85. package/plug-api/types/client.ts +4 -3
  86. package/plug-api/types/datastore.ts +11 -2
  87. package/plug-api/types/index.ts +10 -0
  88. package/plug-api/types/profile.ts +13 -0
  89. package/plug-api/types/revisions.ts +67 -0
  90. package/plug-api/ui/description.ts +64 -0
  91. package/plug-api/ui/hover.ts +69 -0
  92. package/plug-api/ui/index.ts +58 -2
  93. package/plug-api/ui/scroll.ts +30 -0
  94. package/plug-api/ui/tree_model.ts +263 -0
  95. package/plug-api/ui/tree_types.ts +66 -0
  96. package/plug-api/ui/use_fit_collapse.ts +67 -0
  97. package/plugs/builtin_plugs.ts +0 -1
  98. package/plugs/index/types.ts +9 -0
  99. package/plug-api/lib/memory_cache.ts +0 -21
  100. /package/plug-api/{ui → lib}/panel_styles.ts +0 -0
@@ -147,7 +147,6 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
147
147
  const dow = () => wd(d, utc);
148
148
 
149
149
  return {
150
- // Date
151
150
  Y: () => yr(d, utc).toString(),
152
151
  y: () => pad2(yr(d, utc) % 100),
153
152
  C: () => pad2(Math.floor(yr(d, utc) / 100)),
@@ -156,14 +155,12 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
156
155
  e: () => da(d, utc).toString().padStart(2, " "),
157
156
  j: () => pad3(dayOfYear(d, utc)),
158
157
 
159
- // Time
160
158
  H: () => pad2(h()),
161
159
  I: () => pad2(h12()),
162
160
  M: () => pad2(mi(d, utc)),
163
161
  S: () => pad2(sc(d, utc)),
164
162
  p: () => (h() >= 12 ? "PM" : "AM"),
165
163
 
166
- // Weekday
167
164
  A: () =>
168
165
  d.toLocaleString("en-US", {
169
166
  weekday: "long",
@@ -177,7 +174,6 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
177
174
  w: () => dow().toString(),
178
175
  u: () => (dow() === 0 ? 7 : dow()).toString(),
179
176
 
180
- // Month name
181
177
  b: () =>
182
178
  d.toLocaleString("en-US", {
183
179
  month: "short",
@@ -194,14 +190,12 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
194
190
  ...(utc ? { timeZone: "UTC" } : {}),
195
191
  }),
196
192
 
197
- // Week number
198
193
  U: () => pad2(weekNumber(d, utc, 0, false)),
199
194
  W: () => pad2(weekNumber(d, utc, 1, false)),
200
195
  V: () => pad2(weekNumber(d, utc, 1, true)),
201
196
  G: () => isoWeekYear(d, utc).toString(),
202
197
  g: () => pad2(isoWeekYear(d, utc) % 100),
203
198
 
204
- // Composite specifiers
205
199
  c: () =>
206
200
  d.toLocaleString("en-US", {
207
201
  ...(utc ? { timeZone: "UTC" } : {}),
@@ -225,14 +219,11 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
225
219
  h() >= 12 ? "PM" : "AM"
226
220
  }`,
227
221
 
228
- // Epoch
229
222
  s: () => Math.floor(d.getTime() / 1000).toString(),
230
223
 
231
- // Whitespace
232
224
  n: () => "\n",
233
225
  t: () => "\t",
234
226
 
235
- // Timezone
236
227
  Z: () => {
237
228
  if (utc) return "UTC";
238
229
  const match = d.toTimeString().match(/\((.*)\)/);
@@ -246,7 +237,6 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
246
237
  return `${sign}${pad2(Math.floor(abs / 60))}${pad2(abs % 60)}`;
247
238
  },
248
239
 
249
- // Literal
250
240
  "%": () => "%",
251
241
  };
252
242
  }
@@ -1,5 +1,3 @@
1
- // Lua Optimized Pattern Matching Engine Implementation
2
-
3
1
  const CH_ESC = 37; // '%'
4
2
  const CH_OPEN = 40; // '('
5
3
  const CH_CLOSE = 41; // ')'
@@ -40,21 +38,21 @@ interface Capture {
40
38
  }
41
39
 
42
40
  export interface MatchState {
43
- src: string; // original source string (for substring extraction)
44
- s: Uint8Array; // source bytes
41
+ src: string;
42
+ s: Uint16Array;
45
43
  slen: number;
46
- p: Uint8Array; // pattern bytes
44
+ p: Uint16Array;
47
45
  plen: number;
48
46
  level: number;
49
47
  capture: Capture[]; // pre-allocated, length `MAX_CAPTURES`
50
48
  matchdepth: number;
51
49
  }
52
50
 
53
- function toBytes(s: string): Uint8Array {
51
+ function toCodeUnits(s: string): Uint16Array {
54
52
  const len = s.length;
55
- const arr = new Uint8Array(len);
53
+ const arr = new Uint16Array(len);
56
54
  for (let i = 0; i < len; i++) {
57
- arr[i] = s.charCodeAt(i) & 0xff;
55
+ arr[i] = s.charCodeAt(i);
58
56
  }
59
57
  return arr;
60
58
  }
@@ -108,7 +106,7 @@ function matchClass(c: number, cl: number): boolean {
108
106
  return cl >= 97 && cl <= 122 ? res : !res;
109
107
  }
110
108
 
111
- function classEnd(p: Uint8Array, plen: number, pi: number): number {
109
+ function classEnd(p: Uint16Array, plen: number, pi: number): number {
112
110
  const ch = p[pi];
113
111
  pi++;
114
112
  if (ch === CH_ESC) {
@@ -138,7 +136,7 @@ function classEnd(p: Uint8Array, plen: number, pi: number): number {
138
136
 
139
137
  function matchBracketClass(
140
138
  c: number,
141
- p: Uint8Array,
139
+ p: Uint16Array,
142
140
  pi: number,
143
141
  ec: number,
144
142
  ): boolean {
@@ -404,8 +402,8 @@ function match(ms: MatchState, si: number, pi: number): number {
404
402
  }
405
403
 
406
404
  function createMatchState(s: string, p: string): MatchState {
407
- const sb = toBytes(s);
408
- const pb = toBytes(p);
405
+ const sb = toCodeUnits(s);
406
+ const pb = toCodeUnits(p);
409
407
  const capture: Capture[] = new Array(MAX_CAPTURES);
410
408
  for (let i = 0; i < MAX_CAPTURES; i++) {
411
409
  capture[i] = { init: 0, len: 0 };
@@ -434,8 +432,6 @@ function noSpecials(p: string): boolean {
434
432
  return true;
435
433
  }
436
434
 
437
- // Public API
438
-
439
435
  export type CaptureResult = { s: string } | { position: number };
440
436
 
441
437
  interface RawCapture {
@@ -615,7 +611,6 @@ function expandReplacementString(
615
611
  throw new Error("invalid use of '%' in replacement string");
616
612
  }
617
613
  } else {
618
- // Collect consecutive literal characters
619
614
  let j = i + 1;
620
615
  while (j < repl.length && repl.charCodeAt(j) !== CH_ESC) j++;
621
616
  parts.push(repl.substring(i, j));
@@ -180,7 +180,6 @@ export async function interpolateLuaString(
180
180
 
181
181
  result += template.slice(currentIndex, startIndex);
182
182
 
183
- // Find matching closing brace by counting nesting
184
183
  let nestLevel = 1;
185
184
  let endIndex = startIndex + 2;
186
185
  while (nestLevel > 0 && endIndex < template.length) {
@@ -519,7 +518,7 @@ export const spaceluaApi = new LuaTable({
519
518
  */
520
519
  baseUrl: new LuaBuiltinFunction({
521
520
  callback: () => {
522
- //NOTE: Removing trailing slash to stay compatible with original code: `location.protocol + "//" + location.host;`
521
+ // The origin contract excludes a trailing slash.
523
522
  return document.baseURI.replace(/\/*$/, "");
524
523
  },
525
524
  description:
@@ -145,7 +145,6 @@ function getOption(
145
145
  }
146
146
  }
147
147
 
148
- // Compute alignment padding
149
148
  function getDetails(
150
149
  fmt: string,
151
150
  pos: number,
@@ -264,7 +263,6 @@ export const strPackFn = new LuaBuiltinFunction({
264
263
  let opt: ParsedOption;
265
264
  [opt, pos] = getDetails(fmt, pos, h, totalsize);
266
265
 
267
- // alignment padding
268
266
  if (opt.ntoalign > 0) {
269
267
  parts.push(new Uint8Array(opt.ntoalign));
270
268
  totalsize += opt.ntoalign;
@@ -349,7 +347,6 @@ export const strPackFn = new LuaBuiltinFunction({
349
347
  }
350
348
  }
351
349
 
352
- // Concatenate all parts into one binary string (latin-1 encoding)
353
350
  let total = 0;
354
351
  for (const p of parts) total += p.length;
355
352
  const out = new Uint8Array(total);
@@ -274,12 +274,10 @@ export const tableApi = new LuaTable({
274
274
  t: number,
275
275
  a2?: LuaTable | any[],
276
276
  ) => {
277
- // a2 defaults to a1
278
277
  if (a2 === undefined || a2 === null) {
279
278
  a2 = a1;
280
279
  }
281
280
 
282
- // Empty range: nothing to do, return destination
283
281
  if (e < f) {
284
282
  return a2;
285
283
  }
@@ -422,7 +420,6 @@ export const tableApi = new LuaTable({
422
420
  return false;
423
421
  }
424
422
  if (tbl instanceof LuaTable) {
425
- // Iterate over the table
426
423
  for (const key of tbl.keys()) {
427
424
  if (luaEquals(tbl.get(key), value)) {
428
425
  return true;
@@ -459,9 +456,7 @@ export const tableApi = new LuaTable({
459
456
  tbl: LuaTable | Record<string, any>,
460
457
  ...keys: LuaValue[]
461
458
  ) => {
462
- // Normalize arguments
463
459
  if (Array.isArray(keys[0])) {
464
- // First argument is key array, let's unpack
465
460
  keys = keys[0];
466
461
  } else if (keys[0] instanceof LuaTable) {
467
462
  keys = keys[0].toJSArray();
@@ -1,3 +1,4 @@
1
+ import { LuaBudgetStopped } from "./budget.ts";
1
2
  import {
2
3
  getMetatable,
3
4
  type ILuaFunction,
@@ -101,7 +102,6 @@ const ipairsFunction = new LuaBuiltinFunction({
101
102
 
102
103
  const pairsFunction = new LuaBuiltinFunction({
103
104
  callback: (sf, t: LuaTable | any[] | Record<string, any>) => {
104
- // Respect `__pairs` metamethod for Lua tables
105
105
  if (isLuaTable(t)) {
106
106
  const mt = (t as any).metatable as LuaTable | null | undefined;
107
107
  if (mt) {
@@ -295,27 +295,24 @@ async function pcallBoundary(
295
295
  const msg = errMsgOf(e);
296
296
  try {
297
297
  await luaCloseFromMark(sf, mark, msg);
298
- return { ok: false, message: msg };
299
298
  } catch (closeErr: any) {
300
- return { ok: false, message: errMsgOf(closeErr) };
299
+ if (!(e instanceof LuaBudgetStopped)) {
300
+ return { ok: false, message: errMsgOf(closeErr) };
301
+ }
302
+ }
303
+ // A user-initiated stop is not a recoverable Lua error.
304
+ if (e instanceof LuaBudgetStopped) {
305
+ throw e;
301
306
  }
307
+ return { ok: false, message: msg };
302
308
  }
303
309
  }
304
310
 
305
311
  const pcallFunction = new LuaBuiltinFunction({
306
312
  callback: async (sf, fn: ILuaFunction, ...args) => {
307
- // To-be-closed variables must be closed when unwinding to the
308
- // protected call boundary. Space Lua uses a per-thread close
309
- // stack, so we snapshot its length and close anything pushed
310
- // after that.
311
- //
312
- // The protected call boundary must be established *before*
313
- // evaluating the function and its arguments. Otherwise, any
314
- // `<close>` locals created while evaluating `pcall`'s arguments
315
- // will be wrongly treated as "inside" the protected call, and
316
- // `pcall` may end up closing them (or affecting close ordering).
317
- //
318
- // `threadState` is read-only on the stack frame; do not reassign!
313
+ // Snapshot the close stack before evaluating the function and arguments,
314
+ // so argument-owned <close> locals stay outside the protected boundary.
315
+ // Do not reassign the frame’s read-only threadState.
319
316
  const res = await pcallBoundary(sf, fn, args);
320
317
  if (res.ok) {
321
318
  return new LuaMultiRes([true, ...res.values]);
@@ -345,7 +342,6 @@ const xpcallFunction = new LuaBuiltinFunction({
345
342
  errorHandler: ILuaFunction,
346
343
  ...args
347
344
  ) => {
348
- // Same semantic as `pcall` (see comments there)
349
345
  const res = await pcallBoundary(sf, fn, args);
350
346
  if (res.ok) {
351
347
  return new LuaMultiRes([true, ...res.values]);
@@ -505,6 +501,12 @@ const dofileFunction = new LuaBuiltinFunction({
505
501
  const env = new LuaEnv(global);
506
502
  await evalStatement(parsedExpr, env, sf.withCtx(parsedExpr.ctx));
507
503
  } catch (e: any) {
504
+ // A user-initiated stop must keep its identity so an enclosing
505
+ // pcall/xpcall still recognizes and rethrows it, rather than being
506
+ // rewrapped into a recoverable LuaRuntimeError.
507
+ if (e instanceof LuaBudgetStopped) {
508
+ throw e;
509
+ }
508
510
  throw new LuaRuntimeError(
509
511
  `Error evaluating "${filename}": ${e.message}`,
510
512
  sf,
@@ -582,30 +584,24 @@ const nextFunction = new LuaBuiltinFunction({
582
584
  index: number | null = null,
583
585
  ) => {
584
586
  if (!table) {
585
- // When nil value
586
587
  return null;
587
588
  }
588
589
  const keys = luaKeys(table);
589
590
 
590
- // Empty table -> null return value
591
591
  if (keys.length === 0) {
592
592
  return null;
593
593
  }
594
594
 
595
595
  if (index === null) {
596
- // Return the first key, value
597
596
  const key = keys[0];
598
597
  return new LuaMultiRes([key, luaGet(table, key, sf.astCtx ?? null, sf)]);
599
598
  }
600
- // Find index in the key list
601
599
  const idx = keys.indexOf(index);
602
600
  if (idx === -1) {
603
- // Not found
604
601
  throw new LuaRuntimeError("invalid key to 'next': key not found", sf);
605
602
  }
606
603
  const key = keys[idx + 1];
607
604
  if (key === undefined) {
608
- // When called with the last key, should return nil
609
605
  return null;
610
606
  }
611
607
  return new LuaMultiRes([key, luaGet(table, key, sf.astCtx ?? null, sf)]);
@@ -622,7 +618,6 @@ const nextFunction = new LuaBuiltinFunction({
622
618
  ],
623
619
  });
624
620
 
625
- // Non-standard, but useful
626
621
  const someFunction = new LuaBuiltinFunction({
627
622
  callback: async (_sf, value: any) => {
628
623
  switch (await luaTypeOf(value)) {
@@ -691,13 +686,11 @@ function annotateBuiltinApi(
691
686
 
692
687
  export function luaBuildStandardEnv() {
693
688
  const env = new LuaEnv();
694
- // _G global
695
689
  env.set("_G", env);
696
690
  // Lua version string - for now it signals Lua 5.4 compatibility with
697
691
  // selective 5.5 features; kept non-standard so callers can distinguish
698
692
  // Space Lua from a plain Lua runtime.
699
693
  env.set("_VERSION", "Lua 5.4+");
700
- // Top-level builtins
701
694
  env.set("print", printFunction);
702
695
  env.set("assert", assertFunction);
703
696
  env.set("type", typeFunction);
@@ -705,10 +698,8 @@ export function luaBuildStandardEnv() {
705
698
  env.set("tonumber", tonumberFunction);
706
699
  env.set("select", selectFunction);
707
700
  env.set("next", nextFunction);
708
- // Iterators
709
701
  env.set("pairs", pairsFunction);
710
702
  env.set("ipairs", ipairsFunction);
711
- // meta table stuff
712
703
  env.set("setmetatable", setmetatableFunction);
713
704
  env.set("getmetatable", getmetatableFunction);
714
705
  env.set("rawlen", rawlenFunction);
@@ -716,19 +707,15 @@ export function luaBuildStandardEnv() {
716
707
  env.set("rawget", rawgetFunction);
717
708
  env.set("rawequal", rawequalFunction);
718
709
  env.set("dofile", dofileFunction);
719
- // Error handling
720
710
  env.set("error", errorFunction);
721
711
  env.set("pcall", pcallFunction);
722
712
  env.set("xpcall", xpcallFunction);
723
- // Evaluation
724
713
  env.set("load", loadFunction);
725
- // APIs
726
714
  env.set("string", stringApi);
727
715
  env.set("table", tableApi);
728
716
  env.set("os", osApi);
729
717
  env.set("js", jsApi);
730
718
  env.set("math", mathApi);
731
- // Non-standard
732
719
  env.set("each", eachFunction);
733
720
  env.set("spacelua", spaceluaApi);
734
721
  env.set("encoding", encodingApi);
@@ -142,7 +142,6 @@ function parseInt(s: string): { ok: boolean; value: number } {
142
142
  let acc = 0;
143
143
  let any = false;
144
144
 
145
- // hex?
146
145
  if (s.charCodeAt(i) === 48 && i + 1 < n) {
147
146
  // '0'
148
147
  const x = s.charCodeAt(i + 1);
@@ -179,7 +178,6 @@ function parseInt(s: string): { ok: boolean; value: number } {
179
178
  }
180
179
  }
181
180
 
182
- // decimal integer
183
181
  while (i < n) {
184
182
  const c = s.charCodeAt(i);
185
183
  if (c < 48 || c > 57) {
@@ -221,7 +219,6 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
221
219
  let fracAny = false;
222
220
  let val = 0;
223
221
 
224
- // integer part
225
222
  while (i < n) {
226
223
  const c = s.charCodeAt(i);
227
224
  if (c < 48 || c > 57) {
@@ -233,7 +230,6 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
233
230
  i++;
234
231
  }
235
232
 
236
- // fractional part
237
233
  if (i < n) {
238
234
  if (s.charCodeAt(i) === 46) {
239
235
  // '.'
@@ -253,7 +249,6 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
253
249
  }
254
250
  }
255
251
 
256
- // exponent
257
252
  let exp = 0;
258
253
  let hasExp = false;
259
254
  if (i < n) {
@@ -347,7 +342,6 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
347
342
  let anyHex = false;
348
343
  let sawDot = false;
349
344
 
350
- // integer hex digits
351
345
  while (i < n) {
352
346
  const c = s.charCodeAt(i);
353
347
  let d = -1;
@@ -371,7 +365,6 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
371
365
  i++;
372
366
  }
373
367
 
374
- // optional fractional part
375
368
  if (i < n) {
376
369
  if (s.charCodeAt(i) === 46) {
377
370
  // '.'
@@ -444,7 +437,6 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
444
437
  return { ok: false, value: 0 };
445
438
  }
446
439
  } else {
447
- // no exponent marker
448
440
  if (!sawDot) {
449
441
  // without dot we must reject so parseInt can claim it as integer
450
442
  return { ok: false, value: 0 };
@@ -456,7 +448,6 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
456
448
  exp = 0; // implicit p0
457
449
  }
458
450
  } else {
459
- // end of string
460
451
  if (!sawDot) {
461
452
  return { ok: false, value: 0 };
462
453
  }
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // version.ts
7
- var version = "2.10.0";
7
+ var version = "2.11.0";
8
8
 
9
9
  // client/plugos/plug_compile.ts
10
10
  import * as path from "node:path";
@@ -1371,8 +1371,7 @@ ${Object.entries(manifest.functions).map(([funcName, def]) => {
1371
1371
  }
1372
1372
  let [filePath, jsFunctionName] = def.path.split(":");
1373
1373
  filePath = path.join(rootPath, filePath);
1374
- return `import {${jsFunctionName} as ${funcName}} from "${// Replacing \ with / for Windows
1375
- path.resolve(filePath).replaceAll("\\", "\\\\")}";
1374
+ return `import {${jsFunctionName} as ${funcName}} from "${path.resolve(filePath).replaceAll("\\", "\\\\")}";
1376
1375
  `;
1377
1376
  }).join("")}
1378
1377
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silverbulletmd/silverbullet",
3
- "version": "2.10.0",
3
+ "version": "2.11.0",
4
4
  "type": "module",
5
5
  "description": "A self-hosted, web-based note taking app",
6
6
  "publishConfig": {
@@ -15,20 +15,26 @@
15
15
  "./lib/tree": "./plug-api/lib/tree.ts",
16
16
  "./lib/ref": "./plug-api/lib/ref.ts",
17
17
  "./lib/resolve": "./plug-api/lib/resolve.ts",
18
+ "./lib/resolve_path": "./plug-api/lib/resolve_path.ts",
19
+ "./lib/link_write": "./plug-api/lib/link_write.ts",
18
20
  "./lib/dates": "./plug-api/lib/dates.ts",
21
+ "./lib/fuzzy": "./plug-api/lib/fuzzy.ts",
19
22
  "./lib/async": "./plug-api/lib/async.ts",
20
23
  "./lib/crypto": "./plug-api/lib/crypto.ts",
21
24
  "./lib/limited_map": "./plug-api/lib/limited_map.ts",
22
25
  "./lib/tags": "./plug-api/lib/tags.ts",
26
+ "./lib/collation": "./plug-api/lib/collation.ts",
23
27
  "./lib/transclusion": "./plug-api/lib/transclusion.ts",
24
28
  "./lib/native_fetch": "./plug-api/lib/native_fetch.ts",
25
29
  "./lib/query_expression": "./plug-api/lib/query_expression.ts",
30
+ "./lib/panel_styles": "./plug-api/lib/panel_styles.ts",
26
31
  "./type/client": "./plug-api/types/client.ts",
27
32
  "./type/config": "./plug-api/types/config.ts",
28
33
  "./type/manifest": "./plug-api/types/manifest.ts",
29
34
  "./type/namespace": "./plug-api/types/namespace.ts",
30
35
  "./type/datastore": "./plug-api/types/datastore.ts",
31
36
  "./type/event": "./plug-api/types/event.ts",
37
+ "./type/revisions": "./plug-api/types/revisions.ts",
32
38
  "./type/index": "./plug-api/types/index.ts"
33
39
  },
34
40
  "bin": {
@@ -41,6 +47,7 @@
41
47
  "client/plugos/**/*.ts",
42
48
  "!client/plugos/**/*.test.ts",
43
49
  "client/asset_bundle/bundle.ts",
50
+ "client/config.ts",
44
51
  "client/data/kv_primitives.ts",
45
52
  "client/data/datastore.ts",
46
53
  "client/space_lua/**/*.ts",
@@ -48,6 +55,7 @@
48
55
  "!client/space_lua/**/*.bench.ts",
49
56
  "client/markdown_parser/constants.ts",
50
57
  "plugs/builtin_plugs.ts",
58
+ "plugs/index/types.ts",
51
59
  "dist/plug-compile.js"
52
60
  ],
53
61
  "scripts": {
@@ -55,9 +63,10 @@
55
63
  "build:plugs": "tsx build/build_plugs.ts",
56
64
  "build:client": "tsx build/build_client.ts",
57
65
  "build:plug-compile": "tsx build/build_plug_compile.ts",
66
+ "prebuild:client": "node build/patch_codemirror_view.mjs",
58
67
  "prepublishOnly": "npm run build:plug-compile",
59
68
  "test": "vitest run",
60
- "check": "tsc --noEmit",
69
+ "check": "tsc --noEmit && tsc -p tsconfig.e2e.json",
61
70
  "lint": "biome lint .",
62
71
  "fmt": "biome format --write .",
63
72
  "fmt:check": "biome format .",
@@ -92,8 +101,10 @@
92
101
  "@lezer/lr": "1.4.8",
93
102
  "@lezer/markdown": "1.6.3",
94
103
  "@msgpack/msgpack": "3.1.3",
104
+ "@noble/hashes": "2.0.1",
95
105
  "@replit/codemirror-lang-nix": "6.0.1",
96
106
  "@replit/codemirror-vim": "6.3.0",
107
+ "@types/js-yaml": "^4.0.9",
97
108
  "commander": "^14.0.3",
98
109
  "crelt": "1.0.6",
99
110
  "d3-force": "^3.0.0",
@@ -118,7 +129,6 @@
118
129
  "@preact/preset-vite": "^2.10.3",
119
130
  "@types/d3-force": "^3.0.10",
120
131
  "@types/gitignore-parser": "^0.0.3",
121
- "@types/js-yaml": "^4.0.9",
122
132
  "@types/node": "^22.0.0",
123
133
  "@types/turndown": "^5.0.6",
124
134
  "fake-indexeddb": "6.0.1",
@@ -11,7 +11,6 @@ export function throttle(
11
11
  }, limit);
12
12
  }
13
13
  };
14
- // Immediately execute any pending call and cancel the timer
15
14
  throttled.flush = () => {
16
15
  if (timer) {
17
16
  clearTimeout(timer);
@@ -91,7 +90,7 @@ export class PromiseQueue {
91
90
  reject(error);
92
91
  }
93
92
 
94
- void this.process(); // Continue processing the next promise in the queue
93
+ void this.process();
95
94
  }
96
95
  }
97
96
 
@@ -108,17 +107,13 @@ export async function batchRequests<I, O>(
108
107
  batchSize: number,
109
108
  ): Promise<O[]> {
110
109
  const results: O[] = [];
111
- // Split values into batches of batchSize
112
110
  const batches: I[][] = [];
113
111
  for (let i = 0; i < values.length; i += batchSize) {
114
112
  batches.push(values.slice(i, i + batchSize));
115
113
  }
116
- // Run fn on them in parallel
117
114
  const batchResults = await Promise.all(batches.map(fn));
118
- // Flatten the results
119
115
  for (const batchResult of batchResults) {
120
116
  if (Array.isArray(batchResult)) {
121
- // If fn returns an array, collect them
122
117
  results.push(...batchResult);
123
118
  }
124
119
  }
@@ -0,0 +1,22 @@
1
+ import type { QueryCollationConfig } from "../types/config.ts";
2
+
3
+ /**
4
+ * Compares two values the same way the query engine's `order by` does:
5
+ * codepoint order by default, or `Intl.Collator` order when the space's
6
+ * `queryCollation` config turns it on and both sides are strings.
7
+ */
8
+ export function compareCollated(
9
+ // deno-lint-ignore no-explicit-any
10
+ a: any,
11
+ // deno-lint-ignore no-explicit-any
12
+ b: any,
13
+ collation: QueryCollationConfig | undefined,
14
+ collator: Intl.Collator,
15
+ ): number {
16
+ if (collation?.enabled && typeof a === "string" && typeof b === "string") {
17
+ return collator.compare(a, b);
18
+ }
19
+ if (a < b) return -1;
20
+ if (a > b) return 1;
21
+ return 0;
22
+ }
@@ -1,3 +1,5 @@
1
+ import { sha256 } from "@noble/hashes/sha2.js";
2
+
1
3
  export function base64Decode(s: string): Uint8Array {
2
4
  const binString = atob(s);
3
5
  const len = binString.length;
@@ -32,31 +34,33 @@ export function base64DecodeDataUrl(dataUrl: string): Uint8Array {
32
34
  return base64Decode(b64Encoded);
33
35
  }
34
36
 
35
- /**
36
- * Perform sha256 hash using the browser's crypto APIs
37
- * Note: this will only work over HTTPS
38
- * @param message
39
- */
40
37
  export async function hashSHA256(
41
38
  message: string | Uint8Array,
42
39
  ): Promise<string> {
43
- // Transform the string into an ArrayBuffer
44
40
  const encoder = new TextEncoder();
45
41
  const data: Uint8Array =
46
42
  typeof message === "string" ? encoder.encode(message) : message;
47
43
 
48
- // Generate the hash
49
- const hashBuffer = await globalThis.crypto.subtle.digest(
50
- "SHA-256",
51
- data as BufferSource,
52
- );
44
+ const hashBuffer = globalThis.crypto.subtle
45
+ ? await globalThis.crypto.subtle.digest("SHA-256", data as BufferSource)
46
+ : sha256(data);
53
47
 
54
- // Transform the hash into a hex string
55
48
  return Array.from(new Uint8Array(hashBuffer))
56
49
  .map((b) => b.toString(16).padStart(2, "0"))
57
50
  .join("");
58
51
  }
59
52
 
53
+ export function randomUUID(): string {
54
+ if (globalThis.crypto.randomUUID) return globalThis.crypto.randomUUID();
55
+ const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
56
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
57
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
58
+ const hex = Array.from(bytes, (byte) =>
59
+ byte.toString(16).padStart(2, "0"),
60
+ ).join("");
61
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
62
+ }
63
+
60
64
  /**
61
65
  * To avoid database clashes based on space folder path name, base URLs and encryption keys we derive
62
66
  * a database name from a hash of all these combined together
@@ -117,7 +121,6 @@ export async function encryptAesGcm(
117
121
  );
118
122
  const encrypted = new Uint8Array(encryptedBuffer);
119
123
 
120
- // Prepend IV to ciphertext
121
124
  const result = new Uint8Array(iv.length + encrypted.length);
122
125
  result.set(iv, 0);
123
126
  result.set(encrypted, iv.length);
@@ -143,10 +146,8 @@ export async function deriveCTRKeyFromPassword(
143
146
  password: string,
144
147
  salt: Uint8Array,
145
148
  ): Promise<CryptoKey> {
146
- // Encode password to ArrayBuffer
147
149
  const passwordBytes = new TextEncoder().encode(password);
148
150
 
149
- // Import password as a CryptoKey
150
151
  const baseKey = await globalThis.crypto.subtle.importKey(
151
152
  "raw",
152
153
  passwordBytes,