diff --git a/app/electron/main.js b/app/electron/main.js index 46d61f7dc..7e300f7b5 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -84,8 +84,10 @@ try { } } catch (e) { console.error(e) - require('electron').dialog.showErrorBox('创建配置目录失败 Failed to create config directory', - '思源需要在用户家目录下创建配置文件夹(~/.config/siyuan),请确保该路径具有写入权限。\n\nSiYuan needs to create a configuration folder (~/.config/siyuan) in the user\'s home directory. Please make sure that the path has write permissions.') + require('electron'). + dialog. + showErrorBox('创建配置目录失败 Failed to create config directory', + '思源需要在用户家目录下创建配置文件夹(~/.config/siyuan),请确保该路径具有写入权限。\n\nSiYuan needs to create a configuration folder (~/.config/siyuan) in the user\'s home directory. Please make sure that the path has write permissions.') app.exit() } @@ -186,7 +188,8 @@ const boot = () => { }) require('@electron/remote/main').enable(mainWindow.webContents) - mainWindow.webContents.userAgent = 'SiYuan/' + appVer + ' https://b3log.org/siyuan Electron' + mainWindow.webContents.userAgent = 'SiYuan/' + appVer + + ' https://b3log.org/siyuan Electron' // 发起互联网服务请求时绕过安全策略 https://github.com/siyuan-note/siyuan/issues/5516 mainWindow.webContents.session.webRequest.onBeforeSendHeaders( @@ -348,6 +351,12 @@ const boot = () => { tray.destroy() } }) + ipcMain.on('siyuan-export-pdf', (event, data) => { + mainWindow.webContents.send('siyuan-export-pdf', data) + }) + ipcMain.on('siyuan-export-close', (event, data) => { + mainWindow.webContents.send('siyuan-export-close', data) + }) ipcMain.on('siyuan-quit', () => { try { const bounds = mainWindow.getBounds() @@ -520,11 +529,13 @@ const initKernel = (initData) => { '
net stop winnat\nnetsh interface ipv4 add excludedportrange protocol=tcp startport=6806 numberofports=1\nnet start winnat
') break case 22: - showErrorWindow('⚠️ 创建配置目录失败 Failed to create config directory', + showErrorWindow( + '⚠️ 创建配置目录失败 Failed to create config directory', `
思源需要在用户家目录下创建配置文件夹(~/.config/siyuan),请确保该路径具有写入权限。
SiYuan needs to create a configuration folder (~/.config/siyuan) in the user\'s home directory. Please make sure that the path has write permissions.
`) break case 23: - showErrorWindow('⚠️ 无法读写块树文件 Failed to access blocktree file', + showErrorWindow( + '⚠️ 无法读写块树文件 Failed to access blocktree file', `
块树文件正在被其他程序锁定或者已经损坏,请删除 工作空间/temp/ 文件夹后重启
The block tree file is being locked by another program or is damaged, please delete the workspace/temp/ folder and restart.
`) break case 0: diff --git a/app/src/config/search.ts b/app/src/config/search.ts index b98a37c77..5a21fc608 100644 --- a/app/src/config/search.ts +++ b/app/src/config/search.ts @@ -64,7 +64,8 @@ export const initConfigSearch = (element: HTMLElement) => { // 关于 getLang(["about", "about1", "about2", "about3", "about4", "about5", "about6", "about7", "about8", "about11", "about12", "about13", "about14", "about15", "about16", - "slogan", "currentVer", "checkUpdate", "updatePath", "snapshotPassword", "systemLog"]), + "slogan", "currentVer", "checkUpdate", "updatePath", "snapshotPassword", "systemLog", + "autoDownloadUpdatePkg"]), ]; const inputElement = element.querySelector(".b3-form__icon input") as HTMLInputElement; if (window.siyuan.config.system.container !== "ios") { diff --git a/app/src/constants.ts b/app/src/constants.ts index 020c563f7..603b6bd4e 100644 --- a/app/src/constants.ts +++ b/app/src/constants.ts @@ -29,6 +29,8 @@ export abstract class Constants { public static readonly SIYUAN_INIT: string = "siyuan-init"; public static readonly SIYUAN_OPENURL: string = "siyuan-openurl"; public static readonly SIYUAN_SAVE_CLOSE: string = "siyuan-save-close"; + public static readonly SIYUAN_EXPORT_PDF: string = "siyuan-export-pdf"; + public static readonly SIYUAN_EXPORT_CLOSE: string = "siyuan-export-close"; // size public static readonly SIZE_TOOLBAR_HEIGHT: number = 30; diff --git a/app/src/protyle/export/index.ts b/app/src/protyle/export/index.ts index 626ccd2b0..f10c433f8 100644 --- a/app/src/protyle/export/index.ts +++ b/app/src/protyle/export/index.ts @@ -1,7 +1,7 @@ import {hideMessage, showMessage} from "../../dialog/message"; import {Constants} from "../../constants"; /// #if !BROWSER -import {PrintToPDFOptions, OpenDialogReturnValue} from "electron"; +import {OpenDialogReturnValue, ipcRenderer} from "electron"; import {BrowserWindow, dialog} from "@electron/remote"; import * as fs from "fs"; import * as path from "path"; @@ -18,94 +18,13 @@ import {replaceLocalPath} from "../../editor/rename"; export const saveExport = (option: { type: string, id: string }) => { /// #if !BROWSER if (option.type === "pdf") { - const localData = JSON.parse(localStorage.getItem(Constants.LOCAL_EXPORTPDF) || JSON.stringify({ - printBackground: true, - landscape: false, - marginsType: 0, - scaleFactor: 100, - pageSize: "A4", - removeAssets: true, - })); - const pdfDialog = new Dialog({ - title: "PDF " + window.siyuan.languages.config, - content: `
- - - - - -
-
-
- -
`, - width: "520px", - }); - const btnsElement = pdfDialog.element.querySelectorAll(".b3-button"); - btnsElement[0].addEventListener("click", () => { - pdfDialog.destroy(); - }); - btnsElement[1].addEventListener("click", () => { - const options: PrintToPDFOptions = { - printBackground: true, - landscape: (pdfDialog.element.querySelector("#landscape") as HTMLInputElement).checked, - marginsType: parseInt((pdfDialog.element.querySelector("#marginsType") as HTMLInputElement).value), - scaleFactor: parseInt((pdfDialog.element.querySelector("#scaleFactor") as HTMLInputElement).value), - pageSize: (pdfDialog.element.querySelector("#pageSize") as HTMLSelectElement).value, - }; - const removeAssets = (pdfDialog.element.querySelector("#removeAssets") as HTMLInputElement).checked; - localStorage.setItem(Constants.LOCAL_EXPORTPDF, JSON.stringify(Object.assign(options, {removeAssets}))); - if (window.siyuan.config.appearance.mode === 1) { - confirmDialog(window.siyuan.languages.pdfTip, window.siyuan.languages.pdfConfirm, () => { - getExportPath(option, options, removeAssets); - pdfDialog.destroy(); - }); - } else { - getExportPath(option, options, removeAssets); - pdfDialog.destroy(); - } - }); + if (window.siyuan.config.appearance.mode === 1) { + confirmDialog(window.siyuan.languages.pdfTip, window.siyuan.languages.pdfConfirm, () => { + renderPDF(option.id); + }); + } else { + renderPDF(option.id); + } } else if (option.type === "word") { const localData = localStorage.getItem(Constants.LOCAL_EXPORTWORD); const wordDialog = new Dialog({ @@ -132,7 +51,7 @@ export const saveExport = (option: { type: string, id: string }) => { btnsElement[1].addEventListener("click", () => { const removeAssets = (wordDialog.element.querySelector("#removeAssets") as HTMLInputElement).checked; localStorage.setItem(Constants.LOCAL_EXPORTWORD, removeAssets.toString()); - getExportPath(option, undefined, removeAssets); + getExportPath(option, removeAssets); wordDialog.destroy(); }); } else { @@ -142,7 +61,289 @@ export const saveExport = (option: { type: string, id: string }) => { }; /// #if !BROWSER -const getExportPath = (option: { type: string, id: string }, pdfOption?: PrintToPDFOptions, removeAssets?: boolean) => { +const renderPDF = (id: string) => { + const localData = JSON.parse(localStorage.getItem(Constants.LOCAL_EXPORTPDF) || JSON.stringify({ + printBackground: true, + landscape: false, + marginsType: 0, + scaleFactor: 100, + pageSize: "A4", + removeAssets: true, + })); + const servePath = window.location.protocol + "//" + window.location.host; + const win = new BrowserWindow({ + show: true, + width: 1024, // 860 + webPreferences: { + contextIsolation: false, + nodeIntegration: true, + webviewTag: true, + webSecurity: false, + }, + }); + ipcRenderer.on(Constants.SIYUAN_EXPORT_CLOSE, () => { + win.destroy(); + }); + ipcRenderer.on(Constants.SIYUAN_EXPORT_PDF, (e, ipcData) => { + dialog.showOpenDialog({ + title: window.siyuan.languages.export + " PDF", + properties: ["createDirectory", "openDirectory"], + }).then((result: OpenDialogReturnValue) => { + if (!result.canceled) { + const msgId = showMessage(window.siyuan.languages.exporting, -1); + const filePath = result.filePaths[0].endsWith(ipcData.rootTitle) ? result.filePaths[0] : path.join(result.filePaths[0], replaceLocalPath(ipcData.rootTitle)); + localStorage.setItem(Constants.LOCAL_EXPORTPDF, JSON.stringify(Object.assign(ipcData.pdfOptions, {removeAssets: ipcData.removeAssets}))); + try { + win.webContents.printToPDF(ipcData.pdfOptions).then((pdfData) => { + fetchPost("/api/export/exportHTML", { + id: ipcData.rootId, + pdf: true, + removeAssets: ipcData.removeAssets, + savePath: filePath + }, () => { + const pdfFilePath = path.join(filePath, path.basename(filePath) + ".pdf"); + fs.writeFileSync(pdfFilePath, pdfData); + fetchPost("/api/export/addPDFOutline", { + id: ipcData.rootId, + path: pdfFilePath + }, () => { + afterExport(pdfFilePath, msgId); + if (ipcData.removeAssets) { + const removePromise = (dir: string) => { + return new Promise(function (resolve) { + //先读文件夹 + fs.stat(dir, function (err, stat) { + if (stat) { + if (stat.isDirectory()) { + fs.readdir(dir, function (err, files) { + files = files.map(file => path.join(dir, file)); // a/b a/m + Promise.all(files.map(file => removePromise(file))).then(function () { + fs.rmdir(dir, resolve); + }); + }); + } else { + fs.unlink(dir, resolve); + } + } + }); + }); + }; + removePromise(path.join(filePath, "assets")); + } + win.destroy(); + }); + }); + }).catch((error: string) => { + showMessage("Export PDF error:" + error, 0, "error", msgId); + win.destroy(); + }); + } catch (e) { + showMessage("Export PDF error:" + e + ". Export HTML and use Chrome's printing function to convert to PDF", 0, "error", msgId); + } + } + }) + }) + fetchPost("/api/export/exportPreviewHTML", { + id, + }, response => { + let pdfWidth = ""; + if (localData.pageSize === "A3") { + if (localData.landscape) { + pdfWidth = "16.5"; + } else { + pdfWidth = "11.7"; + } + } else if (localData.pageSize === "A4") { + if (localData.landscape) { + pdfWidth = "11.7"; + } else { + pdfWidth = "8.8"; + } + } else if (localData.pageSize === "A5") { + if (localData.landscape) { + pdfWidth = "8.3"; + } else { + pdfWidth = "5.8"; + } + } else if (localData.pageSize === "Legal") { + if (localData.landscape) { + pdfWidth = "14"; + } else { + pdfWidth = "8.5"; + } + } else if (localData.pageSize === "Letter") { + if (localData.landscape) { + pdfWidth = "11"; + } else { + pdfWidth = "8.5"; + } + } else if (localData.pageSize === "Tabloid") { + if (localData.landscape) { + pdfWidth = "17"; + } else { + pdfWidth = "11"; + } + } + let pdfMargin = "0.66"; + if (localData.landscape) { + pdfMargin = "1.69"; + } + if (localData.marginsType !== 0) { + if (localData.landscape) { + pdfMargin = "1.69"; + } else { + pdfMargin = "0.3"; + } + } + const html = ` + + + + + + + + + + ${response.data.name} - ${window.siyuan.languages.siyuanNote} v${Constants.SIYUAN_VERSION} + + + +
+
${response.data.content.replace(/