@particle-academy/react-fancy 5.66.0 → 5.68.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 +91 -0
- package/dist/ContentRenderer.types-RO_l7_6N.d.cts +137 -0
- package/dist/ContentRenderer.types-RO_l7_6N.d.ts +137 -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-L24LUSEE.js → chunk-2LQR4EAN.js} +553 -35
- package/dist/chunk-2LQR4EAN.js.map +1 -0
- package/dist/{chunk-C5YX5XVL.cjs → chunk-3U7PUZLI.cjs} +214 -4
- package/dist/chunk-3U7PUZLI.cjs.map +1 -0
- package/dist/{chunk-MCQJFMDP.cjs → chunk-464QSZ4A.cjs} +553 -35
- package/dist/chunk-464QSZ4A.cjs.map +1 -0
- 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-ATIDUWY6.cjs → chunk-E4DXICU3.cjs} +19 -13
- package/dist/chunk-E4DXICU3.cjs.map +1 -0
- 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-CAUEKGMR.js → chunk-TQ77ZWBD.js} +11 -5
- package/dist/chunk-TQ77ZWBD.js.map +1 -0
- 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 +4 -21
- package/dist/content-renderer.d.ts +4 -21
- package/dist/content-renderer.js +4 -2
- package/dist/editor.cjs +6 -5
- package/dist/editor.d.cts +19 -2
- package/dist/editor.d.ts +19 -2
- package/dist/editor.js +4 -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 +192 -3
- package/docs/Editor.md +38 -0
- package/package.json +1 -1
- package/dist/chunk-6TBRBN7H.js +0 -212
- package/dist/chunk-6TBRBN7H.js.map +0 -1
- package/dist/chunk-ATIDUWY6.cjs.map +0 -1
- package/dist/chunk-C5YX5XVL.cjs.map +0 -1
- package/dist/chunk-CAUEKGMR.js.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
- package/dist/extensions-6PVd3-vB.d.cts +0 -38
- package/dist/extensions-6PVd3-vB.d.ts +0 -38
|
@@ -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,569 @@ function extensionEditorClasses(extensions) {
|
|
|
811
832
|
].join(" ");
|
|
812
833
|
}).join(" ");
|
|
813
834
|
}
|
|
814
|
-
|
|
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, openFence: null };
|
|
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, openFence: fence };
|
|
931
|
+
}
|
|
932
|
+
function partialTagAt(lastLine, names, markdown) {
|
|
933
|
+
if (!names.length) return -1;
|
|
934
|
+
const masked = markdown ? maskInlineCode(lastLine) : lastLine;
|
|
935
|
+
const lt = masked.lastIndexOf("<");
|
|
936
|
+
if (lt < 0) return -1;
|
|
937
|
+
const m = /^<(\/?)([A-Za-z][\w-]*)?([^>]*)$/.exec(masked.slice(lt));
|
|
938
|
+
if (!m) return -1;
|
|
939
|
+
const name = (m[2] ?? "").toLowerCase();
|
|
940
|
+
const rest = m[3];
|
|
941
|
+
let partial = false;
|
|
942
|
+
if (!name) partial = rest === "";
|
|
943
|
+
else if (names.includes(name)) partial = rest === "" || /^\s/.test(rest);
|
|
944
|
+
else partial = rest === "" && names.some((n) => n.startsWith(name));
|
|
945
|
+
return partial ? lt : -1;
|
|
946
|
+
}
|
|
947
|
+
function openTagsBefore(tokens, cut) {
|
|
948
|
+
const stack = [];
|
|
949
|
+
for (const t of tokens) {
|
|
950
|
+
if (t.start >= cut) break;
|
|
951
|
+
if (t.kind === "open") {
|
|
952
|
+
stack.push(t);
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
for (let k = stack.length - 1; k >= 0; k--) {
|
|
956
|
+
if (stack[k].name === t.name) {
|
|
957
|
+
stack.length = k;
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return stack;
|
|
963
|
+
}
|
|
964
|
+
function splitMarkdownBlocks(src, tags) {
|
|
965
|
+
const names = tags.map((t) => t.toLowerCase());
|
|
966
|
+
if (names.length === 0) return src ? [{ type: "markdown", source: src }] : [];
|
|
967
|
+
const { tokens } = scan(src, names, true);
|
|
968
|
+
const blocks = [];
|
|
969
|
+
let cursor = 0;
|
|
970
|
+
let i = 0;
|
|
971
|
+
while (i < tokens.length) {
|
|
972
|
+
const open = tokens[i];
|
|
973
|
+
if (open.kind !== "open" || !open.lineStart || open.start < cursor) {
|
|
974
|
+
i++;
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
let depth = 0;
|
|
978
|
+
let closeIdx = -1;
|
|
979
|
+
for (let j = i; j < tokens.length; j++) {
|
|
980
|
+
const t = tokens[j];
|
|
981
|
+
if (t.name !== open.name) continue;
|
|
982
|
+
depth += t.kind === "open" ? 1 : -1;
|
|
983
|
+
if (depth === 0) {
|
|
984
|
+
closeIdx = j;
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (closeIdx < 0) {
|
|
989
|
+
i++;
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
const close = tokens[closeIdx];
|
|
993
|
+
if (open.start > cursor) blocks.push({ type: "markdown", source: src.slice(cursor, open.start) });
|
|
994
|
+
blocks.push({ type: "tag", tag: open.name, attrs: open.attrs, body: src.slice(open.end, close.start) });
|
|
995
|
+
cursor = close.end;
|
|
996
|
+
i = closeIdx + 1;
|
|
997
|
+
}
|
|
998
|
+
if (cursor < src.length) blocks.push({ type: "markdown", source: src.slice(cursor) });
|
|
999
|
+
return blocks;
|
|
1000
|
+
}
|
|
1001
|
+
function streamingCut(src, tags, markdown) {
|
|
1002
|
+
const names = tags.map((t) => t.toLowerCase());
|
|
1003
|
+
const { tokens, openFenceStart } = scan(src, names, markdown);
|
|
1004
|
+
let cut = src.length;
|
|
1005
|
+
if (openFenceStart >= 0) cut = Math.min(cut, openFenceStart);
|
|
1006
|
+
const stack = openTagsBefore(tokens, cut);
|
|
1007
|
+
if (stack.length) cut = Math.min(cut, stack[0].start);
|
|
1008
|
+
const lastLineStart = src.lastIndexOf("\n", cut - 1) + 1;
|
|
1009
|
+
const lastLine = src.slice(lastLineStart, cut);
|
|
1010
|
+
if (markdown && /^ {0,3}(`{1,2}|~{1,2})$/.test(lastLine)) {
|
|
1011
|
+
return Math.min(cut, lastLineStart);
|
|
1012
|
+
}
|
|
1013
|
+
const lt = partialTagAt(lastLine, names, markdown);
|
|
1014
|
+
if (lt >= 0) cut = Math.min(cut, lastLineStart + lt);
|
|
1015
|
+
return cut;
|
|
1016
|
+
}
|
|
1017
|
+
function streamingProgressive(src, tags, markdown) {
|
|
1018
|
+
const names = tags.map((t) => t.toLowerCase());
|
|
1019
|
+
const { tokens, openFenceStart, openFence } = scan(src, names, markdown);
|
|
1020
|
+
let cut = src.length;
|
|
1021
|
+
let fence = openFenceStart >= 0 ? openFence : null;
|
|
1022
|
+
const lastLineStart = src.lastIndexOf("\n", cut - 1) + 1;
|
|
1023
|
+
const lastLine = src.slice(lastLineStart);
|
|
1024
|
+
if (fence) {
|
|
1025
|
+
if (openFenceStart === lastLineStart) {
|
|
1026
|
+
cut = openFenceStart;
|
|
1027
|
+
fence = null;
|
|
1028
|
+
} else {
|
|
1029
|
+
const m = /^ {0,3}(`+|~+)$/.exec(lastLine);
|
|
1030
|
+
if (m && m[1][0] === fence.char && m[1].length < fence.len) cut = lastLineStart;
|
|
1031
|
+
}
|
|
1032
|
+
} else if (markdown && /^ {0,3}(`{1,2}|~{1,2})$/.test(lastLine)) {
|
|
1033
|
+
cut = lastLineStart;
|
|
1034
|
+
} else {
|
|
1035
|
+
const lt = partialTagAt(lastLine, names, markdown);
|
|
1036
|
+
if (lt >= 0) cut = lastLineStart + lt;
|
|
1037
|
+
}
|
|
1038
|
+
let out = src.slice(0, cut);
|
|
1039
|
+
if (fence) {
|
|
1040
|
+
out += (out.endsWith("\n") ? "" : "\n") + fence.char.repeat(fence.len) + "\n";
|
|
1041
|
+
}
|
|
1042
|
+
const stack = openTagsBefore(tokens, fence ? openFenceStart : cut);
|
|
1043
|
+
for (let k = stack.length - 1; k >= 0; k--) out += `</${stack[k].name}>`;
|
|
1044
|
+
return { source: out, openFence: fence !== null };
|
|
1045
|
+
}
|
|
1046
|
+
var FEEDBACK_MS = 2e3;
|
|
1047
|
+
var WRITE_TIMEOUT_MS = 5e3;
|
|
1048
|
+
var CopyContext = createContext(null);
|
|
1049
|
+
async function defaultClipboardWriter(payload) {
|
|
1050
|
+
const clip = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
1051
|
+
if (!clip) throw new Error("Clipboard API unavailable");
|
|
1052
|
+
const Item = globalThis.ClipboardItem;
|
|
1053
|
+
if (payload.html && Item && typeof clip.write === "function") {
|
|
1054
|
+
try {
|
|
1055
|
+
await clip.write([
|
|
1056
|
+
new Item({
|
|
1057
|
+
"text/html": new Blob([payload.html], { type: "text/html" }),
|
|
1058
|
+
"text/plain": new Blob([payload.text], { type: "text/plain" })
|
|
1059
|
+
})
|
|
1060
|
+
]);
|
|
1061
|
+
return;
|
|
1062
|
+
} catch {
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
if (typeof clip.writeText !== "function") throw new Error("Clipboard writeText unavailable");
|
|
1066
|
+
await clip.writeText(payload.text);
|
|
1067
|
+
}
|
|
1068
|
+
async function runWriter(writer, payload) {
|
|
1069
|
+
let timer;
|
|
1070
|
+
try {
|
|
1071
|
+
const result = await Promise.race([
|
|
1072
|
+
Promise.resolve().then(() => writer(payload)),
|
|
1073
|
+
new Promise((resolve) => {
|
|
1074
|
+
timer = setTimeout(() => resolve(false), WRITE_TIMEOUT_MS);
|
|
1075
|
+
})
|
|
1076
|
+
]);
|
|
1077
|
+
return result !== false;
|
|
1078
|
+
} catch {
|
|
1079
|
+
return false;
|
|
1080
|
+
} finally {
|
|
1081
|
+
if (timer) clearTimeout(timer);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function codeText(pre, fromMarkdown) {
|
|
1085
|
+
const text = (pre.querySelector("code") ?? pre).textContent ?? "";
|
|
1086
|
+
return fromMarkdown && text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
1087
|
+
}
|
|
1088
|
+
function tableTsv(table) {
|
|
1089
|
+
const rows = [];
|
|
1090
|
+
for (const tr of Array.from(table.querySelectorAll("tr"))) {
|
|
1091
|
+
if (tr.closest("table") !== table) continue;
|
|
1092
|
+
const cells = Array.from(tr.children).filter((c) => c.tagName === "TD" || c.tagName === "TH");
|
|
1093
|
+
rows.push(cells.map((c) => cellText(c)).join(" "));
|
|
1094
|
+
}
|
|
1095
|
+
return rows.join("\n");
|
|
1096
|
+
}
|
|
1097
|
+
function cellText(cell) {
|
|
1098
|
+
const clone = cell.cloneNode(true);
|
|
1099
|
+
for (const br of Array.from(clone.querySelectorAll("br"))) br.replaceWith(" ");
|
|
1100
|
+
return (clone.textContent ?? "").replace(/[\t\r\n]+/g, " ").replace(/ {2,}/g, " ").trim();
|
|
1101
|
+
}
|
|
1102
|
+
function HtmlChunk({
|
|
815
1103
|
html,
|
|
816
|
-
|
|
817
|
-
|
|
1104
|
+
fromMarkdown,
|
|
1105
|
+
tail = false
|
|
818
1106
|
}) {
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
1107
|
+
const ref = useRef(null);
|
|
1108
|
+
const ctx = useContext(CopyContext);
|
|
1109
|
+
const [targets, setTargets] = useState([]);
|
|
1110
|
+
const code = ctx?.code ?? false;
|
|
1111
|
+
const table = ctx?.table ?? false;
|
|
1112
|
+
const holdLastCode = tail && (ctx?.holdTailCode ?? false);
|
|
1113
|
+
const inner = useMemo(() => ({ __html: html }), [html]);
|
|
1114
|
+
useLayoutEffect(() => {
|
|
1115
|
+
const root = ref.current;
|
|
1116
|
+
if (!root || !code && !table) {
|
|
1117
|
+
setTargets([]);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const found = [];
|
|
1121
|
+
const selector = [code && "pre", table && "table"].filter(Boolean).join(",");
|
|
1122
|
+
const inProgress = holdLastCode ? Array.from(root.querySelectorAll("pre")).pop() : void 0;
|
|
1123
|
+
for (const el of Array.from(root.querySelectorAll(selector))) {
|
|
1124
|
+
if (el === inProgress) continue;
|
|
1125
|
+
const kind = el.tagName === "PRE" ? "code" : "table";
|
|
1126
|
+
const wrapper = document.createElement("div");
|
|
1127
|
+
wrapper.setAttribute("data-react-fancy-content-copy-block", kind);
|
|
1128
|
+
wrapper.className = kind === "code" ? "group relative" : "group relative flex flex-col";
|
|
1129
|
+
const host = document.createElement("div");
|
|
1130
|
+
host.setAttribute("data-react-fancy-content-copy-host", "");
|
|
1131
|
+
host.className = kind === "code" ? "absolute right-2 top-2 z-10" : "flex justify-end";
|
|
1132
|
+
el.replaceWith(wrapper);
|
|
1133
|
+
if (kind === "table") wrapper.append(host, el);
|
|
1134
|
+
else wrapper.append(el, host);
|
|
1135
|
+
found.push({ kind, el, host });
|
|
1136
|
+
}
|
|
1137
|
+
setTargets(found);
|
|
1138
|
+
return () => {
|
|
1139
|
+
for (const t of found) {
|
|
1140
|
+
const wrapper = t.host.parentElement;
|
|
1141
|
+
if (wrapper?.parentNode) wrapper.replaceWith(t.el);
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
}, [html, code, table, holdLastCode]);
|
|
1145
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1146
|
+
/* @__PURE__ */ jsx("div", { ref, dangerouslySetInnerHTML: inner }),
|
|
1147
|
+
targets.map(
|
|
1148
|
+
(t, i) => createPortal(
|
|
1149
|
+
/* @__PURE__ */ jsx(CopyButton, { kind: t.kind, target: t.el, fromMarkdown }),
|
|
1150
|
+
t.host,
|
|
1151
|
+
`${i}`
|
|
1152
|
+
)
|
|
1153
|
+
)
|
|
1154
|
+
] });
|
|
1155
|
+
}
|
|
1156
|
+
function CopyButton({
|
|
1157
|
+
kind,
|
|
1158
|
+
target,
|
|
1159
|
+
fromMarkdown
|
|
1160
|
+
}) {
|
|
1161
|
+
const ctx = useContext(CopyContext);
|
|
1162
|
+
const [state, setState] = useState("idle");
|
|
1163
|
+
const timer = useRef(void 0);
|
|
1164
|
+
const btn = useRef(null);
|
|
1165
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
1166
|
+
const onClick = useCallback(async () => {
|
|
1167
|
+
if (!ctx || !btn.current) return;
|
|
1168
|
+
const text = kind === "code" ? codeText(target, fromMarkdown) : tableTsv(target);
|
|
1169
|
+
const ok = await ctx.copy({ kind, text, html: target.outerHTML }, btn.current);
|
|
1170
|
+
setState(ok ? "copied" : "failed");
|
|
1171
|
+
clearTimeout(timer.current);
|
|
1172
|
+
timer.current = setTimeout(() => setState("idle"), FEEDBACK_MS);
|
|
1173
|
+
}, [ctx, kind, target, fromMarkdown]);
|
|
1174
|
+
const idleLabel = kind === "code" ? "Copy code" : "Copy table";
|
|
1175
|
+
const label = state === "copied" ? "Copied" : state === "failed" ? "Copy failed" : idleLabel;
|
|
1176
|
+
return /* @__PURE__ */ jsx(
|
|
1177
|
+
Button,
|
|
1178
|
+
{
|
|
1179
|
+
ref: btn,
|
|
1180
|
+
type: "button",
|
|
1181
|
+
size: "xs",
|
|
1182
|
+
variant: "ghost",
|
|
1183
|
+
icon: state === "copied" ? "check" : state === "failed" ? "x" : "copy",
|
|
1184
|
+
"aria-label": label,
|
|
1185
|
+
title: label,
|
|
1186
|
+
"data-react-fancy-content-copy": kind,
|
|
1187
|
+
"data-copy-state": state,
|
|
1188
|
+
onClick,
|
|
1189
|
+
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"
|
|
1190
|
+
}
|
|
822
1191
|
);
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1192
|
+
}
|
|
1193
|
+
function useCopyPipeline({
|
|
1194
|
+
writer,
|
|
1195
|
+
onCopy,
|
|
1196
|
+
rootRef,
|
|
1197
|
+
announce
|
|
1198
|
+
}) {
|
|
1199
|
+
const latest = useRef({ writer, onCopy, announce });
|
|
1200
|
+
latest.current = { writer, onCopy, announce };
|
|
1201
|
+
return useCallback(
|
|
1202
|
+
async (payload, button) => {
|
|
1203
|
+
const { writer: w, onCopy: report, announce: say } = latest.current;
|
|
1204
|
+
const ok = await runWriter(w ?? defaultClipboardWriter, payload);
|
|
1205
|
+
say(ok ? "Copied" : "Copy failed");
|
|
1206
|
+
const all = rootRef.current ? Array.from(rootRef.current.querySelectorAll("[data-react-fancy-content-copy]")) : [];
|
|
1207
|
+
report?.({ ...payload, index: all.indexOf(button), ok });
|
|
1208
|
+
return ok;
|
|
1209
|
+
},
|
|
1210
|
+
[rootRef]
|
|
826
1211
|
);
|
|
1212
|
+
}
|
|
1213
|
+
var MAX_DEPTH = 8;
|
|
1214
|
+
function lastIndexWith(items, renders) {
|
|
1215
|
+
for (let i = items.length - 1; i >= 0; i--) if (renders(items[i])) return i;
|
|
1216
|
+
return -1;
|
|
1217
|
+
}
|
|
1218
|
+
function ContentBody({ source, format, extensions, unsafe, depth = 0, tail = false }) {
|
|
1219
|
+
const active = depth >= MAX_DEPTH ? [] : extensions;
|
|
1220
|
+
const blocks = useMemo(() => {
|
|
1221
|
+
if (format !== "markdown") return null;
|
|
1222
|
+
const mdTags = active.filter((e) => e.markdown).map((e) => e.tag);
|
|
1223
|
+
return splitMarkdownBlocks(source, mdTags);
|
|
1224
|
+
}, [source, format, active]);
|
|
1225
|
+
if (!blocks) {
|
|
1226
|
+
const html = unsafe ? source : sanitizeHtml(source);
|
|
1227
|
+
return /* @__PURE__ */ jsx(
|
|
1228
|
+
RenderedContent,
|
|
1229
|
+
{
|
|
1230
|
+
html,
|
|
1231
|
+
extensions: active,
|
|
1232
|
+
unsafe,
|
|
1233
|
+
fromMarkdown: false,
|
|
1234
|
+
depth,
|
|
1235
|
+
tail
|
|
1236
|
+
}
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
const last = tail ? lastIndexWith(blocks, (b) => b.type === "tag" || b.source.trim() !== "") : -1;
|
|
1240
|
+
return /* @__PURE__ */ jsx(Fragment, { children: blocks.map((block, i) => {
|
|
1241
|
+
if (block.type === "markdown") {
|
|
1242
|
+
const raw = markdownToRawHtml(block.source);
|
|
1243
|
+
const html = unsafe ? raw : sanitizeHtml(raw);
|
|
1244
|
+
return /* @__PURE__ */ jsx(
|
|
1245
|
+
RenderedContent,
|
|
1246
|
+
{
|
|
1247
|
+
html,
|
|
1248
|
+
extensions: active,
|
|
1249
|
+
unsafe,
|
|
1250
|
+
fromMarkdown: true,
|
|
1251
|
+
depth,
|
|
1252
|
+
tail: i === last
|
|
1253
|
+
},
|
|
1254
|
+
i
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
const ext = active.find((e) => e.tag.toLowerCase() === block.tag);
|
|
1258
|
+
if (!ext) return null;
|
|
1259
|
+
const Component = ext.component;
|
|
1260
|
+
const Wrapper = ext.block !== false ? "div" : "span";
|
|
1261
|
+
return /* @__PURE__ */ jsx(Wrapper, { "data-render-extension": block.tag, children: /* @__PURE__ */ jsx(Component, { content: block.body, attributes: tagAttributes(block.attrs, unsafe), children: /* @__PURE__ */ jsx(
|
|
1262
|
+
ContentBody,
|
|
1263
|
+
{
|
|
1264
|
+
source: block.body,
|
|
1265
|
+
format: "markdown",
|
|
1266
|
+
extensions: active,
|
|
1267
|
+
unsafe,
|
|
1268
|
+
depth: depth + 1,
|
|
1269
|
+
tail: i === last
|
|
1270
|
+
}
|
|
1271
|
+
) }) }, i);
|
|
1272
|
+
}) });
|
|
1273
|
+
}
|
|
1274
|
+
function RenderedContent({
|
|
1275
|
+
html,
|
|
1276
|
+
extensions,
|
|
1277
|
+
unsafe,
|
|
1278
|
+
fromMarkdown,
|
|
1279
|
+
depth,
|
|
1280
|
+
tail = false
|
|
1281
|
+
}) {
|
|
1282
|
+
const segments = useMemo(() => parseSegments(html, extensions), [html, extensions]);
|
|
827
1283
|
const renderHtml = (content) => unsafe ? content : sanitizeHtml(content);
|
|
1284
|
+
if (segments.length === 0) return null;
|
|
828
1285
|
if (segments.length === 1 && segments[0].type === "html") {
|
|
829
|
-
return /* @__PURE__ */ jsx(
|
|
830
|
-
}
|
|
831
|
-
if (segments.length === 0) {
|
|
832
|
-
return null;
|
|
1286
|
+
return /* @__PURE__ */ jsx(HtmlChunk, { html: renderHtml(segments[0].content), fromMarkdown, tail });
|
|
833
1287
|
}
|
|
1288
|
+
const last = tail ? lastIndexWith(segments, (s) => s.type === "extension" || s.content.trim() !== "") : -1;
|
|
834
1289
|
return /* @__PURE__ */ jsx(Fragment, { children: segments.map((segment, i) => {
|
|
835
1290
|
if (segment.type === "html") {
|
|
836
|
-
return segment.content ? /* @__PURE__ */ jsx(
|
|
1291
|
+
return segment.content ? /* @__PURE__ */ jsx(
|
|
1292
|
+
HtmlChunk,
|
|
1293
|
+
{
|
|
1294
|
+
html: renderHtml(segment.content),
|
|
1295
|
+
fromMarkdown,
|
|
1296
|
+
tail: i === last
|
|
1297
|
+
},
|
|
1298
|
+
i
|
|
1299
|
+
) : null;
|
|
837
1300
|
}
|
|
838
|
-
const ext = extensions.find(
|
|
839
|
-
(e) => e.tag.toLowerCase() === segment.tag
|
|
840
|
-
);
|
|
1301
|
+
const ext = extensions.find((e) => e.tag.toLowerCase() === segment.tag);
|
|
841
1302
|
if (!ext) return null;
|
|
842
1303
|
const Component = ext.component;
|
|
843
|
-
const
|
|
844
|
-
const
|
|
845
|
-
|
|
1304
|
+
const Wrapper = ext.block !== false ? "div" : "span";
|
|
1305
|
+
const children = ext.markdown ? /* @__PURE__ */ jsx(
|
|
1306
|
+
ContentBody,
|
|
1307
|
+
{
|
|
1308
|
+
source: segment.content,
|
|
1309
|
+
format: "html",
|
|
1310
|
+
extensions,
|
|
1311
|
+
unsafe,
|
|
1312
|
+
depth: depth + 1,
|
|
1313
|
+
tail: i === last
|
|
1314
|
+
}
|
|
1315
|
+
) : void 0;
|
|
1316
|
+
return /* @__PURE__ */ jsx(Wrapper, { "data-render-extension": segment.tag, children: /* @__PURE__ */ jsx(Component, { content: segment.content, attributes: segment.attributes, children }) }, i);
|
|
846
1317
|
}) });
|
|
847
1318
|
}
|
|
848
1319
|
RenderedContent.displayName = "RenderedContent";
|
|
1320
|
+
ContentBody.displayName = "ContentBody";
|
|
849
1321
|
function ContentRenderer({
|
|
850
1322
|
value,
|
|
851
1323
|
format = "auto",
|
|
852
1324
|
lineSpacing = 1.6,
|
|
853
1325
|
className,
|
|
854
1326
|
extensions: instanceExtensions,
|
|
855
|
-
unsafe = false
|
|
1327
|
+
unsafe = false,
|
|
1328
|
+
copyButtons = true,
|
|
1329
|
+
onCopy,
|
|
1330
|
+
clipboardWriter,
|
|
1331
|
+
streaming = false
|
|
856
1332
|
}) {
|
|
857
1333
|
const extensions = useMemo(
|
|
858
1334
|
() => mergeExtensions(instanceExtensions),
|
|
859
1335
|
[instanceExtensions]
|
|
860
1336
|
);
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
return
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1337
|
+
const safe = value ?? "";
|
|
1338
|
+
const resolvedFormat = format === "auto" ? detectFormat(safe) : format;
|
|
1339
|
+
const progressive = streaming === "progressive";
|
|
1340
|
+
const { source, openFence } = useMemo(() => {
|
|
1341
|
+
if (!streaming) return { source: safe, openFence: false };
|
|
1342
|
+
const tags = extensions.map((e) => e.tag);
|
|
1343
|
+
const markdown = resolvedFormat === "markdown";
|
|
1344
|
+
if (progressive) return streamingProgressive(safe, tags, markdown);
|
|
1345
|
+
return { source: safe.slice(0, streamingCut(safe, tags, markdown)), openFence: false };
|
|
1346
|
+
}, [safe, streaming, progressive, extensions, resolvedFormat]);
|
|
1347
|
+
const codeOn = copyButtons === true || typeof copyButtons === "object" && copyButtons.code !== false;
|
|
1348
|
+
const tableOn = copyButtons === true || typeof copyButtons === "object" && copyButtons.table !== false;
|
|
1349
|
+
const copyOn = codeOn || tableOn;
|
|
1350
|
+
const rootRef = useRef(null);
|
|
1351
|
+
const [announcement, setAnnouncement] = useState("");
|
|
1352
|
+
const announce = useCallback((message) => {
|
|
1353
|
+
setAnnouncement("");
|
|
1354
|
+
queueMicrotask(() => setAnnouncement(message));
|
|
1355
|
+
}, []);
|
|
1356
|
+
const copy = useCopyPipeline({ writer: clipboardWriter, onCopy, rootRef, announce });
|
|
1357
|
+
const copyContext = useMemo(
|
|
1358
|
+
() => ({ code: codeOn, table: tableOn, copy, holdTailCode: openFence }),
|
|
1359
|
+
[codeOn, tableOn, copy, openFence]
|
|
1360
|
+
);
|
|
1361
|
+
return /* @__PURE__ */ jsxs(
|
|
869
1362
|
"div",
|
|
870
1363
|
{
|
|
1364
|
+
ref: rootRef,
|
|
871
1365
|
"data-react-fancy-content-renderer": "",
|
|
1366
|
+
"data-streaming": streaming ? "true" : void 0,
|
|
1367
|
+
"data-streaming-mode": progressive ? "progressive" : void 0,
|
|
872
1368
|
style: { lineHeight: lineSpacing },
|
|
873
1369
|
className: cn("text-sm", proseClasses, className),
|
|
874
|
-
children:
|
|
1370
|
+
children: [
|
|
1371
|
+
/* @__PURE__ */ jsx(CopyContext.Provider, { value: copyContext, children: /* @__PURE__ */ jsx(
|
|
1372
|
+
ContentBody,
|
|
1373
|
+
{
|
|
1374
|
+
source,
|
|
1375
|
+
format: resolvedFormat,
|
|
1376
|
+
extensions,
|
|
1377
|
+
unsafe,
|
|
1378
|
+
tail: true
|
|
1379
|
+
}
|
|
1380
|
+
) }),
|
|
1381
|
+
copyOn && /* @__PURE__ */ jsx(
|
|
1382
|
+
"span",
|
|
1383
|
+
{
|
|
1384
|
+
role: "status",
|
|
1385
|
+
"aria-live": "polite",
|
|
1386
|
+
"aria-atomic": "true",
|
|
1387
|
+
"data-react-fancy-content-renderer-live": "",
|
|
1388
|
+
className: "sr-only",
|
|
1389
|
+
children: announcement
|
|
1390
|
+
}
|
|
1391
|
+
)
|
|
1392
|
+
]
|
|
875
1393
|
}
|
|
876
1394
|
);
|
|
877
1395
|
}
|
|
878
1396
|
ContentRenderer.displayName = "ContentRenderer";
|
|
879
1397
|
|
|
880
1398
|
export { ContentRenderer, detectFormat, extensionEditorClasses, htmlToMarkdown, markdownToEditorHtml, mergeExtensions, proseClasses, registerExtension, registerExtensions, renderMarkdown };
|
|
881
|
-
//# sourceMappingURL=chunk-
|
|
882
|
-
//# sourceMappingURL=chunk-
|
|
1399
|
+
//# sourceMappingURL=chunk-2LQR4EAN.js.map
|
|
1400
|
+
//# sourceMappingURL=chunk-2LQR4EAN.js.map
|