186 lines
6.6 KiB
TypeScript
186 lines
6.6 KiB
TypeScript
export const usePdfExportSafe = () => {
|
|
const exportToPDF = async (elementSelector: string, filename: string) => {
|
|
// Only run on client side
|
|
if (process.server || typeof window === "undefined") {
|
|
throw new Error("PDF generation is only available on the client side");
|
|
}
|
|
|
|
try {
|
|
// Dynamic import for client-side only
|
|
const html2pdf = (await import("html2pdf.js")).default;
|
|
|
|
// Get the element to export
|
|
const element = document.querySelector(elementSelector);
|
|
if (!element) {
|
|
throw new Error(`Element with selector "${elementSelector}" not found`);
|
|
}
|
|
|
|
// Create a completely clean version of the content
|
|
const createCleanContent = () => {
|
|
const cleanDiv = document.createElement("div");
|
|
cleanDiv.style.cssText = `
|
|
width: 8.5in;
|
|
padding: 0.5in;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
font-size: 12px;
|
|
line-height: 1.5;
|
|
color: #000000;
|
|
background: #ffffff;
|
|
`;
|
|
|
|
// Extract text content and basic structure
|
|
const extractContent = (sourceEl: Element, targetEl: HTMLElement) => {
|
|
const children = sourceEl.children;
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
const child = children[i];
|
|
|
|
// Skip export controls
|
|
if (
|
|
child.classList.contains("export-controls") ||
|
|
child.classList.contains("no-pdf") ||
|
|
child.classList.contains("no-print")
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
let newEl: HTMLElement;
|
|
|
|
// Create appropriate element based on tag
|
|
switch (child.tagName.toLowerCase()) {
|
|
case "h1":
|
|
newEl = document.createElement("h1");
|
|
newEl.style.cssText =
|
|
"font-size: 24px; font-weight: bold; margin: 20px 0 10px 0; color: #000;";
|
|
break;
|
|
case "h2":
|
|
newEl = document.createElement("h2");
|
|
newEl.style.cssText =
|
|
"font-size: 20px; font-weight: bold; margin: 16px 0 8px 0; color: #000;";
|
|
break;
|
|
case "h3":
|
|
newEl = document.createElement("h3");
|
|
newEl.style.cssText =
|
|
"font-size: 16px; font-weight: bold; margin: 12px 0 6px 0; color: #000;";
|
|
break;
|
|
case "p":
|
|
newEl = document.createElement("p");
|
|
newEl.style.cssText = "margin: 8px 0; color: #000;";
|
|
break;
|
|
case "ul":
|
|
newEl = document.createElement("ul");
|
|
newEl.style.cssText =
|
|
"margin: 8px 0; padding-left: 20px; color: #000;";
|
|
break;
|
|
case "ol":
|
|
newEl = document.createElement("ol");
|
|
newEl.style.cssText =
|
|
"margin: 8px 0; padding-left: 20px; color: #000;";
|
|
break;
|
|
case "li":
|
|
newEl = document.createElement("li");
|
|
newEl.style.cssText = "margin: 4px 0; color: #000;";
|
|
break;
|
|
case "input":
|
|
newEl = document.createElement("span");
|
|
const inputEl = child as HTMLInputElement;
|
|
newEl.textContent = inputEl.value || "[_____]";
|
|
newEl.style.cssText =
|
|
"border-bottom: 1px solid #000; padding: 2px; color: #000;";
|
|
break;
|
|
case "textarea":
|
|
newEl = document.createElement("span");
|
|
const textareaEl = child as HTMLTextAreaElement;
|
|
newEl.textContent = textareaEl.value || "[_____]";
|
|
newEl.style.cssText =
|
|
"border: 1px solid #000; padding: 4px; display: inline-block; color: #000;";
|
|
break;
|
|
case "select":
|
|
newEl = document.createElement("span");
|
|
const selectEl = child as HTMLSelectElement;
|
|
newEl.textContent = selectEl.value || "[_____]";
|
|
newEl.style.cssText =
|
|
"border: 1px solid #000; padding: 2px; color: #000;";
|
|
break;
|
|
default:
|
|
newEl = document.createElement("div");
|
|
newEl.style.cssText = "color: #000;";
|
|
}
|
|
|
|
// Set text content for elements that should have it
|
|
if (
|
|
child.tagName.toLowerCase() !== "input" &&
|
|
child.tagName.toLowerCase() !== "textarea" &&
|
|
child.tagName.toLowerCase() !== "select"
|
|
) {
|
|
// Get only direct text content, not from children
|
|
const directText = Array.from(child.childNodes)
|
|
.filter((node) => node.nodeType === Node.TEXT_NODE)
|
|
.map((node) => node.textContent)
|
|
.join("");
|
|
|
|
if (directText.trim()) {
|
|
newEl.appendChild(document.createTextNode(directText));
|
|
}
|
|
}
|
|
|
|
targetEl.appendChild(newEl);
|
|
|
|
// Recursively process children
|
|
if (child.children.length > 0) {
|
|
extractContent(child, newEl);
|
|
}
|
|
}
|
|
};
|
|
|
|
extractContent(element, cleanDiv);
|
|
return cleanDiv;
|
|
};
|
|
|
|
const cleanElement = createCleanContent();
|
|
|
|
// Temporarily add to DOM
|
|
cleanElement.style.position = "absolute";
|
|
cleanElement.style.left = "-9999px";
|
|
cleanElement.style.top = "-9999px";
|
|
document.body.appendChild(cleanElement);
|
|
|
|
// Simple options - no complex CSS processing
|
|
const options = {
|
|
margin: 0.5,
|
|
filename: filename,
|
|
image: { type: "jpeg", quality: 0.98 },
|
|
html2canvas: {
|
|
scale: 2,
|
|
useCORS: true,
|
|
allowTaint: false,
|
|
logging: false,
|
|
backgroundColor: "#ffffff",
|
|
},
|
|
jsPDF: {
|
|
unit: "in",
|
|
format: "letter",
|
|
orientation: "portrait",
|
|
},
|
|
};
|
|
|
|
console.log("Generating PDF with clean content...");
|
|
|
|
try {
|
|
// Generate PDF from the clean element
|
|
await html2pdf().set(options).from(cleanElement).save();
|
|
console.log("PDF generated successfully!");
|
|
} finally {
|
|
// Clean up
|
|
if (cleanElement.parentNode) {
|
|
document.body.removeChild(cleanElement);
|
|
}
|
|
}
|
|
} catch (error: any) {
|
|
console.error("PDF generation error:", error);
|
|
throw new Error(`PDF generation failed: ${error.message || error}`);
|
|
}
|
|
};
|
|
|
|
return { exportToPDF };
|
|
};
|