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
|
<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' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -756,20 +756,86 @@ 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 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();
|
const mirror = createMirror();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
rebuild(snapshotData.snapshot, {
|
rebuild(snapshotData.snapshot, {
|
||||||
doc: tempDoc,
|
doc: iframeDoc,
|
||||||
mirror: mirror,
|
mirror: mirror,
|
||||||
cache: { stylesWithHoverClass: new Map() },
|
cache: { stylesWithHoverClass: new Map() },
|
||||||
afterAppend: (node) => {
|
afterAppend: (node) => {
|
||||||
@@ -793,126 +859,8 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
|||||||
throw new Error(`rrweb rebuild failed: ${rebuildError}`);
|
throw new Error(`rrweb rebuild failed: ${rebuildError}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let rebuiltHTML = tempDoc.documentElement.outerHTML;
|
setIsRendered(true);
|
||||||
|
setupIframeInteractions(iframeDoc);
|
||||||
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);
|
|
||||||
};
|
|
||||||
} 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));
|
||||||
|
|||||||
Reference in New Issue
Block a user