@uniweb/semantic-parser 1.12.2 → 1.13.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
@@ -298,6 +298,13 @@ A renderer that reads `text` as HTML and does not know a marker draws
298
298
  nothing for it, as for any empty element it does not know. kit's `<Render>`
299
299
  draws an icon with `<Icon>` and a picture with `<Image>`.
300
300
 
301
+ An icon **before any text** is the element's own icon, not part of its prose:
302
+ `![](lu-cloud)` on a line of its own, `- ![](lu-globe) Web App`, or
303
+ `## ![](lu-zap) Fast`. The flat fields (`title`, `paragraphs`, a list item's)
304
+ leave out its marker, since a component draws that icon from `icons`, beside
305
+ the words. So a line holding only icons adds no paragraph. The `sequence`
306
+ keeps every marker, for a renderer that draws each icon where it sat.
307
+
301
308
  ## Use Cases
302
309
 
303
310
  - **Component-based websites**: Extract structured data for React/Vue components
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/semantic-parser",
3
- "version": "1.12.2",
3
+ "version": "1.13.0",
4
4
  "description": "Semantic parser for ProseMirror/TipTap content structures",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -286,6 +286,29 @@ function readStack(sequence, startIdx, options = {}) {
286
286
  return { pre, title, sub, length: i - startIdx };
287
287
  }
288
288
 
289
+ /**
290
+ * An element's text as a flat field holds it, without the markers of the icons before its words.
291
+ *
292
+ * ⭐ An icon before any text is the element's own icon, not part of its prose: `![](lu-cloud)` on
293
+ * a line of its own, `- ![](lu-globe) Web App`, `## ![](lu-zap) Fast`. The flat fields (`title`,
294
+ * `paragraphs`, a list item's) hold the text a component draws beside that icon, which it finds
295
+ * in `icons`. So the markers before the words go, and a line holding only icons leaves no
296
+ * paragraph. An icon inside the prose keeps its marker, with its ordinal unchanged, since `icons`
297
+ * still lists the leading ones. The `sequence` keeps every marker: kit's `Render` draws each icon
298
+ * where it sat [Diego, 2026-10-24].
299
+ *
300
+ * ⛔ From 2026-08-12 to 2026-10-24 the flat fields kept the leading markers. An icon-only line
301
+ * became a paragraph of nothing but `<uniweb-icon data-index="0"></uniweb-icon>`, which moved
302
+ * `paragraphs[0]` off the text, and a component drawing a paragraph as text showed the markup.
303
+ *
304
+ * @param {*} text
305
+ * @returns {*} the text without its leading icon markers; anything that is no string, as it came
306
+ */
307
+ function flatText(text) {
308
+ if (typeof text !== "string") return text;
309
+ return text.replace(/^(?:\s*<uniweb-icon data-index="\d+"><\/uniweb-icon>)+\s*/, "");
310
+ }
311
+
289
312
  /** Collapse a slot's texts: none → '', one → string, several → array. */
290
313
  function flattenSlot(texts) {
291
314
  if (texts.length === 0) return "";
@@ -346,9 +369,9 @@ function processGroupContent(elements, options = {}) {
346
369
  let start = 0;
347
370
  if (elements[0]?.type === "heading") {
348
371
  const stack = readStack(elements, 0, options);
349
- header.pretitle = flattenSlot(stack.pre.map((el) => el.text));
350
- header.title = flattenSlot(stack.title.map((el) => el.text));
351
- header.subtitle = flattenSlot(stack.sub.map((el) => el.text));
372
+ header.pretitle = flattenSlot(stack.pre.map((el) => flatText(el.text)));
373
+ header.title = flattenSlot(stack.title.map((el) => flatText(el.text)));
374
+ header.subtitle = flattenSlot(stack.sub.map((el) => flatText(el.text)));
352
375
  metadata.level = stack.title[0]?.level ?? null;
353
376
  for (const el of [...stack.pre, ...stack.title, ...stack.sub]) {
354
377
  if (el.children && Array.isArray(el.children))
@@ -366,7 +389,7 @@ function processGroupContent(elements, options = {}) {
366
389
  // array — section content is split into groups before this runs.
367
390
  if (element.children && Array.isArray(element.children))
368
391
  processInlineElements(element.children, body);
369
- body.headings.push(element.text);
392
+ body.headings.push(flatText(element.text));
370
393
  } else if (element.type === "list") {
371
394
  const listItems = element.children;
372
395
 
@@ -383,7 +406,10 @@ function processGroupContent(elements, options = {}) {
383
406
  if (element.children && Array.isArray(element.children))
384
407
  processInlineElements(element.children, body);
385
408
 
386
- if (element.text) body.paragraphs.push(element.text);
409
+ {
410
+ const flat = flatText(element.text);
411
+ if (flat) body.paragraphs.push(flat);
412
+ }
387
413
  break;
388
414
 
389
415
  case "image":