- Replace data URL loading with temporary HTML file to avoid URL length limits - Embed font files as data URLs in print document CSS for offline rendering - Add font asset registry for Merriweather, Crimson Pro, Roboto Serif, and Average - Implement font file caching and blob-to-data-URL conversion - Clean up temporary HTML file after PDF generation - Fix sync to refresh notes after favorite status sync completes
165 lines
4.3 KiB
JavaScript
165 lines
4.3 KiB
JavaScript
const fs = require('node:fs/promises');
|
|
const path = require('node:path');
|
|
const { app, BrowserWindow, dialog, ipcMain } = require('electron');
|
|
|
|
const rendererUrl = process.env.ELECTRON_RENDERER_URL;
|
|
const isDev = Boolean(rendererUrl);
|
|
let mainWindow = null;
|
|
|
|
const waitForPrintDocument = () => `
|
|
new Promise((resolve) => {
|
|
const pendingImages = Array.from(document.images).filter((image) => !image.complete);
|
|
const waitForImages = Promise.all(
|
|
pendingImages.map(
|
|
(image) =>
|
|
new Promise((done) => {
|
|
image.addEventListener('load', () => done(), { once: true });
|
|
image.addEventListener('error', () => done(), { once: true });
|
|
})
|
|
)
|
|
);
|
|
const waitForFonts = document.fonts ? document.fonts.ready.catch(() => undefined) : Promise.resolve();
|
|
|
|
Promise.all([waitForImages, waitForFonts]).then(() => {
|
|
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
});
|
|
});
|
|
`;
|
|
|
|
function createMainWindow() {
|
|
mainWindow = new BrowserWindow({
|
|
width: 1300,
|
|
height: 800,
|
|
minWidth: 800,
|
|
minHeight: 600,
|
|
show: false,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'preload.cjs'),
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
sandbox: false,
|
|
},
|
|
});
|
|
|
|
mainWindow.once('ready-to-show', () => {
|
|
mainWindow.show();
|
|
});
|
|
|
|
if (isDev) {
|
|
void mainWindow.loadURL(rendererUrl);
|
|
mainWindow.webContents.openDevTools({ mode: 'detach' });
|
|
} else {
|
|
void mainWindow.loadFile(path.join(__dirname, '..', 'dist', 'index.html'));
|
|
}
|
|
}
|
|
|
|
app.whenReady().then(() => {
|
|
createMainWindow();
|
|
|
|
app.on('activate', () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
createMainWindow();
|
|
}
|
|
});
|
|
});
|
|
|
|
app.on('window-all-closed', () => {
|
|
if (process.platform !== 'darwin') {
|
|
app.quit();
|
|
}
|
|
});
|
|
|
|
ipcMain.handle('desktop:show-message', async (event, options) => {
|
|
const ownerWindow = BrowserWindow.fromWebContents(event.sender) ?? mainWindow;
|
|
const typeMap = {
|
|
info: 'info',
|
|
warning: 'warning',
|
|
error: 'error',
|
|
};
|
|
|
|
await dialog.showMessageBox(ownerWindow, {
|
|
type: typeMap[options.kind] || 'info',
|
|
title: options.title || app.name,
|
|
message: options.message,
|
|
buttons: ['OK'],
|
|
defaultId: 0,
|
|
});
|
|
});
|
|
|
|
ipcMain.handle('desktop:http-request', async (_event, payload) => {
|
|
const body =
|
|
payload.bodyBase64 != null
|
|
? Buffer.from(payload.bodyBase64, 'base64')
|
|
: payload.bodyText;
|
|
|
|
const response = await fetch(payload.url, {
|
|
method: payload.method || 'GET',
|
|
headers: payload.headers,
|
|
body,
|
|
});
|
|
|
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
|
|
return {
|
|
ok: response.ok,
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
headers: Object.fromEntries(response.headers.entries()),
|
|
bodyBase64: buffer.toString('base64'),
|
|
};
|
|
});
|
|
|
|
ipcMain.handle('desktop:export-pdf', async (event, payload) => {
|
|
const ownerWindow = BrowserWindow.fromWebContents(event.sender) ?? mainWindow;
|
|
const saveResult = await dialog.showSaveDialog(ownerWindow, {
|
|
title: 'Export PDF',
|
|
defaultPath: payload.fileName,
|
|
filters: [{ name: 'PDF Document', extensions: ['pdf'] }],
|
|
});
|
|
|
|
if (saveResult.canceled || !saveResult.filePath) {
|
|
return { canceled: true };
|
|
}
|
|
|
|
const tempHtmlPath = path.join(
|
|
app.getPath('temp'),
|
|
`nextcloud-notes-export-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.html`
|
|
);
|
|
|
|
const pdfWindow = new BrowserWindow({
|
|
width: 960,
|
|
height: 1100,
|
|
show: false,
|
|
parent: ownerWindow ?? undefined,
|
|
webPreferences: {
|
|
sandbox: false,
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
spellcheck: false,
|
|
},
|
|
});
|
|
|
|
try {
|
|
await fs.writeFile(tempHtmlPath, payload.documentHtml, 'utf8');
|
|
await pdfWindow.loadFile(tempHtmlPath);
|
|
await pdfWindow.webContents.executeJavaScript(waitForPrintDocument(), true);
|
|
|
|
const pdfData = await pdfWindow.webContents.printToPDF({
|
|
printBackground: true,
|
|
preferCSSPageSize: true,
|
|
});
|
|
|
|
await fs.writeFile(saveResult.filePath, pdfData);
|
|
|
|
return {
|
|
canceled: false,
|
|
filePath: saveResult.filePath,
|
|
};
|
|
} finally {
|
|
await fs.unlink(tempHtmlPath).catch(() => undefined);
|
|
if (!pdfWindow.isDestroyed()) {
|
|
pdfWindow.destroy();
|
|
}
|
|
}
|
|
});
|