Merge pull request #674 from getmaxun/perfect-ui
feat: enhance snapshot rendering
This commit is contained in:
@@ -734,7 +734,7 @@ export const IntegrationSettingsModal = ({
|
||||
<Button
|
||||
variant="outlined"
|
||||
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' }}
|
||||
>
|
||||
|
||||
@@ -756,20 +756,86 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const iframe = iframeRef.current;
|
||||
if (isInCaptureMode) {
|
||||
return; // Skip rendering in capture mode
|
||||
}
|
||||
|
||||
try {
|
||||
setRenderError(null);
|
||||
setIsRendered(false);
|
||||
|
||||
const tempDoc =
|
||||
document.implementation.createHTMLDocument("RRWeb Snapshot");
|
||||
const iframe = iframeRef.current!;
|
||||
const iframeDoc = iframe.contentDocument!;
|
||||
|
||||
const styleTags = Array.from(
|
||||
document.querySelectorAll('link[rel="stylesheet"], style')
|
||||
)
|
||||
.map((tag) => tag.outerHTML)
|
||||
.join("\n");
|
||||
|
||||
const enhancedCSS = `
|
||||
/* rrweb rebuilt content styles */
|
||||
html, body {
|
||||
margin: 0 !important;
|
||||
padding: 8px !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar,
|
||||
body::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* Hide scrollbars for all elements */
|
||||
*::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: none !important; /* Firefox */
|
||||
-ms-overflow-style: none !important; /* Internet Explorer 10+ */
|
||||
}
|
||||
|
||||
/* Make everything interactive */
|
||||
* {
|
||||
cursor: "pointer" !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const skeleton = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<base href="${snapshotData.baseUrl}">
|
||||
${styleTags}
|
||||
<style>${enhancedCSS}</style>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
if (!iframeDoc) {
|
||||
throw new Error("Cannot access iframe document");
|
||||
}
|
||||
|
||||
// Write the skeleton into the iframe
|
||||
iframeDoc.open();
|
||||
iframeDoc.write(skeleton);
|
||||
iframeDoc.close();
|
||||
|
||||
const mirror = createMirror();
|
||||
|
||||
try {
|
||||
rebuild(snapshotData.snapshot, {
|
||||
doc: tempDoc,
|
||||
doc: iframeDoc,
|
||||
mirror: mirror,
|
||||
cache: { stylesWithHoverClass: new Map() },
|
||||
afterAppend: (node) => {
|
||||
@@ -793,126 +859,8 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
||||
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 = `
|
||||
/* rrweb rebuilt content styles */
|
||||
html, body {
|
||||
margin: 0 !important;
|
||||
padding: 8px !important;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif !important;
|
||||
background: white !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar,
|
||||
body::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* Hide scrollbars for all elements */
|
||||
*::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: none !important; /* Firefox */
|
||||
-ms-overflow-style: none !important; /* Internet Explorer 10+ */
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
|
||||
/* Make everything interactive */
|
||||
* {
|
||||
cursor: "pointer" !important;
|
||||
}
|
||||
|
||||
/* Additional CSS from resources */
|
||||
${additionalCSS.join("\n\n")}
|
||||
`;
|
||||
|
||||
|
||||
const headTagRegex = /<head[^>]*>/i;
|
||||
const cssInjection = `
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<base href="${snapshotData.baseUrl}">
|
||||
<style>${enhancedCSS}</style>
|
||||
`;
|
||||
|
||||
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) {
|
||||
throw new Error("Cannot access iframe document");
|
||||
}
|
||||
|
||||
iframeDoc.open();
|
||||
iframeDoc.write(rebuiltHTML);
|
||||
iframeDoc.close();
|
||||
|
||||
iframe.onload = () => {
|
||||
setIsRendered(true);
|
||||
setupIframeInteractions(iframeDoc);
|
||||
};
|
||||
setIsRendered(true);
|
||||
setupIframeInteractions(iframeDoc);
|
||||
} catch (error) {
|
||||
console.error("Error rendering rrweb snapshot:", error);
|
||||
setRenderError(error instanceof Error ? error.message : String(error));
|
||||
|
||||
Reference in New Issue
Block a user