diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx index 1851223a1..48b5ccd09 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -288,6 +288,25 @@ export function ArtifactFilePreview({ language: string; url?: string; }) { + const [htmlPreviewUrl, setHtmlPreviewUrl] = useState(); + + useEffect(() => { + if (language !== "html" || isWriteFile) { + setHtmlPreviewUrl(undefined); + return; + } + + const blob = new Blob([htmlWithBaseHref(content ?? "", url)], { + type: "text/html", + }); + const objectUrl = URL.createObjectURL(blob); + setHtmlPreviewUrl(objectUrl); + + return () => { + URL.revokeObjectURL(objectUrl); + }; + }, [content, isWriteFile, language, url]); + if (language === "markdown") { return (
@@ -311,10 +330,35 @@ export function ArtifactFilePreview({ ? "allow-scripts allow-forms" : "allow-scripts allow-forms allow-same-origin" } - src={isWriteFile ? undefined : url} + src={isWriteFile ? undefined : htmlPreviewUrl} srcDoc={isWriteFile ? content : undefined} /> ); } return null; } + +function htmlWithBaseHref(content: string, url?: string) { + if (!url || content.match(/`; + if (content.match(/]*>/i)) { + return content.replace(/]*)>/i, `${baseElement}`); + } + return `${baseElement}${content}`; +} + +function htmlBaseHref(url: string) { + const baseUrl = new URL(url, window.location.href); + baseUrl.pathname = baseUrl.pathname.replace(/\/[^/]*$/, "/"); + baseUrl.search = ""; + baseUrl.hash = ""; + return baseUrl.toString(); +} + +function escapeHtmlAttribute(value: string) { + return value.replaceAll("&", "&").replaceAll('"', """); +}