analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
636 lines (608 loc) • 21.5 kB
JavaScript
;Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _chunkAKAHADV7js = require('./chunk-AKAHADV7.js');
var _chunk6N4P3WKFjs = require('./chunk-6N4P3WKF.js');
var _chunkTN3AYOMVjs = require('./chunk-TN3AYOMV.js');
// src/components/HtmlMathRenderer/HtmlMathRenderer.tsx
var _react = require('react');
require('katex/dist/katex.min.css');
// src/components/MarkdownMathRenderer/MarkdownMathRenderer.tsx
var _reactmarkdown = require('react-markdown'); var _reactmarkdown2 = _interopRequireDefault(_reactmarkdown);
var _remarkgfm = require('remark-gfm'); var _remarkgfm2 = _interopRequireDefault(_remarkgfm);
var _remarkmath = require('remark-math'); var _remarkmath2 = _interopRequireDefault(_remarkmath);
var _rehypekatex = require('rehype-katex'); var _rehypekatex2 = _interopRequireDefault(_rehypekatex);
// src/components/HtmlMathRenderer/utils.ts
var generateSecureRandomId = () => {
return crypto.randomUUID();
};
var cleanLatex = (str) => {
return str.replaceAll(/[\u200B-\u200D\uFEFF]/g, "").replaceAll(/&lt;|</gi, String.raw`\lt `).replaceAll(/&gt;|>/gi, String.raw`\gt `).replaceAll(/&amp;|&/gi, "&").trim();
};
var looksLikeLatex = (str) => {
if (/[\\^_{}]/.test(str)) return true;
const words = str.match(/[a-zA-Z]{3,}/g);
if (words && words.length >= 2) return false;
return true;
};
var HTML_TAG_NAMES = /* @__PURE__ */ new Set([
"p",
"div",
"span",
"br",
"b",
"strong",
"i",
"em",
"u",
"s",
"ul",
"ol",
"li",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"a",
"img",
"table",
"thead",
"tbody",
"tfoot",
"tr",
"td",
"th",
"blockquote",
"pre",
"code",
"sub",
"sup",
"font",
"latex"
]);
var containsHtmlTag = (content) => {
for (const match of content.matchAll(/<\/?([a-z][a-z0-9]*)/gi)) {
if (HTML_TAG_NAMES.has(match[1].toLowerCase())) return true;
}
return false;
};
var isLikelyMarkdown = (content) => {
if (!content) return false;
if (containsHtmlTag(content)) return false;
const MARKDOWN_SIGNALS = [
/^#{1,6}[ \t]+\S/m,
// ATX heading (#, ##, ... ######)
/\*\*[^*\n]+\*\*/,
// bold
/__[^_\n]+__/,
// bold (underscore)
/^[ \t]*[-*+][ \t]+\S/m,
// bullet list
/^[ \t]*\d+\.[ \t]+\S/m,
// ordered list
/\|[ \t]*:?-+:?[ \t]*\|/,
// GFM table delimiter row (|---|---|)
/\n\n/
// paragraph break (only reached when no HTML tag is present)
];
return MARKDOWN_SIGNALS.some((pattern) => pattern.test(content));
};
var recoverFromKatexErrorSpans = (htmlContent) => {
if (typeof document === "undefined" || !/class="[^"]*katex-error/i.test(htmlContent)) {
return htmlContent;
}
const tempContainer = document.createElement("div");
tempContainer.innerHTML = htmlContent;
const sanitizeRecoveredLatex = (raw) => raw.replaceAll(/[\u0300-\u036F]/g, "").replaceAll(/<\/?[a-zA-Z][a-zA-Z0-9]*\s*>?$/g, "").replaceAll(/&lt;|<|</gi, String.raw`\lt `).replaceAll(/&gt;|>|>/gi, String.raw`\gt `).replaceAll(/&amp;|&/gi, "&").trim();
Array.from(tempContainer.querySelectorAll(".katex-error")).forEach(
(errorNode) => {
if (!tempContainer.contains(errorNode)) return;
const title = errorNode.getAttribute("title") || "";
const positionMatch = /at position\s+\d+:(.+)$/.exec(title);
let recovered = positionMatch ? positionMatch[1] : "";
if (!recovered) {
const parts = [];
const walk = (n) => {
if (n.nodeType === Node.TEXT_NODE) {
parts.push(n.textContent || "");
return;
}
if (n.nodeType !== Node.ELEMENT_NODE) return;
const el = n;
if (el.tagName.toLowerCase() === "annotation") {
parts.push(el.textContent || "");
return;
}
if (el.classList.contains("katex-html")) return;
Array.from(el.childNodes).forEach(walk);
};
Array.from(errorNode.childNodes).forEach(walk);
recovered = parts.join(" ");
}
recovered = sanitizeRecoveredLatex(recovered);
if (recovered) {
errorNode.replaceWith(document.createTextNode(`$${recovered}$`));
} else {
errorNode.remove();
}
}
);
return tempContainer.innerHTML;
};
var decodeDollarEscapes = (text) => text.replaceAll(String.raw`\$`, "$");
var DANGEROUS_ATTRIBUTES = /* @__PURE__ */ new Set([
"contenteditable",
"srcdoc",
"formaction",
"xlink:href"
]);
var DANGEROUS_URI_PATTERN = /^\s*(javascript|vbscript|data):/i;
var sanitizeHtmlForDisplay = (htmlContent) => {
if (!htmlContent) return htmlContent;
if (typeof document === "undefined") {
let sanitized = htmlContent;
sanitized = sanitized.replaceAll(
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
""
);
sanitized = sanitized.replaceAll(
/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi,
""
);
sanitized = sanitized.replaceAll(/ on[a-z]+="[^"]*"/gi, "");
sanitized = sanitized.replaceAll(/ on[a-z]+='[^']*'/gi, "");
sanitized = sanitized.replaceAll(/ on[a-z]+=[^\s>"']+/gi, "");
sanitized = sanitized.replaceAll(
/ href="(?:javascript|vbscript|data):[^"]*"/gi,
""
);
sanitized = sanitized.replaceAll(
/ href='(?:javascript|vbscript|data):[^']*'/gi,
""
);
sanitized = sanitized.replaceAll(
/ src="(?:javascript|vbscript|data):[^"]*"/gi,
""
);
sanitized = sanitized.replaceAll(
/ src='(?:javascript|vbscript|data):[^']*'/gi,
""
);
sanitized = sanitized.replaceAll(
/ action="(?:javascript|vbscript|data):[^"]*"/gi,
""
);
sanitized = sanitized.replaceAll(
/ action='(?:javascript|vbscript|data):[^']*'/gi,
""
);
return sanitized;
}
const tempDiv = document.createElement("div");
tempDiv.innerHTML = htmlContent;
const dangerousTags = tempDiv.querySelectorAll(
"script, style, iframe, object, embed"
);
dangerousTags.forEach((element) => element.remove());
const allElements = tempDiv.querySelectorAll("*");
allElements.forEach((element) => {
const attributeNames = element.getAttributeNames();
attributeNames.forEach((attrName) => {
const lowerAttrName = attrName.toLowerCase();
if (lowerAttrName.startsWith("on")) {
element.removeAttribute(attrName);
return;
}
if (DANGEROUS_ATTRIBUTES.has(lowerAttrName)) {
element.removeAttribute(attrName);
return;
}
if (lowerAttrName === "href" || lowerAttrName === "src" || lowerAttrName === "action") {
const value = element.getAttribute(attrName);
if (value && DANGEROUS_URI_PATTERN.test(value)) {
element.removeAttribute(attrName);
}
}
});
});
return tempDiv.innerHTML;
};
var processHtmlWithMath = (htmlContent) => {
if (!htmlContent) return [];
let processedContent = recoverFromKatexErrorSpans(htmlContent);
const parts = [];
const sentinel = `__MATH_${generateSecureRandomId()}_`;
const mathFormulaPattern = /<span[^>]*class="math-formula"[^>]*data-latex="([^"]*)"[^>]*>[\s\S]*?<\/span>/g;
processedContent = processedContent.replaceAll(
mathFormulaPattern,
(match2, latex) => {
const isDisplayMode = match2.includes('data-display-mode="true"');
const placeholder = `${sentinel}${parts.length}__`;
parts.push({
type: isDisplayMode ? "block-math" : "math",
content: match2,
latex: cleanLatex(latex)
});
return placeholder;
}
);
const wrappedMathPattern = /<span[^>]*class="math-expression"[^>]*data-math="([^"]*)"[^>]*>.*?<\/span>/g;
processedContent = processedContent.replaceAll(
wrappedMathPattern,
(match2, latex) => {
const placeholder = `${sentinel}${parts.length}__`;
parts.push({
type: "math",
content: match2,
latex: cleanLatex(latex)
});
return placeholder;
}
);
const doubleDollarPattern = /(?<!\\)\$\$([\s\S]+?)\$\$/g;
processedContent = processedContent.replaceAll(
doubleDollarPattern,
(match2, latex) => {
const placeholder = `${sentinel}${parts.length}__`;
parts.push({
type: "block-math",
content: match2,
latex: cleanLatex(latex)
});
return placeholder;
}
);
const singleDollarPattern = /(?<!\\)\$([\s\S]+?)\$/g;
processedContent = processedContent.replaceAll(
singleDollarPattern,
(match2, latex) => {
if (!looksLikeLatex(latex)) return match2;
const placeholder = `${sentinel}${parts.length}__`;
parts.push({
type: "math",
content: match2,
latex: cleanLatex(latex)
});
return placeholder;
}
);
const latexTagPattern = /(?:<latex>|<latex>)([\s\S]*?)(?:<\/latex>|<\/latex>)/g;
processedContent = processedContent.replaceAll(
latexTagPattern,
(match2, latex) => {
const placeholder = `${sentinel}${parts.length}__`;
parts.push({
type: "math",
content: match2,
latex: cleanLatex(latex)
});
return placeholder;
}
);
const latexEnvPattern = /\\begin\{([^}]+)\}([\s\S]*?)\\end\{\1\}/g;
processedContent = processedContent.replaceAll(latexEnvPattern, (match2) => {
const placeholder = `${sentinel}${parts.length}__`;
parts.push({
type: "block-math",
content: match2,
latex: cleanLatex(match2)
});
return placeholder;
});
const finalParts = [];
let currentIndex = 0;
const escapedSentinel = sentinel.replaceAll(
/[.*+?^${}()|[\]\\]/g,
String.raw`\$&`
);
const placeholderPattern = new RegExp(
String.raw`${escapedSentinel}(\d+)__`,
"g"
);
let match;
while ((match = placeholderPattern.exec(processedContent)) !== null) {
if (match.index > currentIndex) {
finalParts.push({
type: "text",
content: decodeDollarEscapes(
processedContent.slice(currentIndex, match.index)
)
});
}
const mathIndex = Number.parseInt(match[1], 10);
if (parts[mathIndex]) {
finalParts.push(parts[mathIndex]);
}
currentIndex = match.index + match[0].length;
}
if (currentIndex < processedContent.length) {
finalParts.push({
type: "text",
content: decodeDollarEscapes(processedContent.slice(currentIndex))
});
}
return finalParts;
};
var containsMath = (content) => {
if (!content) return false;
const patterns = [
/\$\$[\s\S]+?\$\$/,
// Display mode $$...$$
/(?<!\\)\$[\s\S]+?\$/,
// Inline mode $...$
/<span[^>]*class="math-formula"/,
// Editor spans
/<span[^>]*class="math-expression"/,
// Legacy spans
/<latex>|<latex>/,
// LaTeX tags
/\\begin\{[^}]+\}/
// LaTeX environments
];
return patterns.some((pattern) => pattern.test(content));
};
var stripHtml = (htmlContent) => {
if (!htmlContent) return "";
let content = htmlContent;
content = content.replaceAll(
/<span[^>]*class="math-formula"[^>]*>[\s\S]*?<\/span>/g,
""
);
content = content.replaceAll(
/<span[^>]*class="math-expression"[^>]*>[\s\S]*?<\/span>/g,
""
);
content = content.replaceAll(/\$\$[\s\S]+?\$\$/g, "");
content = content.replaceAll(/\$[^$]+\$/g, "");
content = content.replaceAll(
/(?:<latex>|<latex>)[\s\S]*?(?:<\/latex>|<\/latex>)/g,
""
);
content = content.replaceAll(
/\\begin\{[a-zA-Z*]+\}[\s\S]*?\\end\{[a-zA-Z*]+\}/g,
""
);
if (typeof document === "undefined") {
return content.replaceAll(/<[^<>]*>/g, "").trim();
}
const tempDiv = document.createElement("div");
tempDiv.innerHTML = content;
return (tempDiv.textContent || tempDiv.innerText || "").trim();
};
// src/components/MarkdownMathRenderer/MarkdownMathRenderer.tsx
var _jsxruntime = require('react/jsx-runtime');
var stripInvisibleChars = (str) => str.replaceAll(/[\u200B-\u200D\uFEFF]/g, "");
var protectCurrencyInlineMath = (markdown) => markdown.replaceAll(
/(?<!\\)\$(?!\$)([^\n$]+?)(?<!\\)\$(?!\$)/g,
(match, inner) => looksLikeLatex(inner) ? match : match.replaceAll("$", String.raw`\$`)
);
var reflowDisplayMath = (markdown) => markdown.replaceAll(
/(?<!\$)\$\$(?!\$)([^\n]+?)\$\$(?!\$)/g,
(_match, inner) => `
$$
${inner.trim()}
$$
`
);
var withProtectedCode = (markdown, transform) => {
const stash = [];
const tokenized = markdown.replaceAll(
/```[\s\S]*?```|`[^`\n]*`/g,
(segment) => {
const token = `__CODE_SEG_${stash.length}__`;
stash.push(segment);
return token;
}
);
return transform(tokenized).replaceAll(
/__CODE_SEG_(\d+)__/g,
(_match, index) => stash[Number(index)]
);
};
var preprocessMarkdown = (content) => withProtectedCode(
stripInvisibleChars(content),
(safe) => reflowDisplayMath(protectCurrencyInlineMath(safe))
);
var MarkdownMathRenderer = _react.forwardRef.call(void 0, ({ content, className, style, testId }, ref) => {
const sharedClassName = _chunkTN3AYOMVjs.cn.call(void 0,
"leading-relaxed",
// Paragraph spacing
"[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0",
// Headings
"[&_h1]:text-xl [&_h1]:font-bold [&_h1]:mt-3 [&_h1]:mb-2",
"[&_h2]:text-lg [&_h2]:font-bold [&_h2]:mt-3 [&_h2]:mb-2",
"[&_h3]:text-base [&_h3]:font-semibold [&_h3]:mt-2.5 [&_h3]:mb-1.5",
"[&_h4]:text-base [&_h4]:font-semibold [&_h4]:mt-2.5 [&_h4]:mb-1.5",
"[&_h5]:font-semibold [&_h6]:font-semibold",
// Lists
"[&_ul]:list-disc [&_ul]:pl-5 [&_ul]:my-2",
"[&_ol]:list-decimal [&_ol]:pl-5 [&_ol]:my-2",
"[&_li]:my-1 [&_li>ul]:my-1 [&_li>ol]:my-1",
// Text formatting
"[&_b]:font-bold [&_strong]:font-bold",
"[&_i]:italic [&_em]:italic",
"[&_u]:underline",
"[&_blockquote]:border-l-4 [&_blockquote]:border-border-200 [&_blockquote]:pl-3 [&_blockquote]:text-text-700",
"[&_code]:rounded [&_code]:bg-background-100 [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-sm",
// Hide the KaTeX MathML accessibility layer visually (still readable to
// screen readers). Tailwind preflight overrides KaTeX's default clip
// rule, so without this the MathML layer duplicates every formula as
// raw text next to the visual render.
"[&_.katex-mathml]:sr-only",
// Display math spacing
"[&_.katex-display]:my-2.5",
// Tables (GFM)
"[&_table]:border-collapse [&_table]:w-full [&_table]:my-2.5 [&_table]:table-auto",
"[&_table_td]:border [&_table_td]:border-border-200 [&_table_td]:p-2 [&_table_td]:min-w-[50px] [&_table_td]:align-top",
"[&_table_th]:border [&_table_th]:border-border-200 [&_table_th]:p-2 [&_table_th]:min-w-[50px] [&_table_th]:align-top [&_table_th]:bg-background-50 [&_table_th]:font-semibold",
// Images and links
"[&_img]:max-w-full [&_img]:h-auto [&_img]:rounded-md [&_img]:my-2",
"[&_a]:text-primary-500 [&_a]:underline [&_a:hover]:text-primary-600",
className
);
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"div",
{
ref,
className: sharedClassName,
style,
"data-testid": testId,
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_reactmarkdown2.default,
{
remarkPlugins: [_remarkgfm2.default, _remarkmath2.default],
rehypePlugins: [_rehypekatex2.default],
children: preprocessMarkdown(content)
}
)
}
);
});
MarkdownMathRenderer.displayName = "MarkdownMathRenderer";
var MarkdownMathRenderer_default = _react.memo.call(void 0, MarkdownMathRenderer);
// src/components/HtmlMathRenderer/HtmlMathRenderer.tsx
var HtmlMathRenderer = _react.forwardRef.call(void 0,
({
content,
className,
style,
sanitize = true,
renderMathError,
testId,
inline = false
}, ref) => {
if (!inline && content && isLikelyMarkdown(content)) {
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
MarkdownMathRenderer_default,
{
ref,
content,
className,
style,
testId
}
);
}
const defaultErrorRenderer = (latex) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "text-error-600 text-sm", children: [
"Math Error: ",
latex
] });
const errorRenderer = renderMathError || defaultErrorRenderer;
const renderContent = () => {
if (!content) return null;
const normalizedContent = _chunkAKAHADV7js.normalizeLineBreaksInHtml.call(void 0, content, {
inline: true
});
const processedContent = sanitize ? sanitizeHtmlForDisplay(normalizedContent) : normalizedContent;
const parts = processHtmlWithMath(processedContent);
if (parts.every((part) => part.type === "text")) {
const joinedHtml = parts.map((part) => part.content).join("");
const Element = inline ? "span" : "div";
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
Element,
{
dangerouslySetInnerHTML: {
__html: joinedHtml || processedContent
}
}
);
}
const getPartKey = (part, idx) => {
const contentHash = (part.latex || part.content).slice(0, 20);
return `${part.type}-${idx}-${contentHash}`;
};
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children: parts.map((part, index) => {
const key = getPartKey(part, index);
if (part.type === "math" && part.latex) {
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk6N4P3WKFjs.KatexMath,
{
math: part.latex,
renderError: () => errorRenderer(part.latex)
},
key
);
} else if (part.type === "block-math" && part.latex) {
if (inline) {
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk6N4P3WKFjs.KatexMath,
{
math: part.latex,
renderError: () => errorRenderer(part.latex)
},
key
);
}
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "my-2.5 text-center", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk6N4P3WKFjs.KatexMath,
{
math: part.latex,
displayMode: true,
renderError: () => errorRenderer(part.latex)
}
) }, key);
} else {
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"span",
{
dangerouslySetInnerHTML: { __html: part.content }
},
key
);
}
}) });
};
const sharedClassName = _chunkTN3AYOMVjs.cn.call(void 0,
// Base styles
"leading-relaxed",
// Paragraph styles
"[&_p]:mb-0",
// Hide the KaTeX MathML accessibility layer visually (still readable
// to screen readers). Tailwind preflight overrides the KaTeX default
// `position: absolute; clip:...` rule, so the MathML layer ends up
// duplicating every formula as raw text next to the visual render.
"[&_.katex-mathml]:sr-only",
// Table styles (only relevant for block mode, but harmless for inline)
"[&_table]:border-collapse [&_table]:w-full [&_table]:my-2.5 [&_table]:table-auto",
"[&_table_td]:border [&_table_td]:border-border-200 [&_table_td]:p-2 [&_table_td]:min-w-[50px] [&_table_td]:align-top",
"[&_table_th]:border [&_table_th]:border-border-200 [&_table_th]:p-2 [&_table_th]:min-w-[50px] [&_table_th]:align-top [&_table_th]:bg-background-50 [&_table_th]:font-semibold",
"[&_table_tr:nth-child(even)]:bg-background-50/50",
"[&_table_tr:hover]:bg-background-100/50",
// Image styles
"[&_img]:max-w-full [&_img]:h-auto [&_img]:rounded-md [&_img]:my-2",
// Link styles
"[&_a]:text-primary-500 [&_a]:underline [&_a:hover]:text-primary-600",
// Text formatting styles
"[&_b]:font-bold [&_strong]:font-bold",
"[&_i]:italic [&_em]:italic",
"[&_u]:underline",
className
);
if (inline) {
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"span",
{
ref,
className: sharedClassName,
style,
"data-testid": testId,
children: renderContent()
}
);
}
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"div",
{
ref,
className: sharedClassName,
style,
"data-testid": testId,
children: renderContent()
}
);
}
);
HtmlMathRenderer.displayName = "HtmlMathRenderer";
var HtmlMathRenderer_default = _react.memo.call(void 0, HtmlMathRenderer);
exports.cleanLatex = cleanLatex; exports.looksLikeLatex = looksLikeLatex; exports.isLikelyMarkdown = isLikelyMarkdown; exports.sanitizeHtmlForDisplay = sanitizeHtmlForDisplay; exports.processHtmlWithMath = processHtmlWithMath; exports.containsMath = containsMath; exports.stripHtml = stripHtml; exports.protectCurrencyInlineMath = protectCurrencyInlineMath; exports.reflowDisplayMath = reflowDisplayMath; exports.MarkdownMathRenderer_default = MarkdownMathRenderer_default; exports.HtmlMathRenderer_default = HtmlMathRenderer_default;
//# sourceMappingURL=chunk-HZE6VBF5.js.map