@ai-matrx/rich-content 0.2.8 → 0.2.9

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,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.9
4
+
5
+ AP-2 (2026-10-07) — the kit and Alchemy doors are the only doors in this package (aidream guard `apps/shared/scripts/check-shared-doors.mjs`). New dependency `@ai-matrx/alchemy`; `html2canvas` dependency removed. Every table/code/SVG/JSON/mermaid/diagram download saves through `@ai-matrx/kit/download` (`downloadFile` / `downloadUrl`) — the tables' "Download Again" toast action now really downloads again (it clicked a revoked URL before). Link/URL/mermaid copies go through `@ai-matrx/kit/clipboard` `copyText` (copied state from its boolean; `copyMermaidSource`/`copyMermaidSvg` throw when refused, as before); ImageBlock's image copy uses `useClipboard().copyImage`. The diagram PNG export captures through `@ai-matrx/alchemy/operate/capture` `captureElement` with `safeColors` (the local getComputedStyle patch is gone). No consumer action.
6
+
3
7
  ## 0.2.8
4
8
  ### Unreleased
5
9
 
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { downloadFile } from "@ai-matrx/kit/download";
3
4
  import { useClipboard } from "@ai-matrx/kit/clipboard";
4
5
  import { useRef, useState, useEffect } from "react";
5
6
  import { KindDataGate } from "../host/app-bindings.js";
@@ -184,15 +185,9 @@ const CodeBlockBody = ({
184
185
  };
185
186
  const handleDownload = (e) => {
186
187
  e.stopPropagation();
187
- const blob = new Blob([code], { type: "text/plain" });
188
- const url = window.URL.createObjectURL(blob);
189
- const a = document.createElement("a");
190
- a.href = url;
191
188
  const titleFile = fence.title && /^[\w./-]+\.\w+$/.test(fence.title) ? fence.title.split("/").pop() : void 0;
192
189
  const ext = codeLanguageToExtension(rawLanguage || "txt");
193
- a.download = titleFile ?? `code${ext}`;
194
- a.click();
195
- window.URL.revokeObjectURL(url);
190
+ downloadFile(titleFile ?? `code${ext}`, code, "text/plain");
196
191
  };
197
192
  const toggleLineNumbers = (e) => {
198
193
  e.stopPropagation();
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { downloadFile } from "@ai-matrx/kit/download";
3
4
  import { useState, useRef, useEffect } from "react";
4
5
  import {
5
6
  Maximize2,
@@ -93,15 +94,7 @@ const BlockHeaderWrapper = ({
93
94
  };
94
95
  const handleExport = () => {
95
96
  if (exportData === void 0) return;
96
- const blob = new Blob([JSON.stringify(exportData, null, 2)], {
97
- type: "application/json"
98
- });
99
- const url = URL.createObjectURL(blob);
100
- const a = document.createElement("a");
101
- a.href = url;
102
- a.download = `${exportFilename}.json`;
103
- a.click();
104
- URL.revokeObjectURL(url);
97
+ downloadFile(`${exportFilename}.json`, JSON.stringify(exportData, null, 2), "application/json");
105
98
  };
106
99
  const handleImportClick = () => fileInputRef.current?.click();
107
100
  const handleFileChange = (e) => {
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { captureElement } from "@ai-matrx/alchemy/operate/capture";
4
+ import { downloadFile, downloadUrl } from "@ai-matrx/kit/download";
3
5
  import { Button, Chip } from "@ai-matrx/design-system/controls";
4
6
  import {
5
7
  useState,
@@ -121,7 +123,6 @@ import {
121
123
  DropdownMenuSeparator,
122
124
  DropdownMenuTrigger
123
125
  } from "@ai-matrx/design-system";
124
- const UNSAFE_COLOR_RE = /\b(oklch|oklab|lab|lch|color)\s*\(/i;
125
126
  function backgroundVariantFromDiagram(diagram) {
126
127
  if (diagram.renderHints?.background === "lines")
127
128
  return BackgroundVariant.Lines;
@@ -129,42 +130,6 @@ function backgroundVariantFromDiagram(diagram) {
129
130
  return BackgroundVariant.Cross;
130
131
  return BackgroundVariant.Dots;
131
132
  }
132
- function safenColor(value, prop, isDark) {
133
- if (!UNSAFE_COLOR_RE.test(value)) return value;
134
- const lp = prop.toLowerCase();
135
- if (lp.includes("background")) return isDark ? "#1f2937" : "#ffffff";
136
- if (lp === "color") return isDark ? "#f3f4f6" : "#111827";
137
- if (lp.includes("border") || lp.includes("outline"))
138
- return isDark ? "#4b5563" : "#d1d5db";
139
- if (lp === "fill" || lp === "stroke") return isDark ? "#9ca3af" : "#374151";
140
- return isDark ? "#6b7280" : "#6b7280";
141
- }
142
- function patchComputedStyleForCapture(isDark) {
143
- const original = window.getComputedStyle.bind(window);
144
- window.getComputedStyle = function(elt, pseudo) {
145
- const computed = original(elt, pseudo);
146
- return new Proxy(computed, {
147
- get(target, prop) {
148
- const value = target[prop];
149
- if (typeof prop === "string" && typeof value === "string" && value && UNSAFE_COLOR_RE.test(value)) {
150
- return safenColor(value, prop, isDark);
151
- }
152
- if (prop === "getPropertyValue") {
153
- return (p) => {
154
- const raw = target.getPropertyValue(p);
155
- return raw && UNSAFE_COLOR_RE.test(raw) ? safenColor(raw, p, isDark) : raw;
156
- };
157
- }
158
- if (typeof value === "function")
159
- return value.bind(target);
160
- return value;
161
- }
162
- });
163
- };
164
- return () => {
165
- window.getComputedStyle = original;
166
- };
167
- }
168
133
  const PedigreeNode = ({
169
134
  data,
170
135
  selected
@@ -1081,22 +1046,20 @@ const DiagramFlow = ({
1081
1046
  }
1082
1047
  exportContainer.appendChild(cloned);
1083
1048
  document.body.appendChild(exportContainer);
1084
- import("html2canvas").then((mod) => {
1085
- const restore = patchComputedStyleForCapture(isDark);
1086
- return mod.default(exportContainer, {
1087
- backgroundColor: bgColor,
1088
- scale: 2,
1089
- useCORS: true,
1090
- allowTaint: true,
1091
- width: imageWidth,
1092
- height: imageHeight,
1093
- ignoreElements: (el) => el.classList.contains("react-flow__controls") || el.classList.contains("react-flow__minimap") || el.classList.contains("react-flow__panel")
1094
- }).finally(restore);
1049
+ captureElement(exportContainer, {
1050
+ backgroundColor: bgColor,
1051
+ scale: 2,
1052
+ useCORS: true,
1053
+ allowTaint: true,
1054
+ width: imageWidth,
1055
+ height: imageHeight,
1056
+ safeColors: { dark: isDark },
1057
+ ignoreElements: (el) => el.classList.contains("react-flow__controls") || el.classList.contains("react-flow__minimap") || el.classList.contains("react-flow__panel")
1095
1058
  }).then((canvas) => {
1096
- const a = document.createElement("a");
1097
- a.download = `${diagram.title.replace(/[^a-z0-9]/gi, "_").toLowerCase()}_diagram${isDark ? "_dark" : "_light"}.png`;
1098
- a.href = canvas.toDataURL("image/png");
1099
- a.click();
1059
+ downloadUrl(
1060
+ canvas.toDataURL("image/png"),
1061
+ `${diagram.title.replace(/[^a-z0-9]/gi, "_").toLowerCase()}_diagram${isDark ? "_dark" : "_light"}.png`
1062
+ );
1100
1063
  }).catch((err) => console.warn("Diagram export failed:", err)).finally(() => document.body.removeChild(exportContainer));
1101
1064
  }, [getNodes, diagram.title, onExportImage]);
1102
1065
  const measuredWidth = useStore((s) => s.width);
@@ -2133,15 +2096,11 @@ const InteractiveDiagramBlock = ({
2133
2096
  triggerPrint
2134
2097
  } = usePrintOptions(diagramPrinter, diagram);
2135
2098
  const exportDiagramJSON = useCallback(() => {
2136
- const blob = new Blob([JSON.stringify({ diagram }, null, 2)], {
2137
- type: "application/json"
2138
- });
2139
- const url = URL.createObjectURL(blob);
2140
- const a = document.createElement("a");
2141
- a.href = url;
2142
- a.download = `${diagram.title.replace(/[^a-z0-9]/gi, "_").toLowerCase()}.json`;
2143
- a.click();
2144
- URL.revokeObjectURL(url);
2099
+ downloadFile(
2100
+ `${diagram.title.replace(/[^a-z0-9]/gi, "_").toLowerCase()}.json`,
2101
+ JSON.stringify({ diagram }, null, 2),
2102
+ "application/json"
2103
+ );
2145
2104
  }, [diagram]);
2146
2105
  const toggleEditing = () => {
2147
2106
  const next = !editing;
@@ -2154,18 +2113,16 @@ const InteractiveDiagramBlock = ({
2154
2113
  );
2155
2114
  if (!viewport) return;
2156
2115
  const isDark = document.documentElement.classList.contains("dark") || window.matchMedia("(prefers-color-scheme: dark)").matches;
2157
- import("html2canvas").then((mod) => {
2158
- const restore = patchComputedStyleForCapture(isDark);
2159
- return mod.default(viewport, {
2160
- backgroundColor: isDark ? "#111827" : "#ffffff",
2161
- scale: 2,
2162
- useCORS: true
2163
- }).finally(restore);
2116
+ captureElement(viewport, {
2117
+ backgroundColor: isDark ? "#111827" : "#ffffff",
2118
+ scale: 2,
2119
+ useCORS: true,
2120
+ safeColors: { dark: isDark }
2164
2121
  }).then((canvas) => {
2165
- const a = document.createElement("a");
2166
- a.download = `${diagram.title.replace(/[^a-z0-9]/gi, "_").toLowerCase()}_diagram.png`;
2167
- a.href = canvas.toDataURL("image/png");
2168
- a.click();
2122
+ downloadUrl(
2123
+ canvas.toDataURL("image/png"),
2124
+ `${diagram.title.replace(/[^a-z0-9]/gi, "_").toLowerCase()}_diagram.png`
2125
+ );
2169
2126
  }).catch((err) => console.warn("Image export failed:", err));
2170
2127
  }, [diagram.title]);
2171
2128
  const renderLegend = () => {
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { copyText, useClipboard } from "@ai-matrx/kit/clipboard";
4
+ import { downloadFile } from "@ai-matrx/kit/download";
3
5
  import { useState, useRef, useEffect } from "react";
4
6
  import {
5
7
  DownloadIcon,
@@ -46,6 +48,7 @@ const ImageBlockImpl = ({ src: srcProp, alt = "Image" }) => {
46
48
  const editableFileId = ourFile?.fileId ?? null;
47
49
  const [feedback, setFeedback] = useState("none");
48
50
  const [showCopySuccess, setShowCopySuccess] = useState(false);
51
+ const { copyImage } = useClipboard();
49
52
  const [showShareModal, setShowShareModal] = useState(false);
50
53
  const [showExpandedView, setShowExpandedView] = useState(false);
51
54
  const [zoomLevel, setZoomLevel] = useState(1);
@@ -55,45 +58,24 @@ const ImageBlockImpl = ({ src: srcProp, alt = "Image" }) => {
55
58
  try {
56
59
  const response = await fetch(src);
57
60
  const blob = await response.blob();
58
- const url = window.URL.createObjectURL(blob);
59
- const link = document.createElement("a");
60
- link.href = url;
61
- link.download = src.split("/").pop() || "image";
62
- document.body.appendChild(link);
63
- link.click();
64
- document.body.removeChild(link);
65
- window.URL.revokeObjectURL(url);
61
+ downloadFile(src.split("/").pop() || "image", blob, blob.type);
66
62
  } catch (err) {
67
63
  console.error("Failed to download image:", err);
68
64
  }
69
65
  };
70
66
  const handleCopyUrl = (e) => {
71
67
  e?.preventDefault();
72
- navigator.clipboard.writeText(src).then(() => {
68
+ void copyText(src).then((landed) => {
69
+ if (!landed) return;
73
70
  setShowCopySuccess(true);
74
71
  setTimeout(() => setShowCopySuccess(false), 2e3);
75
- }).catch((err) => {
76
- console.error("Failed to copy URL:", err);
77
72
  });
78
73
  };
79
74
  const handleCopyImage = async (e) => {
80
75
  e?.preventDefault();
81
- try {
82
- const response = await fetch(src);
83
- const blob = await response.blob();
84
- if (navigator.clipboard && navigator.clipboard.write) {
85
- await navigator.clipboard.write([
86
- new ClipboardItem({
87
- [blob.type]: blob
88
- })
89
- ]);
90
- setShowCopySuccess(true);
91
- setTimeout(() => setShowCopySuccess(false), 2e3);
92
- } else {
93
- console.error("Clipboard API not supported");
94
- }
95
- } catch (err) {
96
- console.error("Failed to copy image to clipboard:", err);
76
+ if (await copyImage(src)) {
77
+ setShowCopySuccess(true);
78
+ setTimeout(() => setShowCopySuccess(false), 2e3);
97
79
  }
98
80
  };
99
81
  const handleShare = async (e) => {
@@ -1,3 +1,4 @@
1
+ import { downloadFile } from "@ai-matrx/kit/download";
1
2
  import { announceBareHost, hostCapability } from "../../../host/registry.js";
2
3
  function parseJsonSafe(text) {
3
4
  if (!text || !text.trim()) {
@@ -89,15 +90,7 @@ function rowsToNdjson(rows) {
89
90
  return rows.map((r) => JSON.stringify(r)).join("\n");
90
91
  }
91
92
  function downloadText(filename, content, mime = "text/plain") {
92
- const blob = new Blob([content], { type: `${mime};charset=utf-8` });
93
- const url = window.URL.createObjectURL(blob);
94
- const a = document.createElement("a");
95
- a.href = url;
96
- a.download = filename;
97
- document.body.appendChild(a);
98
- a.click();
99
- document.body.removeChild(a);
100
- window.URL.revokeObjectURL(url);
93
+ downloadFile(filename, content, `${mime};charset=utf-8`);
101
94
  }
102
95
  async function rowsToXlsx(rows, columns, filename) {
103
96
  const impl = hostCapability("app")?.rowsToXlsx;
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { copyText } from "@ai-matrx/kit/clipboard";
3
4
  import React, { useState, useRef, useEffect, useCallback, useId, memo } from "react";
4
5
  import { createPortal } from "react-dom";
5
6
  import { cn } from "@ai-matrx/design-system";
@@ -164,15 +165,10 @@ const ValidLinkComponentCore = ({
164
165
  clearOpenTimer();
165
166
  }, [clearOpenTimer]);
166
167
  const handleCopyLink = useCallback(() => {
167
- if (!navigator.clipboard) {
168
- console.error("Clipboard API not available");
169
- return;
170
- }
171
- navigator.clipboard.writeText(finalHref).then(() => {
168
+ void copyText(finalHref).then((landed) => {
169
+ if (!landed) return;
172
170
  setCopied(true);
173
171
  setTimeout(() => setCopied(false), 2e3);
174
- }).catch((err) => {
175
- console.error("Failed to copy link:", err);
176
172
  });
177
173
  }, [finalHref]);
178
174
  const handleOpenInNewTab = useCallback(
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { downloadFile } from "@ai-matrx/kit/download";
3
4
  import { useClipboard } from "@ai-matrx/kit/clipboard";
4
5
  import { useEffect, useMemo, useState } from "react";
5
6
  import { createPortal } from "react-dom";
@@ -49,13 +50,7 @@ const SvgBlock = ({ content = "", isStreamActive = false, className }) => {
49
50
  };
50
51
  const handleDownload = () => {
51
52
  try {
52
- const blob = new Blob([svg], { type: "image/svg+xml" });
53
- const url = URL.createObjectURL(blob);
54
- const a = document.createElement("a");
55
- a.href = url;
56
- a.download = `${(title ?? "illustration").replace(/[^\w-]+/g, "-").toLowerCase()}.svg`;
57
- a.click();
58
- URL.revokeObjectURL(url);
53
+ downloadFile(`${(title ?? "illustration").replace(/[^\w-]+/g, "-").toLowerCase()}.svg`, svg, "image/svg+xml");
59
54
  } catch {
60
55
  toast.error("Couldn't download the SVG");
61
56
  }
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { downloadFile } from "@ai-matrx/kit/download";
3
4
  import { useClipboard } from "@ai-matrx/kit/clipboard";
4
5
  import { toast as copyToast } from "../../../host/toast.js";
5
6
  import { rewriteTableSource } from "../../../utils/table-source.js";
@@ -300,17 +301,12 @@ const StreamingTableRendererCore = ({
300
301
  }).join(",")
301
302
  )
302
303
  ].join("\n");
303
- const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
304
- const url = URL.createObjectURL(blob);
305
- const link = document.createElement("a");
306
- link.href = url;
307
- link.download = "table_data.csv";
308
- link.click();
309
- URL.revokeObjectURL(url);
304
+ const saveCsv = () => downloadFile("table_data.csv", csvContent, "text/csv;charset=utf-8");
305
+ saveCsv();
310
306
  toast.success("Table exported to CSV", {
311
307
  action: {
312
308
  label: "Download Again",
313
- onClick: () => link.click(),
309
+ onClick: saveCsv,
314
310
  className: "font-medium"
315
311
  }
316
312
  });
@@ -324,28 +320,12 @@ const StreamingTableRendererCore = ({
324
320
  try {
325
321
  const markdownContent = content || "";
326
322
  const fileName = headers && headers[0] ? `${headers[0].replace(/[^a-z0-9]/gi, "_").toLowerCase()}.md` : "table_data.md";
327
- const blob = new Blob([markdownContent], {
328
- type: "text/markdown;charset=utf-8"
329
- });
330
- const url = URL.createObjectURL(blob);
331
- const link = document.createElement("a");
332
- link.href = url;
333
- link.download = fileName;
334
- document.body.appendChild(link);
335
- link.click();
336
- document.body.removeChild(link);
337
- URL.revokeObjectURL(url);
323
+ const saveMarkdown = () => downloadFile(fileName, markdownContent, "text/markdown;charset=utf-8");
324
+ saveMarkdown();
338
325
  toast.success("Markdown file downloaded", {
339
326
  action: {
340
327
  label: "Download Again",
341
- onClick: () => {
342
- const newLink = document.createElement("a");
343
- newLink.href = url;
344
- newLink.download = fileName;
345
- document.body.appendChild(newLink);
346
- newLink.click();
347
- document.body.removeChild(newLink);
348
- },
328
+ onClick: saveMarkdown,
349
329
  className: "font-medium"
350
330
  }
351
331
  });
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { downloadFile } from "@ai-matrx/kit/download";
3
4
  import { useClipboard } from "@ai-matrx/kit/clipboard";
4
5
  import { toast as copyToast } from "../../host/toast.js";
5
6
  import { rewriteTableSource } from "../../utils/table-source.js";
@@ -299,17 +300,12 @@ const MarkdownTable = ({
299
300
  }).join(",")
300
301
  )
301
302
  ].join("\n");
302
- const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
303
- const url = URL.createObjectURL(blob);
304
- const link = document.createElement("a");
305
- link.href = url;
306
- link.download = "table_data.csv";
307
- link.click();
308
- URL.revokeObjectURL(url);
303
+ const saveCsv = () => downloadFile("table_data.csv", csvContent, "text/csv;charset=utf-8");
304
+ saveCsv();
309
305
  toast.success("Table exported to CSV", {
310
306
  action: {
311
307
  label: "Download Again",
312
- onClick: () => link.click(),
308
+ onClick: saveCsv,
313
309
  className: "font-medium"
314
310
  }
315
311
  });
@@ -321,28 +317,12 @@ const MarkdownTable = ({
321
317
  try {
322
318
  const markdownContent = content || "";
323
319
  const fileName = internalTableData.headers && internalTableData.headers[0] ? `${internalTableData.headers[0].replace(/[^a-z0-9]/gi, "_").toLowerCase()}.md` : "table_data.md";
324
- const blob = new Blob([markdownContent], {
325
- type: "text/markdown;charset=utf-8"
326
- });
327
- const url = URL.createObjectURL(blob);
328
- const link = document.createElement("a");
329
- link.href = url;
330
- link.download = fileName;
331
- document.body.appendChild(link);
332
- link.click();
333
- document.body.removeChild(link);
334
- URL.revokeObjectURL(url);
320
+ const saveMarkdown = () => downloadFile(fileName, markdownContent, "text/markdown;charset=utf-8");
321
+ saveMarkdown();
335
322
  toast.success("Markdown file downloaded", {
336
323
  action: {
337
324
  label: "Download Again",
338
- onClick: () => {
339
- const newLink = document.createElement("a");
340
- newLink.href = url;
341
- newLink.download = fileName;
342
- document.body.appendChild(newLink);
343
- newLink.click();
344
- document.body.removeChild(newLink);
345
- },
325
+ onClick: saveMarkdown,
346
326
  className: "font-medium"
347
327
  }
348
328
  });
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { downloadFile } from "@ai-matrx/kit/download";
3
4
  import { useClipboard } from "@ai-matrx/kit/clipboard";
4
5
  import { toast as copyToast } from "../../host/toast.js";
5
6
  import { rewriteTableSource } from "../../utils/table-source.js";
@@ -116,17 +117,12 @@ const TableControls = ({
116
117
  }).join(",")
117
118
  )
118
119
  ].join("\n");
119
- const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
120
- const url = URL.createObjectURL(blob);
121
- const link = document.createElement("a");
122
- link.href = url;
123
- link.download = "table_data.csv";
124
- link.click();
125
- URL.revokeObjectURL(url);
120
+ const saveCsv = () => downloadFile("table_data.csv", csvContent, "text/csv;charset=utf-8");
121
+ saveCsv();
126
122
  toast.success("Table exported to CSV", {
127
123
  action: {
128
124
  label: "Download Again",
129
- onClick: () => link.click(),
125
+ onClick: saveCsv,
130
126
  className: "font-medium"
131
127
  }
132
128
  });
@@ -138,28 +134,12 @@ const TableControls = ({
138
134
  try {
139
135
  const markdownContent = content || "";
140
136
  const fileName = tableData.headers && tableData.headers[0] ? `${tableData.headers[0].replace(/[^a-z0-9]/gi, "_").toLowerCase()}.md` : "table_data.md";
141
- const blob = new Blob([markdownContent], {
142
- type: "text/markdown;charset=utf-8"
143
- });
144
- const url = URL.createObjectURL(blob);
145
- const link = document.createElement("a");
146
- link.href = url;
147
- link.download = fileName;
148
- document.body.appendChild(link);
149
- link.click();
150
- document.body.removeChild(link);
151
- URL.revokeObjectURL(url);
137
+ const saveMarkdown = () => downloadFile(fileName, markdownContent, "text/markdown;charset=utf-8");
138
+ saveMarkdown();
152
139
  toast.success("Markdown file downloaded", {
153
140
  action: {
154
141
  label: "Download Again",
155
- onClick: () => {
156
- const newLink = document.createElement("a");
157
- newLink.href = url;
158
- newLink.download = fileName;
159
- document.body.appendChild(newLink);
160
- newLink.click();
161
- document.body.removeChild(newLink);
162
- },
142
+ onClick: saveMarkdown,
163
143
  className: "font-medium"
164
144
  }
165
145
  });
@@ -1,4 +1,5 @@
1
1
  import type { NormalizedFile } from "@ai-matrx/media/files/engine/handler/types";
2
+ /** Throws when the browser refused the copy — callers say so. */
2
3
  export declare function copyMermaidSource(source: string): Promise<void>;
3
4
  /** Copies the SVG markup as text — the universally pasteable form. */
4
5
  export declare function copyMermaidSvg(svg: string): Promise<void>;
@@ -1,25 +1,22 @@
1
1
  "use client";
2
+ import { copyText } from "@ai-matrx/kit/clipboard";
3
+ import { downloadFile } from "@ai-matrx/kit/download";
2
4
  import { fileHandler } from "@ai-matrx/media/files/engine/handler/handler";
3
5
  function svgBlob(svg) {
4
6
  return new Blob([svg], { type: "image/svg+xml;charset=utf-8" });
5
7
  }
6
8
  function triggerDownload(blob, fileName) {
7
- const url = URL.createObjectURL(blob);
8
- const a = document.createElement("a");
9
- a.href = url;
10
- a.download = fileName;
11
- a.click();
12
- URL.revokeObjectURL(url);
9
+ downloadFile(fileName, blob, blob.type);
13
10
  }
14
11
  function safeFileName(title, extension) {
15
12
  const base = (title || "diagram").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
16
13
  return `${base || "diagram"}.${extension}`;
17
14
  }
18
15
  async function copyMermaidSource(source) {
19
- await navigator.clipboard.writeText(source);
16
+ if (!await copyText(source)) throw new Error("The browser refused the clipboard.");
20
17
  }
21
18
  async function copyMermaidSvg(svg) {
22
- await navigator.clipboard.writeText(svg);
19
+ if (!await copyText(svg)) throw new Error("The browser refused the clipboard.");
23
20
  }
24
21
  function downloadMermaidSvg(svg, title) {
25
22
  triggerDownload(svgBlob(svg), safeFileName(title, "svg"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/rich-content",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
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",
@@ -85,7 +85,6 @@
85
85
  "hast-util-sanitize": "^5.0.2",
86
86
  "hast-util-to-string": "^3.0.1",
87
87
  "hast-util-to-text": "^4.0.2",
88
- "html2canvas": "^1.4.1",
89
88
  "katex": "^0.18.9",
90
89
  "lucide-react": "^1.48.0",
91
90
  "marked": "^18.0.14",
@@ -116,19 +115,20 @@
116
115
  "unist-util-visit": "^5.1.0",
117
116
  "unist-util-visit-parents": "^6.0.2",
118
117
  "yaml": "^2.9.1",
118
+ "@ai-matrx/agents": "latest",
119
+ "@ai-matrx/alchemy": "latest",
119
120
  "@ai-matrx/canvas": "latest",
120
- "@ai-matrx/data": "latest",
121
- "@ai-matrx/content-ir": "latest",
122
121
  "@ai-matrx/content-ir-react": "latest",
122
+ "@ai-matrx/content-ir": "latest",
123
+ "@ai-matrx/data": "latest",
123
124
  "@ai-matrx/design-system": "latest",
124
- "@ai-matrx/agents": "latest",
125
125
  "@ai-matrx/diff": "latest",
126
126
  "@ai-matrx/icons": "latest",
127
- "@ai-matrx/kit": "latest",
128
- "@ai-matrx/media": "latest",
129
127
  "@ai-matrx/print": "latest",
130
128
  "@ai-matrx/records": "latest",
131
- "@ai-matrx/tap-target": "latest"
129
+ "@ai-matrx/tap-target": "latest",
130
+ "@ai-matrx/media": "latest",
131
+ "@ai-matrx/kit": "latest"
132
132
  },
133
133
  "peerDependencies": {
134
134
  "react": ">=19.0.0",