Replace translate preview popup with inline iframe
Firefox (and other browsers) block window.open popups from the translate modal. Render translated HTML in an iframe via blob URL instead. Fix translate modal header layout by removing negative margin hack, use proper article padding structure. Add CSS for iframe preview and raw HTML toggle.
This commit is contained in:
parent
dfc1cb2a36
commit
00ef650b0e
4 changed files with 176 additions and 94 deletions
|
|
@ -487,101 +487,91 @@ ${samplePreserved}`;
|
|||
translatedText.includes("<section")));
|
||||
|
||||
if (isHTML) {
|
||||
// Auto-open preview in new tab
|
||||
const previewWindow = window.open("", "_blank");
|
||||
if (previewWindow) {
|
||||
// Inject base URL for relative resources to work
|
||||
const baseUrl =
|
||||
window.location.origin +
|
||||
window.location.pathname.substring(
|
||||
0,
|
||||
window.location.pathname.lastIndexOf("/") + 1,
|
||||
);
|
||||
// Inject base URL for relative resources to work
|
||||
const baseUrl =
|
||||
window.location.origin +
|
||||
window.location.pathname.substring(
|
||||
0,
|
||||
window.location.pathname.lastIndexOf("/") + 1,
|
||||
);
|
||||
|
||||
let htmlWithBase;
|
||||
const scriptTag = `
|
||||
let htmlWithBase;
|
||||
const scriptTag = `
|
||||
<script>
|
||||
window.UNCLOSEAI_SKIP_INIT = true;
|
||||
</script>
|
||||
<script src="https://uncloseai.com/uncloseai.js" type="module"></script>`;
|
||||
<script src="https://uncloseai.com/uncloseai.js" type="module"><\/script>`;
|
||||
|
||||
if (translatedText.includes("<!DOCTYPE")) {
|
||||
// Already has DOCTYPE, just add base and script
|
||||
htmlWithBase = translatedText.includes("<head>")
|
||||
? translatedText.replace(
|
||||
"<head>",
|
||||
`<head><base href="${baseUrl}">${scriptTag}`,
|
||||
)
|
||||
: translatedText.replace(
|
||||
"<html>",
|
||||
`<html><head><base href="${baseUrl}">${scriptTag}</head>`,
|
||||
);
|
||||
} else if (translatedText.includes("<head>")) {
|
||||
// Has head but no DOCTYPE
|
||||
htmlWithBase = `<!DOCTYPE html>\n${translatedText.replace("<head>", `<head><base href="${baseUrl}">${scriptTag}`)}`;
|
||||
} else {
|
||||
// No DOCTYPE or head
|
||||
htmlWithBase = `<!DOCTYPE html>\n<html><head><base href="${baseUrl}">${scriptTag}</head><body>${translatedText}</body></html>`;
|
||||
}
|
||||
|
||||
previewWindow.document.write(htmlWithBase);
|
||||
previewWindow.document.close();
|
||||
|
||||
// Set title based on translation source
|
||||
let windowTitle;
|
||||
if (activeTab === "remote") {
|
||||
const urlInput = document.querySelector('.translate-url-input');
|
||||
const sourceUrl = urlInput ? urlInput.value.trim() : 'Remote URL';
|
||||
windowTitle = `${sourceUrl} - translated into ${targetLanguageName}`;
|
||||
} else if (activeTab === "custom") {
|
||||
windowTitle = `Custom Text - translated into ${targetLanguageName}`;
|
||||
} else {
|
||||
windowTitle = `${document.title} - translated into ${targetLanguageName}`;
|
||||
}
|
||||
previewWindow.document.title = windowTitle;
|
||||
|
||||
// Add a notice about AI functionality only in preview window (wait for body to be available)
|
||||
const addNotice = () => {
|
||||
if (
|
||||
previewWindow &&
|
||||
previewWindow.document &&
|
||||
previewWindow.document.body
|
||||
) {
|
||||
console.log("Adding AI notice to preview window");
|
||||
const notice = previewWindow.document.createElement("div");
|
||||
notice.className = "translate-ai-notice";
|
||||
notice.textContent = `🤖 AI features should work in ${targetLanguageName}!`;
|
||||
previewWindow.document.body.appendChild(notice);
|
||||
|
||||
// Auto-hide notice after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (notice.parentNode) notice.parentNode.removeChild(notice);
|
||||
}, 5000);
|
||||
} else if (previewWindow) {
|
||||
// Wait a bit and try again
|
||||
setTimeout(addNotice, 100);
|
||||
}
|
||||
};
|
||||
|
||||
// Only add notice if we have a valid preview window
|
||||
if (previewWindow) {
|
||||
addNotice();
|
||||
}
|
||||
if (translatedText.includes("<!DOCTYPE")) {
|
||||
htmlWithBase = translatedText.includes("<head>")
|
||||
? translatedText.replace(
|
||||
"<head>",
|
||||
`<head><base href="${baseUrl}">${scriptTag}`,
|
||||
)
|
||||
: translatedText.replace(
|
||||
"<html>",
|
||||
`<html><head><base href="${baseUrl}">${scriptTag}</head>`,
|
||||
);
|
||||
} else if (translatedText.includes("<head>")) {
|
||||
htmlWithBase = `<!DOCTYPE html>\n${translatedText.replace("<head>", `<head><base href="${baseUrl}">${scriptTag}`)}`;
|
||||
} else {
|
||||
htmlWithBase = `<!DOCTYPE html>\n<html><head><base href="${baseUrl}">${scriptTag}</head><body>${translatedText}</body></html>`;
|
||||
}
|
||||
|
||||
// Show raw HTML toggle in modal
|
||||
resultArea.innerHTML = `
|
||||
<div style="margin-bottom: 15px;">
|
||||
<button onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'; this.textContent = this.textContent.includes('Raw') ? 'Show Raw HTML' : 'Hide Raw HTML'" class="translate-raw-toggle-btn">Show Raw HTML</button>
|
||||
<div style="display: none; margin: 10px 0; padding: 15px; background: #f0f0f0; border-radius: 4px; white-space: pre-wrap; font-family: monospace; font-size: 0.85em; max-height: 300px; overflow-y: auto; border: 1px solid #ddd;">${translatedText.replace(/</g, "<").replace(/>/g, ">")}</div>
|
||||
</div>
|
||||
<div style="border: 2px solid #4CAF50; border-radius: 8px; background: #f0fff0; padding: 15px; text-align: center;">
|
||||
<div style="color: #4CAF50; font-weight: bold; margin-bottom: 10px;">✅ Preview Opened in New Window</div>
|
||||
<p style="margin: 0; color: #666;">The translated HTML page has opened in a new window with full AI functionality!</p>
|
||||
${previewWindow ? "" : '<p style="margin: 10px 0 0 0; color: #d32f2f;"><strong>Note:</strong> Please allow popups for preview window</p>'}
|
||||
</div>
|
||||
`;
|
||||
// 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";
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = blobUrl;
|
||||
iframe.className = "translate-preview-iframe";
|
||||
|
||||
// Set title for accessibility
|
||||
let iframeTitle;
|
||||
if (activeTab === "remote") {
|
||||
const urlEl = document.querySelector('.translate-url-input');
|
||||
const sourceUrl = urlEl ? urlEl.value.trim() : 'Remote URL';
|
||||
iframeTitle = `${sourceUrl} translated into ${targetLanguageName}`;
|
||||
} else if (activeTab === "custom") {
|
||||
iframeTitle = `Custom text translated into ${targetLanguageName}`;
|
||||
} else {
|
||||
iframeTitle = `${document.title} translated into ${targetLanguageName}`;
|
||||
}
|
||||
iframe.title = iframeTitle;
|
||||
|
||||
// Clean up blob URL when iframe loads
|
||||
iframe.addEventListener("load", () => URL.revokeObjectURL(blobUrl));
|
||||
|
||||
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";
|
||||
}
|
||||
};
|
||||
|
||||
const rawHtml = document.createElement("div");
|
||||
rawHtml.className = "translate-raw-html";
|
||||
rawHtml.style.display = "none";
|
||||
rawHtml.textContent = translatedText;
|
||||
|
||||
toggleContainer.appendChild(toggleBtn);
|
||||
toggleContainer.appendChild(rawHtml);
|
||||
|
||||
resultArea.appendChild(iframe);
|
||||
resultArea.appendChild(toggleContainer);
|
||||
} else {
|
||||
resultArea.textContent = translatedText;
|
||||
resultArea.className = "translate-result-area pre-wrap";
|
||||
|
|
|
|||
|
|
@ -314,12 +314,31 @@ dialog#translate-modal {
|
|||
background: white;
|
||||
}
|
||||
|
||||
/* Translate modal article layout */
|
||||
dialog#translate-modal > article {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Translate modal body content padding */
|
||||
dialog#translate-modal > article > :not(.translate-modal-header) {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
dialog#translate-modal > article > .translate-btn {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
/* Translate modal header */
|
||||
.translate-modal-header {
|
||||
background: linear-gradient(45deg, #4CAF50 0%, #45a049 100%);
|
||||
color: white;
|
||||
padding: 16px 20px;
|
||||
margin: -1em -1em 1em -1em;
|
||||
margin: 0;
|
||||
border-radius: 8px 8px 0 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
|
|
@ -440,6 +459,29 @@ dialog#translate-modal {
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* Inline translated page preview */
|
||||
.translate-preview-iframe {
|
||||
width: 100%;
|
||||
height: 60vh;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 12px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.translate-raw-html {
|
||||
margin: 10px 0;
|
||||
padding: 15px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
font-size: 0.85em;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.translate-tab-nav li {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -359,12 +359,35 @@ dialog#translate-modal {
|
|||
--pico-modal-overlay-background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Translate modal article resets PicoCSS padding */
|
||||
dialog#translate-modal > article {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Translate modal body content padding */
|
||||
dialog#translate-modal > article > :not(.translate-modal-header) {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
dialog#translate-modal > article > .translate-btn {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
/* Translate modal header */
|
||||
.translate-modal-header {
|
||||
background: linear-gradient(45deg, #4CAF50 0%, #45a049 100%);
|
||||
color: white;
|
||||
padding: 16px 20px;
|
||||
margin: -1em -1em 1em -1em;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
|
|
@ -485,6 +508,29 @@ dialog#translate-modal {
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* Inline translated page preview */
|
||||
.translate-preview-iframe {
|
||||
width: 100%;
|
||||
height: 60vh;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 12px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.translate-raw-html {
|
||||
margin: 10px 0;
|
||||
padding: 15px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
font-size: 0.85em;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.translate-tab-nav li {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -351,11 +351,15 @@ export async function handleSmartTranslate(button) {
|
|||
// Translate the page
|
||||
const translatedHtml = await translateCurrentPage(code);
|
||||
|
||||
// Open in new tab
|
||||
const newWindow = window.open("", "_blank");
|
||||
newWindow.document.write(translatedHtml);
|
||||
newWindow.document.close();
|
||||
newWindow.document.title = `${document.title} - translated into ${nativeName}`;
|
||||
// Render inline via blob URL (no popup)
|
||||
const blob = new Blob([translatedHtml], { type: "text/html" });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
const previewWindow = window.open(blobUrl, "_blank");
|
||||
if (previewWindow) {
|
||||
previewWindow.addEventListener("load", () => URL.revokeObjectURL(blobUrl));
|
||||
} else {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
|
||||
// Close dropdown
|
||||
dropdown.remove();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue