Skip to content

Commit 07924b8

Browse files
committed
Open artifact links in new tabs
1 parent cb25320 commit 07924b8

2 files changed

Lines changed: 47 additions & 25 deletions

File tree

‎client/artifact-preview.ts‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const PREVIEW_HEAD = `<base href="about:srcdoc">
2+
<script>
3+
document.addEventListener("click", (event) => {
4+
const link = event.composedPath().find(
5+
(element) => element instanceof HTMLAnchorElement
6+
);
7+
const href = link?.getAttribute("href")?.trimStart();
8+
if (
9+
!link ||
10+
!href ||
11+
(link.protocol !== "http:" &&
12+
link.protocol !== "https:" &&
13+
!href.startsWith("//"))
14+
) {
15+
return;
16+
}
17+
18+
link.target = "_blank";
19+
link.relList.remove("opener");
20+
link.relList.add("noopener", "noreferrer");
21+
}, true);
22+
</script>`;
23+
24+
export function prepareArtifactPreview(html: string): string {
25+
const head = /<head(?:\s[^>]*)?>/i.exec(html);
26+
if (head) {
27+
const insertionPoint = head.index + head[0].length;
28+
return `${html.slice(0, insertionPoint)}${PREVIEW_HEAD}${html.slice(insertionPoint)}`;
29+
}
30+
31+
const documentElement = /<html(?:\s[^>]*)?>/i.exec(html);
32+
if (documentElement) {
33+
const insertionPoint = documentElement.index + documentElement[0].length;
34+
return `${html.slice(0, insertionPoint)}<head>${PREVIEW_HEAD}</head>${html.slice(insertionPoint)}`;
35+
}
36+
37+
const doctype = /<!doctype(?:\s[^>]*)?>/i.exec(html);
38+
if (doctype) {
39+
const insertionPoint = doctype.index + doctype[0].length;
40+
return `${html.slice(0, insertionPoint)}<head>${PREVIEW_HEAD}</head>${html.slice(insertionPoint)}`;
41+
}
42+
43+
return `<head>${PREVIEW_HEAD}</head>${html}`;
44+
}

‎client/index.tsx‎

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useEffect, useMemo, useState, type FormEvent } from "react";
1010
import faviconUrl from "../favicon.svg";
1111
import type { OwnedArtifact, ViewedArtifact } from "../shared/api";
1212
import { client } from "./api";
13+
import { prepareArtifactPreview } from "./artifact-preview";
1314
import { AuthProvider, SignInWithGoogle, signOut, useAuth } from "./auth";
1415
import { RequestFailure } from "./request-failure";
1516
import {
@@ -26,7 +27,6 @@ import {
2627

2728
const DEFAULT_DOCUMENT_TITLE = "Codex Artifacts";
2829
const KNOWN_EMAILS_KEY = "codex-artifacts:known-emails";
29-
const SRCDOC_BASE = '<base href="about:srcdoc">';
3030
const EXPIRATION_OPTIONS = [
3131
{ label: "1 hour", value: "3600" },
3232
{ label: "1 day", value: "86400" },
@@ -65,28 +65,6 @@ function messageFromError(error: unknown): string {
6565
return error instanceof Error ? error.message : "Something went wrong.";
6666
}
6767

68-
function withSrcdocBase(html: string): string {
69-
const head = /<head(?:\s[^>]*)?>/i.exec(html);
70-
if (head) {
71-
const insertionPoint = head.index + head[0].length;
72-
return `${html.slice(0, insertionPoint)}${SRCDOC_BASE}${html.slice(insertionPoint)}`;
73-
}
74-
75-
const documentElement = /<html(?:\s[^>]*)?>/i.exec(html);
76-
if (documentElement) {
77-
const insertionPoint = documentElement.index + documentElement[0].length;
78-
return `${html.slice(0, insertionPoint)}<head>${SRCDOC_BASE}</head>${html.slice(insertionPoint)}`;
79-
}
80-
81-
const doctype = /<!doctype(?:\s[^>]*)?>/i.exec(html);
82-
if (doctype) {
83-
const insertionPoint = doctype.index + doctype[0].length;
84-
return `${html.slice(0, insertionPoint)}<head>${SRCDOC_BASE}</head>${html.slice(insertionPoint)}`;
85-
}
86-
87-
return `<head>${SRCDOC_BASE}</head>${html}`;
88-
}
89-
9068
function readKnownEmails(): string[] {
9169
try {
9270
const value = JSON.parse(window.localStorage.getItem(KNOWN_EMAILS_KEY) ?? "[]");
@@ -662,7 +640,7 @@ function ArtifactFrame({ slug }: { slug: string }) {
662640
return () => URL.revokeObjectURL(url);
663641
}, [artifact?.html]);
664642
const previewHtml = useMemo(
665-
() => artifact ? withSrcdocBase(artifact.html) : "",
643+
() => artifact ? prepareArtifactPreview(artifact.html) : "",
666644
[artifact?.html]
667645
);
668646

@@ -854,7 +832,7 @@ function ArtifactFrame({ slug }: { slug: string }) {
854832
<iframe
855833
className="min-h-[700px] flex-1 border-0 bg-white"
856834
referrerPolicy="no-referrer"
857-
sandbox="allow-downloads allow-forms allow-modals allow-popups allow-scripts"
835+
sandbox="allow-downloads allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-scripts"
858836
srcDoc={previewHtml}
859837
title={artifact.title}
860838
/>

0 commit comments

Comments
 (0)