@seed-hypermedia/client 0.0.1 → 0.0.3

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/dist/index.mjs CHANGED
@@ -2,23 +2,21 @@ import {
2
2
  HMActionSchema,
3
3
  HMRequestSchema,
4
4
  HYPERMEDIA_SCHEME,
5
+ codePointLength,
5
6
  entityQueryPathToHmIdPath,
6
7
  getHMQueryString,
7
8
  hmIdPathToEntityQueryPath,
9
+ isSurrogate,
8
10
  packBaseId,
9
11
  packHmId,
10
- serializeBlockRange
11
- } from "./chunk-YUKHBJYL.mjs";
12
-
13
- // src/change.ts
14
- import { decode as cborDecode, encode as cborEncode3 } from "@ipld/dag-cbor";
15
- import { CID as CID2 } from "multiformats";
16
- import * as Block from "multiformats/block";
17
- import { sha256 } from "multiformats/hashes/sha2";
12
+ parseCustomURL,
13
+ parseFragment,
14
+ serializeBlockRange,
15
+ unpackHmId
16
+ } from "./chunk-X57BIZUK.mjs";
18
17
 
19
- // src/ref.ts
18
+ // src/capability.ts
20
19
  import { encode as cborEncode2 } from "@ipld/dag-cbor";
21
- import { CID } from "multiformats";
22
20
  import { base58btc } from "multiformats/bases/base58";
23
21
 
24
22
  // src/signing.ts
@@ -48,7 +46,38 @@ function toPublishInput(blobData, extraBlobs) {
48
46
  };
49
47
  }
50
48
 
49
+ // src/capability.ts
50
+ async function createCapability(input, signer) {
51
+ const signerKey = await signer.getPublicKey();
52
+ const ts = BigInt(Date.now());
53
+ const unsigned = {
54
+ type: "Capability",
55
+ signer: new Uint8Array(signerKey),
56
+ sig: new Uint8Array(64),
57
+ ts,
58
+ delegate: new Uint8Array(base58btc.decode(input.delegateUid)),
59
+ role: input.role
60
+ };
61
+ if (input.path) {
62
+ unsigned.path = input.path;
63
+ }
64
+ if (input.label) {
65
+ unsigned.label = input.label;
66
+ }
67
+ unsigned.sig = await signObject(signer, unsigned);
68
+ return toPublishInput(cborEncode2(unsigned));
69
+ }
70
+
71
+ // src/change.ts
72
+ import { decode as cborDecode, encode as cborEncode4 } from "@ipld/dag-cbor";
73
+ import { CID as CID2 } from "multiformats";
74
+ import * as Block from "multiformats/block";
75
+ import { sha256 } from "multiformats/hashes/sha2";
76
+
51
77
  // src/ref.ts
78
+ import { encode as cborEncode3 } from "@ipld/dag-cbor";
79
+ import { CID } from "multiformats";
80
+ import { base58btc as base58btc2 } from "multiformats/bases/base58";
52
81
  function bytesEqual(a, b) {
53
82
  if (a.length !== b.length) return false;
54
83
  for (let i = 0; i < a.length; i++) {
@@ -62,10 +91,11 @@ function buildUnsignedRef({
62
91
  path,
63
92
  genesis,
64
93
  generation,
65
- capability
94
+ capability,
95
+ visibility
66
96
  }) {
67
97
  const signerBytes = new Uint8Array(signerKey);
68
- const spaceBytes = new Uint8Array(base58btc.decode(space));
98
+ const spaceBytes = new Uint8Array(base58btc2.decode(space));
69
99
  const unsigned = {
70
100
  type: "Ref",
71
101
  signer: signerBytes,
@@ -83,6 +113,9 @@ function buildUnsignedRef({
83
113
  if (capability) {
84
114
  unsigned.capability = CID.parse(capability);
85
115
  }
116
+ if (visibility) {
117
+ unsigned.visibility = visibility;
118
+ }
86
119
  return unsigned;
87
120
  }
88
121
  async function createVersionRef(input, signer) {
@@ -93,11 +126,12 @@ async function createVersionRef(input, signer) {
93
126
  path: input.path,
94
127
  genesis: input.genesis,
95
128
  generation: input.generation,
96
- capability: input.capability
129
+ capability: input.capability,
130
+ visibility: input.visibility
97
131
  });
98
132
  unsigned.heads = input.version.split(".").map((v) => CID.parse(v));
99
133
  unsigned.sig = await signObject(signer, unsigned);
100
- return toPublishInput(cborEncode2(unsigned));
134
+ return toPublishInput(cborEncode3(unsigned));
101
135
  }
102
136
  async function createTombstoneRef(input, signer) {
103
137
  const signerKey = await signer.getPublicKey();
@@ -111,7 +145,7 @@ async function createTombstoneRef(input, signer) {
111
145
  });
112
146
  unsigned.heads = [];
113
147
  unsigned.sig = await signObject(signer, unsigned);
114
- return toPublishInput(cborEncode2(unsigned));
148
+ return toPublishInput(cborEncode3(unsigned));
115
149
  }
116
150
  async function createRedirectRef(input, signer) {
117
151
  const signerKey = await signer.getPublicKey();
@@ -128,14 +162,14 @@ async function createRedirectRef(input, signer) {
128
162
  path: input.targetPath
129
163
  };
130
164
  if (input.targetSpace && input.targetSpace !== input.space) {
131
- redirect.space = new Uint8Array(base58btc.decode(input.targetSpace));
165
+ redirect.space = new Uint8Array(base58btc2.decode(input.targetSpace));
132
166
  }
133
167
  if (input.republish) {
134
168
  redirect.republish = true;
135
169
  }
136
170
  unsigned.redirect = redirect;
137
171
  unsigned.sig = await signObject(signer, unsigned);
138
- return toPublishInput(cborEncode2(unsigned));
172
+ return toPublishInput(cborEncode3(unsigned));
139
173
  }
140
174
 
141
175
  // src/change.ts
@@ -154,14 +188,14 @@ function createChangeOps(input) {
154
188
  deps: input.deps,
155
189
  depth: input.depth
156
190
  };
157
- return { unsignedBytes: cborEncode3(unsigned), ts };
191
+ return { unsignedBytes: cborEncode4(unsigned), ts };
158
192
  }
159
193
  async function createChange(unsignedBytes, signer) {
160
194
  const change = cborDecode(unsignedBytes);
161
195
  const genesis = change.genesis instanceof CID2 ? change.genesis : null;
162
196
  change.signer = new Uint8Array(await signer.getPublicKey());
163
197
  change.sig = new Uint8Array(64);
164
- change.sig = await signer.sign(cborEncode3(change));
198
+ change.sig = await signer.sign(cborEncode4(change));
165
199
  const block = await Block.encode({ value: change, codec: cborCodec, hasher: sha256 });
166
200
  return { bytes: block.bytes, cid: block.cid, genesis };
167
201
  }
@@ -177,7 +211,7 @@ async function createGenesisChange(signer) {
177
211
  sig: new Uint8Array(64),
178
212
  ts: BigInt(0)
179
213
  };
180
- unsigned.sig = await signer.sign(cborEncode3(unsigned));
214
+ unsigned.sig = await signer.sign(cborEncode4(unsigned));
181
215
  const block = await Block.encode({ value: unsigned, codec: cborCodec, hasher: sha256 });
182
216
  return { bytes: block.bytes, cid: block.cid };
183
217
  }
@@ -209,6 +243,10 @@ function protoChangesToOps(changes) {
209
243
  }
210
244
  });
211
245
  }
246
+ var RESOURCE_VISIBILITY_PRIVATE = 2;
247
+ function visibilityToCbor(protoVisibility) {
248
+ return protoVisibility === RESOURCE_VISIBILITY_PRIVATE ? "Private" : void 0;
249
+ }
212
250
  async function signDocumentChange(input, signer) {
213
251
  const { bytes: signedBytes, cid: changeCid, genesis: changeGenesis } = await createChange(input.unsignedChange, signer);
214
252
  const effectiveGenesis = input.genesis || (changeGenesis ? changeGenesis.toString() : changeCid.toString());
@@ -220,7 +258,8 @@ async function signDocumentChange(input, signer) {
220
258
  genesis: effectiveGenesis,
221
259
  version: changeCid.toString(),
222
260
  generation: effectiveGeneration,
223
- capability: input.capability
261
+ capability: input.capability,
262
+ visibility: visibilityToCbor(input.visibility)
224
263
  },
225
264
  signer
226
265
  );
@@ -233,7 +272,7 @@ async function signDocumentChange(input, signer) {
233
272
  }
234
273
 
235
274
  // src/client.ts
236
- import { encode as cborEncode4 } from "@ipld/dag-cbor";
275
+ import { encode as cborEncode5 } from "@ipld/dag-cbor";
237
276
  import { deserialize } from "superjson";
238
277
 
239
278
  // src/errors.ts
@@ -331,7 +370,7 @@ function createSeedClient(baseUrl, options) {
331
370
  "Content-Type": "application/cbor",
332
371
  ...defaultHeaders
333
372
  },
334
- body: new Uint8Array(cborEncode4(stripUndefined(validatedInput)))
373
+ body: new Uint8Array(cborEncode5(stripUndefined(validatedInput)))
335
374
  });
336
375
  } catch (err) {
337
376
  throw new SeedNetworkError(
@@ -387,7 +426,7 @@ function createSeedClient(baseUrl, options) {
387
426
  return request("PublishBlobs", input);
388
427
  }
389
428
  async function publishDocument(input, signer) {
390
- if (!input.genesis && !input.baseVersion) {
429
+ if (!input.genesis && !input.baseVersion && !input.path) {
391
430
  const genesisChange = await createGenesisChange(signer);
392
431
  const contentChange = await createDocumentChange(
393
432
  {
@@ -401,7 +440,7 @@ function createSeedClient(baseUrl, options) {
401
440
  const ref = await createVersionRef(
402
441
  {
403
442
  space: input.account,
404
- path: input.path ?? "",
443
+ path: "",
405
444
  genesis: genesisChange.cid.toString(),
406
445
  version: contentChange.cid.toString(),
407
446
  generation: input.generation != null ? Number(input.generation) : 1,
@@ -433,7 +472,8 @@ function createSeedClient(baseUrl, options) {
433
472
  unsignedChange,
434
473
  genesis: input.genesis,
435
474
  generation: input.generation,
436
- capability: input.capability
475
+ capability: input.capability,
476
+ visibility: input.visibility
437
477
  },
438
478
  signer
439
479
  );
@@ -459,9 +499,9 @@ function stripUndefined(obj) {
459
499
  }
460
500
 
461
501
  // src/comment.ts
462
- import { encode as cborEncode5 } from "@ipld/dag-cbor";
502
+ import { decode as cborDecode2, encode as cborEncode6 } from "@ipld/dag-cbor";
463
503
  import { CID as CID3 } from "multiformats";
464
- import { base58btc as base58btc2 } from "multiformats/bases/base58";
504
+ import { base58btc as base58btc3 } from "multiformats/bases/base58";
465
505
  function trimTrailingEmptyBlocks(blocks) {
466
506
  let end = blocks.length;
467
507
  while (end > 0) {
@@ -619,7 +659,7 @@ function createUnsignedComment({
619
659
  const unsignedComment = {
620
660
  type: "Comment",
621
661
  body: blocksToPublishable(content),
622
- space: new Uint8Array(base58btc2.decode(docId.uid)),
662
+ space: new Uint8Array(base58btc3.decode(docId.uid)),
623
663
  version: docVersion,
624
664
  signer: new Uint8Array(signerKey),
625
665
  ts: BigInt(Date.now()),
@@ -664,7 +704,7 @@ async function createCommentBlob({
664
704
  visibility
665
705
  });
666
706
  const signedComment = await createSignedComment(unsignedComment, signer);
667
- return cborEncode5(signedComment);
707
+ return cborEncode6(signedComment);
668
708
  }
669
709
  function generateBlockId(length = 8) {
670
710
  const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -740,7 +780,7 @@ async function deleteComment(input, signer) {
740
780
  type: "Comment",
741
781
  id: tsid,
742
782
  body: [],
743
- space: new Uint8Array(base58btc2.decode(input.targetAccount)),
783
+ space: new Uint8Array(base58btc3.decode(input.targetAccount)),
744
784
  path: input.targetPath,
745
785
  version: input.targetVersion.split(".").map((v) => CID3.parse(v)),
746
786
  signer: new Uint8Array(signerKey),
@@ -749,13 +789,32 @@ async function deleteComment(input, signer) {
749
789
  };
750
790
  if (input.visibility) tombstone.visibility = input.visibility;
751
791
  tombstone.sig = await signObject(signer, tombstone);
752
- const encoded = cborEncode5(tombstone);
792
+ const encoded = cborEncode6(tombstone);
753
793
  return toPublishInput(encoded, []);
754
794
  }
795
+ async function commentRecordIdFromBlob(blobData) {
796
+ const decoded = cborDecode2(blobData);
797
+ if (decoded.type !== "Comment") {
798
+ throw new Error(`Expected Comment blob, got "${decoded.type}"`);
799
+ }
800
+ const signerBytes = decoded.signer;
801
+ const ts = BigInt(decoded.ts);
802
+ const authority = base58btc3.encode(new Uint8Array(signerBytes));
803
+ const buf = new ArrayBuffer(8);
804
+ new DataView(buf).setBigUint64(0, ts, false);
805
+ const tsBytes = new Uint8Array(buf, 2, 6);
806
+ const hashBuffer = await crypto.subtle.digest("SHA-256", blobData);
807
+ const hashBytes = new Uint8Array(hashBuffer, 0, 4);
808
+ const tsidBytes = new Uint8Array(10);
809
+ tsidBytes.set(tsBytes, 0);
810
+ tsidBytes.set(hashBytes, 6);
811
+ const tsid = base58btc3.encode(tsidBytes);
812
+ return `${authority}/${tsid}`;
813
+ }
755
814
 
756
815
  // src/contact.ts
757
- import { encode as cborEncode6, decode as cborDecode2 } from "@ipld/dag-cbor";
758
- import { base58btc as base58btc3 } from "multiformats/bases/base58";
816
+ import { decode as cborDecode3, encode as cborEncode7 } from "@ipld/dag-cbor";
817
+ import { base58btc as base58btc4 } from "multiformats/bases/base58";
759
818
  function extractTsid(contactId) {
760
819
  const parts = contactId.split("/");
761
820
  const tsid = parts[1];
@@ -764,6 +823,14 @@ function extractTsid(contactId) {
764
823
  }
765
824
  return tsid;
766
825
  }
826
+ function extractAuthority(contactId) {
827
+ const parts = contactId.split("/");
828
+ const authority = parts[0];
829
+ if (!authority || parts.length !== 2) {
830
+ throw new Error(`Invalid contact ID format: ${contactId}. Expected "authority/tsid".`);
831
+ }
832
+ return authority;
833
+ }
767
834
  async function computeTSID(timestampMs, blobData) {
768
835
  const buf = new ArrayBuffer(8);
769
836
  new DataView(buf).setBigUint64(0, timestampMs, false);
@@ -773,22 +840,32 @@ async function computeTSID(timestampMs, blobData) {
773
840
  const tsidBytes = new Uint8Array(10);
774
841
  tsidBytes.set(tsBytes, 0);
775
842
  tsidBytes.set(hashBytes, 6);
776
- return base58btc3.encode(tsidBytes);
843
+ return base58btc4.encode(tsidBytes);
777
844
  }
778
845
  async function createContact(input, signer) {
779
846
  const signerKey = await signer.getPublicKey();
780
847
  const ts = BigInt(Date.now());
848
+ const signerUid = base58btc4.encode(new Uint8Array(signerKey));
849
+ const authority = input.accountUid || signerUid;
781
850
  const unsigned = {
782
851
  type: "Contact",
783
852
  signer: new Uint8Array(signerKey),
784
853
  sig: new Uint8Array(64),
785
854
  ts,
786
- subject: new Uint8Array(base58btc3.decode(input.subjectUid)),
787
- name: input.name
855
+ subject: new Uint8Array(base58btc4.decode(input.subjectUid))
788
856
  };
857
+ if (input.name) {
858
+ unsigned.name = input.name;
859
+ }
860
+ if (input.accountUid) {
861
+ unsigned.account = new Uint8Array(base58btc4.decode(input.accountUid));
862
+ }
863
+ if (input.subscribe) {
864
+ unsigned.subscribe = input.subscribe;
865
+ }
866
+ console.log("SIGNING BLOB", unsigned);
789
867
  unsigned.sig = await signObject(signer, unsigned);
790
- const encoded = cborEncode6(unsigned);
791
- const authority = base58btc3.encode(new Uint8Array(signerKey));
868
+ const encoded = cborEncode7(unsigned);
792
869
  const tsid = await computeTSID(ts, encoded);
793
870
  return {
794
871
  ...toPublishInput(encoded),
@@ -798,21 +875,31 @@ async function createContact(input, signer) {
798
875
  async function updateContact(input, signer) {
799
876
  const signerKey = await signer.getPublicKey();
800
877
  const tsid = extractTsid(input.contactId);
878
+ const accountUid = input.accountUid || extractAuthority(input.contactId);
801
879
  const unsigned = {
802
880
  type: "Contact",
803
881
  signer: new Uint8Array(signerKey),
804
882
  sig: new Uint8Array(64),
805
883
  ts: BigInt(Date.now()),
806
884
  id: tsid,
807
- subject: new Uint8Array(base58btc3.decode(input.subjectUid)),
808
- name: input.name
885
+ subject: new Uint8Array(base58btc4.decode(input.subjectUid))
809
886
  };
887
+ if (input.name) {
888
+ unsigned.name = input.name;
889
+ }
890
+ if (accountUid) {
891
+ unsigned.account = new Uint8Array(base58btc4.decode(accountUid));
892
+ }
893
+ if (input.subscribe) {
894
+ unsigned.subscribe = input.subscribe;
895
+ }
810
896
  unsigned.sig = await signObject(signer, unsigned);
811
- return toPublishInput(cborEncode6(unsigned));
897
+ return toPublishInput(cborEncode7(unsigned));
812
898
  }
813
899
  async function deleteContact(input, signer) {
814
900
  const signerKey = await signer.getPublicKey();
815
901
  const tsid = extractTsid(input.contactId);
902
+ const accountUid = input.accountUid || extractAuthority(input.contactId);
816
903
  const unsigned = {
817
904
  type: "Contact",
818
905
  signer: new Uint8Array(signerKey),
@@ -820,50 +907,2253 @@ async function deleteContact(input, signer) {
820
907
  ts: BigInt(Date.now()),
821
908
  id: tsid
822
909
  };
910
+ if (accountUid) {
911
+ unsigned.account = new Uint8Array(base58btc4.decode(accountUid));
912
+ }
823
913
  unsigned.sig = await signObject(signer, unsigned);
824
- return toPublishInput(cborEncode6(unsigned));
914
+ return toPublishInput(cborEncode7(unsigned));
825
915
  }
826
916
  async function contactRecordIdFromBlob(blobData) {
827
- const decoded = cborDecode2(blobData);
917
+ const decoded = cborDecode3(blobData);
828
918
  if (decoded.type !== "Contact") {
829
919
  throw new Error(`Expected Contact blob, got "${decoded.type}"`);
830
920
  }
831
921
  const signerBytes = decoded.signer;
832
922
  const ts = BigInt(decoded.ts);
833
- const authority = base58btc3.encode(new Uint8Array(signerBytes));
923
+ const accountBytes = decoded.account;
924
+ const authority = base58btc4.encode(new Uint8Array(accountBytes || signerBytes));
834
925
  const tsid = await computeTSID(ts, blobData);
835
926
  return `${authority}/${tsid}`;
836
927
  }
837
928
 
838
- // src/capability.ts
839
- import { encode as cborEncode7 } from "@ipld/dag-cbor";
840
- import { base58btc as base58btc4 } from "multiformats/bases/base58";
841
- async function createCapability(input, signer) {
842
- const signerKey = await signer.getPublicKey();
843
- const ts = BigInt(Date.now());
844
- const unsigned = {
845
- type: "Capability",
846
- signer: new Uint8Array(signerKey),
847
- sig: new Uint8Array(64),
848
- ts,
849
- delegate: new Uint8Array(base58btc4.decode(input.delegateUid)),
850
- role: input.role
929
+ // src/tei-to-blocks.ts
930
+ function generateBlockId2() {
931
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
932
+ let id = "";
933
+ for (let i = 0; i < 8; i++) {
934
+ id += chars[Math.floor(Math.random() * chars.length)];
935
+ }
936
+ return id;
937
+ }
938
+ function makeBlock(type, text, extra = {}) {
939
+ return {
940
+ block: {
941
+ id: generateBlockId2(),
942
+ type,
943
+ text,
944
+ revision: "",
945
+ link: "",
946
+ attributes: {},
947
+ annotations: [],
948
+ ...extra
949
+ },
950
+ children: []
851
951
  };
852
- if (input.path) {
853
- unsigned.path = input.path;
952
+ }
953
+ function makeHeading(text) {
954
+ return makeBlock("Heading", text, {
955
+ attributes: { childrenType: "Group" }
956
+ });
957
+ }
958
+ function makeParagraph(text, annotations = []) {
959
+ return makeBlock("Paragraph", text, { annotations });
960
+ }
961
+ function makeMath(text) {
962
+ return makeBlock("Math", text);
963
+ }
964
+ function makeCode(text, language = "") {
965
+ return makeBlock("Code", text, {
966
+ attributes: language ? { language } : {}
967
+ });
968
+ }
969
+ function makeImage(link, caption = "") {
970
+ return makeBlock("Image", caption, { link });
971
+ }
972
+ function makeList(items, listType) {
973
+ const container = makeBlock("Paragraph", "", {
974
+ attributes: { childrenType: listType }
975
+ });
976
+ container.children = items;
977
+ return container;
978
+ }
979
+ function parseInlineContent(_$, el) {
980
+ const annotations = [];
981
+ let text = "";
982
+ function walk(node, active) {
983
+ var _a, _b, _c;
984
+ if (node.type === "text") {
985
+ const raw = node.data || "";
986
+ const normalized = raw.replace(/\s+/g, " ");
987
+ text += normalized;
988
+ return;
989
+ }
990
+ if (node.type !== "tag") return;
991
+ let isBold = active.bold || false;
992
+ let isItalic = active.italic || false;
993
+ let isCode = active.code || false;
994
+ let linkHref = active.link;
995
+ const tagName = ((_a = node.name) == null ? void 0 : _a.toLowerCase()) || "";
996
+ if (tagName === "hi") {
997
+ const rend = ((_b = node.attribs) == null ? void 0 : _b.rend) || "";
998
+ if (rend === "bold" || rend === "b") isBold = true;
999
+ if (rend === "italic" || rend === "i") isItalic = true;
1000
+ if (rend === "monospace" || rend === "code") isCode = true;
1001
+ }
1002
+ if (tagName === "ref") {
1003
+ const target = (_c = node.attribs) == null ? void 0 : _c.target;
1004
+ if (target && target.startsWith("http")) {
1005
+ linkHref = target;
1006
+ }
1007
+ }
1008
+ const start = codePointLength(text);
1009
+ for (const child of node.children || []) {
1010
+ walk(child, { bold: isBold, italic: isItalic, code: isCode, link: linkHref });
1011
+ }
1012
+ const end = codePointLength(text);
1013
+ if (end <= start) return;
1014
+ if (isBold && !active.bold && tagName === "hi") {
1015
+ annotations.push({ type: "Bold", starts: [start], ends: [end] });
1016
+ }
1017
+ if (isItalic && !active.italic && tagName === "hi") {
1018
+ annotations.push({ type: "Italic", starts: [start], ends: [end] });
1019
+ }
1020
+ if (isCode && !active.code && tagName === "hi") {
1021
+ annotations.push({ type: "Code", starts: [start], ends: [end] });
1022
+ }
1023
+ if (linkHref && !active.link && tagName === "ref") {
1024
+ annotations.push({
1025
+ type: "Link",
1026
+ starts: [start],
1027
+ ends: [end],
1028
+ link: linkHref
1029
+ });
1030
+ }
854
1031
  }
855
- if (input.label) {
856
- unsigned.label = input.label;
1032
+ for (const child of el.contents().toArray()) {
1033
+ walk(child, {});
857
1034
  }
858
- unsigned.sig = await signObject(signer, unsigned);
859
- return toPublishInput(cborEncode7(unsigned));
1035
+ const trimmedText = text.trim();
1036
+ const leadingLen = text.length - text.trimStart().length;
1037
+ const adjustedAnnotations = annotations.map((a) => ({
1038
+ ...a,
1039
+ starts: a.starts.map((p) => Math.max(0, p - leadingLen)),
1040
+ ends: a.ends.map((p) => Math.max(0, p - leadingLen))
1041
+ })).filter((a) => (a.ends[0] ?? 0) > (a.starts[0] ?? 0));
1042
+ adjustedAnnotations.sort((a, b) => {
1043
+ const aStart = a.starts[0] ?? 0;
1044
+ const bStart = b.starts[0] ?? 0;
1045
+ if (aStart !== bStart) return aStart - bStart;
1046
+ const aEnd = a.ends[0] ?? 0;
1047
+ const bEnd = b.ends[0] ?? 0;
1048
+ if (aEnd !== bEnd) return aEnd - bEnd;
1049
+ return a.type < b.type ? -1 : a.type > b.type ? 1 : 0;
1050
+ });
1051
+ return { text: trimmedText, annotations: adjustedAnnotations };
1052
+ }
1053
+ function extractTitle($) {
1054
+ const titleEl = $('teiHeader fileDesc titleStmt title[level="a"]');
1055
+ if (titleEl.length) return titleEl.text().trim();
1056
+ const anyTitle = $("teiHeader fileDesc titleStmt title");
1057
+ if (anyTitle.length) return anyTitle.first().text().trim();
1058
+ return "";
1059
+ }
1060
+ function extractAuthors($) {
1061
+ const authors = [];
1062
+ $("teiHeader fileDesc sourceDesc biblStruct analytic author persName").each((_i, el) => {
1063
+ const forenames = [];
1064
+ $(el).find("forename").each((_j, fn) => {
1065
+ forenames.push($(fn).text().trim());
1066
+ });
1067
+ const surname = $(el).find("surname").text().trim();
1068
+ const name = [...forenames, surname].filter(Boolean).join(" ");
1069
+ if (name) authors.push(name);
1070
+ });
1071
+ if (authors.length === 0) {
1072
+ $("teiHeader sourceDesc biblStruct analytic author").each((_i, el) => {
1073
+ const persName = $(el).find("persName");
1074
+ if (persName.length) {
1075
+ const forenames = [];
1076
+ persName.find("forename").each((_j, fn) => {
1077
+ forenames.push($(fn).text().trim());
1078
+ });
1079
+ const surname = persName.find("surname").text().trim();
1080
+ const name = [...forenames, surname].filter(Boolean).join(" ");
1081
+ if (name) authors.push(name);
1082
+ }
1083
+ });
1084
+ }
1085
+ return authors.join(", ");
1086
+ }
1087
+ function extractAbstract($) {
1088
+ const abs = $("teiHeader profileDesc abstract");
1089
+ if (!abs.length) return "";
1090
+ const parts = [];
1091
+ abs.find("p").each((_i, el) => {
1092
+ const t = $(el).text().trim();
1093
+ if (t) parts.push(t);
1094
+ });
1095
+ if (parts.length) return parts.join("\n\n");
1096
+ return abs.text().trim();
1097
+ }
1098
+ function extractDate($) {
1099
+ const dateEl = $('teiHeader fileDesc publicationStmt date[type="published"]');
1100
+ const when = dateEl.attr("when");
1101
+ if (when) return when;
1102
+ return dateEl.text().trim();
1103
+ }
1104
+ function parseCoords(coordsStr) {
1105
+ if (!coordsStr) return null;
1106
+ const first = coordsStr.split(";")[0];
1107
+ if (!first) return null;
1108
+ const parts = first.split(",").map(Number);
1109
+ if (parts.length < 5 || parts.some(isNaN)) return null;
1110
+ return {
1111
+ page: parts[0],
1112
+ x: parts[1],
1113
+ y: parts[2],
1114
+ width: parts[3],
1115
+ height: parts[4]
1116
+ };
1117
+ }
1118
+ function formatTable($, tableEl) {
1119
+ const rows = [];
1120
+ tableEl.find("row").each((_i, row) => {
1121
+ const cells = [];
1122
+ $(row).find("cell").each((_j, cell) => {
1123
+ cells.push($(cell).text().trim());
1124
+ });
1125
+ rows.push(cells);
1126
+ });
1127
+ if (rows.length === 0) return tableEl.text().trim();
1128
+ const colWidths = [];
1129
+ for (const row of rows) {
1130
+ for (let i = 0; i < row.length; i++) {
1131
+ colWidths[i] = Math.max(colWidths[i] || 0, (row[i] || "").length);
1132
+ }
1133
+ }
1134
+ const lines = [];
1135
+ for (let r = 0; r < rows.length; r++) {
1136
+ const row = rows[r];
1137
+ const line = row.map((cell, c) => cell.padEnd(colWidths[c] || 0)).join(" | ");
1138
+ lines.push(line);
1139
+ if (r === 0) {
1140
+ lines.push(colWidths.map((w) => "-".repeat(w)).join("-+-"));
1141
+ }
1142
+ }
1143
+ return lines.join("\n");
1144
+ }
1145
+ function formatBiblStruct($, bib) {
1146
+ const authors = [];
1147
+ bib.find("author persName").each((_i, el) => {
1148
+ const forenames = [];
1149
+ $(el).find("forename").each((_j, fn) => {
1150
+ forenames.push($(fn).text().trim());
1151
+ });
1152
+ const surname = $(el).find("surname").text().trim();
1153
+ const name = [...forenames, surname].filter(Boolean).join(" ");
1154
+ if (name) authors.push(name);
1155
+ });
1156
+ const title = bib.find("analytic title").text().trim() || bib.find("monogr title").text().trim();
1157
+ const journal = bib.find('monogr title[level="j"]').text().trim();
1158
+ const volume = bib.find('biblScope[unit="volume"]').text().trim();
1159
+ const pages = bib.find('biblScope[unit="page"]');
1160
+ const from = pages.attr("from") || "";
1161
+ const to = pages.attr("to") || "";
1162
+ const pageStr = from && to ? `${from}-${to}` : pages.text().trim();
1163
+ const year = bib.find('date[type="published"]').attr("when") || bib.find('date[type="published"]').text().trim();
1164
+ const doi = bib.find('idno[type="DOI"]').text().trim();
1165
+ const parts = [];
1166
+ if (authors.length) parts.push(authors.join(", "));
1167
+ if (title) parts.push(`"${title}"`);
1168
+ if (journal) {
1169
+ let jPart = journal;
1170
+ if (volume) jPart += ` ${volume}`;
1171
+ if (pageStr) jPart += `: ${pageStr}`;
1172
+ parts.push(jPart);
1173
+ }
1174
+ if (year) parts.push(`(${year})`);
1175
+ let result = parts.join(". ");
1176
+ if (doi) result += `. DOI: ${doi}`;
1177
+ return result || bib.text().trim().replace(/\s+/g, " ");
1178
+ }
1179
+ function inferHeadingLevel(_$, headEl) {
1180
+ var _a, _b;
1181
+ let depth = 0;
1182
+ let parent = headEl.parent();
1183
+ while (parent.length) {
1184
+ const tag = (_b = (_a = parent[0]) == null ? void 0 : _a.name) == null ? void 0 : _b.toLowerCase();
1185
+ if (tag === "body" || tag === "text") break;
1186
+ if (tag === "div") depth++;
1187
+ parent = parent.parent();
1188
+ }
1189
+ return Math.min(depth + 1, 6);
1190
+ }
1191
+ async function processBodyElements($, bodyEl, opts) {
1192
+ const elements = [];
1193
+ async function walkChildren(container) {
1194
+ var _a;
1195
+ for (const child of container.children().toArray()) {
1196
+ const $child = $(child);
1197
+ const tagName = ((_a = child.name) == null ? void 0 : _a.toLowerCase()) || "";
1198
+ if (tagName === "div") {
1199
+ await walkChildren($child);
1200
+ continue;
1201
+ }
1202
+ if (tagName === "head") {
1203
+ const text = $child.text().trim();
1204
+ if (!text) continue;
1205
+ const level = inferHeadingLevel($, $child);
1206
+ const heading = makeHeading(text);
1207
+ elements.push({ type: "heading", level, blockNode: heading });
1208
+ continue;
1209
+ }
1210
+ if (tagName === "p") {
1211
+ const { text, annotations } = parseInlineContent($, $child);
1212
+ if (!text) continue;
1213
+ elements.push({
1214
+ type: "content",
1215
+ blockNode: makeParagraph(text, annotations)
1216
+ });
1217
+ continue;
1218
+ }
1219
+ if (tagName === "formula") {
1220
+ const label = $child.find("label").text().trim();
1221
+ $child.find("label").remove();
1222
+ let formulaText = $child.text().trim();
1223
+ if (label) formulaText = `${formulaText} (${label})`;
1224
+ if (formulaText) {
1225
+ elements.push({
1226
+ type: "content",
1227
+ blockNode: makeMath(formulaText)
1228
+ });
1229
+ }
1230
+ continue;
1231
+ }
1232
+ if (tagName === "figure") {
1233
+ const figType = $child.attr("type");
1234
+ if (figType === "table") {
1235
+ const label2 = $child.find("head").text().trim();
1236
+ const caption2 = $child.find("figDesc").text().trim();
1237
+ const tableEl = $child.find("table");
1238
+ let tableText = "";
1239
+ if (tableEl.length) {
1240
+ tableText = formatTable($, tableEl);
1241
+ } else {
1242
+ tableText = $child.text().trim();
1243
+ }
1244
+ const header = [label2, caption2].filter(Boolean).join(": ");
1245
+ const fullText = header ? `${header}
1246
+
1247
+ ${tableText}` : tableText;
1248
+ if (fullText) {
1249
+ elements.push({
1250
+ type: "content",
1251
+ blockNode: makeCode(fullText)
1252
+ });
1253
+ }
1254
+ continue;
1255
+ }
1256
+ const figId = $child.attr("xml:id") || "";
1257
+ const label = $child.find("label").text().trim();
1258
+ const caption = $child.find("figDesc").text().trim();
1259
+ const coordsStr = $child.attr("coords");
1260
+ const coords = parseCoords(coordsStr);
1261
+ const figureInfo = { id: figId, label, caption, coords };
1262
+ let imageLink = null;
1263
+ if (opts.extractFigureImage && coords) {
1264
+ imageLink = await opts.extractFigureImage(figureInfo);
1265
+ }
1266
+ if (imageLink) {
1267
+ const captionText = [label ? `Figure ${label}` : "", caption].filter(Boolean).join(": ");
1268
+ elements.push({
1269
+ type: "content",
1270
+ blockNode: makeImage(imageLink, captionText)
1271
+ });
1272
+ } else {
1273
+ const captionText = [label ? `Figure ${label}` : "Figure", caption].filter(Boolean).join(": ");
1274
+ if (captionText) {
1275
+ elements.push({
1276
+ type: "content",
1277
+ blockNode: makeParagraph(captionText, [
1278
+ {
1279
+ type: "Italic",
1280
+ starts: [0],
1281
+ ends: [codePointLength(captionText)]
1282
+ }
1283
+ ])
1284
+ });
1285
+ }
1286
+ }
1287
+ continue;
1288
+ }
1289
+ if (tagName === "list") {
1290
+ const items = [];
1291
+ $child.find("item").each((_i, item) => {
1292
+ const { text, annotations } = parseInlineContent($, $(item));
1293
+ if (text) items.push(makeParagraph(text, annotations));
1294
+ });
1295
+ if (items.length) {
1296
+ elements.push({
1297
+ type: "content",
1298
+ blockNode: makeList(items, "Unordered")
1299
+ });
1300
+ }
1301
+ continue;
1302
+ }
1303
+ if (tagName === "note") {
1304
+ const { text, annotations } = parseInlineContent($, $child);
1305
+ if (text) {
1306
+ annotations.push({
1307
+ type: "Italic",
1308
+ starts: [0],
1309
+ ends: [codePointLength(text)]
1310
+ });
1311
+ elements.push({
1312
+ type: "content",
1313
+ blockNode: makeParagraph(text, annotations)
1314
+ });
1315
+ }
1316
+ continue;
1317
+ }
1318
+ }
1319
+ }
1320
+ await walkChildren(bodyEl);
1321
+ return elements;
1322
+ }
1323
+ function processReferences($) {
1324
+ const elements = [];
1325
+ const biblStructs = $("back listBibl biblStruct");
1326
+ if (!biblStructs.length) return elements;
1327
+ elements.push({
1328
+ type: "heading",
1329
+ level: 2,
1330
+ blockNode: makeHeading("References")
1331
+ });
1332
+ biblStructs.each((_i, bib) => {
1333
+ const formatted = formatBiblStruct($, $(bib));
1334
+ if (formatted) {
1335
+ elements.push({
1336
+ type: "content",
1337
+ blockNode: makeParagraph(`[${_i + 1}] ${formatted}`)
1338
+ });
1339
+ }
1340
+ });
1341
+ return elements;
1342
+ }
1343
+ function buildHierarchy(elements) {
1344
+ var _a;
1345
+ const blocks = [];
1346
+ const headingStack = [];
1347
+ for (const element of elements) {
1348
+ if (element.type === "heading" && element.level && element.blockNode) {
1349
+ while (headingStack.length > 0 && (((_a = headingStack[headingStack.length - 1]) == null ? void 0 : _a.level) ?? 0) >= element.level) {
1350
+ headingStack.pop();
1351
+ }
1352
+ if (headingStack.length === 0) {
1353
+ blocks.push(element.blockNode);
1354
+ } else {
1355
+ const parent = headingStack[headingStack.length - 1].blockNode;
1356
+ if (!parent.children) parent.children = [];
1357
+ parent.children.push(element.blockNode);
1358
+ }
1359
+ headingStack.push({ level: element.level, blockNode: element.blockNode });
1360
+ } else if (element.type === "content" && element.blockNode) {
1361
+ if (headingStack.length === 0) {
1362
+ blocks.push(element.blockNode);
1363
+ } else {
1364
+ const parent = headingStack[headingStack.length - 1].blockNode;
1365
+ if (!parent.children) parent.children = [];
1366
+ parent.children.push(element.blockNode);
1367
+ }
1368
+ }
1369
+ }
1370
+ return blocks;
1371
+ }
1372
+ async function teiToBlocks(teiXml, opts = {}) {
1373
+ const { load } = await import("cheerio");
1374
+ const $ = load(teiXml, { xml: true });
1375
+ const title = extractTitle($);
1376
+ const displayAuthor = extractAuthors($);
1377
+ const summary = extractAbstract($);
1378
+ const displayPublishTime = extractDate($);
1379
+ const metadata = {
1380
+ name: title,
1381
+ ...displayAuthor ? { displayAuthor } : {},
1382
+ ...summary ? { summary } : {},
1383
+ ...displayPublishTime ? { displayPublishTime } : {}
1384
+ };
1385
+ const allElements = [];
1386
+ const abstractEl = $("teiHeader profileDesc abstract");
1387
+ if (abstractEl.length) {
1388
+ allElements.push({
1389
+ type: "heading",
1390
+ level: 2,
1391
+ blockNode: makeHeading("Abstract")
1392
+ });
1393
+ abstractEl.find("p").each((_i, p) => {
1394
+ const { text, annotations } = parseInlineContent($, $(p));
1395
+ if (text) {
1396
+ allElements.push({
1397
+ type: "content",
1398
+ blockNode: makeParagraph(text, annotations)
1399
+ });
1400
+ }
1401
+ });
1402
+ if (!abstractEl.find("p").length) {
1403
+ const absText = abstractEl.text().trim();
1404
+ if (absText) {
1405
+ allElements.push({
1406
+ type: "content",
1407
+ blockNode: makeParagraph(absText)
1408
+ });
1409
+ }
1410
+ }
1411
+ }
1412
+ const body = $("text body");
1413
+ if (body.length) {
1414
+ const bodyElements = await processBodyElements($, body, opts);
1415
+ allElements.push(...bodyElements);
1416
+ }
1417
+ const ack = $('back div[type="acknowledgement"], back div[type="acknowledgment"]');
1418
+ if (ack.length) {
1419
+ allElements.push({
1420
+ type: "heading",
1421
+ level: 2,
1422
+ blockNode: makeHeading("Acknowledgements")
1423
+ });
1424
+ ack.find("p").each((_i, p) => {
1425
+ const { text, annotations } = parseInlineContent($, $(p));
1426
+ if (text) {
1427
+ allElements.push({
1428
+ type: "content",
1429
+ blockNode: makeParagraph(text, annotations)
1430
+ });
1431
+ }
1432
+ });
1433
+ }
1434
+ allElements.push(...processReferences($));
1435
+ const blocks = buildHierarchy(allElements);
1436
+ return { metadata, blocks };
1437
+ }
1438
+
1439
+ // src/grobid.ts
1440
+ var DEFAULT_GROBID_URL = "http://localhost:8070";
1441
+ async function isGrobidAvailable(grobidUrl = DEFAULT_GROBID_URL, timeoutMs = 3e3) {
1442
+ try {
1443
+ const controller = new AbortController();
1444
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1445
+ const response = await fetch(`${grobidUrl}/api/isalive`, {
1446
+ signal: controller.signal
1447
+ });
1448
+ clearTimeout(timer);
1449
+ return response.ok;
1450
+ } catch {
1451
+ return false;
1452
+ }
1453
+ }
1454
+ async function processFulltextDocument(pdfBuffer, options) {
1455
+ const baseUrl = (options == null ? void 0 : options.grobidUrl) || DEFAULT_GROBID_URL;
1456
+ const url = `${baseUrl}/api/processFulltextDocument`;
1457
+ const formData = new FormData();
1458
+ formData.append("input", new Blob([pdfBuffer]), "document.pdf");
1459
+ const coords = (options == null ? void 0 : options.teiCoordinates) || ["figure"];
1460
+ for (const coord of coords) {
1461
+ formData.append("teiCoordinates", coord);
1462
+ }
1463
+ const response = await fetch(url, {
1464
+ method: "POST",
1465
+ body: formData
1466
+ });
1467
+ if (!response.ok) {
1468
+ const body = await response.text().catch(() => "");
1469
+ throw new Error(`GROBID error (${response.status}): ${response.statusText}${body ? ` \u2014 ${body}` : ""}`);
1470
+ }
1471
+ return response.text();
1472
+ }
1473
+
1474
+ // src/pdf-to-blocks-embedded.ts
1475
+ function generateBlockId3() {
1476
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
1477
+ let id = "";
1478
+ for (let i = 0; i < 8; i++) {
1479
+ id += chars[Math.floor(Math.random() * chars.length)];
1480
+ }
1481
+ return id;
1482
+ }
1483
+ function isTextItem(item) {
1484
+ return "str" in item;
1485
+ }
1486
+ function isBoldFont(fontName) {
1487
+ const lower = fontName.toLowerCase();
1488
+ return lower.includes("bold") || lower.includes("heavy") || lower.includes("black");
1489
+ }
1490
+ function isItalicFont(fontName) {
1491
+ const lower = fontName.toLowerCase();
1492
+ return lower.includes("italic") || lower.includes("oblique") || lower.includes("slant");
1493
+ }
1494
+ function isMonospaceFont(fontName) {
1495
+ const lower = fontName.toLowerCase();
1496
+ return lower.includes("mono") || lower.includes("courier") || lower.includes("consolas") || lower.includes("menlo") || lower.includes("firacode") || lower.includes("inconsolata") || lower.includes("source code") || lower.includes("dejavu sans mono") || lower.includes("lucida console");
1497
+ }
1498
+ function makeBlock2(type, text, extra = {}) {
1499
+ return {
1500
+ block: {
1501
+ id: generateBlockId3(),
1502
+ type,
1503
+ text,
1504
+ revision: "",
1505
+ link: "",
1506
+ attributes: {},
1507
+ annotations: [],
1508
+ ...extra
1509
+ },
1510
+ children: []
1511
+ };
1512
+ }
1513
+ function makeHeading2(text, annotations = []) {
1514
+ return makeBlock2("Heading", text, {
1515
+ attributes: { childrenType: "Group" },
1516
+ annotations
1517
+ });
1518
+ }
1519
+ function makeParagraph2(text, annotations = []) {
1520
+ return makeBlock2("Paragraph", text, { annotations });
1521
+ }
1522
+ function makeCode2(text, language = "") {
1523
+ return makeBlock2("Code", text, {
1524
+ attributes: language ? { language } : {}
1525
+ });
1526
+ }
1527
+ function makeList2(items, listType) {
1528
+ const container = makeBlock2("Paragraph", "", {
1529
+ attributes: { childrenType: listType }
1530
+ });
1531
+ container.children = items;
1532
+ return container;
1533
+ }
1534
+ var MIN_HEADING_TEXT_LENGTH = 4;
1535
+ function clusterFontSizes(lines) {
1536
+ const sizeCount = /* @__PURE__ */ new Map();
1537
+ for (const line of lines) {
1538
+ if (line.runs.length === 0) continue;
1539
+ const size = Math.round(line.fontSize * 10) / 10;
1540
+ sizeCount.set(size, (sizeCount.get(size) || 0) + 1);
1541
+ }
1542
+ if (sizeCount.size === 0) {
1543
+ return { bodySize: 12, headingLevels: /* @__PURE__ */ new Map(), boldHeadingLevel: 1 };
1544
+ }
1545
+ const clusters = Array.from(sizeCount.entries()).map(([size, count]) => ({ size, count })).sort((a, b) => b.count - a.count);
1546
+ const bodySize = clusters[0].size;
1547
+ const headingSizeCount = /* @__PURE__ */ new Map();
1548
+ for (const line of lines) {
1549
+ if (line.runs.length === 0) continue;
1550
+ const text = line.runs.map((r) => r.text).join("").trim();
1551
+ if (text.length < MIN_HEADING_TEXT_LENGTH) continue;
1552
+ const size = Math.round(line.fontSize * 10) / 10;
1553
+ if (size > bodySize + 1) {
1554
+ headingSizeCount.set(size, (headingSizeCount.get(size) || 0) + 1);
1555
+ }
1556
+ }
1557
+ const headingSizes = Array.from(headingSizeCount.keys()).sort((a, b) => b - a);
1558
+ const headingLevels = /* @__PURE__ */ new Map();
1559
+ for (let i = 0; i < headingSizes.length && i < 6; i++) {
1560
+ headingLevels.set(headingSizes[i], i + 1);
1561
+ }
1562
+ const boldHeadingLevel = Math.min(headingLevels.size + 1, 6);
1563
+ return { bodySize, headingLevels, boldHeadingLevel };
1564
+ }
1565
+ var BULLET_PATTERNS = /^[\u2022\u2023\u25E6\u2043\u2219•·‣⁃○◦▪▸►–—-]\s+/;
1566
+ var NUMBERED_PATTERN = /^(\d+)[.)]\s+/;
1567
+ var LETTER_PATTERN = /^[a-zA-Z][.)]\s+/;
1568
+ function detectListType(text) {
1569
+ const bulletMatch = text.match(BULLET_PATTERNS);
1570
+ if (bulletMatch) {
1571
+ return { type: "bullet", content: text.slice(bulletMatch[0].length) };
1572
+ }
1573
+ const numberedMatch = text.match(NUMBERED_PATTERN);
1574
+ if (numberedMatch) {
1575
+ return { type: "numbered", content: text.slice(numberedMatch[0].length) };
1576
+ }
1577
+ const letterMatch = text.match(LETTER_PATTERN);
1578
+ if (letterMatch) {
1579
+ return { type: "numbered", content: text.slice(letterMatch[0].length) };
1580
+ }
1581
+ return null;
1582
+ }
1583
+ async function extractTextRuns(doc) {
1584
+ const allLines = [];
1585
+ for (let pageNum = 1; pageNum <= doc.numPages; pageNum++) {
1586
+ const page = await doc.getPage(pageNum);
1587
+ await page.getOperatorList();
1588
+ const textContent = await page.getTextContent({ includeMarkedContent: false });
1589
+ const items = textContent.items.filter(isTextItem);
1590
+ const styles = textContent.styles;
1591
+ if (items.length === 0) continue;
1592
+ const fontInfoCache = /* @__PURE__ */ new Map();
1593
+ const getFontInfo = (fontName) => {
1594
+ const cached = fontInfoCache.get(fontName);
1595
+ if (cached) return cached;
1596
+ let bold = false;
1597
+ let italic = false;
1598
+ let monospace = false;
1599
+ try {
1600
+ if (page.commonObjs.has(fontName)) {
1601
+ const fontObj = page.commonObjs.get(fontName);
1602
+ if (fontObj) {
1603
+ bold = fontObj.bold === true || fontObj.black === true;
1604
+ italic = fontObj.italic === true;
1605
+ monospace = fontObj.isMonospace === true;
1606
+ if (fontObj.name) {
1607
+ if (!monospace) monospace = isMonospaceFont(fontObj.name);
1608
+ if (!bold) bold = isBoldFont(fontObj.name);
1609
+ if (!italic) italic = isItalicFont(fontObj.name);
1610
+ }
1611
+ }
1612
+ }
1613
+ } catch {
1614
+ }
1615
+ if (!bold && !italic && !monospace) {
1616
+ const style = styles[fontName];
1617
+ if (style == null ? void 0 : style.fontFamily) {
1618
+ monospace = style.fontFamily === "monospace" || isMonospaceFont(style.fontFamily);
1619
+ }
1620
+ }
1621
+ if (!bold) bold = isBoldFont(fontName);
1622
+ if (!italic) italic = isItalicFont(fontName);
1623
+ if (!monospace) monospace = isMonospaceFont(fontName);
1624
+ const info = { bold, italic, monospace };
1625
+ fontInfoCache.set(fontName, info);
1626
+ return info;
1627
+ };
1628
+ const lineGroups = [];
1629
+ let currentLineItems = [];
1630
+ let currentY = null;
1631
+ const Y_TOLERANCE = 2;
1632
+ const sorted = [...items].sort((a, b) => {
1633
+ const yDiff = b.transform[5] - a.transform[5];
1634
+ if (Math.abs(yDiff) > Y_TOLERANCE) return yDiff;
1635
+ return a.transform[4] - b.transform[4];
1636
+ });
1637
+ for (const item of sorted) {
1638
+ const y = item.transform[5];
1639
+ if (currentY === null || Math.abs(y - currentY) > Y_TOLERANCE) {
1640
+ if (currentLineItems.length > 0) lineGroups.push(currentLineItems);
1641
+ currentLineItems = [item];
1642
+ currentY = y;
1643
+ } else {
1644
+ currentLineItems.push(item);
1645
+ }
1646
+ }
1647
+ if (currentLineItems.length > 0) lineGroups.push(currentLineItems);
1648
+ for (const group of lineGroups) {
1649
+ group.sort((a, b) => a.transform[4] - b.transform[4]);
1650
+ const runs = group.filter((item) => item.str.length > 0).map((item) => {
1651
+ const fontInfo = getFontInfo(item.fontName);
1652
+ return {
1653
+ text: item.str,
1654
+ fontSize: Math.abs(item.transform[3]),
1655
+ fontName: item.fontName,
1656
+ x: item.transform[4],
1657
+ y: item.transform[5],
1658
+ width: item.width,
1659
+ bold: fontInfo.bold,
1660
+ italic: fontInfo.italic,
1661
+ monospace: fontInfo.monospace
1662
+ };
1663
+ });
1664
+ if (runs.length === 0) continue;
1665
+ const firstRun = runs[0];
1666
+ allLines.push({
1667
+ runs,
1668
+ y: firstRun.y,
1669
+ x: firstRun.x,
1670
+ fontSize: firstRun.fontSize,
1671
+ fontName: firstRun.fontName,
1672
+ bold: runs.every((r) => r.bold),
1673
+ italic: runs.every((r) => r.italic),
1674
+ monospace: runs.every((r) => r.monospace),
1675
+ pageIndex: pageNum - 1
1676
+ });
1677
+ }
1678
+ }
1679
+ return allLines;
1680
+ }
1681
+ var BOLD_HEADING_MAX_LENGTH = 100;
1682
+ function normalizeForComparison(text) {
1683
+ return text.trim().replace(/\s+/g, " ").toLowerCase();
1684
+ }
1685
+ function groupLinesIntoParagraphs(lines, bodySize, headingLevels, boldHeadingLevel) {
1686
+ const groups = [];
1687
+ let currentGroup = null;
1688
+ for (let i = 0; i < lines.length; i++) {
1689
+ const line = lines[i];
1690
+ const roundedSize = Math.round(line.fontSize * 10) / 10;
1691
+ let headingLevel = headingLevels.get(roundedSize);
1692
+ const lineText = line.runs.map((r) => r.text).join("");
1693
+ const listInfo = detectListType(lineText);
1694
+ const isCode = line.monospace && !headingLevel;
1695
+ if (!headingLevel && !isCode && line.bold && lineText.length > 0 && lineText.length <= BOLD_HEADING_MAX_LENGTH && Math.abs(roundedSize - bodySize) <= 1.5) {
1696
+ headingLevel = boldHeadingLevel;
1697
+ }
1698
+ let lineType;
1699
+ if (headingLevel) {
1700
+ lineType = "heading";
1701
+ } else if (isCode) {
1702
+ lineType = "code";
1703
+ } else if (listInfo) {
1704
+ lineType = listInfo.type === "bullet" ? "list-bullet" : "list-numbered";
1705
+ } else {
1706
+ lineType = "paragraph";
1707
+ }
1708
+ const isPageBreak = i > 0 && lines[i - 1].pageIndex !== line.pageIndex;
1709
+ const shouldStartNew = !currentGroup || isPageBreak || lineType === "heading" || currentGroup.type === "heading" || lineType === "list-bullet" || lineType === "list-numbered" || currentGroup.type === "list-bullet" || currentGroup.type === "list-numbered" || lineType === "code" !== (currentGroup.type === "code") || i > 0 && !isPageBreak && Math.abs(lines[i - 1].y - line.y) > line.fontSize * 1.8 && lineType !== "code";
1710
+ if (shouldStartNew) {
1711
+ if (currentGroup && currentGroup.lines.length > 0) groups.push(currentGroup);
1712
+ currentGroup = { lines: [line], type: lineType, headingLevel };
1713
+ } else {
1714
+ currentGroup.lines.push(line);
1715
+ }
1716
+ }
1717
+ if (currentGroup && currentGroup.lines.length > 0) groups.push(currentGroup);
1718
+ return groups;
1719
+ }
1720
+ function runsToTextAndAnnotations(runs, allBold, allItalic) {
1721
+ const annotations = [];
1722
+ let text = "";
1723
+ for (const run of runs) {
1724
+ if (run.text.length === 0) continue;
1725
+ const start = text.length;
1726
+ text += run.text;
1727
+ const end = text.length;
1728
+ if (run.bold && !allBold) {
1729
+ annotations.push({ type: "Bold", starts: [start], ends: [end] });
1730
+ }
1731
+ if (run.italic && !allItalic) {
1732
+ annotations.push({ type: "Italic", starts: [start], ends: [end] });
1733
+ }
1734
+ if (run.monospace) {
1735
+ annotations.push({ type: "Code", starts: [start], ends: [end] });
1736
+ }
1737
+ }
1738
+ const merged = mergeAdjacentAnnotations(annotations);
1739
+ return { text, annotations: merged };
1740
+ }
1741
+ function mergeAdjacentAnnotations(annotations) {
1742
+ if (annotations.length <= 1) return annotations;
1743
+ const byType = /* @__PURE__ */ new Map();
1744
+ for (const ann of annotations) {
1745
+ const list = byType.get(ann.type) || [];
1746
+ list.push(ann);
1747
+ byType.set(ann.type, list);
1748
+ }
1749
+ const result = [];
1750
+ for (const [, anns] of Array.from(byType)) {
1751
+ anns.sort((a, b) => (a.starts[0] ?? 0) - (b.starts[0] ?? 0));
1752
+ let current = { ...anns[0], starts: [...anns[0].starts], ends: [...anns[0].ends] };
1753
+ for (let i = 1; i < anns.length; i++) {
1754
+ const next = anns[i];
1755
+ if ((current.ends[0] ?? 0) >= (next.starts[0] ?? 0)) {
1756
+ current.ends[0] = Math.max(current.ends[0] ?? 0, next.ends[0] ?? 0);
1757
+ } else {
1758
+ result.push(current);
1759
+ current = { ...next, starts: [...next.starts], ends: [...next.ends] };
1760
+ }
1761
+ }
1762
+ result.push(current);
1763
+ }
1764
+ result.sort((a, b) => (a.starts[0] ?? 0) - (b.starts[0] ?? 0));
1765
+ return result;
1766
+ }
1767
+ function buildHierarchy2(blocks) {
1768
+ const result = [];
1769
+ const stack = [];
1770
+ for (const { block, headingLevel } of blocks) {
1771
+ if (headingLevel && headingLevel > 0) {
1772
+ while (stack.length && stack[stack.length - 1].level >= headingLevel) {
1773
+ stack.pop();
1774
+ }
1775
+ const parent = stack[stack.length - 1];
1776
+ if (parent) {
1777
+ if (!parent.node.children) parent.node.children = [];
1778
+ parent.node.children.push(block);
1779
+ } else {
1780
+ result.push(block);
1781
+ }
1782
+ stack.push({ level: headingLevel, node: block });
1783
+ } else {
1784
+ const parent = stack[stack.length - 1];
1785
+ if (parent) {
1786
+ if (!parent.node.children) parent.node.children = [];
1787
+ parent.node.children.push(block);
1788
+ } else {
1789
+ result.push(block);
1790
+ }
1791
+ }
1792
+ }
1793
+ return result;
1794
+ }
1795
+ async function extractTitle2(doc) {
1796
+ var _a;
1797
+ if (doc.numPages === 0) return void 0;
1798
+ const page = await doc.getPage(1);
1799
+ await page.getOperatorList();
1800
+ const textContent = await page.getTextContent({ includeMarkedContent: false });
1801
+ const items = textContent.items.filter(isTextItem);
1802
+ if (items.length === 0) return void 0;
1803
+ let maxFontSize = 0;
1804
+ let titleItems = [];
1805
+ for (const item of items) {
1806
+ const fontSize = Math.abs(item.transform[3]);
1807
+ if (fontSize > maxFontSize + 0.5) {
1808
+ maxFontSize = fontSize;
1809
+ titleItems = [item];
1810
+ } else if (Math.abs(fontSize - maxFontSize) <= 0.5) {
1811
+ titleItems.push(item);
1812
+ }
1813
+ }
1814
+ const sizeCount = /* @__PURE__ */ new Map();
1815
+ for (const item of items) {
1816
+ const size = Math.round(Math.abs(item.transform[3]) * 10) / 10;
1817
+ sizeCount.set(size, (sizeCount.get(size) || 0) + 1);
1818
+ }
1819
+ const bodySize = ((_a = Array.from(sizeCount.entries()).sort((a, b) => b[1] - a[1])[0]) == null ? void 0 : _a[0]) || 12;
1820
+ if (maxFontSize <= bodySize + 0.5) {
1821
+ return items.slice(0, 10).map((item) => item.str).join("").trim().slice(0, 100) || void 0;
1822
+ }
1823
+ titleItems.sort((a, b) => a.transform[4] - b.transform[4]);
1824
+ return titleItems.map((item) => item.str).join("").trim() || void 0;
1825
+ }
1826
+ async function embeddedPdfToBlocks(pdfData) {
1827
+ let pdfjsLib;
1828
+ try {
1829
+ pdfjsLib = await import("pdfjs-dist");
1830
+ } catch {
1831
+ throw new Error(
1832
+ "pdfjs-dist is not installed. Install it for embedded PDF extraction:\n pnpm add pdfjs-dist\nOr provide a GROBID server URL for higher-fidelity extraction."
1833
+ );
1834
+ }
1835
+ const loadingTask = pdfjsLib.getDocument({ data: new Uint8Array(pdfData) });
1836
+ const doc = await loadingTask.promise;
1837
+ if (doc.numPages === 0) {
1838
+ return { metadata: { name: void 0 }, blocks: [] };
1839
+ }
1840
+ const title = await extractTitle2(doc);
1841
+ const lines = await extractTextRuns(doc);
1842
+ if (lines.length === 0) {
1843
+ return { metadata: { name: title }, blocks: [] };
1844
+ }
1845
+ const { bodySize, headingLevels, boldHeadingLevel } = clusterFontSizes(lines);
1846
+ const paragraphGroups = groupLinesIntoParagraphs(lines, bodySize, headingLevels, boldHeadingLevel);
1847
+ const flatBlocks = [];
1848
+ for (const group of paragraphGroups) {
1849
+ switch (group.type) {
1850
+ case "heading": {
1851
+ const allRuns = group.lines.flatMap((l) => l.runs);
1852
+ const { text, annotations } = runsToTextAndAnnotations(allRuns, true, false);
1853
+ if (text) {
1854
+ flatBlocks.push({
1855
+ block: makeHeading2(text, annotations),
1856
+ headingLevel: group.headingLevel
1857
+ });
1858
+ }
1859
+ break;
1860
+ }
1861
+ case "code": {
1862
+ const codeText = group.lines.map((l) => l.runs.map((r) => r.text).join("")).join("\n");
1863
+ if (codeText) {
1864
+ flatBlocks.push({ block: makeCode2(codeText) });
1865
+ }
1866
+ break;
1867
+ }
1868
+ case "list-bullet":
1869
+ case "list-numbered": {
1870
+ const listType = group.type === "list-bullet" ? "Unordered" : "Ordered";
1871
+ const items = [];
1872
+ for (const line of group.lines) {
1873
+ const lineText = line.runs.map((r) => r.text).join("");
1874
+ const listInfo = detectListType(lineText);
1875
+ const contentText = listInfo ? listInfo.content : lineText;
1876
+ items.push(makeParagraph2(contentText));
1877
+ }
1878
+ if (items.length) {
1879
+ flatBlocks.push({ block: makeList2(items, listType) });
1880
+ }
1881
+ break;
1882
+ }
1883
+ case "paragraph":
1884
+ default: {
1885
+ const allRuns = [];
1886
+ const allBold = group.lines.every((l) => l.bold);
1887
+ const allItalic = group.lines.every((l) => l.italic);
1888
+ for (let i = 0; i < group.lines.length; i++) {
1889
+ const line = group.lines[i];
1890
+ if (i > 0 && allRuns.length > 0) {
1891
+ const lastRun = allRuns[allRuns.length - 1];
1892
+ if (lastRun.text.endsWith("-")) {
1893
+ lastRun.text = lastRun.text.slice(0, -1);
1894
+ } else if (!lastRun.text.endsWith(" ")) {
1895
+ allRuns.push({ ...line.runs[0], text: " ", width: 0 });
1896
+ }
1897
+ }
1898
+ allRuns.push(...line.runs);
1899
+ }
1900
+ const { text, annotations } = runsToTextAndAnnotations(allRuns, allBold, allItalic);
1901
+ if (text) {
1902
+ flatBlocks.push({ block: makeParagraph2(text, annotations) });
1903
+ }
1904
+ break;
1905
+ }
1906
+ }
1907
+ }
1908
+ const PAGE_NUMBER_RE = /^\d{1,3}$/;
1909
+ for (let i = flatBlocks.length - 1; i >= 0; i--) {
1910
+ const entry = flatBlocks[i];
1911
+ if (entry.headingLevel) continue;
1912
+ const blockText = (entry.block.block.text || "").trim();
1913
+ if (PAGE_NUMBER_RE.test(blockText)) {
1914
+ flatBlocks.splice(i, 1);
1915
+ }
1916
+ }
1917
+ if (title && flatBlocks.length > 0) {
1918
+ const first = flatBlocks[0];
1919
+ if (first.headingLevel) {
1920
+ const blockText = first.block.block.text || "";
1921
+ if (normalizeForComparison(blockText) === normalizeForComparison(title)) {
1922
+ flatBlocks.shift();
1923
+ }
1924
+ }
1925
+ }
1926
+ const blocks = buildHierarchy2(flatBlocks);
1927
+ return { metadata: { name: title }, blocks };
1928
+ }
1929
+
1930
+ // src/pdf-to-blocks.ts
1931
+ async function pdfToBlocks(pdfData, options = {}) {
1932
+ const { grobidUrl, grobidTimeoutMs = 3e3, teiOptions = {}, grobidOptions = {} } = options;
1933
+ if (grobidUrl) {
1934
+ const available = await isGrobidAvailable(grobidUrl, grobidTimeoutMs);
1935
+ if (!available) {
1936
+ throw new Error(
1937
+ `GROBID server is not reachable at ${grobidUrl}.
1938
+ Start a GROBID instance with:
1939
+ docker run --rm -p 8070:8070 grobid/grobid:0.8.2-full`
1940
+ );
1941
+ }
1942
+ return useGrobidExtraction(pdfData, grobidUrl, grobidOptions, teiOptions);
1943
+ }
1944
+ return useEmbeddedExtraction(pdfData);
1945
+ }
1946
+ async function useGrobidExtraction(pdfData, grobidUrl, grobidOpts, teiOpts) {
1947
+ const teiXml = await processFulltextDocument(pdfData, {
1948
+ grobidUrl,
1949
+ teiCoordinates: grobidOpts.teiCoordinates || ["figure"]
1950
+ });
1951
+ const { metadata, blocks } = await teiToBlocks(teiXml, teiOpts);
1952
+ return { metadata, blocks, source: "grobid" };
1953
+ }
1954
+ async function useEmbeddedExtraction(pdfData) {
1955
+ const { metadata, blocks } = await embeddedPdfToBlocks(pdfData);
1956
+ return {
1957
+ metadata: {
1958
+ name: metadata.name
1959
+ },
1960
+ blocks,
1961
+ source: "embedded"
1962
+ };
1963
+ }
1964
+
1965
+ // src/file-to-ipfs.ts
1966
+ import { MemoryBlockstore } from "blockstore-core/memory";
1967
+ import { importer as unixFSImporter } from "ipfs-unixfs-importer";
1968
+ async function fileToIpfsBlobs(data) {
1969
+ const blockstore = new MemoryBlockstore();
1970
+ const results = unixFSImporter([{ content: data }], blockstore);
1971
+ const result = await results.next();
1972
+ if (!result.value) {
1973
+ throw new Error("Failed to chunk file into IPFS blocks");
1974
+ }
1975
+ const rootCid = result.value.cid.toString();
1976
+ const blobs = [];
1977
+ for await (const pair of blockstore.getAll()) {
1978
+ blobs.push({
1979
+ cid: pair.cid.toString(),
1980
+ data: pair.block
1981
+ });
1982
+ }
1983
+ return { cid: rootCid, blobs };
1984
+ }
1985
+ async function filesToIpfsBlobs(binaries) {
1986
+ const blockstore = new MemoryBlockstore();
1987
+ const resultCIDs = [];
1988
+ for (const binary of binaries) {
1989
+ const results = unixFSImporter([{ content: binary }], blockstore);
1990
+ const result = await results.next();
1991
+ if (!result.value) {
1992
+ throw new Error("Failed to chunk file into IPFS blocks");
1993
+ }
1994
+ resultCIDs.push(result.value.cid.toString());
1995
+ }
1996
+ const blobs = [];
1997
+ for await (const pair of blockstore.getAll()) {
1998
+ blobs.push({
1999
+ cid: pair.cid.toString(),
2000
+ data: pair.block
2001
+ });
2002
+ }
2003
+ return { resultCIDs, blobs };
2004
+ }
2005
+ var FILE_PROTOCOL = "file://";
2006
+ function getBlockLink(block) {
2007
+ const b = block;
2008
+ return typeof b.link === "string" ? b.link : void 0;
2009
+ }
2010
+ function setBlockLink(block, link) {
2011
+ return { ...block, link };
2012
+ }
2013
+ async function resolveFileLinksInBlocks(nodes, readFile) {
2014
+ const blockstore = new MemoryBlockstore();
2015
+ const cidMap = /* @__PURE__ */ new Map();
2016
+ const resolved = await resolveNodes(nodes, blockstore, cidMap, readFile);
2017
+ const blobs = [];
2018
+ for await (const pair of blockstore.getAll()) {
2019
+ blobs.push({
2020
+ cid: pair.cid.toString(),
2021
+ data: pair.block
2022
+ });
2023
+ }
2024
+ return { nodes: resolved, blobs };
2025
+ }
2026
+ async function resolveNodes(nodes, blockstore, cidMap, readFile) {
2027
+ const result = [];
2028
+ for (const node of nodes) {
2029
+ let resolvedBlock = node.block;
2030
+ const link = getBlockLink(resolvedBlock);
2031
+ if (link && link.startsWith(FILE_PROTOCOL)) {
2032
+ const filePath = link.slice(FILE_PROTOCOL.length);
2033
+ const ipfsUrl = await resolveFileToCid(filePath, blockstore, cidMap, readFile);
2034
+ resolvedBlock = setBlockLink(resolvedBlock, ipfsUrl);
2035
+ }
2036
+ const resolvedChildren = node.children ? await resolveNodes(node.children, blockstore, cidMap, readFile) : void 0;
2037
+ result.push({ block: resolvedBlock, children: resolvedChildren });
2038
+ }
2039
+ return result;
2040
+ }
2041
+ async function resolveFileToCid(filePath, blockstore, cidMap, readFile) {
2042
+ const cached = cidMap.get(filePath);
2043
+ if (cached) return cached;
2044
+ const data = await readFile(filePath);
2045
+ const results = unixFSImporter([{ content: data }], blockstore);
2046
+ const result = await results.next();
2047
+ if (!result.value) {
2048
+ throw new Error(`Failed to process file for IPFS: ${filePath}`);
2049
+ }
2050
+ const ipfsUrl = `ipfs://${result.value.cid.toString()}`;
2051
+ cidMap.set(filePath, ipfsUrl);
2052
+ return ipfsUrl;
2053
+ }
2054
+ function hasFileLinks(nodes) {
2055
+ for (const node of nodes) {
2056
+ const link = getBlockLink(node.block);
2057
+ if (link && link.startsWith(FILE_PROTOCOL)) {
2058
+ return true;
2059
+ }
2060
+ if (node.children && hasFileLinks(node.children)) {
2061
+ return true;
2062
+ }
2063
+ }
2064
+ return false;
2065
+ }
2066
+
2067
+ // src/markdown-to-blocks.ts
2068
+ import { parse as parseYaml } from "yaml";
2069
+ var ID_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
2070
+ function generateBlockId4() {
2071
+ let id = "";
2072
+ for (let i = 0; i < 8; i++) {
2073
+ id += ID_CHARS[Math.floor(Math.random() * ID_CHARS.length)];
2074
+ }
2075
+ return id;
2076
+ }
2077
+ function parseInlineFormatting(raw) {
2078
+ const annotations = [];
2079
+ let text = "";
2080
+ let i = 0;
2081
+ while (i < raw.length) {
2082
+ if (raw[i] === "\\" && i + 1 < raw.length) {
2083
+ text += raw[i + 1];
2084
+ i += 2;
2085
+ continue;
2086
+ }
2087
+ if (raw[i] === "!" && i + 1 < raw.length && raw[i + 1] === "[") {
2088
+ const closeBracket = findClosingBracket(raw, i + 1);
2089
+ if (closeBracket !== -1 && closeBracket + 1 < raw.length && raw[closeBracket + 1] === "(") {
2090
+ const closeParen = raw.indexOf(")", closeBracket + 2);
2091
+ if (closeParen !== -1) {
2092
+ const altText = raw.slice(i + 2, closeBracket);
2093
+ if (altText) {
2094
+ text += altText;
2095
+ }
2096
+ i = closeParen + 1;
2097
+ continue;
2098
+ }
2099
+ }
2100
+ }
2101
+ if (raw[i] === "[") {
2102
+ const closeBracket = findClosingBracket(raw, i);
2103
+ if (closeBracket !== -1 && closeBracket + 1 < raw.length && raw[closeBracket + 1] === "(") {
2104
+ const closeParen = raw.indexOf(")", closeBracket + 2);
2105
+ if (closeParen !== -1) {
2106
+ const linkText = raw.slice(i + 1, closeBracket);
2107
+ const url = raw.slice(closeBracket + 2, closeParen);
2108
+ const parsed = parseInlineFormatting(linkText);
2109
+ const start = text.length;
2110
+ text += parsed.text;
2111
+ const end = text.length;
2112
+ for (const ann of parsed.annotations) {
2113
+ annotations.push({
2114
+ ...ann,
2115
+ starts: ann.starts.map((s) => s + start),
2116
+ ends: ann.ends.map((e) => e + start)
2117
+ });
2118
+ }
2119
+ annotations.push({
2120
+ type: "Link",
2121
+ starts: [start],
2122
+ ends: [end],
2123
+ link: url
2124
+ });
2125
+ i = closeParen + 1;
2126
+ continue;
2127
+ }
2128
+ }
2129
+ }
2130
+ if (raw[i] === "*" && raw[i + 1] === "*") {
2131
+ const end = raw.indexOf("**", i + 2);
2132
+ if (end !== -1) {
2133
+ const inner = raw.slice(i + 2, end);
2134
+ const parsed = parseInlineFormatting(inner);
2135
+ const start = text.length;
2136
+ text += parsed.text;
2137
+ for (const ann of parsed.annotations) {
2138
+ annotations.push({
2139
+ ...ann,
2140
+ starts: ann.starts.map((s) => s + start),
2141
+ ends: ann.ends.map((e) => e + start)
2142
+ });
2143
+ }
2144
+ annotations.push({
2145
+ type: "Bold",
2146
+ starts: [start],
2147
+ ends: [text.length]
2148
+ });
2149
+ i = end + 2;
2150
+ continue;
2151
+ }
2152
+ }
2153
+ if (raw[i] === "*" && raw[i + 1] !== "*") {
2154
+ const end = findSingleDelimiter(raw, "*", i + 1);
2155
+ if (end !== -1) {
2156
+ const inner = raw.slice(i + 1, end);
2157
+ const parsed = parseInlineFormatting(inner);
2158
+ const start = text.length;
2159
+ text += parsed.text;
2160
+ for (const ann of parsed.annotations) {
2161
+ annotations.push({
2162
+ ...ann,
2163
+ starts: ann.starts.map((s) => s + start),
2164
+ ends: ann.ends.map((e) => e + start)
2165
+ });
2166
+ }
2167
+ annotations.push({
2168
+ type: "Italic",
2169
+ starts: [start],
2170
+ ends: [text.length]
2171
+ });
2172
+ i = end + 1;
2173
+ continue;
2174
+ }
2175
+ }
2176
+ if (raw[i] === "`") {
2177
+ const end = raw.indexOf("`", i + 1);
2178
+ if (end !== -1) {
2179
+ const start = text.length;
2180
+ text += raw.slice(i + 1, end);
2181
+ annotations.push({
2182
+ type: "Code",
2183
+ starts: [start],
2184
+ ends: [text.length]
2185
+ });
2186
+ i = end + 1;
2187
+ continue;
2188
+ }
2189
+ }
2190
+ text += raw[i];
2191
+ i++;
2192
+ }
2193
+ return { text, annotations };
2194
+ }
2195
+ function findClosingBracket(s, openPos) {
2196
+ let depth = 0;
2197
+ for (let i = openPos; i < s.length; i++) {
2198
+ if (s[i] === "[") depth++;
2199
+ if (s[i] === "]") {
2200
+ depth--;
2201
+ if (depth === 0) return i;
2202
+ }
2203
+ }
2204
+ return -1;
2205
+ }
2206
+ function findSingleDelimiter(s, delim, start) {
2207
+ for (let i = start; i < s.length; i++) {
2208
+ if (s[i] === delim && s[i + 1] !== delim && (i === 0 || s[i - 1] !== delim)) {
2209
+ return i;
2210
+ }
2211
+ }
2212
+ return -1;
2213
+ }
2214
+ var BLOCK_ID_RE = /\s*<!--\s*id:([A-Za-z0-9_-]+)\s*-->\s*$/;
2215
+ function stripBlockId(s) {
2216
+ const m = s.match(BLOCK_ID_RE);
2217
+ if (m) {
2218
+ return { text: s.slice(0, m.index).trimEnd(), id: m[1] };
2219
+ }
2220
+ return { text: s };
2221
+ }
2222
+ var STANDALONE_ID_RE = /^\s*<!--\s*id:([A-Za-z0-9_-]+)\s*-->\s*$/;
2223
+ function tokenize(markdown) {
2224
+ const lines = markdown.split("\n");
2225
+ const blocks = [];
2226
+ let i = 0;
2227
+ let pendingContainerId;
2228
+ while (i < lines.length) {
2229
+ const line = lines[i];
2230
+ if (line.trim() === "") {
2231
+ i++;
2232
+ continue;
2233
+ }
2234
+ const standaloneMatch = line.match(STANDALONE_ID_RE);
2235
+ if (standaloneMatch) {
2236
+ pendingContainerId = standaloneMatch[1];
2237
+ i++;
2238
+ continue;
2239
+ }
2240
+ if (line.trim().startsWith("```")) {
2241
+ const fenceContent = line.trim().slice(3).trim();
2242
+ const { text: language, id } = stripBlockId(fenceContent);
2243
+ const codeLines = [];
2244
+ i++;
2245
+ while (i < lines.length && !lines[i].trim().startsWith("```")) {
2246
+ codeLines.push(lines[i]);
2247
+ i++;
2248
+ }
2249
+ i++;
2250
+ blocks.push({ kind: "code", language, text: codeLines.join("\n"), id });
2251
+ pendingContainerId = void 0;
2252
+ continue;
2253
+ }
2254
+ if (line.trim().startsWith("$$")) {
2255
+ const fenceContent = line.trim().slice(2).trim();
2256
+ const { id } = stripBlockId(fenceContent);
2257
+ const mathLines = [];
2258
+ i++;
2259
+ while (i < lines.length && !lines[i].trim().startsWith("$$")) {
2260
+ mathLines.push(lines[i]);
2261
+ i++;
2262
+ }
2263
+ i++;
2264
+ blocks.push({ kind: "math", text: mathLines.join("\n"), id });
2265
+ pendingContainerId = void 0;
2266
+ continue;
2267
+ }
2268
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
2269
+ if (headingMatch) {
2270
+ const { text, id } = stripBlockId(headingMatch[2]);
2271
+ blocks.push({ kind: "heading", level: headingMatch[1].length, text, id });
2272
+ i++;
2273
+ pendingContainerId = void 0;
2274
+ continue;
2275
+ }
2276
+ const imageMatch = line.trim().match(/^!\[([^\]]*)\]\(([^)]+)\)(.*)$/);
2277
+ if (imageMatch) {
2278
+ let url = imageMatch[2];
2279
+ const trailing = imageMatch[3] || "";
2280
+ const { id } = stripBlockId(trailing);
2281
+ if (url && !url.match(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//)) {
2282
+ url = `file://${url}`;
2283
+ }
2284
+ blocks.push({ kind: "image", alt: imageMatch[1], url, id });
2285
+ i++;
2286
+ pendingContainerId = void 0;
2287
+ continue;
2288
+ }
2289
+ if (line.trim().startsWith("|")) {
2290
+ const tableLines = [];
2291
+ while (i < lines.length && lines[i].trim().startsWith("|")) {
2292
+ tableLines.push(lines[i]);
2293
+ i++;
2294
+ }
2295
+ blocks.push({ kind: "table", text: tableLines.join("\n") });
2296
+ pendingContainerId = void 0;
2297
+ continue;
2298
+ }
2299
+ if (/^[-*+]\s+/.test(line.trim())) {
2300
+ const items = [];
2301
+ while (i < lines.length && /^[-*+]\s+/.test(lines[i].trim())) {
2302
+ const raw = lines[i].trim().replace(/^[-*+]\s+/, "");
2303
+ const { text, id } = stripBlockId(raw);
2304
+ items.push({ text, id });
2305
+ i++;
2306
+ }
2307
+ blocks.push({ kind: "ul", items, containerId: pendingContainerId });
2308
+ pendingContainerId = void 0;
2309
+ continue;
2310
+ }
2311
+ if (/^\d+\.\s+/.test(line.trim())) {
2312
+ const items = [];
2313
+ while (i < lines.length && /^\d+\.\s+/.test(lines[i].trim())) {
2314
+ const raw = lines[i].trim().replace(/^\d+\.\s+/, "");
2315
+ const { text, id } = stripBlockId(raw);
2316
+ items.push({ text, id });
2317
+ i++;
2318
+ }
2319
+ blocks.push({ kind: "ol", items, containerId: pendingContainerId });
2320
+ pendingContainerId = void 0;
2321
+ continue;
2322
+ }
2323
+ const paraLines = [];
2324
+ while (i < lines.length && lines[i].trim() !== "" && !lines[i].trim().startsWith("```") && !lines[i].trim().startsWith("$$") && !lines[i].trim().startsWith("#") && !lines[i].trim().startsWith("|") && !lines[i].match(STANDALONE_ID_RE) && !/^!\[([^\]]*)\]\(([^)]+)\)/.test(lines[i].trim()) && !/^[-*+]\s+/.test(lines[i].trim()) && !/^\d+\.\s+/.test(lines[i].trim())) {
2325
+ paraLines.push(lines[i]);
2326
+ i++;
2327
+ }
2328
+ if (paraLines.length > 0) {
2329
+ const firstLine = paraLines[0];
2330
+ const { text: cleanFirst, id } = stripBlockId(firstLine);
2331
+ paraLines[0] = cleanFirst;
2332
+ blocks.push({ kind: "paragraph", text: paraLines.join("\n"), id });
2333
+ pendingContainerId = void 0;
2334
+ }
2335
+ }
2336
+ return blocks;
2337
+ }
2338
+ function makeBlockNode(block, children = []) {
2339
+ return { block, children };
2340
+ }
2341
+ function createParagraphNode(rawText, id) {
2342
+ const { text, annotations } = parseInlineFormatting(rawText);
2343
+ return makeBlockNode({
2344
+ type: "Paragraph",
2345
+ id: id || generateBlockId4(),
2346
+ text,
2347
+ annotations
2348
+ });
2349
+ }
2350
+ function createHeadingNode(rawText, id) {
2351
+ const { text, annotations } = parseInlineFormatting(rawText);
2352
+ return makeBlockNode(
2353
+ {
2354
+ type: "Heading",
2355
+ id: id || generateBlockId4(),
2356
+ text,
2357
+ annotations,
2358
+ childrenType: "Group"
2359
+ },
2360
+ []
2361
+ );
2362
+ }
2363
+ function createCodeNode(text, language, id) {
2364
+ return makeBlockNode({
2365
+ type: "Code",
2366
+ id: id || generateBlockId4(),
2367
+ text,
2368
+ annotations: [],
2369
+ language: language || void 0
2370
+ });
2371
+ }
2372
+ function createMathNode(text, id) {
2373
+ return makeBlockNode({
2374
+ type: "Math",
2375
+ id: id || generateBlockId4(),
2376
+ text,
2377
+ annotations: []
2378
+ });
2379
+ }
2380
+ function createImageNode(alt, url, id) {
2381
+ return makeBlockNode({
2382
+ type: "Image",
2383
+ id: id || generateBlockId4(),
2384
+ text: alt,
2385
+ annotations: [],
2386
+ link: url
2387
+ });
2388
+ }
2389
+ function createListNode(items, listType, containerId) {
2390
+ const children = items.map((item) => createParagraphNode(item.text, item.id));
2391
+ const container = makeBlockNode(
2392
+ {
2393
+ type: "Paragraph",
2394
+ id: containerId || generateBlockId4(),
2395
+ text: "",
2396
+ annotations: [],
2397
+ childrenType: listType
2398
+ },
2399
+ children
2400
+ );
2401
+ return [container];
2402
+ }
2403
+ function coerceString(value) {
2404
+ if (value == null) return void 0;
2405
+ if (value instanceof Date) {
2406
+ return value.toISOString().split("T")[0];
2407
+ }
2408
+ if (typeof value === "string") return value;
2409
+ return String(value);
2410
+ }
2411
+ var METADATA_STRING_KEYS = [
2412
+ "name",
2413
+ "summary",
2414
+ "displayAuthor",
2415
+ "displayPublishTime",
2416
+ "icon",
2417
+ "cover",
2418
+ "siteUrl",
2419
+ "layout",
2420
+ "seedExperimentalLogo",
2421
+ "importCategories",
2422
+ "importTags"
2423
+ ];
2424
+ var METADATA_BOOLEAN_KEYS = ["showOutline", "showActivity"];
2425
+ var METADATA_ENUM_KEYS = ["seedExperimentalHomeOrder", "contentWidth"];
2426
+ function parseFrontmatter(markdown) {
2427
+ const trimmed = markdown.trimStart();
2428
+ if (!trimmed.startsWith("---")) {
2429
+ return { content: markdown, metadata: {} };
2430
+ }
2431
+ const endIndex = trimmed.indexOf("\n---", 3);
2432
+ if (endIndex === -1) {
2433
+ return { content: markdown, metadata: {} };
2434
+ }
2435
+ const yamlBlock = trimmed.slice(3, endIndex).trim();
2436
+ const rest = trimmed.slice(endIndex + 4);
2437
+ const metadata = {};
2438
+ try {
2439
+ const parsed = parseYaml(yamlBlock);
2440
+ if (!parsed || typeof parsed !== "object") {
2441
+ return { content: rest, metadata };
2442
+ }
2443
+ for (const key of METADATA_STRING_KEYS) {
2444
+ const val = coerceString(parsed[key]);
2445
+ if (val !== void 0) metadata[key] = val;
2446
+ }
2447
+ if (!metadata.name && parsed["title"]) {
2448
+ const val = coerceString(parsed["title"]);
2449
+ if (val !== void 0) metadata.name = val;
2450
+ }
2451
+ for (const key of METADATA_BOOLEAN_KEYS) {
2452
+ if (parsed[key] != null) metadata[key] = Boolean(parsed[key]);
2453
+ }
2454
+ for (const key of METADATA_ENUM_KEYS) {
2455
+ const val = coerceString(parsed[key]);
2456
+ if (val !== void 0) metadata[key] = val;
2457
+ }
2458
+ if (parsed["theme"] && typeof parsed["theme"] === "object") {
2459
+ const themeInput = parsed["theme"];
2460
+ const theme = {};
2461
+ const hl = themeInput["headerLayout"];
2462
+ if (hl === "Center" || hl === "") theme.headerLayout = hl;
2463
+ if (Object.keys(theme).length > 0) metadata.theme = theme;
2464
+ }
2465
+ } catch {
2466
+ return { content: markdown, metadata: {} };
2467
+ }
2468
+ return { content: rest, metadata };
2469
+ }
2470
+ function parseMarkdown(markdown) {
2471
+ const { content, metadata } = parseFrontmatter(markdown);
2472
+ const tokens = tokenize(content);
2473
+ const rootNodes = [];
2474
+ const headingStack = [];
2475
+ function addToCurrentParent(node) {
2476
+ const nodes = Array.isArray(node) ? node : [node];
2477
+ if (headingStack.length === 0) {
2478
+ rootNodes.push(...nodes);
2479
+ } else {
2480
+ const parent = headingStack[headingStack.length - 1].node;
2481
+ parent.children.push(...nodes);
2482
+ }
2483
+ }
2484
+ for (const token of tokens) {
2485
+ switch (token.kind) {
2486
+ case "heading": {
2487
+ const headingNode = createHeadingNode(token.text, token.id);
2488
+ while (headingStack.length > 0 && headingStack[headingStack.length - 1].level >= token.level) {
2489
+ headingStack.pop();
2490
+ }
2491
+ addToCurrentParent(headingNode);
2492
+ headingStack.push({ level: token.level, node: headingNode });
2493
+ break;
2494
+ }
2495
+ case "paragraph":
2496
+ addToCurrentParent(createParagraphNode(token.text, token.id));
2497
+ break;
2498
+ case "code":
2499
+ addToCurrentParent(createCodeNode(token.text, token.language, token.id));
2500
+ break;
2501
+ case "math":
2502
+ addToCurrentParent(createMathNode(token.text, token.id));
2503
+ break;
2504
+ case "image":
2505
+ addToCurrentParent(createImageNode(token.alt, token.url, token.id));
2506
+ break;
2507
+ case "table":
2508
+ addToCurrentParent(createCodeNode(token.text, "", token.id));
2509
+ break;
2510
+ case "ul":
2511
+ addToCurrentParent(createListNode(token.items, "Unordered", token.containerId));
2512
+ break;
2513
+ case "ol":
2514
+ addToCurrentParent(createListNode(token.items, "Ordered", token.containerId));
2515
+ break;
2516
+ }
2517
+ }
2518
+ return { tree: rootNodes, metadata };
2519
+ }
2520
+ function flattenToOperations(tree, parentId = "") {
2521
+ const ops = [];
2522
+ const blockIds = [];
2523
+ for (const node of tree) {
2524
+ const block = {
2525
+ type: node.block.type,
2526
+ id: node.block.id,
2527
+ text: node.block.text,
2528
+ annotations: node.block.annotations
2529
+ };
2530
+ if (node.block.language !== void 0) {
2531
+ block["language"] = node.block.language;
2532
+ }
2533
+ if (node.block.childrenType !== void 0) {
2534
+ block["childrenType"] = node.block.childrenType;
2535
+ }
2536
+ if (node.block.link !== void 0) {
2537
+ block["link"] = node.block.link;
2538
+ }
2539
+ ops.push({ type: "ReplaceBlock", block });
2540
+ blockIds.push(node.block.id);
2541
+ if (node.children.length > 0) {
2542
+ ops.push(...flattenToOperations(node.children, node.block.id));
2543
+ }
2544
+ }
2545
+ if (blockIds.length > 0) {
2546
+ ops.push({ type: "MoveBlocks", blocks: blockIds, parent: parentId });
2547
+ }
2548
+ return ops;
2549
+ }
2550
+
2551
+ // src/blocks-to-markdown.ts
2552
+ var FM_STRING_KEYS = [
2553
+ "name",
2554
+ "summary",
2555
+ "displayAuthor",
2556
+ "displayPublishTime",
2557
+ "icon",
2558
+ "cover",
2559
+ "siteUrl",
2560
+ "layout",
2561
+ "seedExperimentalLogo",
2562
+ "seedExperimentalHomeOrder",
2563
+ "contentWidth",
2564
+ "importCategories",
2565
+ "importTags"
2566
+ ];
2567
+ var FM_BOOLEAN_KEYS = ["showOutline", "showActivity"];
2568
+ function emitFrontmatter(metadata) {
2569
+ const lines = [];
2570
+ for (const key of FM_STRING_KEYS) {
2571
+ const val = metadata[key];
2572
+ if (val !== void 0 && val !== "") {
2573
+ lines.push(`${key}: ${JSON.stringify(val)}`);
2574
+ }
2575
+ }
2576
+ for (const key of FM_BOOLEAN_KEYS) {
2577
+ const val = metadata[key];
2578
+ if (val !== void 0) {
2579
+ lines.push(`${key}: ${val}`);
2580
+ }
2581
+ }
2582
+ if (metadata.theme && typeof metadata.theme === "object") {
2583
+ const entries = [];
2584
+ if (metadata.theme.headerLayout !== void 0) {
2585
+ entries.push(` headerLayout: ${JSON.stringify(metadata.theme.headerLayout)}`);
2586
+ }
2587
+ if (entries.length > 0) {
2588
+ lines.push("theme:");
2589
+ lines.push(...entries);
2590
+ }
2591
+ }
2592
+ if (lines.length === 0) return "---\n---\n";
2593
+ return "---\n" + lines.join("\n") + "\n---\n";
2594
+ }
2595
+ function idComment(id) {
2596
+ return `<!-- id:${id} -->`;
2597
+ }
2598
+ function appendIdToFirstLine(md, id) {
2599
+ const newline = md.indexOf("\n");
2600
+ if (newline === -1) {
2601
+ return md ? `${md} ${idComment(id)}` : idComment(id);
2602
+ }
2603
+ return `${md.slice(0, newline)} ${idComment(id)}${md.slice(newline)}`;
2604
+ }
2605
+ function blocksToMarkdown(doc, options) {
2606
+ const opts = { ipfsGateway: true, ...options };
2607
+ const lines = [];
2608
+ lines.push(emitFrontmatter(doc.metadata || {}));
2609
+ for (const node of doc.content) {
2610
+ const blockMd = blockNodeToMarkdown(node, 0, opts);
2611
+ if (blockMd) {
2612
+ lines.push(blockMd);
2613
+ }
2614
+ }
2615
+ return lines.join("\n");
2616
+ }
2617
+ function blockNodeToMarkdown(node, depth, opts) {
2618
+ var _a;
2619
+ const block = node.block;
2620
+ const children = node.children || [];
2621
+ const childrenType = (_a = block.attributes) == null ? void 0 : _a.childrenType;
2622
+ const isListContainer = block.type === "Paragraph" && !block.text && (childrenType === "Ordered" || childrenType === "Unordered" || childrenType === "Blockquote");
2623
+ let result;
2624
+ if (isListContainer) {
2625
+ result = idComment(block.id);
2626
+ } else {
2627
+ result = blockToMarkdown(block, depth, opts);
2628
+ }
2629
+ for (const child of children) {
2630
+ const childMd = blockNodeToMarkdown(child, depth + 1, opts);
2631
+ if (childMd) {
2632
+ if (childrenType === "Ordered") {
2633
+ result += "\n" + indent(depth + 1) + "1. " + childMd.trim();
2634
+ } else if (childrenType === "Unordered") {
2635
+ result += "\n" + indent(depth + 1) + "- " + childMd.trim();
2636
+ } else if (childrenType === "Blockquote") {
2637
+ result += "\n" + indent(depth + 1) + "> " + childMd.trim();
2638
+ } else {
2639
+ result += "\n" + childMd;
2640
+ }
2641
+ }
2642
+ }
2643
+ return result;
2644
+ }
2645
+ function blockToMarkdown(block, depth, opts) {
2646
+ var _a, _b;
2647
+ const ind = indent(depth);
2648
+ const b = block;
2649
+ const text = b.text || "";
2650
+ const link = b.link || "";
2651
+ const annotations = b.annotations;
2652
+ const id = b.id;
2653
+ switch (block.type) {
2654
+ case "Paragraph": {
2655
+ const rendered = ind + applyAnnotations(text, annotations);
2656
+ return appendIdToFirstLine(rendered, id);
2657
+ }
2658
+ case "Heading": {
2659
+ const level = Math.min(depth + 1, 6);
2660
+ const hashes = "#".repeat(level);
2661
+ const rendered = `${hashes} ${applyAnnotations(text, annotations)}`;
2662
+ return appendIdToFirstLine(rendered, id);
2663
+ }
2664
+ case "Code": {
2665
+ const lang = ((_a = b.attributes) == null ? void 0 : _a.language) || "";
2666
+ return ind + "```" + lang + " " + idComment(id) + "\n" + ind + text + "\n" + ind + "```";
2667
+ }
2668
+ case "Math": {
2669
+ return ind + "$$ " + idComment(id) + "\n" + ind + text + "\n" + ind + "$$";
2670
+ }
2671
+ case "Image": {
2672
+ const altText = text || "image";
2673
+ const imgUrl = formatMediaUrl(link, opts.ipfsGateway);
2674
+ return ind + `![${altText}](${imgUrl}) ${idComment(id)}`;
2675
+ }
2676
+ case "Video": {
2677
+ const videoUrl = formatMediaUrl(link, opts.ipfsGateway);
2678
+ return ind + `[Video](${videoUrl}) ${idComment(id)}`;
2679
+ }
2680
+ case "File": {
2681
+ const fileName = ((_b = b.attributes) == null ? void 0 : _b.name) || "file";
2682
+ const fileUrl = formatMediaUrl(link, opts.ipfsGateway);
2683
+ return ind + `[${fileName}](${fileUrl}) ${idComment(id)}`;
2684
+ }
2685
+ case "Embed": {
2686
+ return ind + `> [Embed: ${link}](${link}) ${idComment(id)}`;
2687
+ }
2688
+ case "WebEmbed": {
2689
+ return ind + `[Web Embed](${link}) ${idComment(id)}`;
2690
+ }
2691
+ case "Button": {
2692
+ const buttonText = text || "Button";
2693
+ return ind + `[${buttonText}](${link}) ${idComment(id)}`;
2694
+ }
2695
+ case "Query": {
2696
+ return ind + `<!-- Query block --> ${idComment(id)}`;
2697
+ }
2698
+ case "Nostr": {
2699
+ return ind + `[Nostr: ${link}](${link}) ${idComment(id)}`;
2700
+ }
2701
+ default: {
2702
+ if (text) {
2703
+ return appendIdToFirstLine(ind + text, id);
2704
+ }
2705
+ return "";
2706
+ }
2707
+ }
2708
+ }
2709
+ function applyAnnotations(text, annotations) {
2710
+ if (!annotations || annotations.length === 0) {
2711
+ return text;
2712
+ }
2713
+ const markers = [];
2714
+ for (const ann of annotations) {
2715
+ const starts = ann.starts || [];
2716
+ const ends = ann.ends || [];
2717
+ for (let i = 0; i < starts.length; i++) {
2718
+ markers.push({ pos: starts[i], type: "open", annotation: ann });
2719
+ if (ends[i] !== void 0) {
2720
+ markers.push({ pos: ends[i], type: "close", annotation: ann });
2721
+ }
2722
+ }
2723
+ }
2724
+ markers.sort((a, b) => {
2725
+ if (a.pos !== b.pos) return a.pos - b.pos;
2726
+ return a.type === "open" ? -1 : 1;
2727
+ });
2728
+ let result = "";
2729
+ let lastPos = 0;
2730
+ for (const marker of markers) {
2731
+ result += text.slice(lastPos, marker.pos);
2732
+ lastPos = marker.pos;
2733
+ result += getAnnotationMarker(marker.annotation, marker.type);
2734
+ }
2735
+ result += text.slice(lastPos);
2736
+ result = result.replace(/\uFFFC/g, "");
2737
+ return result;
2738
+ }
2739
+ function getAnnotationMarker(ann, type) {
2740
+ switch (ann.type) {
2741
+ case "Bold":
2742
+ return "**";
2743
+ case "Italic":
2744
+ return "_";
2745
+ case "Strike":
2746
+ return "~~";
2747
+ case "Code":
2748
+ return "`";
2749
+ case "Underline":
2750
+ return type === "open" ? "<u>" : "</u>";
2751
+ case "Link":
2752
+ if (type === "open") {
2753
+ return "[";
2754
+ } else {
2755
+ return `](${ann.link || ""})`;
2756
+ }
2757
+ case "Embed": {
2758
+ const link = "link" in ann ? ann.link || "" : "";
2759
+ if (type === "open") {
2760
+ const pathPart = link.includes("/") ? link.split("/").pop() || "embed" : link.replace("hm://", "").slice(0, 12);
2761
+ return `[\u2197 ${pathPart}`;
2762
+ } else {
2763
+ return `](${link})`;
2764
+ }
2765
+ }
2766
+ default:
2767
+ return "";
2768
+ }
2769
+ }
2770
+ function formatMediaUrl(url, useGateway) {
2771
+ if (useGateway && url.startsWith("ipfs://")) {
2772
+ const cid = url.slice(7);
2773
+ return `https://ipfs.io/ipfs/${cid}`;
2774
+ }
2775
+ return url;
2776
+ }
2777
+ function indent(depth) {
2778
+ return " ".repeat(depth);
2779
+ }
2780
+
2781
+ // src/block-diff.ts
2782
+ function createBlocksMap(nodes, parentId = "") {
2783
+ const result = {};
2784
+ nodes.forEach((node, idx) => {
2785
+ var _a, _b, _c;
2786
+ if (!((_a = node.block) == null ? void 0 : _a.id)) return;
2787
+ const prev = idx > 0 ? nodes[idx - 1] : void 0;
2788
+ const prevId = ((_b = prev == null ? void 0 : prev.block) == null ? void 0 : _b.id) || "";
2789
+ result[node.block.id] = {
2790
+ parent: parentId,
2791
+ left: prevId,
2792
+ block: node.block
2793
+ };
2794
+ if ((_c = node.children) == null ? void 0 : _c.length) {
2795
+ Object.assign(result, createBlocksMap(node.children, node.block.id));
2796
+ }
2797
+ });
2798
+ return result;
2799
+ }
2800
+ function matchBlockIds(oldNodes, newNodes) {
2801
+ return newNodes.map((newNode, idx) => {
2802
+ const oldNode = idx < oldNodes.length ? oldNodes[idx] : void 0;
2803
+ let matchedId = newNode.block.id;
2804
+ if (oldNode && oldNode.block.type === newNode.block.type) {
2805
+ matchedId = oldNode.block.id;
2806
+ }
2807
+ const matchedChildren = matchBlockIds((oldNode == null ? void 0 : oldNode.children) ?? [], newNode.children);
2808
+ return {
2809
+ block: { ...newNode.block, id: matchedId },
2810
+ children: matchedChildren
2811
+ };
2812
+ });
2813
+ }
2814
+ function computeReplaceOps(oldMap, matchedTree, parentId = "") {
2815
+ const ops = [];
2816
+ const touchedIds = /* @__PURE__ */ new Set();
2817
+ const blockIdsAtLevel = [];
2818
+ matchedTree.forEach((node, idx) => {
2819
+ const blockId = node.block.id;
2820
+ touchedIds.add(blockId);
2821
+ blockIdsAtLevel.push(blockId);
2822
+ const oldEntry = oldMap[blockId];
2823
+ const isNew = !oldEntry;
2824
+ const block = {
2825
+ type: node.block.type,
2826
+ id: blockId,
2827
+ text: node.block.text,
2828
+ annotations: node.block.annotations
2829
+ };
2830
+ if (node.block.language !== void 0) {
2831
+ block.language = node.block.language;
2832
+ }
2833
+ if (node.block.childrenType !== void 0) {
2834
+ block.childrenType = node.block.childrenType;
2835
+ }
2836
+ if (isNew) {
2837
+ ops.push({ type: "ReplaceBlock", block });
2838
+ } else {
2839
+ if (!isBlockContentEqual(oldEntry.block, node.block)) {
2840
+ ops.push({ type: "ReplaceBlock", block });
2841
+ }
2842
+ const prevNode = idx > 0 ? matchedTree[idx - 1] : void 0;
2843
+ const expectedLeft = (prevNode == null ? void 0 : prevNode.block.id) ?? "";
2844
+ if (oldEntry.parent !== parentId || oldEntry.left !== expectedLeft) {
2845
+ }
2846
+ }
2847
+ if (node.children.length > 0) {
2848
+ const childOps = computeReplaceOps(oldMap, node.children, blockId);
2849
+ ops.push(...childOps);
2850
+ collectIds(node.children, touchedIds);
2851
+ }
2852
+ });
2853
+ if (blockIdsAtLevel.length > 0) {
2854
+ ops.push({
2855
+ type: "MoveBlocks",
2856
+ blocks: blockIdsAtLevel,
2857
+ parent: parentId
2858
+ });
2859
+ }
2860
+ const deletedIds = [];
2861
+ for (const [id, entry] of Object.entries(oldMap)) {
2862
+ if (entry.parent === parentId && !touchedIds.has(id)) {
2863
+ deletedIds.push(id);
2864
+ }
2865
+ }
2866
+ if (deletedIds.length > 0) {
2867
+ ops.push({ type: "DeleteBlocks", blocks: deletedIds });
2868
+ }
2869
+ return ops;
2870
+ }
2871
+ function collectIds(nodes, set) {
2872
+ for (const node of nodes) {
2873
+ set.add(node.block.id);
2874
+ collectIds(node.children, set);
2875
+ }
2876
+ }
2877
+ function isBlockContentEqual(old, newBlock) {
2878
+ if (old.type !== newBlock.type) return false;
2879
+ if ((old.text || "") !== (newBlock.text || "")) return false;
2880
+ const oldAnn = old.annotations || [];
2881
+ const newAnn = newBlock.annotations || [];
2882
+ if (oldAnn.length !== newAnn.length) return false;
2883
+ if (oldAnn.length > 0 && JSON.stringify(oldAnn) !== JSON.stringify(newAnn)) {
2884
+ return false;
2885
+ }
2886
+ const oldAttrs = old.attributes || {};
2887
+ if ((oldAttrs.childrenType || "") !== (newBlock.childrenType || "")) {
2888
+ return false;
2889
+ }
2890
+ if ((oldAttrs.language || "") !== (newBlock.language || "")) return false;
2891
+ return true;
2892
+ }
2893
+ function hmBlockNodeToBlockNode(node) {
2894
+ const b = node.block;
2895
+ const attrs = b.attributes || {};
2896
+ const block = {
2897
+ type: b.type || "",
2898
+ id: b.id || "",
2899
+ text: b.text || "",
2900
+ annotations: b.annotations || [],
2901
+ ...attrs.childrenType ? { childrenType: attrs.childrenType } : {},
2902
+ ...attrs.language ? { language: attrs.language } : {},
2903
+ ...b.link ? { link: b.link } : {}
2904
+ };
2905
+ return {
2906
+ block,
2907
+ children: (node.children || []).map(hmBlockNodeToBlockNode)
2908
+ };
2909
+ }
2910
+
2911
+ // src/document-state.ts
2912
+ async function resolveDocumentState(client, targetId) {
2913
+ const unpacked = unpackHmId(targetId);
2914
+ if (!unpacked) throw new Error(`Invalid ID: ${targetId}`);
2915
+ const changesResp = await client.request("ListChanges", { targetId: unpacked });
2916
+ if (!changesResp.changes || changesResp.changes.length === 0) {
2917
+ throw new Error(`No changes found for ${targetId}. Document may not exist.`);
2918
+ }
2919
+ const depthMap = computeDepths(changesResp);
2920
+ let genesis = "";
2921
+ for (const change of changesResp.changes) {
2922
+ if (!change.deps || change.deps.length === 0) {
2923
+ genesis = change.id;
2924
+ break;
2925
+ }
2926
+ }
2927
+ if (!genesis) {
2928
+ throw new Error("Could not find genesis change (change with no deps).");
2929
+ }
2930
+ const allDeps = /* @__PURE__ */ new Set();
2931
+ for (const change of changesResp.changes) {
2932
+ if (change.deps) {
2933
+ for (const dep of change.deps) {
2934
+ allDeps.add(dep);
2935
+ }
2936
+ }
2937
+ }
2938
+ const heads = [];
2939
+ for (const change of changesResp.changes) {
2940
+ if (change.id && !allDeps.has(change.id)) {
2941
+ heads.push(change.id);
2942
+ }
2943
+ }
2944
+ if (heads.length === 0) {
2945
+ throw new Error("Could not determine document heads.");
2946
+ }
2947
+ let maxHeadDepth = 0;
2948
+ for (const head of heads) {
2949
+ const d = depthMap.get(head) ?? 0;
2950
+ if (d > maxHeadDepth) maxHeadDepth = d;
2951
+ }
2952
+ const version = heads.join(".");
2953
+ return {
2954
+ genesis,
2955
+ heads,
2956
+ headDepth: maxHeadDepth,
2957
+ version
2958
+ };
2959
+ }
2960
+ function computeDepths(changesResp) {
2961
+ const depthMap = /* @__PURE__ */ new Map();
2962
+ const depsMap = /* @__PURE__ */ new Map();
2963
+ const dependents = /* @__PURE__ */ new Map();
2964
+ for (const change of changesResp.changes) {
2965
+ const id = change.id;
2966
+ const deps = change.deps ?? [];
2967
+ depsMap.set(id, deps);
2968
+ for (const dep of deps) {
2969
+ const existing = dependents.get(dep) ?? [];
2970
+ existing.push(id);
2971
+ dependents.set(dep, existing);
2972
+ }
2973
+ }
2974
+ const queue = [];
2975
+ for (const change of changesResp.changes) {
2976
+ const deps = change.deps ?? [];
2977
+ if (deps.length === 0) {
2978
+ depthMap.set(change.id, 0);
2979
+ queue.push(change.id);
2980
+ }
2981
+ }
2982
+ while (queue.length > 0) {
2983
+ const current = queue.shift();
2984
+ const children = dependents.get(current) ?? [];
2985
+ for (const child of children) {
2986
+ const childDeps = depsMap.get(child) ?? [];
2987
+ let allResolved = true;
2988
+ let maxDepDepth = 0;
2989
+ for (const dep of childDeps) {
2990
+ const d = depthMap.get(dep);
2991
+ if (d === void 0) {
2992
+ allResolved = false;
2993
+ break;
2994
+ }
2995
+ if (d > maxDepDepth) maxDepDepth = d;
2996
+ }
2997
+ if (allResolved && !depthMap.has(child)) {
2998
+ depthMap.set(child, maxDepDepth + 1);
2999
+ queue.push(child);
3000
+ }
3001
+ }
3002
+ }
3003
+ return depthMap;
3004
+ }
3005
+
3006
+ // src/auto-link.ts
3007
+ import { CID as CID4 } from "multiformats/cid";
3008
+ function documentContainsLinkToChild(document, childId) {
3009
+ const childUrlNormalized = packHmId({ ...childId, version: null, latest: null });
3010
+ function searchBlocks(nodes) {
3011
+ for (const node of nodes) {
3012
+ if (node.block.type === "Embed") {
3013
+ const linkId = unpackHmId(node.block.link);
3014
+ if (linkId) {
3015
+ const linkUrl = packHmId({ ...linkId, version: null, latest: null });
3016
+ if (linkUrl === childUrlNormalized) return true;
3017
+ }
3018
+ }
3019
+ const annotations = "annotations" in node.block ? node.block.annotations : void 0;
3020
+ if (annotations) {
3021
+ for (const ann of annotations) {
3022
+ if (ann.type === "Link" || ann.type === "Embed") {
3023
+ const linkId = unpackHmId(ann.link);
3024
+ if (linkId) {
3025
+ const linkUrl = packHmId({ ...linkId, version: null, latest: null });
3026
+ if (linkUrl === childUrlNormalized) return true;
3027
+ }
3028
+ }
3029
+ }
3030
+ }
3031
+ if (node.children && searchBlocks(node.children)) return true;
3032
+ }
3033
+ return false;
3034
+ }
3035
+ return searchBlocks(document.content || []);
3036
+ }
3037
+ function documentHasSelfQuery(document, documentId) {
3038
+ var _a;
3039
+ const documentPathWithSlash = hmIdPathToEntityQueryPath(documentId.path);
3040
+ const documentPathWithoutSlash = ((_a = documentId.path) == null ? void 0 : _a.join("/")) || "";
3041
+ function searchBlocks(nodes) {
3042
+ var _a2;
3043
+ for (const node of nodes) {
3044
+ if (node.block.type === "Query") {
3045
+ const query = (_a2 = node.block.attributes) == null ? void 0 : _a2.query;
3046
+ if (query == null ? void 0 : query.includes) {
3047
+ for (const inc of query.includes) {
3048
+ const isSpaceMatch = !inc.space || inc.space === documentId.uid;
3049
+ const isPathMatch = !inc.path || inc.path === documentPathWithSlash || inc.path === documentPathWithoutSlash;
3050
+ if (isSpaceMatch && isPathMatch) return true;
3051
+ }
3052
+ }
3053
+ }
3054
+ if (node.children && searchBlocks(node.children)) return true;
3055
+ }
3056
+ return false;
3057
+ }
3058
+ return searchBlocks(document.content || []);
3059
+ }
3060
+ function shouldAutoLinkParent(isPrivate, parentDocument, editableLocation, parentId) {
3061
+ if (isPrivate) return false;
3062
+ if (parentDocument) {
3063
+ if (documentContainsLinkToChild(parentDocument, editableLocation)) {
3064
+ return false;
3065
+ }
3066
+ if (documentHasSelfQuery(parentDocument, parentId)) {
3067
+ return false;
3068
+ }
3069
+ }
3070
+ return true;
3071
+ }
3072
+ function createAutoLinkOps(parentDocument, childHmUrl, newBlockId) {
3073
+ const existingRootBlockIds = (parentDocument.content || []).map((node) => node.block.id).filter(Boolean);
3074
+ const embedBlock = {
3075
+ id: newBlockId,
3076
+ type: "Embed",
3077
+ link: childHmUrl,
3078
+ attributes: { view: "Card" }
3079
+ };
3080
+ return [
3081
+ { type: "ReplaceBlock", block: embedBlock },
3082
+ { type: "MoveBlocks", blocks: [...existingRootBlockIds, newBlockId], parent: "" }
3083
+ ];
3084
+ }
3085
+ async function autoLinkChildToParent(opts) {
3086
+ const { client, account, path, childHmUrl, signer } = opts;
3087
+ const segments = path.replace(/^\//, "").split("/");
3088
+ if (segments.length <= 1) return false;
3089
+ const parentSegments = segments.slice(0, -1);
3090
+ const parentPath = "/" + parentSegments.join("/");
3091
+ const parentId = unpackHmId(`hm://${account}${parentPath}`);
3092
+ if (!parentId) return false;
3093
+ const childId = unpackHmId(childHmUrl);
3094
+ if (!childId) return false;
3095
+ let parentDocument = null;
3096
+ try {
3097
+ const resource = await client.request("Resource", { ...parentId, latest: true });
3098
+ if (resource.type === "document") {
3099
+ parentDocument = resource.document;
3100
+ }
3101
+ } catch {
3102
+ return false;
3103
+ }
3104
+ if (!parentDocument) return false;
3105
+ if (!shouldAutoLinkParent(false, parentDocument, childId, parentId)) {
3106
+ return false;
3107
+ }
3108
+ const newBlockId = generateBlockId5();
3109
+ const ops = createAutoLinkOps(parentDocument, childHmUrl, newBlockId);
3110
+ const hmUrl = `hm://${account}${parentPath}`;
3111
+ const state = await resolveDocumentState(client, hmUrl);
3112
+ const genesisCid = CID4.parse(state.genesis);
3113
+ const depCids = state.heads.map((h) => CID4.parse(h));
3114
+ const newDepth = state.headDepth + 1;
3115
+ const { unsignedBytes, ts } = createChangeOps({ ops, genesisCid, deps: depCids, depth: newDepth });
3116
+ const changeBlock = await createChange(unsignedBytes, signer);
3117
+ const generation = Number(ts);
3118
+ const refInput = await createVersionRef(
3119
+ {
3120
+ space: account,
3121
+ path: parentPath,
3122
+ genesis: state.genesis,
3123
+ version: changeBlock.cid.toString(),
3124
+ generation
3125
+ },
3126
+ signer
3127
+ );
3128
+ await client.publish({
3129
+ blobs: [{ data: new Uint8Array(changeBlock.bytes), cid: changeBlock.cid.toString() }, ...refInput.blobs]
3130
+ });
3131
+ return true;
3132
+ }
3133
+ function generateBlockId5() {
3134
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
3135
+ let id = "";
3136
+ const bytes = new Uint8Array(10);
3137
+ crypto.getRandomValues(bytes);
3138
+ for (let i = 0; i < 10; i++) {
3139
+ id += chars[bytes[i] % chars.length];
3140
+ }
3141
+ return id;
860
3142
  }
861
3143
  export {
3144
+ DEFAULT_GROBID_URL,
862
3145
  HYPERMEDIA_SCHEME,
863
3146
  SeedClientError,
864
3147
  SeedNetworkError,
865
3148
  SeedValidationError,
3149
+ autoLinkChildToParent,
3150
+ blocksToMarkdown,
3151
+ codePointLength,
3152
+ commentRecordIdFromBlob,
3153
+ computeReplaceOps,
866
3154
  contactRecordIdFromBlob,
3155
+ createAutoLinkOps,
3156
+ createBlocksMap,
867
3157
  createCapability,
868
3158
  createChange,
869
3159
  createChangeOps,
@@ -878,14 +3168,38 @@ export {
878
3168
  createVersionRef,
879
3169
  deleteComment,
880
3170
  deleteContact,
3171
+ documentContainsLinkToChild,
3172
+ documentHasSelfQuery,
3173
+ embeddedPdfToBlocks,
3174
+ emitFrontmatter,
881
3175
  entityQueryPathToHmIdPath,
3176
+ fileToIpfsBlobs,
3177
+ filesToIpfsBlobs,
3178
+ flattenToOperations,
882
3179
  getHMQueryString,
3180
+ hasFileLinks,
3181
+ hmBlockNodeToBlockNode,
883
3182
  hmIdPathToEntityQueryPath,
3183
+ isGrobidAvailable,
3184
+ isSurrogate,
3185
+ matchBlockIds,
884
3186
  packBaseId,
885
3187
  packHmId,
3188
+ parseCustomURL,
3189
+ parseFragment,
3190
+ parseFrontmatter,
3191
+ parseInlineFormatting,
3192
+ parseMarkdown,
3193
+ pdfToBlocks,
3194
+ processFulltextDocument,
3195
+ resolveDocumentState,
3196
+ resolveFileLinksInBlocks,
886
3197
  serializeBlockRange,
3198
+ shouldAutoLinkParent,
887
3199
  signDocumentChange,
888
3200
  signPreparedChange,
3201
+ teiToBlocks,
889
3202
  trimTrailingEmptyBlocks,
3203
+ unpackHmId,
890
3204
  updateContact
891
3205
  };