@particle-academy/react-fancy 5.66.0 → 5.67.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/CHANGELOG.md +52 -0
- package/dist/button.cjs +3 -4
- package/dist/button.js +1 -2
- package/dist/{chunk-J44EV6SP.js → chunk-23AVEYUP.js} +3 -3
- package/dist/{chunk-J44EV6SP.js.map → chunk-23AVEYUP.js.map} +1 -1
- package/dist/{chunk-C5YX5XVL.cjs → chunk-3U7PUZLI.cjs} +214 -4
- package/dist/chunk-3U7PUZLI.cjs.map +1 -0
- package/dist/{chunk-ATIDUWY6.cjs → chunk-4S5DFN5N.cjs} +12 -12
- package/dist/{chunk-ATIDUWY6.cjs.map → chunk-4S5DFN5N.cjs.map} +1 -1
- package/dist/{chunk-QP35Z2WB.js → chunk-BL5IKSB3.js} +3 -3
- package/dist/{chunk-QP35Z2WB.js.map → chunk-BL5IKSB3.js.map} +1 -1
- package/dist/{chunk-4EUDW2WU.js → chunk-DCAD2JPP.js} +3 -3
- package/dist/{chunk-4EUDW2WU.js.map → chunk-DCAD2JPP.js.map} +1 -1
- package/dist/{chunk-MCQJFMDP.cjs → chunk-E5UU666U.cjs} +478 -36
- package/dist/chunk-E5UU666U.cjs.map +1 -0
- package/dist/{chunk-L24LUSEE.js → chunk-FXOT4P4P.js} +478 -36
- package/dist/chunk-FXOT4P4P.js.map +1 -0
- package/dist/{chunk-CAUEKGMR.js → chunk-HEY4SMS2.js} +4 -4
- package/dist/{chunk-CAUEKGMR.js.map → chunk-HEY4SMS2.js.map} +1 -1
- package/dist/{chunk-TLRRMYNV.js → chunk-OXERDFFF.js} +212 -4
- package/dist/chunk-OXERDFFF.js.map +1 -0
- package/dist/{chunk-SYEHWUM5.cjs → chunk-RALJMI7C.cjs} +4 -4
- package/dist/{chunk-SYEHWUM5.cjs.map → chunk-RALJMI7C.cjs.map} +1 -1
- package/dist/{chunk-UM4JXBRZ.cjs → chunk-S3NB6LDV.cjs} +4 -4
- package/dist/{chunk-UM4JXBRZ.cjs.map → chunk-S3NB6LDV.cjs.map} +1 -1
- package/dist/{chunk-MCQWTOTV.cjs → chunk-SAG6DHQK.cjs} +6 -6
- package/dist/{chunk-MCQWTOTV.cjs.map → chunk-SAG6DHQK.cjs.map} +1 -1
- package/dist/{chunk-7HXFEYOV.cjs → chunk-U5LKNGHU.cjs} +15 -15
- package/dist/{chunk-7HXFEYOV.cjs.map → chunk-U5LKNGHU.cjs.map} +1 -1
- package/dist/{chunk-M52S3TFI.js → chunk-WFR7HKJL.js} +3 -3
- package/dist/{chunk-M52S3TFI.js.map → chunk-WFR7HKJL.js.map} +1 -1
- package/dist/content-renderer.cjs +7 -5
- package/dist/content-renderer.d.cts +56 -4
- package/dist/content-renderer.d.ts +56 -4
- package/dist/content-renderer.js +4 -2
- package/dist/editor.cjs +6 -5
- package/dist/editor.d.cts +1 -1
- package/dist/editor.d.ts +1 -1
- package/dist/editor.js +4 -3
- package/dist/{extensions-6PVd3-vB.d.cts → extensions-DiByZIpD.d.cts} +26 -3
- package/dist/{extensions-6PVd3-vB.d.ts → extensions-DiByZIpD.d.ts} +26 -3
- package/dist/index.cjs +37 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -8
- package/dist/index.js.map +1 -1
- package/dist/json-editor.cjs +13 -14
- package/dist/json-editor.js +2 -3
- package/dist/magic-wand.cjs +3 -4
- package/dist/magic-wand.js +2 -3
- package/dist/prompt-input.cjs +5 -6
- package/dist/prompt-input.js +2 -3
- package/dist/reason-tag.cjs +3 -4
- package/dist/reason-tag.js +2 -3
- package/docs/ContentRenderer.md +138 -3
- package/package.json +1 -1
- package/dist/chunk-6TBRBN7H.js +0 -212
- package/dist/chunk-6TBRBN7H.js.map +0 -1
- package/dist/chunk-C5YX5XVL.cjs.map +0 -1
- package/dist/chunk-L24LUSEE.js.map +0 -1
- package/dist/chunk-MCQJFMDP.cjs.map +0 -1
- package/dist/chunk-TLRRMYNV.js.map +0 -1
- package/dist/chunk-X2FC7ODX.cjs +0 -215
- package/dist/chunk-X2FC7ODX.cjs.map +0 -1
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
import { sanitizeHtml } from './chunk-
|
|
1
|
+
import { sanitizeHtml, sanitizeHref, Button } from './chunk-OXERDFFF.js';
|
|
2
2
|
import { cn } from './chunk-MMW2JGKZ.js';
|
|
3
3
|
import { marked } from 'marked';
|
|
4
|
-
import { useMemo } from 'react';
|
|
5
|
-
import {
|
|
4
|
+
import { createContext, useMemo, useRef, useState, useCallback, useContext, useLayoutEffect, useEffect } from 'react';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
6
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
7
|
|
|
7
8
|
// src/components/ContentRenderer/extensions.ts
|
|
9
|
+
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "xlink:href"]);
|
|
10
|
+
function safeAttributes(attrs) {
|
|
11
|
+
const out = {};
|
|
12
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
13
|
+
const lower = name.toLowerCase();
|
|
14
|
+
if (lower.startsWith("on") || lower === "srcdoc") continue;
|
|
15
|
+
if (URL_ATTRS.has(lower)) {
|
|
16
|
+
const safe = sanitizeHref(value);
|
|
17
|
+
if (safe === void 0) continue;
|
|
18
|
+
out[name] = safe;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
out[name] = value;
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
function tagAttributes(raw, unsafe) {
|
|
26
|
+
const attrs = raw ? parseAttributes(raw) : {};
|
|
27
|
+
return unsafe ? attrs : safeAttributes(attrs);
|
|
28
|
+
}
|
|
8
29
|
var globalRegistry = [];
|
|
9
30
|
function registerExtension(extension) {
|
|
10
31
|
const idx = globalRegistry.findIndex(
|
|
@@ -811,72 +832,493 @@ function extensionEditorClasses(extensions) {
|
|
|
811
832
|
].join(" ");
|
|
812
833
|
}).join(" ");
|
|
813
834
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
835
|
+
|
|
836
|
+
// src/components/ContentRenderer/markdown-blocks.ts
|
|
837
|
+
var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
838
|
+
function escapeRegExp(s) {
|
|
839
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
840
|
+
}
|
|
841
|
+
function maskInlineCode(line) {
|
|
842
|
+
let out = "";
|
|
843
|
+
let i = 0;
|
|
844
|
+
while (i < line.length) {
|
|
845
|
+
if (line[i] !== "`") {
|
|
846
|
+
out += line[i];
|
|
847
|
+
i++;
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
let n = 0;
|
|
851
|
+
while (line[i + n] === "`") n++;
|
|
852
|
+
const fence = "`".repeat(n);
|
|
853
|
+
let search = i + n;
|
|
854
|
+
let close = -1;
|
|
855
|
+
while (search < line.length) {
|
|
856
|
+
const at = line.indexOf(fence, search);
|
|
857
|
+
if (at < 0) break;
|
|
858
|
+
let len = 0;
|
|
859
|
+
while (line[at + len] === "`") len++;
|
|
860
|
+
if (len === n) {
|
|
861
|
+
close = at;
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
864
|
+
search = at + len;
|
|
865
|
+
}
|
|
866
|
+
if (close < 0) {
|
|
867
|
+
out += fence;
|
|
868
|
+
i += n;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
out += " ".repeat(close + n - i);
|
|
872
|
+
i = close + n;
|
|
873
|
+
}
|
|
874
|
+
return out;
|
|
875
|
+
}
|
|
876
|
+
function scan(src, names, trackFences) {
|
|
877
|
+
const tokens = [];
|
|
878
|
+
let openFenceStart = -1;
|
|
879
|
+
if (names.length === 0 && !trackFences) return { tokens, openFenceStart };
|
|
880
|
+
const tagRe = names.length ? new RegExp(`<(\\/?)(${names.map(escapeRegExp).join("|")})(?=[\\s>/])([^>]*)>`, "gi") : null;
|
|
881
|
+
let fence = null;
|
|
882
|
+
let pos = 0;
|
|
883
|
+
while (pos <= src.length) {
|
|
884
|
+
const nl = src.indexOf("\n", pos);
|
|
885
|
+
const lineEnd = nl < 0 ? src.length : nl;
|
|
886
|
+
const line = src.slice(pos, lineEnd);
|
|
887
|
+
if (trackFences) {
|
|
888
|
+
if (fence) {
|
|
889
|
+
const close = new RegExp(`^ {0,3}${fence.char === "`" ? "`" : "~"}{${fence.len},}\\s*$`);
|
|
890
|
+
if (close.test(line)) {
|
|
891
|
+
fence = null;
|
|
892
|
+
openFenceStart = -1;
|
|
893
|
+
}
|
|
894
|
+
pos = lineEnd + 1;
|
|
895
|
+
if (nl < 0) break;
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
const m = FENCE_OPEN.exec(line);
|
|
899
|
+
if (m && !(m[1][0] === "`" && m[2].includes("`"))) {
|
|
900
|
+
fence = { char: m[1][0], len: m[1].length };
|
|
901
|
+
openFenceStart = pos;
|
|
902
|
+
pos = lineEnd + 1;
|
|
903
|
+
if (nl < 0) break;
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (tagRe) {
|
|
908
|
+
const masked = trackFences ? maskInlineCode(line) : line;
|
|
909
|
+
const indent2 = /^ {0,3}/.exec(masked)[0].length;
|
|
910
|
+
tagRe.lastIndex = 0;
|
|
911
|
+
let t;
|
|
912
|
+
while ((t = tagRe.exec(masked)) !== null) {
|
|
913
|
+
const isClose = t[1] === "/";
|
|
914
|
+
const attrs = t[3];
|
|
915
|
+
if (isClose && attrs.trim() !== "") continue;
|
|
916
|
+
if (!isClose && attrs.trimEnd().endsWith("/")) continue;
|
|
917
|
+
tokens.push({
|
|
918
|
+
kind: isClose ? "close" : "open",
|
|
919
|
+
name: t[2].toLowerCase(),
|
|
920
|
+
start: pos + t.index,
|
|
921
|
+
end: pos + t.index + t[0].length,
|
|
922
|
+
attrs: isClose ? "" : attrs.trim(),
|
|
923
|
+
lineStart: t.index === indent2
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
if (nl < 0) break;
|
|
928
|
+
pos = lineEnd + 1;
|
|
929
|
+
}
|
|
930
|
+
return { tokens, openFenceStart };
|
|
931
|
+
}
|
|
932
|
+
function splitMarkdownBlocks(src, tags) {
|
|
933
|
+
const names = tags.map((t) => t.toLowerCase());
|
|
934
|
+
if (names.length === 0) return src ? [{ type: "markdown", source: src }] : [];
|
|
935
|
+
const { tokens } = scan(src, names, true);
|
|
936
|
+
const blocks = [];
|
|
937
|
+
let cursor = 0;
|
|
938
|
+
let i = 0;
|
|
939
|
+
while (i < tokens.length) {
|
|
940
|
+
const open = tokens[i];
|
|
941
|
+
if (open.kind !== "open" || !open.lineStart || open.start < cursor) {
|
|
942
|
+
i++;
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
let depth = 0;
|
|
946
|
+
let closeIdx = -1;
|
|
947
|
+
for (let j = i; j < tokens.length; j++) {
|
|
948
|
+
const t = tokens[j];
|
|
949
|
+
if (t.name !== open.name) continue;
|
|
950
|
+
depth += t.kind === "open" ? 1 : -1;
|
|
951
|
+
if (depth === 0) {
|
|
952
|
+
closeIdx = j;
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (closeIdx < 0) {
|
|
957
|
+
i++;
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
const close = tokens[closeIdx];
|
|
961
|
+
if (open.start > cursor) blocks.push({ type: "markdown", source: src.slice(cursor, open.start) });
|
|
962
|
+
blocks.push({ type: "tag", tag: open.name, attrs: open.attrs, body: src.slice(open.end, close.start) });
|
|
963
|
+
cursor = close.end;
|
|
964
|
+
i = closeIdx + 1;
|
|
965
|
+
}
|
|
966
|
+
if (cursor < src.length) blocks.push({ type: "markdown", source: src.slice(cursor) });
|
|
967
|
+
return blocks;
|
|
968
|
+
}
|
|
969
|
+
function streamingCut(src, tags, markdown) {
|
|
970
|
+
const names = tags.map((t) => t.toLowerCase());
|
|
971
|
+
const { tokens, openFenceStart } = scan(src, names, markdown);
|
|
972
|
+
let cut = src.length;
|
|
973
|
+
if (openFenceStart >= 0) cut = Math.min(cut, openFenceStart);
|
|
974
|
+
const stack = [];
|
|
975
|
+
for (const t of tokens) {
|
|
976
|
+
if (t.start >= cut) break;
|
|
977
|
+
if (t.kind === "open") {
|
|
978
|
+
stack.push(t);
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
for (let k = stack.length - 1; k >= 0; k--) {
|
|
982
|
+
if (stack[k].name === t.name) {
|
|
983
|
+
stack.length = k;
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (stack.length) cut = Math.min(cut, stack[0].start);
|
|
989
|
+
const lastLineStart = src.lastIndexOf("\n", cut - 1) + 1;
|
|
990
|
+
const lastLine = src.slice(lastLineStart, cut);
|
|
991
|
+
if (markdown && /^ {0,3}(`{1,2}|~{1,2})$/.test(lastLine)) {
|
|
992
|
+
return Math.min(cut, lastLineStart);
|
|
993
|
+
}
|
|
994
|
+
if (names.length) {
|
|
995
|
+
const masked = markdown ? maskInlineCode(lastLine) : lastLine;
|
|
996
|
+
const lt = masked.lastIndexOf("<");
|
|
997
|
+
if (lt >= 0) {
|
|
998
|
+
const m = /^<(\/?)([A-Za-z][\w-]*)?([^>]*)$/.exec(masked.slice(lt));
|
|
999
|
+
if (m) {
|
|
1000
|
+
const name = (m[2] ?? "").toLowerCase();
|
|
1001
|
+
const rest = m[3];
|
|
1002
|
+
let partial = false;
|
|
1003
|
+
if (!name) partial = rest === "";
|
|
1004
|
+
else if (names.includes(name)) partial = rest === "" || /^\s/.test(rest);
|
|
1005
|
+
else partial = rest === "" && names.some((n) => n.startsWith(name));
|
|
1006
|
+
if (partial) cut = Math.min(cut, lastLineStart + lt);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return cut;
|
|
1011
|
+
}
|
|
1012
|
+
var FEEDBACK_MS = 2e3;
|
|
1013
|
+
var WRITE_TIMEOUT_MS = 5e3;
|
|
1014
|
+
var CopyContext = createContext(null);
|
|
1015
|
+
async function defaultClipboardWriter(payload) {
|
|
1016
|
+
const clip = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
1017
|
+
if (!clip) throw new Error("Clipboard API unavailable");
|
|
1018
|
+
const Item = globalThis.ClipboardItem;
|
|
1019
|
+
if (payload.html && Item && typeof clip.write === "function") {
|
|
1020
|
+
try {
|
|
1021
|
+
await clip.write([
|
|
1022
|
+
new Item({
|
|
1023
|
+
"text/html": new Blob([payload.html], { type: "text/html" }),
|
|
1024
|
+
"text/plain": new Blob([payload.text], { type: "text/plain" })
|
|
1025
|
+
})
|
|
1026
|
+
]);
|
|
1027
|
+
return;
|
|
1028
|
+
} catch {
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (typeof clip.writeText !== "function") throw new Error("Clipboard writeText unavailable");
|
|
1032
|
+
await clip.writeText(payload.text);
|
|
1033
|
+
}
|
|
1034
|
+
async function runWriter(writer, payload) {
|
|
1035
|
+
let timer;
|
|
1036
|
+
try {
|
|
1037
|
+
const result = await Promise.race([
|
|
1038
|
+
Promise.resolve().then(() => writer(payload)),
|
|
1039
|
+
new Promise((resolve) => {
|
|
1040
|
+
timer = setTimeout(() => resolve(false), WRITE_TIMEOUT_MS);
|
|
1041
|
+
})
|
|
1042
|
+
]);
|
|
1043
|
+
return result !== false;
|
|
1044
|
+
} catch {
|
|
1045
|
+
return false;
|
|
1046
|
+
} finally {
|
|
1047
|
+
if (timer) clearTimeout(timer);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
function codeText(pre, fromMarkdown) {
|
|
1051
|
+
const text = (pre.querySelector("code") ?? pre).textContent ?? "";
|
|
1052
|
+
return fromMarkdown && text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
1053
|
+
}
|
|
1054
|
+
function tableTsv(table) {
|
|
1055
|
+
const rows = [];
|
|
1056
|
+
for (const tr of Array.from(table.querySelectorAll("tr"))) {
|
|
1057
|
+
if (tr.closest("table") !== table) continue;
|
|
1058
|
+
const cells = Array.from(tr.children).filter((c) => c.tagName === "TD" || c.tagName === "TH");
|
|
1059
|
+
rows.push(cells.map((c) => cellText(c)).join(" "));
|
|
1060
|
+
}
|
|
1061
|
+
return rows.join("\n");
|
|
1062
|
+
}
|
|
1063
|
+
function cellText(cell) {
|
|
1064
|
+
const clone = cell.cloneNode(true);
|
|
1065
|
+
for (const br of Array.from(clone.querySelectorAll("br"))) br.replaceWith(" ");
|
|
1066
|
+
return (clone.textContent ?? "").replace(/[\t\r\n]+/g, " ").replace(/ {2,}/g, " ").trim();
|
|
1067
|
+
}
|
|
1068
|
+
function HtmlChunk({ html, fromMarkdown }) {
|
|
1069
|
+
const ref = useRef(null);
|
|
1070
|
+
const ctx = useContext(CopyContext);
|
|
1071
|
+
const [targets, setTargets] = useState([]);
|
|
1072
|
+
const code = ctx?.code ?? false;
|
|
1073
|
+
const table = ctx?.table ?? false;
|
|
1074
|
+
const inner = useMemo(() => ({ __html: html }), [html]);
|
|
1075
|
+
useLayoutEffect(() => {
|
|
1076
|
+
const root = ref.current;
|
|
1077
|
+
if (!root || !code && !table) {
|
|
1078
|
+
setTargets([]);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
const found = [];
|
|
1082
|
+
const selector = [code && "pre", table && "table"].filter(Boolean).join(",");
|
|
1083
|
+
for (const el of Array.from(root.querySelectorAll(selector))) {
|
|
1084
|
+
const kind = el.tagName === "PRE" ? "code" : "table";
|
|
1085
|
+
const wrapper = document.createElement("div");
|
|
1086
|
+
wrapper.setAttribute("data-react-fancy-content-copy-block", kind);
|
|
1087
|
+
wrapper.className = kind === "code" ? "group relative" : "group relative flex flex-col";
|
|
1088
|
+
const host = document.createElement("div");
|
|
1089
|
+
host.setAttribute("data-react-fancy-content-copy-host", "");
|
|
1090
|
+
host.className = kind === "code" ? "absolute right-2 top-2 z-10" : "flex justify-end";
|
|
1091
|
+
el.replaceWith(wrapper);
|
|
1092
|
+
if (kind === "table") wrapper.append(host, el);
|
|
1093
|
+
else wrapper.append(el, host);
|
|
1094
|
+
found.push({ kind, el, host });
|
|
1095
|
+
}
|
|
1096
|
+
setTargets(found);
|
|
1097
|
+
return () => {
|
|
1098
|
+
for (const t of found) {
|
|
1099
|
+
const wrapper = t.host.parentElement;
|
|
1100
|
+
if (wrapper?.parentNode) wrapper.replaceWith(t.el);
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
}, [html, code, table]);
|
|
1104
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1105
|
+
/* @__PURE__ */ jsx("div", { ref, dangerouslySetInnerHTML: inner }),
|
|
1106
|
+
targets.map(
|
|
1107
|
+
(t, i) => createPortal(
|
|
1108
|
+
/* @__PURE__ */ jsx(CopyButton, { kind: t.kind, target: t.el, fromMarkdown }),
|
|
1109
|
+
t.host,
|
|
1110
|
+
`${i}`
|
|
1111
|
+
)
|
|
1112
|
+
)
|
|
1113
|
+
] });
|
|
1114
|
+
}
|
|
1115
|
+
function CopyButton({
|
|
1116
|
+
kind,
|
|
1117
|
+
target,
|
|
1118
|
+
fromMarkdown
|
|
818
1119
|
}) {
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
1120
|
+
const ctx = useContext(CopyContext);
|
|
1121
|
+
const [state, setState] = useState("idle");
|
|
1122
|
+
const timer = useRef(void 0);
|
|
1123
|
+
const btn = useRef(null);
|
|
1124
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
1125
|
+
const onClick = useCallback(async () => {
|
|
1126
|
+
if (!ctx || !btn.current) return;
|
|
1127
|
+
const text = kind === "code" ? codeText(target, fromMarkdown) : tableTsv(target);
|
|
1128
|
+
const ok = await ctx.copy({ kind, text, html: target.outerHTML }, btn.current);
|
|
1129
|
+
setState(ok ? "copied" : "failed");
|
|
1130
|
+
clearTimeout(timer.current);
|
|
1131
|
+
timer.current = setTimeout(() => setState("idle"), FEEDBACK_MS);
|
|
1132
|
+
}, [ctx, kind, target, fromMarkdown]);
|
|
1133
|
+
const idleLabel = kind === "code" ? "Copy code" : "Copy table";
|
|
1134
|
+
const label = state === "copied" ? "Copied" : state === "failed" ? "Copy failed" : idleLabel;
|
|
1135
|
+
return /* @__PURE__ */ jsx(
|
|
1136
|
+
Button,
|
|
1137
|
+
{
|
|
1138
|
+
ref: btn,
|
|
1139
|
+
type: "button",
|
|
1140
|
+
size: "xs",
|
|
1141
|
+
variant: "ghost",
|
|
1142
|
+
icon: state === "copied" ? "check" : state === "failed" ? "x" : "copy",
|
|
1143
|
+
"aria-label": label,
|
|
1144
|
+
title: label,
|
|
1145
|
+
"data-react-fancy-content-copy": kind,
|
|
1146
|
+
"data-copy-state": state,
|
|
1147
|
+
onClick,
|
|
1148
|
+
className: kind === "code" ? "text-zinc-300 hover:bg-zinc-800 hover:text-white opacity-70 group-hover:opacity-100 focus-visible:opacity-100" : "opacity-70 group-hover:opacity-100 focus-visible:opacity-100"
|
|
1149
|
+
}
|
|
822
1150
|
);
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1151
|
+
}
|
|
1152
|
+
function useCopyPipeline({
|
|
1153
|
+
writer,
|
|
1154
|
+
onCopy,
|
|
1155
|
+
rootRef,
|
|
1156
|
+
announce
|
|
1157
|
+
}) {
|
|
1158
|
+
const latest = useRef({ writer, onCopy, announce });
|
|
1159
|
+
latest.current = { writer, onCopy, announce };
|
|
1160
|
+
return useCallback(
|
|
1161
|
+
async (payload, button) => {
|
|
1162
|
+
const { writer: w, onCopy: report, announce: say } = latest.current;
|
|
1163
|
+
const ok = await runWriter(w ?? defaultClipboardWriter, payload);
|
|
1164
|
+
say(ok ? "Copied" : "Copy failed");
|
|
1165
|
+
const all = rootRef.current ? Array.from(rootRef.current.querySelectorAll("[data-react-fancy-content-copy]")) : [];
|
|
1166
|
+
report?.({ ...payload, index: all.indexOf(button), ok });
|
|
1167
|
+
return ok;
|
|
1168
|
+
},
|
|
1169
|
+
[rootRef]
|
|
826
1170
|
);
|
|
1171
|
+
}
|
|
1172
|
+
var MAX_DEPTH = 8;
|
|
1173
|
+
function ContentBody({ source, format, extensions, unsafe, depth = 0 }) {
|
|
1174
|
+
const active = depth >= MAX_DEPTH ? [] : extensions;
|
|
1175
|
+
const blocks = useMemo(() => {
|
|
1176
|
+
if (format !== "markdown") return null;
|
|
1177
|
+
const mdTags = active.filter((e) => e.markdown).map((e) => e.tag);
|
|
1178
|
+
return splitMarkdownBlocks(source, mdTags);
|
|
1179
|
+
}, [source, format, active]);
|
|
1180
|
+
if (!blocks) {
|
|
1181
|
+
const html = unsafe ? source : sanitizeHtml(source);
|
|
1182
|
+
return /* @__PURE__ */ jsx(RenderedContent, { html, extensions: active, unsafe, fromMarkdown: false, depth });
|
|
1183
|
+
}
|
|
1184
|
+
return /* @__PURE__ */ jsx(Fragment, { children: blocks.map((block, i) => {
|
|
1185
|
+
if (block.type === "markdown") {
|
|
1186
|
+
const raw = markdownToRawHtml(block.source);
|
|
1187
|
+
const html = unsafe ? raw : sanitizeHtml(raw);
|
|
1188
|
+
return /* @__PURE__ */ jsx(
|
|
1189
|
+
RenderedContent,
|
|
1190
|
+
{
|
|
1191
|
+
html,
|
|
1192
|
+
extensions: active,
|
|
1193
|
+
unsafe,
|
|
1194
|
+
fromMarkdown: true,
|
|
1195
|
+
depth
|
|
1196
|
+
},
|
|
1197
|
+
i
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
const ext = active.find((e) => e.tag.toLowerCase() === block.tag);
|
|
1201
|
+
if (!ext) return null;
|
|
1202
|
+
const Component = ext.component;
|
|
1203
|
+
const Wrapper = ext.block !== false ? "div" : "span";
|
|
1204
|
+
return /* @__PURE__ */ jsx(Wrapper, { "data-render-extension": block.tag, children: /* @__PURE__ */ jsx(Component, { content: block.body, attributes: tagAttributes(block.attrs, unsafe), children: /* @__PURE__ */ jsx(
|
|
1205
|
+
ContentBody,
|
|
1206
|
+
{
|
|
1207
|
+
source: block.body,
|
|
1208
|
+
format: "markdown",
|
|
1209
|
+
extensions: active,
|
|
1210
|
+
unsafe,
|
|
1211
|
+
depth: depth + 1
|
|
1212
|
+
}
|
|
1213
|
+
) }) }, i);
|
|
1214
|
+
}) });
|
|
1215
|
+
}
|
|
1216
|
+
function RenderedContent({ html, extensions, unsafe, fromMarkdown, depth }) {
|
|
1217
|
+
const segments = useMemo(() => parseSegments(html, extensions), [html, extensions]);
|
|
827
1218
|
const renderHtml = (content) => unsafe ? content : sanitizeHtml(content);
|
|
1219
|
+
if (segments.length === 0) return null;
|
|
828
1220
|
if (segments.length === 1 && segments[0].type === "html") {
|
|
829
|
-
return /* @__PURE__ */ jsx(
|
|
830
|
-
}
|
|
831
|
-
if (segments.length === 0) {
|
|
832
|
-
return null;
|
|
1221
|
+
return /* @__PURE__ */ jsx(HtmlChunk, { html: renderHtml(segments[0].content), fromMarkdown });
|
|
833
1222
|
}
|
|
834
1223
|
return /* @__PURE__ */ jsx(Fragment, { children: segments.map((segment, i) => {
|
|
835
1224
|
if (segment.type === "html") {
|
|
836
|
-
return segment.content ? /* @__PURE__ */ jsx(
|
|
1225
|
+
return segment.content ? /* @__PURE__ */ jsx(HtmlChunk, { html: renderHtml(segment.content), fromMarkdown }, i) : null;
|
|
837
1226
|
}
|
|
838
|
-
const ext = extensions.find(
|
|
839
|
-
(e) => e.tag.toLowerCase() === segment.tag
|
|
840
|
-
);
|
|
1227
|
+
const ext = extensions.find((e) => e.tag.toLowerCase() === segment.tag);
|
|
841
1228
|
if (!ext) return null;
|
|
842
1229
|
const Component = ext.component;
|
|
843
|
-
const
|
|
844
|
-
const
|
|
845
|
-
|
|
1230
|
+
const Wrapper = ext.block !== false ? "div" : "span";
|
|
1231
|
+
const children = ext.markdown ? /* @__PURE__ */ jsx(
|
|
1232
|
+
ContentBody,
|
|
1233
|
+
{
|
|
1234
|
+
source: segment.content,
|
|
1235
|
+
format: "html",
|
|
1236
|
+
extensions,
|
|
1237
|
+
unsafe,
|
|
1238
|
+
depth: depth + 1
|
|
1239
|
+
}
|
|
1240
|
+
) : void 0;
|
|
1241
|
+
return /* @__PURE__ */ jsx(Wrapper, { "data-render-extension": segment.tag, children: /* @__PURE__ */ jsx(Component, { content: segment.content, attributes: segment.attributes, children }) }, i);
|
|
846
1242
|
}) });
|
|
847
1243
|
}
|
|
848
1244
|
RenderedContent.displayName = "RenderedContent";
|
|
1245
|
+
ContentBody.displayName = "ContentBody";
|
|
849
1246
|
function ContentRenderer({
|
|
850
1247
|
value,
|
|
851
1248
|
format = "auto",
|
|
852
1249
|
lineSpacing = 1.6,
|
|
853
1250
|
className,
|
|
854
1251
|
extensions: instanceExtensions,
|
|
855
|
-
unsafe = false
|
|
1252
|
+
unsafe = false,
|
|
1253
|
+
copyButtons = true,
|
|
1254
|
+
onCopy,
|
|
1255
|
+
clipboardWriter,
|
|
1256
|
+
streaming = false
|
|
856
1257
|
}) {
|
|
857
1258
|
const extensions = useMemo(
|
|
858
1259
|
() => mergeExtensions(instanceExtensions),
|
|
859
1260
|
[instanceExtensions]
|
|
860
1261
|
);
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1262
|
+
const safe = value ?? "";
|
|
1263
|
+
const resolvedFormat = format === "auto" ? detectFormat(safe) : format;
|
|
1264
|
+
const source = useMemo(() => {
|
|
1265
|
+
if (!streaming) return safe;
|
|
1266
|
+
const cut = streamingCut(
|
|
1267
|
+
safe,
|
|
1268
|
+
extensions.map((e) => e.tag),
|
|
1269
|
+
resolvedFormat === "markdown"
|
|
1270
|
+
);
|
|
1271
|
+
return safe.slice(0, cut);
|
|
1272
|
+
}, [safe, streaming, extensions, resolvedFormat]);
|
|
1273
|
+
const codeOn = copyButtons === true || typeof copyButtons === "object" && copyButtons.code !== false;
|
|
1274
|
+
const tableOn = copyButtons === true || typeof copyButtons === "object" && copyButtons.table !== false;
|
|
1275
|
+
const copyOn = codeOn || tableOn;
|
|
1276
|
+
const rootRef = useRef(null);
|
|
1277
|
+
const [announcement, setAnnouncement] = useState("");
|
|
1278
|
+
const announce = useCallback((message) => {
|
|
1279
|
+
setAnnouncement("");
|
|
1280
|
+
queueMicrotask(() => setAnnouncement(message));
|
|
1281
|
+
}, []);
|
|
1282
|
+
const copy = useCopyPipeline({ writer: clipboardWriter, onCopy, rootRef, announce });
|
|
1283
|
+
const copyContext = useMemo(
|
|
1284
|
+
() => ({ code: codeOn, table: tableOn, copy }),
|
|
1285
|
+
[codeOn, tableOn, copy]
|
|
1286
|
+
);
|
|
1287
|
+
return /* @__PURE__ */ jsxs(
|
|
869
1288
|
"div",
|
|
870
1289
|
{
|
|
1290
|
+
ref: rootRef,
|
|
871
1291
|
"data-react-fancy-content-renderer": "",
|
|
1292
|
+
"data-streaming": streaming ? "true" : void 0,
|
|
872
1293
|
style: { lineHeight: lineSpacing },
|
|
873
1294
|
className: cn("text-sm", proseClasses, className),
|
|
874
|
-
children:
|
|
1295
|
+
children: [
|
|
1296
|
+
/* @__PURE__ */ jsx(CopyContext.Provider, { value: copyContext, children: /* @__PURE__ */ jsx(
|
|
1297
|
+
ContentBody,
|
|
1298
|
+
{
|
|
1299
|
+
source,
|
|
1300
|
+
format: resolvedFormat,
|
|
1301
|
+
extensions,
|
|
1302
|
+
unsafe
|
|
1303
|
+
}
|
|
1304
|
+
) }),
|
|
1305
|
+
copyOn && /* @__PURE__ */ jsx(
|
|
1306
|
+
"span",
|
|
1307
|
+
{
|
|
1308
|
+
role: "status",
|
|
1309
|
+
"aria-live": "polite",
|
|
1310
|
+
"aria-atomic": "true",
|
|
1311
|
+
"data-react-fancy-content-renderer-live": "",
|
|
1312
|
+
className: "sr-only",
|
|
1313
|
+
children: announcement
|
|
1314
|
+
}
|
|
1315
|
+
)
|
|
1316
|
+
]
|
|
875
1317
|
}
|
|
876
1318
|
);
|
|
877
1319
|
}
|
|
878
1320
|
ContentRenderer.displayName = "ContentRenderer";
|
|
879
1321
|
|
|
880
1322
|
export { ContentRenderer, detectFormat, extensionEditorClasses, htmlToMarkdown, markdownToEditorHtml, mergeExtensions, proseClasses, registerExtension, registerExtensions, renderMarkdown };
|
|
881
|
-
//# sourceMappingURL=chunk-
|
|
882
|
-
//# sourceMappingURL=chunk-
|
|
1323
|
+
//# sourceMappingURL=chunk-FXOT4P4P.js.map
|
|
1324
|
+
//# sourceMappingURL=chunk-FXOT4P4P.js.map
|