diff --git a/public/src/translate-modal.js b/public/src/translate-modal.js
index fc8d9a1..e7e9708 100644
--- a/public/src/translate-modal.js
+++ b/public/src/translate-modal.js
@@ -518,12 +518,20 @@ ${samplePreserved}`;
htmlWithBase = `\n
${scriptTag}${translatedText}`;
}
+ // Revoke any blob URL kept alive from a previous translation
+ if (resultArea._translateBlobUrl) {
+ URL.revokeObjectURL(resultArea._translateBlobUrl);
+ }
+
// Render inline via iframe with blob URL (no popup)
const blob = new Blob([htmlWithBase], { type: "text/html" });
const blobUrl = URL.createObjectURL(blob);
resultArea.innerHTML = "";
resultArea.className = "translate-result-area";
+ // Keep the blob URL alive so the iframe and the
+ // open-in-tab link both stay valid until the next render.
+ resultArea._translateBlobUrl = blobUrl;
const iframe = document.createElement("iframe");
iframe.src = blobUrl;
@@ -542,36 +550,46 @@ ${samplePreserved}`;
}
iframe.title = iframeTitle;
- // Clean up blob URL when iframe loads
- iframe.addEventListener("load", () => URL.revokeObjectURL(blobUrl));
+ // Controls bar above the preview
+ const controls = document.createElement("div");
+ controls.className = "translate-result-controls";
- const toggleContainer = document.createElement("div");
- toggleContainer.style.marginBottom = "12px";
-
- const toggleBtn = document.createElement("button");
- toggleBtn.className = "translate-raw-toggle-btn";
- toggleBtn.textContent = "Show Raw HTML";
- toggleBtn.onclick = () => {
- const raw = toggleContainer.querySelector(".translate-raw-html");
- if (raw.style.display === "none") {
- raw.style.display = "block";
- toggleBtn.textContent = "Hide Raw HTML";
- } else {
- raw.style.display = "none";
- toggleBtn.textContent = "Show Raw HTML";
- }
- };
+ // Open the translated page in a real browser tab. A user
+ // click on an anchor is a genuine gesture, so it is not
+ // blocked like the automatic window.open popups we replaced.
+ const openTabLink = document.createElement("a");
+ openTabLink.className =
+ "translate-raw-toggle-btn translate-open-tab-btn";
+ openTabLink.href = blobUrl;
+ openTabLink.target = "_blank";
+ openTabLink.rel = "noopener";
+ openTabLink.textContent = "Open full page ↗";
+ openTabLink.title = iframeTitle;
const rawHtml = document.createElement("div");
rawHtml.className = "translate-raw-html";
rawHtml.style.display = "none";
rawHtml.textContent = translatedText;
- toggleContainer.appendChild(toggleBtn);
- toggleContainer.appendChild(rawHtml);
+ const toggleBtn = document.createElement("button");
+ toggleBtn.className = "translate-raw-toggle-btn";
+ toggleBtn.textContent = "Show Raw HTML";
+ toggleBtn.onclick = () => {
+ if (rawHtml.style.display === "none") {
+ rawHtml.style.display = "block";
+ toggleBtn.textContent = "Hide Raw HTML";
+ } else {
+ rawHtml.style.display = "none";
+ toggleBtn.textContent = "Show Raw HTML";
+ }
+ };
+ controls.appendChild(openTabLink);
+ controls.appendChild(toggleBtn);
+
+ resultArea.appendChild(controls);
resultArea.appendChild(iframe);
- resultArea.appendChild(toggleContainer);
+ resultArea.appendChild(rawHtml);
} else {
resultArea.textContent = translatedText;
resultArea.className = "translate-result-area pre-wrap";
diff --git a/public/src/uncloseai-modal-builtin.css b/public/src/uncloseai-modal-builtin.css
index 035a055..d311a2f 100644
--- a/public/src/uncloseai-modal-builtin.css
+++ b/public/src/uncloseai-modal-builtin.css
@@ -451,6 +451,20 @@ dialog#translate-modal > article > .translate-btn {
cursor: pointer;
}
+.translate-result-controls {
+ display: grid;
+ grid-auto-flow: column;
+ justify-content: start;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.translate-open-tab-btn {
+ background: #0b7285;
+ text-decoration: none;
+ display: inline-block;
+}
+
.translate-result-area {
white-space: normal;
}
diff --git a/public/src/uncloseai-modal-pico.css b/public/src/uncloseai-modal-pico.css
index 0771214..89f9032 100644
--- a/public/src/uncloseai-modal-pico.css
+++ b/public/src/uncloseai-modal-pico.css
@@ -500,6 +500,20 @@ dialog#translate-modal > article > .translate-btn {
cursor: pointer;
}
+.translate-result-controls {
+ display: grid;
+ grid-auto-flow: column;
+ justify-content: start;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.translate-open-tab-btn {
+ background: #0b7285;
+ text-decoration: none;
+ display: inline-block;
+}
+
.translate-result-area {
white-space: normal;
}