@uniweb/semantic-parser 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -74,8 +74,8 @@ result = {
74
74
  // Body fields
75
75
  paragraphs: ["Get started today."],
76
76
  links: [], // All links (including buttons)
77
- images: [],
78
- videos: [],
77
+ images: [], // { url, alt, caption, role, placement?, author?, … }
78
+ videos: [], // { src, poster?, caption, placement?, … }
79
79
  documents: [], // Files marked {role=pdf} — { url, name, mime, size?, alt,
80
80
  // caption, preview?, … }
81
81
  icons: [],
@@ -87,7 +87,8 @@ result = {
87
87
  snippets: [], // Fenced code blocks — { language, code }
88
88
  data: {}, // Structured data (tagged data blocks, forms, cards)
89
89
  headings: [], // Only from nested content (quote/list bodies)
90
- // tables: [], // Only when the content has one — { rows, attrs }
90
+ // tables: [], // Only when the content has one — { rows, attrs } — attrs:
91
+ // caption, id, placement, from its caption line
91
92
  // math: [], // Only when the content has one — { id, latex, mathml }
92
93
 
93
94
  // Additional content groups (from headings after content)
@@ -116,7 +117,7 @@ const content = parseContent(doc);
116
117
 
117
118
  const title = content.title;
118
119
  const description = content.paragraphs.join(" ");
119
- const image = content.images[0]?.src;
120
+ const image = content.images[0]?.url;
120
121
  ```
121
122
 
122
123
  ### Processing Content Sections
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/semantic-parser",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Semantic parser for ProseMirror/TipTap content structures",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -103,12 +103,16 @@ function linkParagraph({ text, label, href, target }) {
103
103
  }
104
104
  }
105
105
 
106
- function imageBlock({ src, url, alt = '', caption = '', direction, role, width, height }) {
106
+ function imageBlock({ src, url, alt = '', caption = '', placement, role, width, height }) {
107
107
  // The node attr is `url` and so is the parsed field; `src` is this builder's
108
108
  // original spelling. Either one in, `url` out.
109
109
  const attrs = { url: src || url, alt }
110
110
  if (caption) attrs.caption = caption
111
- if (direction) attrs.direction = direction
111
+ // The parsed field's own word, so parseContent(buildDoc(x)) keeps it. The
112
+ // editor's node calls it `direction` and drops an attr it does not declare,
113
+ // so the editor reads none until it saves the vocabulary; no starter content
114
+ // sets one. ⛔ Until 2026-10-15 this took and wrote `direction`.
115
+ if (placement) attrs.placement = placement
112
116
  if (role) attrs.role = role
113
117
  if (width && height) {
114
118
  attrs.aspect_ratio = { width, height, ratio: (height / width) * 100 }
@@ -153,10 +157,10 @@ function iconNode({ src, url, svg, library, name, size, color }) {
153
157
  return { type: 'UniwebIcon', attrs }
154
158
  }
155
159
 
156
- function videoNode({ src, url, caption, direction, coverImg }) {
160
+ function videoNode({ src, url, caption, placement, coverImg }) {
157
161
  const attrs = { src: src || url }
158
162
  if (caption) attrs.caption = caption
159
- if (direction) attrs.direction = direction
163
+ if (placement) attrs.placement = placement // as an image's, above
160
164
  // ⛔ The editor's `Video` node holds `coverImg` as an OBJECT — the reader is
161
165
  // `makeAssetUrl`, which looks for `.src` / `.url` / `.identifier`. A bare
162
166
  // string passed straight through parses back as "": `info?.src` on a string
@@ -861,8 +861,8 @@ function parseCardBlock(itemAttrs) {
861
861
  //
862
862
  // Read through `parseImgBlock`, which resolves the address and the preview's the
863
863
  // way every image's are, and then narrowed to what a document IS: where the file
864
- // is, what it is called, and the resource's own metadata. The image layout keys
865
- // (direction, size, credit) describe a picture, not a file, and are left out.
864
+ // is, what it is called, and the resource's own metadata. Where a picture sits
865
+ // in a flow (`placement`) is not a file's, and is left out.
866
866
  function parseMarkdownDocument(itemAttrs, options) {
867
867
  const { url, alt, caption, role, href, target, id, preview, author, description } =
868
868
  parseImgBlock(itemAttrs || {}, options);
@@ -1016,47 +1016,47 @@ function parseImgBlock(itemAttrs, options) {
1016
1016
  info: imgInfo,
1017
1017
  targetId,
1018
1018
  caption = "",
1019
- direction,
1020
- filter,
1021
1019
  alt = "",
1022
1020
  src,
1023
1021
  url,
1024
1022
  href = "",
1025
1023
  target = "",
1026
- theme,
1027
1024
  role,
1028
- credit = "",
1025
+ credit, // the editor's word for `author` — delivered as `author`
1029
1026
  id, // {#fig-id} cross-reference label — preserved so Press
1030
1027
  // adapters can emit \label{id} (LaTeX) / <id> (Typst).
1031
1028
  // ── Markdown-authored attributes ────────────────────────────────
1032
1029
  // This function was written for the EDITOR's `ImageBlock` node and
1033
1030
  // later reused for the markdown `image` node (see `case "image"`), so
1034
- // its vocabulary is the editor's: contentType, direction, filter,
1035
- // theme, credit. Everything markdown declares and the editor does not
1036
- // was therefore tokenized, declared, and then dropped one layer before
1037
- // delivery — the published "Image Attributes" table
1038
- // (docs/reference/content-structure.md) reached no component, and
1039
- // neither did the `role=pdf` set added 2026-07-29 for the editor's
1040
- // `document` fold-in. Carried 2026-07-30.
1031
+ // its vocabulary was the editor's. Everything markdown declares and
1032
+ // the editor does not was therefore tokenized, declared, and then
1033
+ // dropped one layer before delivery — the published "Image
1034
+ // Attributes" table (docs/reference/content-structure.md) reached no
1035
+ // component, and neither did the `role=pdf` set added 2026-07-29 for
1036
+ // the editor's `document` fold-in. Carried 2026-07-30.
1037
+ //
1038
+ // ⭐ An element now holds the vocabulary's words, whichever node it
1039
+ // came from. ⛔ Until 2026-10-15 it also held the editor's `direction`
1040
+ // and two things derived from it — `imgPos` (left/right) and a
1041
+ // layout `size` (basic/lg/full, `basic` for every image without one) —
1042
+ // plus `filter` and `theme`, none of which a stored document holds. A
1043
+ // component handing the element to kit's <Image> passed that `size` as
1044
+ // Image's avatar preset, so a "wide" image drew as a 96px square.
1041
1045
  width,
1042
1046
  height,
1043
1047
  loading,
1044
1048
  fit,
1045
1049
  position,
1050
+ placement, // where it sits in a flow: left, right, wide, full
1046
1051
  preview, // role=pdf — the preview image
1047
- author, // role=pdf — resource metadata, rendered beside the preview
1052
+ author, // the image's attribution; for role=pdf, the resource's
1048
1053
  description, // role=pdf — describes the RESOURCE, not the image (≠ alt)
1049
1054
  } = itemAttrs;
1050
1055
 
1051
1056
  let { contentType, viewType, contentId, identifier } = imgInfo || {};
1052
1057
 
1053
- const sizes = {
1054
- center: "basic",
1055
- wide: "lg",
1056
- fill: "full",
1057
- };
1058
-
1059
1058
  caption = stripTags(caption);
1059
+ const attribution = author || credit;
1060
1060
 
1061
1061
  // Reference precedence. Standard ProseMirror `image` nodes use `src`; the
1062
1062
  // custom `ImageBlock` node uses `url`. Honor either so callers don't have to
@@ -1088,19 +1088,9 @@ function parseImgBlock(itemAttrs, options) {
1088
1088
  value: identifier || "",
1089
1089
  alt: alt || caption,
1090
1090
  caption,
1091
- direction,
1092
- filter,
1093
- imgPos: direction === "left" || direction === "right" ? direction : "",
1094
- // NOTE: `size` here is the direction-derived LAYOUT size
1095
- // (basic/lg/full), not the author's `{size=…}` — that attribute is
1096
- // icon-only and is read by parseUniwebIcon. Do not wire `{size=…}`
1097
- // through here; the two meanings collide on one key.
1098
- size: sizes[direction] || "basic",
1099
1091
  href,
1100
1092
  target,
1101
- theme,
1102
1093
  role,
1103
- credit,
1104
1094
  id: id || undefined,
1105
1095
  // Omitted when absent, so an existing consumer sees no new empty keys.
1106
1096
  ...(width !== undefined && width !== null && { width }),
@@ -1108,12 +1098,13 @@ function parseImgBlock(itemAttrs, options) {
1108
1098
  ...(loading && { loading }),
1109
1099
  ...(fit && { fit }),
1110
1100
  ...(position && { position }),
1101
+ ...(placement && { placement }),
1111
1102
  ...((() => {
1112
1103
  // Same rule for a document's preview image.
1113
1104
  const p = secondaryAssetUrl(itemAttrs, options, "preview");
1114
1105
  return p ? { preview: p } : {};
1115
1106
  })()),
1116
- ...(author && { author }),
1107
+ ...(attribution && { author: attribution }),
1117
1108
  ...(description && { description }),
1118
1109
  };
1119
1110
  }
@@ -1142,6 +1133,7 @@ function parseMarkdownVideo(itemAttrs, options) {
1142
1133
  target = "",
1143
1134
  role,
1144
1135
  id, // {#fig-id} cross-reference label, same as parseImgBlock
1136
+ placement, // where it sits in a flow, same as parseImgBlock
1145
1137
  } = itemAttrs || {};
1146
1138
 
1147
1139
  // Reference precedence, now genuinely identical to parseImgBlock: a
@@ -1175,6 +1167,7 @@ function parseMarkdownVideo(itemAttrs, options) {
1175
1167
  if (loop !== undefined) video.loop = loop;
1176
1168
  if (controls !== undefined) video.controls = controls;
1177
1169
  if (id) video.id = id;
1170
+ if (placement) video.placement = placement;
1178
1171
 
1179
1172
  return video;
1180
1173
  }
@@ -1183,12 +1176,12 @@ function parseVideoBlock(itemAttrs, options) {
1183
1176
  let {
1184
1177
  src,
1185
1178
  caption = "",
1186
- direction,
1187
1179
  info = {},
1188
1180
  coverImg = {},
1189
1181
  alt,
1190
1182
  href = "",
1191
1183
  target = "",
1184
+ placement, // where it sits in a flow; the editor's `direction` is not read
1192
1185
  } = itemAttrs;
1193
1186
 
1194
1187
  // Same precedence as parseImgBlock / parseMarkdownVideo: a resolvable
@@ -1198,11 +1191,11 @@ function parseVideoBlock(itemAttrs, options) {
1198
1191
  return {
1199
1192
  src: video,
1200
1193
  caption,
1201
- direction,
1202
1194
  coverImg: makeAssetUrl(coverImg),
1203
1195
  alt,
1204
1196
  href,
1205
1197
  target,
1198
+ ...(placement && { placement }),
1206
1199
  };
1207
1200
  }
1208
1201