Merge pull request #674 from getmaxun/perfect-ui

feat: enhance snapshot rendering
This commit is contained in:
Rohit
2025-07-07 01:24:54 +05:30
committed by GitHub
2 changed files with 73 additions and 125 deletions

View File

@@ -734,7 +734,7 @@ export const IntegrationSettingsModal = ({
<Button <Button
variant="outlined" variant="outlined"
onClick={() => { onClick={() => {
window.open("https://docs.maxun.dev/mcp/setup", "_blank" "noopener,noreferrer"); window.open("https://docs.maxun.dev/mcp/setup", "_blank", "noopener,noreferrer");
}} }}
style={{ display: "flex", flexDirection: "column", alignItems: "center", background: 'white', color: '#ff00c3' }} style={{ display: "flex", flexDirection: "column", alignItems: "center", background: 'white', color: '#ff00c3' }}
> >

View File

@@ -756,82 +756,29 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
return; return;
} }
const iframe = iframeRef.current; if (isInCaptureMode) {
return; // Skip rendering in capture mode
}
try { try {
setRenderError(null); setRenderError(null);
setIsRendered(false); setIsRendered(false);
const tempDoc = const iframe = iframeRef.current!;
document.implementation.createHTMLDocument("RRWeb Snapshot"); const iframeDoc = iframe.contentDocument!;
const mirror = createMirror(); const styleTags = Array.from(
document.querySelectorAll('link[rel="stylesheet"], style')
try { )
rebuild(snapshotData.snapshot, { .map((tag) => tag.outerHTML)
doc: tempDoc, .join("\n");
mirror: mirror,
cache: { stylesWithHoverClass: new Map() },
afterAppend: (node) => {
if (node.nodeType === Node.TEXT_NODE && node.textContent) {
const text = node.textContent.trim();
if (
text.startsWith("<") &&
text.includes(">") &&
text.length > 50
) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
}
},
});
} catch (rebuildError) {
console.error("rrweb rebuild failed:", rebuildError);
throw new Error(`rrweb rebuild failed: ${rebuildError}`);
}
let rebuiltHTML = tempDoc.documentElement.outerHTML;
rebuiltHTML = "<!DOCTYPE html>\n" + rebuiltHTML;
const additionalCSS = [];
if (snapshotData.resources.fonts?.length > 0) {
const fontCSS = snapshotData.resources.fonts
.map((font) => {
const format = font.format || "woff2";
return `
@font-face {
font-family: 'ProxiedFont-${
font.url.split("/").pop()?.split(".")[0] || "unknown"
}';
src: url("${font.dataUrl}") format("${format}");
font-display: swap;
}
`;
})
.join("\n");
additionalCSS.push(fontCSS);
}
if (snapshotData.resources.stylesheets?.length > 0) {
const externalCSS = snapshotData.resources.stylesheets
.map((stylesheet) => stylesheet.content)
.join("\n\n");
additionalCSS.push(externalCSS);
}
const enhancedCSS = ` const enhancedCSS = `
/* rrweb rebuilt content styles */ /* rrweb rebuilt content styles */
html, body { html, body {
margin: 0 !important; margin: 0 !important;
padding: 8px !important; padding: 8px !important;
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif !important; overflow-x: hidden !important;
background: white !important;
overflow-x: hidden !important;
} }
html::-webkit-scrollbar, html::-webkit-scrollbar,
@@ -855,64 +802,65 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
-ms-overflow-style: none !important; /* Internet Explorer 10+ */ -ms-overflow-style: none !important; /* Internet Explorer 10+ */
} }
img {
max-width: 100% !important;
height: auto !important;
}
/* Make everything interactive */ /* Make everything interactive */
* { * {
cursor: "pointer" !important; cursor: "pointer" !important;
} }
`;
/* Additional CSS from resources */ const skeleton = `
${additionalCSS.join("\n\n")} <!DOCTYPE html>
`; <html>
<head>
<meta charset="utf-8">
const headTagRegex = /<head[^>]*>/i; <meta name="viewport" content="width=device-width, initial-scale=1">
const cssInjection = ` <base href="${snapshotData.baseUrl}">
<meta charset="utf-8"> ${styleTags}
<meta name="viewport" content="width=device-width, initial-scale=1"> <style>${enhancedCSS}</style>
<base href="${snapshotData.baseUrl}"> </head>
<style>${enhancedCSS}</style> <body></body>
`; </html>
`;
if (headTagRegex.test(rebuiltHTML)) {
rebuiltHTML = rebuiltHTML.replace(
"<head>",
`<head><base href="${snapshotData.baseUrl}">${minimalCSS}`
);
} else if (rebuiltHTML.includes("<html>")) {
rebuiltHTML = rebuiltHTML.replace(
"<html>",
`<html><head><base href="${snapshotData.baseUrl}">${minimalCSS}</head>`
);
}
rebuiltHTML = rebuiltHTML
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
.replace(/\s*on\w+\s*=\s*"[^"]*"/gi, "")
.replace(/\s*on\w+\s*=\s*'[^']*'/gi, "")
.replace(/javascript:/gi, "void:")
.replace(/<form\b/gi, '<form onsubmit="return false;"');
const iframeDoc =
iframe.contentDocument || iframe.contentWindow?.document;
if (!iframeDoc) { if (!iframeDoc) {
throw new Error("Cannot access iframe document"); throw new Error("Cannot access iframe document");
} }
// Write the skeleton into the iframe
iframeDoc.open(); iframeDoc.open();
iframeDoc.write(rebuiltHTML); iframeDoc.write(skeleton);
iframeDoc.close(); iframeDoc.close();
iframe.onload = () => { const mirror = createMirror();
setIsRendered(true);
setupIframeInteractions(iframeDoc); try {
}; rebuild(snapshotData.snapshot, {
doc: iframeDoc,
mirror: mirror,
cache: { stylesWithHoverClass: new Map() },
afterAppend: (node) => {
if (node.nodeType === Node.TEXT_NODE && node.textContent) {
const text = node.textContent.trim();
if (
text.startsWith("<") &&
text.includes(">") &&
text.length > 50
) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
}
},
});
} catch (rebuildError) {
console.error("rrweb rebuild failed:", rebuildError);
throw new Error(`rrweb rebuild failed: ${rebuildError}`);
}
setIsRendered(true);
setupIframeInteractions(iframeDoc);
} catch (error) { } catch (error) {
console.error("Error rendering rrweb snapshot:", error); console.error("Error rendering rrweb snapshot:", error);
setRenderError(error instanceof Error ? error.message : String(error)); setRenderError(error instanceof Error ? error.message : String(error));