diff --git a/package-lock.json b/package-lock.json index e773d0b8..198b5d4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "flowbite-svelte": "^0.46.15", "got": "^14.4.2", "html2canvas": "^1.4.1", + "html2canvas-pro": "^1.5.8", "jsonwebtoken": "^9.0.2", "katex": "^0.16.11", "lightweight-charts": "^4.1.3", @@ -5591,6 +5592,20 @@ "node": ">=8.0.0" } }, + "node_modules/html2canvas-pro": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/html2canvas-pro/-/html2canvas-pro-1.5.8.tgz", + "integrity": "sha512-bVGAU7IvhBwBlRAmX6QhekX8lsaxmYoF6zIwf/HNlHscjx+KN8jw/U4PQRYqeEVm9+m13hcS1l5ChJB9/e29Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/http_ece": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.2.0.tgz", diff --git a/package.json b/package.json index 85b6db94..904191ae 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "flowbite-svelte": "^0.46.15", "got": "^14.4.2", "html2canvas": "^1.4.1", + "html2canvas-pro": "^1.5.8", "jsonwebtoken": "^9.0.2", "katex": "^0.16.11", "lightweight-charts": "^4.1.3", diff --git a/src/routes/potus-tracker/+page.svelte b/src/routes/potus-tracker/+page.svelte index 50920a19..9db34eff 100644 --- a/src/routes/potus-tracker/+page.svelte +++ b/src/routes/potus-tracker/+page.svelte @@ -8,9 +8,15 @@ import avatar from "$lib/images/trump-avatar.jpeg"; import { mode } from "mode-watcher"; import { goto } from "$app/navigation"; + import html2canvas from "html2canvas-pro"; export let data; + let postContent = "n/a"; + let postDate = "n/a"; + let postUrl = "#"; + let postTitle = "n/a"; + const updatedSectorList = ["S&P500", ...sectorList]; let rawData = data?.getData?.history || []; @@ -198,6 +204,123 @@ let config = null; + async function captureScreenshot(index) { + const postElement = document.querySelector(`#post-${index}`); + + // Clone the element to avoid modifying the original + const clonedElement = postElement.cloneNode(true); + + // Create a temporary container for the clone with fixed dimensions and styling + const tempContainer = document.createElement("div"); + tempContainer.style.position = "absolute"; + tempContainer.style.left = "-9999px"; + tempContainer.style.top = "0"; + tempContainer.appendChild(clonedElement); + document.body.appendChild(tempContainer); + + // Force light mode styling explicitly + clonedElement.style.cssText = + "background-color: white !important; color: black !important;"; + + // Process all elements recursively to ensure text visibility + function forceVisibleText(element) { + // Apply styles directly + element.style.cssText += + "; color: black !important; background-color: white !important; border-color: #ccc !important;"; + + // For SVG elements, ensure they're visible + if ( + element.tagName.toLowerCase() === "svg" || + element.tagName.toLowerCase() === "path" + ) { + element.style.cssText += + "; fill: #333 !important; stroke: #333 !important;"; + } + + // Remove problematic classes completely instead of just removing dark: prefix + if (element.classList) { + const classesToRemove = []; + element.classList.forEach((cls) => { + if ( + cls.includes("dark:") || + cls.includes("text-gray") || + cls.includes("text-white") + ) { + classesToRemove.push(cls); + } + }); + classesToRemove.forEach((cls) => element.classList.remove(cls)); + + // Add explicit light mode classes + element.classList.add("text-black"); + } + + // Process all child elements + if (element.children && element.children.length > 0) { + Array.from(element.children).forEach((child) => + forceVisibleText(child), + ); + } + } + + // Apply the text visibility fix to all elements + forceVisibleText(clonedElement); + + // Additional specific fixes for elements that might still have issues + const allTextElements = clonedElement.querySelectorAll( + "p, h1, h2, h3, h4, h5, span, a, div, label", + ); + allTextElements.forEach((el) => { + el.style.color = "black"; + el.setAttribute( + "style", + el.getAttribute("style") + "; color: black !important;", + ); + }); + + // Convert any CSS variables that might affect color + const computed = window.getComputedStyle(postElement); + const cssText = + ":root { --text-color: black !important; --background-color: white !important; }"; + const style = document.createElement("style"); + style.textContent = cssText; + clonedElement.appendChild(style); + + // Wait a bit to ensure styles are applied + await new Promise((resolve) => setTimeout(resolve, 50)); + + // Capture screenshot + try { + const canvas = await html2canvas(clonedElement, { + backgroundColor: "white", + logging: true, // Enable logging to help debug + scale: 2, // Higher quality + useCORS: true, + allowTaint: true, + removeContainer: false, // Handle cleanup ourselves + }); + + // Convert to image + const image = canvas.toDataURL("image/png"); + + // Create a download link + const link = document.createElement("a"); + link.href = image; + link.download = `post-${index}.png`; + document.body.appendChild(link); + link.click(); + + // Clean up + document.body.removeChild(link); + document.body.removeChild(tempContainer); + + return image; + } catch (error) { + console.error("Screenshot capture failed:", error); + document.body.removeChild(tempContainer); + throw error; + } + } $: { if (selectedSector || $mode) { config = plotData() || null; @@ -271,7 +394,7 @@ class="w-56 h-fit max-h-72 overflow-y-auto scroller" > Select Sector @@ -411,7 +534,9 @@ {/if} - + {item.time_formatted} {item.location !== null ? `- ${item?.location}` @@ -419,7 +544,7 @@ - + {item.details} @@ -454,140 +579,112 @@ {#each items as item, indexB} + + - + > + + - - - {item?.title} - - - + - {item?.sentiment} - + Donald J. Trump + + + + {item?.title} + + + {item?.sentiment} + + + - - {#if item.description.length > 150} - {expandedDescriptions[item.title] - ? item.description - : truncateText(item.description)} - - (expandedDescriptions[item.title] = - !expandedDescriptions[item.title])} - class="cursor-pointer text-blue-500 sm:hover:text-muted dark:text-blue-400 dark:sm:hover:text-white ml-1" - > - {expandedDescriptions[item.title] - ? "Read less" - : "Read more"} - - {:else} - {item.description} - {/if} - - - - Source - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + {item?.description?.length > 300 + ? item?.description?.slice(0, 300) + "..." + : item?.description} + + + + + + + + + + + + + { + postTitle = item?.title; + postContent = item?.description; + postDate = item?.date; + postUrl = item?.link; + }} + class=" cursor-pointer bg-blue-600 text-white rounded px-3 py-1.5 text-sm font-semibold sm:hover:bg-blue-700 ml-auto" + > + Read More + + {/each} @@ -618,137 +715,148 @@ - - {#each posts as item} - - - - - - - - - Donald J. Trump - - - @realDonaldTrump - · {item?.date} - - - - - - {item?.content} - - + + {#each posts as item, index} + + + + + + + + + Donald J. Trump + + + + + @realDonaldTrump + + + + + + + {item?.content?.length > 400 + ? item?.content?.slice(0, 400) + "..." + : item?.content} + + + + {item?.date} + + + + { + postContent = item?.content; + postDate = item?.date; + }} + class="cursor-pointer bg-blue-600 text-white rounded px-3 py-1.5 text-sm font-semibold sm:hover:bg-blue-700 ml-auto" + > + Read More + {/each} @@ -818,20 +926,146 @@ - + + + + + + + + + + Donald J. Trump + + + {postTitle} + + + + + {postContent} + + + + {new Date(postDate ?? null)?.toLocaleString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + })} + + + + Close + Read Source + + + + + + + + + + + + + + + + + + + Donald J. Trump + + + + + @realDonaldTrump + + + + + + + {postContent} + + + + {postDate} + + + + Close + + +
+ {item?.content?.length > 400 + ? item?.content?.slice(0, 400) + "..." + : item?.content} +
+ {postContent} +