export to pdf hook update

This commit is contained in:
Nate Kelley 2025-08-08 10:33:28 -06:00
parent 078ea21086
commit 1317190edf
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
4 changed files with 29 additions and 74 deletions

View File

@ -22,7 +22,7 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) {
const editor = useEditorRef(); const editor = useEditorRef();
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const { exportToHtml, exportToPdf, exportToImage, exportToMarkdown } = useExportReport(editor); const { exportToHtml, exportToPdf, exportToImage, exportToMarkdown } = useExportReport();
return ( return (
<DropdownMenu open={open} onOpenChange={setOpen} modal={false}> <DropdownMenu open={open} onOpenChange={setOpen} modal={false}>
@ -34,16 +34,16 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) {
<DropdownMenuContent align="start"> <DropdownMenuContent align="start">
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem onSelect={exportToHtml}> <DropdownMenuItem onSelect={() => exportToHtml(editor)}>
{NodeTypeLabels.exportAsHtml.label} {NodeTypeLabels.exportAsHtml.label}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onSelect={exportToPdf}> <DropdownMenuItem onSelect={() => exportToPdf(editor)}>
{NodeTypeLabels.exportAsPdf.label} {NodeTypeLabels.exportAsPdf.label}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onSelect={exportToImage}> <DropdownMenuItem onSelect={() => exportToImage(editor)}>
{NodeTypeLabels.exportAsImage.label} {NodeTypeLabels.exportAsImage.label}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onSelect={exportToMarkdown}> <DropdownMenuItem onSelect={() => exportToMarkdown(editor)}>
{NodeTypeLabels.exportAsMarkdown.label} {NodeTypeLabels.exportAsMarkdown.label}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>

View File

@ -0,0 +1 @@
export * from './useExportReport';

View File

@ -4,11 +4,12 @@ import type { PlateEditor } from 'platejs/react';
import { NodeTypeLabels } from '../config/labels'; import { NodeTypeLabels } from '../config/labels';
import { createSlateEditor, serializeHtml } from 'platejs'; import { createSlateEditor, serializeHtml } from 'platejs';
import { MarkdownPlugin } from '@platejs/markdown'; import { MarkdownPlugin } from '@platejs/markdown';
import { useMemo } from 'react';
export const useExportReport = (editor: PlateEditor) => { export const useExportReport = () => {
const { openErrorMessage, openInfoMessage } = useBusterNotifications(); const { openErrorMessage, openInfoMessage } = useBusterNotifications();
const getCanvas = async () => { const getCanvas = async (editor: PlateEditor) => {
const { default: html2canvas } = await import('html2canvas-pro'); const { default: html2canvas } = await import('html2canvas-pro');
const style = document.createElement('style'); const style = document.createElement('style');
@ -69,9 +70,9 @@ export const useExportReport = (editor: PlateEditor) => {
window.URL.revokeObjectURL(blobUrl); window.URL.revokeObjectURL(blobUrl);
}; };
const exportToPdf = async () => { const exportToPdf = async (editor: PlateEditor) => {
try { try {
const canvas = await getCanvas(); const canvas = await getCanvas(editor);
const PDFLib = await import('pdf-lib'); const PDFLib = await import('pdf-lib');
const pdfDoc = await PDFLib.PDFDocument.create(); const pdfDoc = await PDFLib.PDFDocument.create();
const page = pdfDoc.addPage([canvas.width, canvas.height]); const page = pdfDoc.addPage([canvas.width, canvas.height]);
@ -92,9 +93,9 @@ export const useExportReport = (editor: PlateEditor) => {
} }
}; };
const exportToImage = async () => { const exportToImage = async (editor: PlateEditor) => {
try { try {
const canvas = await getCanvas(); const canvas = await getCanvas(editor);
await downloadFile(canvas.toDataURL('image/png'), 'plate.png'); await downloadFile(canvas.toDataURL('image/png'), 'plate.png');
openInfoMessage(NodeTypeLabels.imageExportedSuccessfully.label); openInfoMessage(NodeTypeLabels.imageExportedSuccessfully.label);
} catch (error) { } catch (error) {
@ -102,7 +103,7 @@ export const useExportReport = (editor: PlateEditor) => {
} }
}; };
const exportToHtml = async () => { const exportToHtml = async (editor: PlateEditor) => {
try { try {
const BaseEditorKit = await import('../editor-base-kit').then( const BaseEditorKit = await import('../editor-base-kit').then(
(module) => module.BaseEditorKit (module) => module.BaseEditorKit
@ -157,7 +158,7 @@ export const useExportReport = (editor: PlateEditor) => {
} }
}; };
const exportToMarkdown = async () => { const exportToMarkdown = async (editor: PlateEditor) => {
try { try {
const md = editor.getApi(MarkdownPlugin).markdown.serialize(); const md = editor.getApi(MarkdownPlugin).markdown.serialize();
const url = `data:text/markdown;charset=utf-8,${encodeURIComponent(md)}`; const url = `data:text/markdown;charset=utf-8,${encodeURIComponent(md)}`;
@ -168,10 +169,13 @@ export const useExportReport = (editor: PlateEditor) => {
} }
}; };
return { return useMemo(
exportToPdf, () => ({
exportToImage, exportToPdf,
exportToHtml, exportToImage,
exportToMarkdown exportToHtml,
}; exportToMarkdown
}),
[]
);
}; };

View File

@ -28,6 +28,7 @@ import type { VerificationStatus } from '@buster/server-shared/share';
import { useSaveToCollectionsDropdownContent } from '@/components/features/dropdowns/SaveToCollectionsDropdown'; import { useSaveToCollectionsDropdownContent } from '@/components/features/dropdowns/SaveToCollectionsDropdown';
import { getReportEditor } from '@/components/ui/report/editorRegistry'; import { getReportEditor } from '@/components/ui/report/editorRegistry';
import { NodeTypeLabels } from '@/components/ui/report/config/labels'; import { NodeTypeLabels } from '@/components/ui/report/config/labels';
import { useExportReport } from '@/components/ui/report/hooks';
export const ReportThreeDotMenu = React.memo( export const ReportThreeDotMenu = React.memo(
({ ({
@ -313,69 +314,18 @@ const useDuplicateReportSelectMenu = (): DropdownItem => {
// Download as PDF // Download as PDF
const useDownloadPdfSelectMenu = ({ reportId }: { reportId: string }): DropdownItem => { const useDownloadPdfSelectMenu = ({ reportId }: { reportId: string }): DropdownItem => {
const { openErrorMessage, openInfoMessage } = useBusterNotifications(); const { openErrorMessage, openInfoMessage } = useBusterNotifications();
const editor = getReportEditor(reportId);
const { exportToPdf } = useExportReport();
const onClick = useMemoizedFn(async () => { const onClick = useMemoizedFn(async () => {
const editor = getReportEditor(reportId);
if (!editor) { if (!editor) {
openErrorMessage(NodeTypeLabels.failedToExportPdf.label); openErrorMessage(NodeTypeLabels.failedToExportPdf.label);
return; return;
} }
try { await exportToPdf(editor);
const html2canvas = await import('html2canvas-pro').then((m) => m.default);
const standardWidth = '850px';
const node = editor.api.toDOMNode(editor);
if (!node) throw new Error('Editor not found');
const style = document.createElement('style'); //
document.head.append(style);
const canvas = await html2canvas(node, {
onclone: (document: Document) => {
const editorElement = document.querySelector('[contenteditable="true"]');
if (editorElement) {
const existingStyle = editorElement.getAttribute('style') || '';
editorElement.setAttribute(
'style',
`${existingStyle}; width: ${standardWidth} !important; max-width: ${standardWidth} !important; min-width: ${standardWidth} !important;`
);
Array.from(editorElement.querySelectorAll('*')).forEach((element) => {
const elementStyle = element.getAttribute('style') || '';
element.setAttribute(
'style',
`${elementStyle}; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important`
);
});
} else {
throw new Error('Editor element not found');
}
}
});
style.remove();
const PDFLib = await import('pdf-lib');
const pdfDoc = await PDFLib.PDFDocument.create();
const page = pdfDoc.addPage([canvas.width, canvas.height]);
const imageEmbed = await pdfDoc.embedPng(canvas.toDataURL('PNG'));
const { height, width } = imageEmbed.scale(1);
page.drawImage(imageEmbed, { height, width, x: 0, y: 0 });
const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true });
// download
const response = await fetch(pdfBase64);
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = 'report.pdf';
document.body.append(link);
link.click();
link.remove();
window.URL.revokeObjectURL(blobUrl);
openInfoMessage(NodeTypeLabels.pdfExportedSuccessfully.label);
} catch (error) {
openErrorMessage(NodeTypeLabels.failedToExportPdf.label);
}
}); });
return useMemo( return useMemo(