From afa2442398fbd23299027b15218aad037036b365 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi Date: Sun, 22 Dec 2024 14:58:16 -0500 Subject: [PATCH] fix(export-element): fallback to export as image when window open is not supported (#1269) * fix: fallback to export as image when window open is not supported * feat: close png modal on escape key --- scripts/export-element/index.user.ts | 63 ++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/scripts/export-element/index.user.ts b/scripts/export-element/index.user.ts index 4856a606..a4187cfd 100644 --- a/scripts/export-element/index.user.ts +++ b/scripts/export-element/index.user.ts @@ -22,6 +22,19 @@ import { html2canvas } from "libraries/html2canvas"; target: null, }; + /** + * Create keyboard event handler that triggers callback only on specific key + * TODO: Handle multiple keys and combinations + * TODO: options to prevent default event action and stop propagation + */ + function onKey(params: { key: string; callback: () => void }) { + return (event: KeyboardEvent) => { + if (event.key === params.key) { + params.callback(); + } + }; + } + /** * Find the uppermost node that satisfies the predicate */ @@ -75,13 +88,6 @@ import { html2canvas } from "libraries/html2canvas"; return containsAll(nodeText, params.matchWords); }, ); - - console.log( - (event.target as Node).textContent, - // @ts-expect-error IE before version 9 - document.selection?.createRange?.().text, - params.target, - ); } document.addEventListener("contextmenu", handleUpdateTarget); document.addEventListener("mouseup", handleUpdateTarget); @@ -171,7 +177,15 @@ import { html2canvas } from "libraries/html2canvas"; "width=800,height=600", ); if (!newWindow) { - alert("Please allow popups for this site and try again."); + const errorMessage = + "Failed to open new window. Please allow popups for this site and try again."; + const isInIframe = window.self !== window.top; + if (isInIframe) { + console.error(errorMessage); + console.error("Falling back to exporting as image."); + cloneAndDownloadImage(node); + } + alert(errorMessage); return; } const newDocument = newWindow.document; @@ -200,14 +214,16 @@ import { html2canvas } from "libraries/html2canvas"; const clone = cloneNodeWithStyles(window, node); const backgroundColor = findBackgroundColor(node); - const modalContent = document.createElement("div"); + const modalContent = document.createElement("a"); modalContent.style.backgroundColor = backgroundColor; modalContent.style.cursor = "pointer"; modalContent.style.display = "block"; modalContent.style.height = "fit-content"; modalContent.style.padding = "min(1vh, 1vw)"; modalContent.style.position = "relative"; + modalContent.style.textDecoration = "none"; modalContent.style.top = "1vh"; + modalContent.style.userSelect = "none"; modalContent.style.width = "fit-content"; const modalWrapper = document.createElement("div"); @@ -264,18 +280,29 @@ import { html2canvas } from "libraries/html2canvas"; .replace(/[/\\?%*:|"<>]+/g, filenameGlue) .replace(/[-]+/g, filenameGlue)}.${imageType.split("/")[1]}`; - const imageLink = document.createElement("a"); - imageLink.target = "_blank"; - imageLink.href = dataURI; - imageLink.download = filename; + modalContent.target = "_blank"; + modalContent.href = dataURI; + modalContent.download = filename; + modalContent.title = `Download ${filename}`; - modalWrapper.addEventListener("click", () => modalWrapper.remove()); - const downloadImage = (e: Event) => { - e.preventDefault(); + // prevent modal from closing when clicking on the link + modalContent.addEventListener("click", (e) => { e.stopPropagation(); - return imageLink.click(); + // TODO: check if download is permitted by browser + }); + const closePreview = () => { + modalWrapper.remove(); + URL.revokeObjectURL(dataURI); // free up memory + document.removeEventListener("keydown", closePreviewOnEscape); }; - modalContent.addEventListener("click", downloadImage); + const closePreviewOnEscape = onKey({ + key: "Escape", + callback: closePreview, + }); + // close modal when clicking outside the link + modalWrapper.addEventListener("click", closePreview); + // close modal when pressing escape + document.addEventListener("keydown", closePreviewOnEscape); } /**