@uniweb/semantic-parser 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/semantic-parser",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Semantic parser for ProseMirror/TipTap content structures",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -116,6 +116,18 @@ function imageBlock({ src, url, alt = '', caption = '', direction, role, width,
116
116
  return { type: 'ImageBlock', attrs }
117
117
  }
118
118
 
119
+ // A document is STORED as the image row with `role: 'pdf'` — the node the
120
+ // markdown reader writes and the parser reads as a `document` — so that is what
121
+ // this emits. `url` (the parser's spelling) or `src` (this builder's) in.
122
+ function documentNode({ url, src, alt = '', caption = '', preview, author, description }) {
123
+ const attrs = { src: url || src, alt, role: 'pdf' }
124
+ if (caption) attrs.caption = caption
125
+ if (preview) attrs.preview = preview
126
+ if (author) attrs.author = author
127
+ if (description) attrs.description = description
128
+ return { type: 'image', attrs }
129
+ }
130
+
119
131
  function iconNode({ src, url, svg, library, name, size, color }) {
120
132
  // UniwebIcon supports multiple source types.
121
133
  //
@@ -293,6 +305,12 @@ function buildGroupNodes(group, titleLevel = 1, options = {}) {
293
305
  }
294
306
  }
295
307
 
308
+ if (group.documents) {
309
+ for (const doc of group.documents) {
310
+ nodes.push(documentNode(doc))
311
+ }
312
+ }
313
+
296
314
  if (group.lists) {
297
315
  for (const list of group.lists) {
298
316
  const node = bulletList(list)
@@ -15,6 +15,7 @@ function flattenGroup(group) {
15
15
  icons: group.body.icons || [],
16
16
  lists: group.body.lists || [],
17
17
  videos: group.body.videos || [],
18
+ documents: group.body.documents || [],
18
19
  insets: group.body.insets || [],
19
20
  snippets: group.body.snippets || [],
20
21
  data: group.body.data || {},
@@ -303,6 +304,7 @@ function processGroupContent(elements, options = {}) {
303
304
  images: [],
304
305
  icons: [],
305
306
  videos: [],
307
+ documents: [],
306
308
  insets: [],
307
309
  snippets: [],
308
310
  paragraphs: [],
@@ -387,6 +389,10 @@ function processGroupContent(elements, options = {}) {
387
389
  body.videos.push(preserveProps);
388
390
  break;
389
391
 
392
+ case "document":
393
+ body.documents.push(preserveProps);
394
+ break;
395
+
390
396
  case "link":
391
397
  if (element.children && Array.isArray(element.children))
392
398
  processInlineElements(element.children, body);
@@ -520,19 +526,6 @@ function processGroupContent(elements, options = {}) {
520
526
  });
521
527
  break;
522
528
 
523
- case "document-group":
524
- // Map documents to links with role=document
525
- element.documents.forEach(doc => {
526
- body.links.push({
527
- href: doc.href || doc.downloadUrl || '',
528
- label: doc.title || '',
529
- role: 'document',
530
- download: true,
531
- preview: doc.coverImg,
532
- fileType: doc.fileType,
533
- });
534
- });
535
- break;
536
529
  }
537
530
  }
538
531
  }
@@ -588,6 +581,9 @@ function processInlineElements(children, body) {
588
581
  // `{role=video}` belongs in videos[] for the same reason a hoisted
589
582
  // one does.
590
583
  body.videos.push(item.attrs);
584
+ } else if (item.type === "document") {
585
+ // And an inline `{role=pdf}` in documents[].
586
+ body.documents.push(item.attrs);
591
587
  } else if (item.type === "link") {
592
588
  // Handle inline links extracted from paragraph text nodes
593
589
  body.links.push(item.attrs);
@@ -210,7 +210,19 @@ function createSequenceElement(node, options = {}) {
210
210
  case "image":
211
211
  // `role` decides which content array this lands in — the contract
212
212
  // published in docs/reference/content-structure.md ("Media Assets"):
213
- // icon → icons, video → videos, everything else → images.
213
+ // icon → icons, video → videos, pdf → documents, everything else →
214
+ // images.
215
+ //
216
+ // A document is stored as this node, as a video is, and delivered as
217
+ // a kind of its own (`parseMarkdownDocument`). Until 2026-09-29 it
218
+ // landed in `images`, drawn as an image of the file, while the
219
+ // runtime had guaranteed an empty `content.documents` since January.
220
+ if (attrs?.role === "pdf") {
221
+ return {
222
+ type: "document",
223
+ attrs: parseMarkdownDocument(attrs, options),
224
+ };
225
+ }
214
226
  //
215
227
  // The video branch was missing until 2026-07-30: every
216
228
  // `{role=video}` node ran parseImgBlock, which does not carry
@@ -315,13 +327,16 @@ function createSequenceElement(node, options = {}) {
315
327
  };
316
328
 
317
329
  case "document-group":
318
- return {
319
- type: "document-group",
320
- documents:
321
- node.content
322
- ?.filter((c) => c.type === "document")
323
- .map((doc) => parseDocumentBlock(doc.attrs)) || [],
324
- };
330
+ // The editor's older container: one `document` element per document
331
+ // it holds, so a document is one kind of content whichever node
332
+ // wrote it (`documentFromEditorNode`). Until 2026-09-29 this was a
333
+ // `document-group` element whose documents became `content.links`.
334
+ return (node.content || [])
335
+ .filter((c) => c.type === "document")
336
+ .map((doc) => ({
337
+ type: "document",
338
+ attrs: documentFromEditorNode(doc.attrs),
339
+ }));
325
340
 
326
341
  // The editor renamed this node to `StructuredContent` (2026-07-31,
327
342
  // editor-internal). Both are accepted: the new name is what the editor
@@ -613,13 +628,15 @@ function processInlineElements(content, options = {}) {
613
628
  // image silently deleted. groups.js has carried the receiving
614
629
  // branch (and a comment describing exactly this failure) since it
615
630
  // was written, but nothing ever emitted the item it reads.
616
- const isVideo = item.attrs?.role === "video";
617
- items.push({
618
- type: isVideo ? "video" : "image",
619
- attrs: isVideo
620
- ? parseMarkdownVideo(item.attrs, options)
621
- : parseImgBlock(item.attrs || {}, options),
622
- });
631
+ // The role partitions it as the block case does.
632
+ const role = item.attrs?.role;
633
+ if (role === "video") {
634
+ items.push({ type: "video", attrs: parseMarkdownVideo(item.attrs, options) });
635
+ } else if (role === "pdf") {
636
+ items.push({ type: "document", attrs: parseMarkdownDocument(item.attrs, options) });
637
+ } else {
638
+ items.push({ type: "image", attrs: parseImgBlock(item.attrs || {}, options) });
639
+ }
623
640
  } else if (item.type === "math-inline" || item.type === "math_inline") {
624
641
  items.push(item);
625
642
  } else if (item.type === "inset_placeholder") {
@@ -802,6 +819,54 @@ function parseCardBlock(itemAttrs) {
802
819
  };
803
820
  }
804
821
 
822
+ // A document authored in markdown: `![Annual report](./report.pdf){role=pdf …}`.
823
+ //
824
+ // Read through `parseImgBlock`, which resolves the address and the preview's the
825
+ // way every image's are, and then narrowed to what a document IS: where the file
826
+ // is, what it is called, and the resource's own metadata. The image layout keys
827
+ // (direction, size, credit) describe a picture, not a file, and are left out.
828
+ function parseMarkdownDocument(itemAttrs, options) {
829
+ const { url, alt, caption, role, href, target, id, preview, author, description } =
830
+ parseImgBlock(itemAttrs || {}, options);
831
+ return {
832
+ url,
833
+ alt,
834
+ caption,
835
+ role,
836
+ ...(href && { href }),
837
+ ...(target && { target }),
838
+ ...(id && { id }),
839
+ ...(preview && { preview }),
840
+ ...(author && { author }),
841
+ ...(description && { description }),
842
+ };
843
+ }
844
+
845
+ // The editor's older `document` node — its `title`, `coverImg` and `src` (or
846
+ // asset identifier) — read into the same shape a markdown document has.
847
+ function documentFromEditorNode(itemAttrs) {
848
+ const {
849
+ href,
850
+ downloadUrl,
851
+ title = "",
852
+ alt = "",
853
+ coverImg,
854
+ author,
855
+ description,
856
+ fileType,
857
+ } = parseDocumentBlock(itemAttrs || {});
858
+ return {
859
+ url: href || downloadUrl || "",
860
+ alt: alt || title,
861
+ caption: title,
862
+ role: "pdf",
863
+ ...(coverImg && { preview: coverImg }),
864
+ ...(author && { author }),
865
+ ...(description && { description }),
866
+ ...(fileType && { fileType }),
867
+ };
868
+ }
869
+
805
870
  function parseDocumentBlock(itemAttrs) {
806
871
  const { src, info = {}, coverImg = null, ...others } = itemAttrs;
807
872