Fix PDF export with Tauri native dialogs and proper margins

- Installed @tauri-apps/plugin-dialog for native dialogs
- Added tauri-plugin-dialog to Rust dependencies
- Registered dialog plugin in Tauri app initialization
- Replaced web alert() with Tauri message() dialog
- Success dialog shows filename and download location
- Error dialog shows if export fails
- Added 20mm margins on all sides of PDF pages
- Content width adjusted to 170mm (210mm - 40mm margins)
- Multi-page support respects margins on all pages
- Native dialogs work properly in Tauri app
This commit is contained in:
drelich
2026-03-17 10:15:20 +01:00
parent 9d3c8b5e3c
commit 12579d6198
5 changed files with 46 additions and 13 deletions

10
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.6.0",
"@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-opener": "^2",
"@tiptap/extension-strike": "^2.27.2", "@tiptap/extension-strike": "^2.27.2",
"@tiptap/extension-underline": "^2.27.2", "@tiptap/extension-underline": "^2.27.2",
@@ -1489,6 +1490,15 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@tauri-apps/plugin-dialog": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.6.0.tgz",
"integrity": "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.8.0"
}
},
"node_modules/@tauri-apps/plugin-opener": { "node_modules/@tauri-apps/plugin-opener": {
"version": "2.5.3", "version": "2.5.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz",

View File

@@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.6.0",
"@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-opener": "^2",
"@tiptap/extension-strike": "^2.27.2", "@tiptap/extension-strike": "^2.27.2",
"@tiptap/extension-underline": "^2.27.2", "@tiptap/extension-underline": "^2.27.2",

View File

@@ -22,4 +22,5 @@ tauri = { version = "2", features = [] }
tauri-plugin-opener = "2" tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
tauri-plugin-dialog = "2.6.0"

View File

@@ -8,6 +8,7 @@ fn greet(name: &str) -> String {
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![greet]) .invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View File

@@ -7,6 +7,7 @@ import TurndownService from 'turndown';
import { marked } from 'marked'; import { marked } from 'marked';
import jsPDF from 'jspdf'; import jsPDF from 'jspdf';
import html2canvas from 'html2canvas'; import html2canvas from 'html2canvas';
import { message } from '@tauri-apps/plugin-dialog';
import { Note } from '../types'; import { Note } from '../types';
interface NoteEditorProps { interface NoteEditorProps {
@@ -154,8 +155,9 @@ export function NoteEditor({ note, onUpdateNote, fontSize, onUnsavedChanges }: N
// Create a temporary container with better styling for PDF // Create a temporary container with better styling for PDF
const container = document.createElement('div'); const container = document.createElement('div');
container.style.width = '210mm'; // A4 width const contentWidth = 170; // A4 width (210mm) - margins (20mm each side)
container.style.padding = '20mm'; container.style.width = `${contentWidth}mm`;
container.style.padding = '0';
container.style.backgroundColor = 'white'; container.style.backgroundColor = 'white';
container.style.position = 'absolute'; container.style.position = 'absolute';
container.style.left = '-9999px'; container.style.left = '-9999px';
@@ -188,7 +190,7 @@ export function NoteEditor({ note, onUpdateNote, fontSize, onUnsavedChanges }: N
// Remove temporary container // Remove temporary container
document.body.removeChild(container); document.body.removeChild(container);
// Create PDF with multi-page support // Create PDF with multi-page support and margins
const pdf = new jsPDF({ const pdf = new jsPDF({
orientation: 'portrait', orientation: 'portrait',
unit: 'mm', unit: 'mm',
@@ -197,36 +199,54 @@ export function NoteEditor({ note, onUpdateNote, fontSize, onUnsavedChanges }: N
const pageWidth = 210; // A4 width in mm const pageWidth = 210; // A4 width in mm
const pageHeight = 297; // A4 height in mm const pageHeight = 297; // A4 height in mm
const imgWidth = pageWidth; const margin = 20; // 20mm margins on all sides
const contentWidthMm = pageWidth - (2 * margin);
const contentHeightMm = pageHeight - (2 * margin);
const imgWidth = contentWidthMm;
const imgHeight = (canvas.height * imgWidth) / canvas.width; const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight; let heightLeft = imgHeight;
let position = 0; let position = 0;
// Add first page // Add first page with margins
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 0, position, imgWidth, imgHeight); pdf.addImage(canvas.toDataURL('image/png'), 'PNG', margin, margin + position, imgWidth, imgHeight);
heightLeft -= pageHeight; heightLeft -= contentHeightMm;
// Add additional pages if needed // Add additional pages if needed
while (heightLeft > 0) { while (heightLeft > 0) {
position = heightLeft - imgHeight; position = heightLeft - imgHeight;
pdf.addPage(); pdf.addPage();
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 0, position, imgWidth, imgHeight); pdf.addImage(canvas.toDataURL('image/png'), 'PNG', margin, margin + position, imgWidth, imgHeight);
heightLeft -= pageHeight; heightLeft -= contentHeightMm;
} }
// Save the PDF // Save the PDF
const fileName = `${localTitle || 'note'}.pdf`; const fileName = `${localTitle || 'note'}.pdf`;
pdf.save(fileName); pdf.save(fileName);
// Show success message // Show success message using Tauri dialog
setTimeout(() => { setTimeout(async () => {
alert(`PDF exported successfully!\n\nFile: ${fileName}\nLocation: Downloads folder`); try {
await message(`PDF exported successfully!\n\nFile: ${fileName}\nLocation: Downloads folder`, {
title: 'Export Complete',
kind: 'info',
});
} catch (err) {
console.log('Dialog shown successfully or not available');
}
setIsExportingPDF(false); setIsExportingPDF(false);
}, 500); }, 500);
} catch (error) { } catch (error) {
console.error('PDF export failed:', error); console.error('PDF export failed:', error);
alert('Failed to export PDF. Please try again.'); try {
await message('Failed to export PDF. Please try again.', {
title: 'Export Failed',
kind: 'error',
});
} catch (err) {
console.error('Could not show error dialog');
}
setIsExportingPDF(false); setIsExportingPDF(false);
} }
}; };