@ai-matrx/rich-content 0.2.7 → 0.2.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.8
4
+ ### Unreleased
5
+
6
+ ### Shared fence parsing
7
+
8
+ - The plain-text fallback and Mermaid print reader use Content IR's canonical
9
+ fence rules instead of private regex copies.
10
+ - Mermaid print preserves unfinished bodies and dedenting, ignores malformed
11
+ fence info, and does not render diagrams nested inside another code block.
12
+
13
+ ### Consumer action
14
+
15
+ - None required. Public imports and host setup are unchanged.
16
+
3
17
  ## 0.2.7
4
18
 
5
19
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -178,7 +178,7 @@ const CodeBlockBody = ({
178
178
  });
179
179
  textToCopy = paddedLines.join("\n");
180
180
  }
181
- await copyText(textToCopy);
181
+ if (!await copyText(textToCopy)) return;
182
182
  setIsCopied(true);
183
183
  setTimeout(() => setIsCopied(false), 2e3);
184
184
  };
@@ -38,7 +38,7 @@ const ChartBlock = ({ content = "", isStreamActive = false, className }) => {
38
38
  const paneLayout = chartPaneLayout(presentation, spec?.type ?? "");
39
39
  const handleCopy = async () => {
40
40
  try {
41
- await copyText(source);
41
+ if (!await copyText(source)) return;
42
42
  setCopied(true);
43
43
  setTimeout(() => setCopied(false), 1500);
44
44
  } catch {
@@ -86,7 +86,7 @@ const CsvBlock = ({
86
86
  const headers = data[0] || [];
87
87
  const bodyRows = data.slice(1);
88
88
  const handleCopy = async () => {
89
- await copyText(content);
89
+ if (!await copyText(content)) return;
90
90
  setCopied(true);
91
91
  setTimeout(() => setCopied(false), 2e3);
92
92
  };
@@ -85,7 +85,7 @@ const DiffSpecBlock = ({ content = "", isStreamActive = false, className }) => {
85
85
  });
86
86
  const handleCopy = async () => {
87
87
  try {
88
- await copyText(content.trim());
88
+ if (!await copyText(content.trim())) return;
89
89
  setCopied(true);
90
90
  setTimeout(() => setCopied(false), 1500);
91
91
  } catch {
@@ -47,7 +47,7 @@ function StructuredValueTabs({
47
47
  const json = value !== void 0 && value !== null ? JSON.stringify(value, null, 2) : raw ?? "";
48
48
  const copy = async () => {
49
49
  try {
50
- await copyText(json);
50
+ if (!await copyText(json)) return;
51
51
  setCopied(true);
52
52
  setTimeout(() => setCopied(false), 1500);
53
53
  } catch {
@@ -489,7 +489,7 @@ const JsonViewHeader = ({
489
489
  const [copied, setCopied] = useState(false);
490
490
  const handleCopy = async () => {
491
491
  try {
492
- await copyText(content);
492
+ if (!await copyText(content)) return;
493
493
  setCopied(true);
494
494
  toast.success("Copied JSON to clipboard");
495
495
  setTimeout(() => setCopied(false), 1500);
@@ -24,7 +24,7 @@ const MarkdownPreviewBlock = ({
24
24
  const [mode, setMode] = useState("preview");
25
25
  const [copied, setCopied] = useState(false);
26
26
  const handleCopy = async () => {
27
- await copyText(content);
27
+ if (!await copyText(content)) return;
28
28
  setCopied(true);
29
29
  setTimeout(() => setCopied(false), 2e3);
30
30
  };
@@ -40,7 +40,7 @@ const SvgBlock = ({ content = "", isStreamActive = false, className }) => {
40
40
  const [fullscreen, setFullscreen] = useState(false);
41
41
  const handleCopy = async () => {
42
42
  try {
43
- await copyText(svg);
43
+ if (!await copyText(svg)) return;
44
44
  setCopied(true);
45
45
  setTimeout(() => setCopied(false), 1500);
46
46
  } catch {
@@ -268,9 +268,9 @@ const StreamingTableRendererCore = ({
268
268
  };
269
269
  const copyJsonToClipboard = async () => {
270
270
  try {
271
- await copyText(
271
+ if (!await copyText(
272
272
  JSON.stringify(tableData.normalizedData, null, 2)
273
- );
273
+ )) return;
274
274
  toast.success("JSON copied to clipboard");
275
275
  } catch (err) {
276
276
  toast.error(err instanceof Error ? err.message : "Failed to copy JSON");
@@ -75,7 +75,7 @@ const TomlBlock = ({ content, className }) => {
75
75
  );
76
76
  const lines = parseTomlLines(content);
77
77
  const handleCopy = async () => {
78
- await copyText(content);
78
+ if (!await copyText(content)) return;
79
79
  setCopied(true);
80
80
  setTimeout(() => setCopied(false), 2e3);
81
81
  };
@@ -94,7 +94,7 @@ const TreeBlock = ({ content, className }) => {
94
94
  const lines = parseTreeLines(content);
95
95
  const wrap = treeWrapsLines(useCanvasPresentation());
96
96
  const handleCopy = async () => {
97
- await copyText(content);
97
+ if (!await copyText(content)) return;
98
98
  setCopied(true);
99
99
  setTimeout(() => setCopied(false), 2e3);
100
100
  };
@@ -38,7 +38,7 @@ const XmlBlock = ({
38
38
  const tokens = tokenizeXml(content);
39
39
  const isStreaming = useMarkdownStreaming();
40
40
  const handleCopy = async () => {
41
- await copyText(content);
41
+ if (!await copyText(content)) return;
42
42
  setCopied(true);
43
43
  setTimeout(() => setCopied(false), 2e3);
44
44
  };
@@ -81,7 +81,7 @@ const YamlBlock = ({ content, className }) => {
81
81
  const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
82
82
  const lines = parseYamlLines(content);
83
83
  const handleCopy = async () => {
84
- await copyText(content);
84
+ if (!await copyText(content)) return;
85
85
  setCopied(true);
86
86
  setTimeout(() => setCopied(false), 2e3);
87
87
  };
@@ -91,7 +91,7 @@ const InlineCodeSnippet = ({
91
91
  const langColor = language ? LANGUAGE_COLORS[language] : void 0;
92
92
  const handleCopy = async () => {
93
93
  try {
94
- await copyText(code);
94
+ if (!await copyText(code)) return;
95
95
  setCopied(true);
96
96
  setTimeout(() => setCopied(false), 2e3);
97
97
  } catch {
@@ -3,6 +3,7 @@ import { Suspense, lazy } from "react";
3
3
  import { cn } from "@ai-matrx/design-system";
4
4
  import { MarkdownErrorBoundary } from "./MarkdownErrorBoundary.js";
5
5
  import { findEmbeddedKindJsonRegions } from "@ai-matrx/content-ir/surfaces";
6
+ import { fenceOpenerOf } from "@ai-matrx/content-ir/source";
6
7
  import {
7
8
  firstKindSlug,
8
9
  hasKindKey,
@@ -15,12 +16,11 @@ import { humanizeKind } from "@ai-matrx/content-ir/surfaces";
15
16
  const KindInstanceRender = lazy(
16
17
  () => import("../../../host/app-bindings.js").then((m) => ({ default: m.KindInstanceRender }))
17
18
  );
18
- const FENCE_LINE = /^[ \t]*(`{3,}|~{3,})[^\n`]*$/gm;
19
19
  function isKindObject(value) {
20
20
  return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.__kind === "string";
21
21
  }
22
22
  function textPieces(text) {
23
- const cleaned = text.replace(FENCE_LINE, "");
23
+ const cleaned = text.split("\n").map((line) => fenceOpenerOf(line) ? "" : line).join("\n");
24
24
  if (!hasKindKey(cleaned)) return cleaned.trim() ? [{ type: "text", text: cleaned }] : [];
25
25
  const keyAt = cleaned.search(/"__kind"\s*:/);
26
26
  const objectAt = cleaned.lastIndexOf("{", keyAt);
@@ -269,9 +269,9 @@ const MarkdownTable = ({
269
269
  };
270
270
  const copyJsonToClipboard = async () => {
271
271
  try {
272
- await copyText(
272
+ if (!await copyText(
273
273
  JSON.stringify(internalTableData.normalizedData, null, 2)
274
- );
274
+ )) return;
275
275
  toast.success("JSON copied to clipboard");
276
276
  } catch (err) {
277
277
  toast.error(err instanceof Error ? err.message : "Failed to copy JSON");
@@ -86,9 +86,9 @@ const TableControls = ({
86
86
  }, [generateMarkdownTable, toast]);
87
87
  const copyJsonToClipboard = useCallback(async () => {
88
88
  try {
89
- await copyText(
89
+ if (!await copyText(
90
90
  JSON.stringify(debouncedTableData.normalizedData, null, 2)
91
- );
91
+ )) return;
92
92
  toast.success("JSON copied to clipboard");
93
93
  } catch (err) {
94
94
  toast.error(err instanceof Error ? err.message : "Failed to copy JSON");
@@ -1,22 +1,22 @@
1
1
  import { renderMermaid } from "./runtime.js";
2
+ import { closeFence, fenceOpenerOf } from "@ai-matrx/content-ir/source";
2
3
  const PRINT_OPTIONS = { theme: "default", look: "classic", layout: "dagre" };
3
4
  function mermaidFenceSources(markdown) {
4
5
  const out = [];
5
6
  const lines = markdown.split("\n");
7
+ const fenceLines = { count: lines.length, line: (index) => lines[index] ?? null };
6
8
  for (let i = 0; i < lines.length; i++) {
7
- const open = /^(\s*)(`{3,}|~{3,})\s*mermaid\b[^`]*$/.exec(lines[i]);
8
- if (!open) continue;
9
- const marker = open[2];
10
- const indent = open[1].length;
11
- const body = [];
12
- let j = i + 1;
13
- for (; j < lines.length; j++) {
14
- const close = /^\s*(`{3,}|~{3,})\s*$/.exec(lines[j]);
15
- if (close && close[1][0] === marker[0] && close[1].length >= marker.length) break;
16
- body.push(lines[j]);
9
+ const opener = fenceOpenerOf(lines[i]);
10
+ if (!opener) continue;
11
+ const end = closeFence(fenceLines, i, opener);
12
+ if (opener.lang !== "mermaid") {
13
+ i = end.line;
14
+ continue;
17
15
  }
16
+ const indent = lines[i].length - lines[i].trimStart().length;
17
+ const body = lines.slice(i + 1, end.closed ? end.line : end.line + 1);
18
18
  out.push(body.map((l) => l.slice(Math.min(indent, l.length - l.trimStart().length))).join("\n"));
19
- i = j;
19
+ i = end.line;
20
20
  }
21
21
  return out;
22
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/rich-content",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "The AI Matrx formatted-text engine: the one markdown renderer, streaming markdown, the block splitter and renderer, the generic blocks, <RichContent> levels, the rich-document action registry and menus, copy, and the selection toolbar — as portable React.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -117,18 +117,18 @@
117
117
  "unist-util-visit-parents": "^6.0.2",
118
118
  "yaml": "^2.9.1",
119
119
  "@ai-matrx/canvas": "latest",
120
- "@ai-matrx/content-ir-react": "latest",
121
120
  "@ai-matrx/data": "latest",
122
- "@ai-matrx/design-system": "latest",
123
121
  "@ai-matrx/content-ir": "latest",
122
+ "@ai-matrx/content-ir-react": "latest",
123
+ "@ai-matrx/design-system": "latest",
124
124
  "@ai-matrx/agents": "latest",
125
125
  "@ai-matrx/diff": "latest",
126
126
  "@ai-matrx/icons": "latest",
127
127
  "@ai-matrx/kit": "latest",
128
+ "@ai-matrx/media": "latest",
128
129
  "@ai-matrx/print": "latest",
129
130
  "@ai-matrx/records": "latest",
130
- "@ai-matrx/tap-target": "latest",
131
- "@ai-matrx/media": "latest"
131
+ "@ai-matrx/tap-target": "latest"
132
132
  },
133
133
  "peerDependencies": {
134
134
  "react": ">=19.0.0",