@uniweb/semantic-parser 1.12.0 → 1.12.1

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.12.0",
3
+ "version": "1.12.1",
4
4
  "description": "Semantic parser for ProseMirror/TipTap content structures",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -161,7 +161,7 @@ function createSequenceElement(node, options = {}) {
161
161
  };
162
162
 
163
163
  case "codeBlock": {
164
- const codeText = getTextContent(content, options);
164
+ const codeText = codeTextOf(content);
165
165
  // Tagged code blocks are semantically data blocks, not code — the
166
166
  // tag is what routes them to `data[tag]`, so the routing holds even
167
167
  // though the payload here is raw text.
@@ -374,22 +374,43 @@ function sameLink(a, b) {
374
374
  /**
375
375
  * Wrap already-styled inline content in an anchor.
376
376
  */
377
+ /**
378
+ * A code block's code, as written — the characters a reader sees, not HTML.
379
+ *
380
+ * Every consumer draws it as text: kit's `Code` (and the highlighter, which
381
+ * escapes as it colors), press's `CodeBlock`, a foundation's `{snippet.code}`.
382
+ * ⛔ Until 2026-10-19 it went through `getTextContent`, which escapes text for
383
+ * the HTML strings a paragraph carries, so a `<` arrived as `&lt;`, was escaped
384
+ * again where it was drawn, and a page showed `&lt;p&gt;` for `<p>`.
385
+ */
386
+ function codeTextOf(content) {
387
+ return (content || [])
388
+ .map((node) => (node?.type === "text" ? node.text || "" : node?.type === "hardBreak" ? "\n" : ""))
389
+ .join("");
390
+ }
391
+
377
392
  function wrapLink(inner, linkMark) {
378
- return anchor(linkMark.attrs.href, linkMark.attrs.target, inner);
393
+ return anchor(linkMark.attrs.href, linkMark.attrs.target, inner, linkMark.attrs.role);
379
394
  }
380
395
 
381
396
  /**
382
397
  * An anchor around already-escaped inner HTML. A link whose URL may not be
383
398
  * followed (`javascript:`, `data:`, any scheme `isSafeLink` refuses) is not
384
399
  * drawn as a link: its label stays, as text.
400
+ *
401
+ * Its `role` — `button` for a `[Go](/x){button}`, or an authored hint such as
402
+ * `nav` — rides as `data-role`, a style name or nothing, so a stylesheet can
403
+ * tell a button from a link. ⛔ Until 2026-10-19 the role was dropped here, and
404
+ * a button reached the page as a plain link.
385
405
  */
386
- function anchor(href, target, inner) {
406
+ function anchor(href, target, inner, role) {
387
407
  if (typeof href !== "string" || !isSafeLink(href)) return inner;
388
408
  const t = isLinkTarget(target) ? target : "_self";
389
409
  const extension = href.split(".").pop()?.toLowerCase();
390
410
  const isFileLink = FILE_LINK_EXTENSIONS.includes(extension);
411
+ const roleAttr = isStyleName(role) ? ` data-role="${escapeAttr(role)}"` : "";
391
412
 
392
- return `<a href="${escapeAttr(href)}" target="${t}"${isFileLink ? " download" : ""}>${inner}</a>`;
413
+ return `<a href="${escapeAttr(href)}" target="${t}"${isFileLink ? " download" : ""}${roleAttr}>${inner}</a>`;
393
414
  }
394
415
 
395
416
  function getTextContent(content, options = {}) {
@@ -453,8 +474,8 @@ function renderInlineNode(curr, iconOrdinals, pictureOrdinals) {
453
474
  if (type === "text") {
454
475
  let styledText = escapeText(text || "");
455
476
 
456
- // Apply marks in order: span, code, bold, italic — the link is
457
- // applied per group by getTextContent. This ensures proper nesting.
477
+ // Apply marks in order: span, code, bold, italic, strike — the link
478
+ // is applied per group by getTextContent. This ensures proper nesting.
458
479
  //
459
480
  // ⛔ Until 2026-10-18 the editor's `textStyle` (a theme color) and
460
481
  // `highlight` marks were applied first. The editor saves both as
@@ -513,6 +534,12 @@ function renderInlineNode(curr, iconOrdinals, pictureOrdinals) {
513
534
  styledText = `<em>${styledText}</em>`;
514
535
  }
515
536
 
537
+ // strike — `~~text~~`. ⛔ Until 2026-10-19 nothing applied the
538
+ // mark, so struck text reached the page as ordinary text.
539
+ if (marks.some((mark) => mark.type === "strike")) {
540
+ styledText = `<s>${styledText}</s>`;
541
+ }
542
+
516
543
  // The link mark is deliberately NOT applied here — it is the
517
544
  // outermost wrapper and may span several nodes, so
518
545
  // getTextContent applies it once per group.
@@ -1043,18 +1070,35 @@ function isOnlyLinks(item) {
1043
1070
 
1044
1071
  if (!allLinks) return false;
1045
1072
 
1046
- // Extract links with all their attributes
1073
+ // One link per anchor, as `getTextContent` draws them: consecutive runs that
1074
+ // carry the same link are ONE link whose label has several runs
1075
+ // (`[**Go** now](/go)`), and anything between two links — the space a line
1076
+ // break becomes, an icon — keeps them apart. ⛔ Until 2026-10-19 every run
1077
+ // was a link of its own, so a button labelled in two styles became two
1078
+ // buttons, "Go" and " now".
1047
1079
  // Icons in this paragraph go to body.icons separately (no association)
1048
- return textContent.map((c) => {
1049
- const linkMark = linkMarkOf(c.marks);
1050
- return {
1051
- type: "link",
1052
- attrs: {
1053
- ...linkMark?.attrs, // Preserve all link attributes (role, target, etc.)
1054
- label: c.text || "",
1055
- },
1056
- };
1057
- });
1080
+ const groups = [];
1081
+ let current = null;
1082
+ for (const c of content) {
1083
+ const mark = c.type === "text" ? linkMarkOf(c.marks) : null;
1084
+ if (mark && current && sameLink(current.mark, mark)) {
1085
+ current.label += c.text || "";
1086
+ } else if (mark) {
1087
+ current = { mark, label: c.text || "" };
1088
+ groups.push(current);
1089
+ } else {
1090
+ current = null;
1091
+ }
1092
+ }
1093
+
1094
+ // Extract links with all their attributes
1095
+ return groups.map(({ mark, label }) => ({
1096
+ type: "link",
1097
+ attrs: {
1098
+ ...mark?.attrs, // Preserve all link attributes (role, target, etc.)
1099
+ label,
1100
+ },
1101
+ }));
1058
1102
  }
1059
1103
 
1060
1104
  // method to check if given item has multiple content parts and each of them has the same link attrs with different inline style (plain, em, strong, u)
@@ -1085,7 +1129,7 @@ function isStyledLink(item, options = {}) {
1085
1129
  )
1086
1130
  return false;
1087
1131
 
1088
- const { href, target } = firstLinkMark.attrs;
1132
+ const { href, target, role } = firstLinkMark.attrs;
1089
1133
 
1090
1134
  const cleanedContent = content.map((c) => {
1091
1135
  // remove link marks from content items
@@ -1104,7 +1148,7 @@ function isStyledLink(item, options = {}) {
1104
1148
  return {
1105
1149
  type: "paragraph",
1106
1150
  children: processInlineElements(item.content, options),
1107
- text: anchor(href, target, textContent),
1151
+ text: anchor(href, target, textContent, role),
1108
1152
  attrs: item.attrs,
1109
1153
  };
1110
1154
  }