@seed-hypermedia/client 0.0.1 → 0.0.2
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/auto-link.d.ts +61 -0
- package/dist/block-diff.d.ts +62 -0
- package/dist/blocks-to-markdown.d.ts +33 -0
- package/dist/change.d.ts +7 -0
- package/dist/{chunk-YUKHBJYL.mjs → chunk-PPK3SKPV.mjs} +253 -6
- package/dist/client.d.ts +1 -1
- package/dist/comment.d.ts +5 -0
- package/dist/contact.d.ts +16 -2
- package/dist/document-state.d.ts +23 -0
- package/dist/file-to-ipfs.d.ts +53 -0
- package/dist/grobid.d.ts +22 -0
- package/dist/hm-types.d.ts +2935 -202
- package/dist/hm-types.mjs +47 -1
- package/dist/index.d.ts +33 -13
- package/dist/index.mjs +2378 -64
- package/dist/markdown-to-blocks.d.ts +88 -0
- package/dist/pdf-to-blocks-embedded.d.ts +23 -0
- package/dist/pdf-to-blocks.d.ts +55 -0
- package/dist/ref.d.ts +2 -0
- package/dist/tei-to-blocks.d.ts +52 -0
- package/package.json +45 -17
- package/src/index.ts +74 -0
- package/LICENSE +0 -201
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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-PPK3SKPV.mjs";
|
|
18
17
|
|
|
19
|
-
// src/
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
502
|
+
import { decode as cborDecode2, encode as cborEncode6 } from "@ipld/dag-cbor";
|
|
463
503
|
import { CID as CID3 } from "multiformats";
|
|
464
|
-
import { base58btc as
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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 {
|
|
758
|
-
import { base58btc as
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
914
|
+
return toPublishInput(cborEncode7(unsigned));
|
|
825
915
|
}
|
|
826
916
|
async function contactRecordIdFromBlob(blobData) {
|
|
827
|
-
const decoded =
|
|
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
|
|
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/
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
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
|
-
|
|
853
|
-
|
|
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
|
-
|
|
856
|
-
|
|
1032
|
+
for (const child of el.contents().toArray()) {
|
|
1033
|
+
walk(child, {});
|
|
857
1034
|
}
|
|
858
|
-
|
|
859
|
-
|
|
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 + ` ${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
|
};
|