From 90a447f9143da59517dd4e34fb92c90814a9d1a5 Mon Sep 17 00:00:00 2001 From: Jeffrey Chen <78434827+TCOTC@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:28:56 +0800 Subject: [PATCH] :art: Improve exporting document HTML (#16219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :art: The browser-side supports exporting document HTML fix https://github.com/siyuan-note/siyuan/issues/16213 * 修复导出 HTML 时引入资源没有使用相对路径,修复文档导出 HTML/PDF 时缺失图标 fix https://github.com/siyuan-note/siyuan/issues/16217 fix https://github.com/siyuan-note/siyuan/issues/16216 01 * 修复文档导出 HTML/PDF 时冗余图标 fix https://github.com/siyuan-note/siyuan/issues/16216 02 --- app/src/menus/commonMenuItem.ts | 19 ++++++- app/src/protyle/export/index.ts | 99 ++++++++++++++++++++++----------- app/src/util/assets.ts | 10 ++-- kernel/api/export.go | 88 +++++++++++++++++++++++++++++ kernel/api/router.go | 1 + kernel/model/export.go | 64 ++++++++++++++++++++- 6 files changed, 241 insertions(+), 40 deletions(-) diff --git a/app/src/menus/commonMenuItem.ts b/app/src/menus/commonMenuItem.ts index 01dde9225..3263c5498 100644 --- a/app/src/menus/commonMenuItem.ts +++ b/app/src/menus/commonMenuItem.ts @@ -760,7 +760,7 @@ export const exportMd = (id: string) => { icon: "iconPDF", ignore: !isInAndroid() && !isInHarmony(), click: () => { - const msId = showMessage(window.siyuan.languages.exporting); + const msgId = showMessage(window.siyuan.languages.exporting); const localData = window.siyuan.storage[Constants.LOCAL_EXPORTPDF]; fetchPost("/api/export/exportPreviewHTML", { id, @@ -775,10 +775,25 @@ export const exportMd = (id: string) => { } setTimeout(() => { - hideMessage(msId); + hideMessage(msgId); }, 3000); }); } + }, { + id: "exportHTML_SiYuan", + label: "HTML (SiYuan)", + iconClass: "ft__error", + icon: "iconHTML5", + click: () => { + saveExport({type: "html", id}); + } + }, { + id: "exportHTML_Markdown", + label: "HTML (Markdown)", + icon: "iconHTML5", + click: () => { + saveExport({type: "htmlmd", id}); + } }, /// #endif ] diff --git a/app/src/protyle/export/index.ts b/app/src/protyle/export/index.ts index 2da1fe13e..26edcda10 100644 --- a/app/src/protyle/export/index.ts +++ b/app/src/protyle/export/index.ts @@ -24,8 +24,43 @@ const getPluginStyle = async () => { return css; }; +const getIconScript = (servePath: string) => { + const isBuiltInIcon = ["ant", "material"].includes(window.siyuan.config.appearance.icon); + const html = isBuiltInIcon ? "" : ``; + return html + ``; +} + export const saveExport = (option: IExportOptions) => { - /// #if !BROWSER + /// #if BROWSER + if (["html", "htmlmd"].includes(option.type)) { + const msgId = showMessage(window.siyuan.languages.exporting, -1); + // 浏览器环境:先调用 API 生成资源文件,再在前端生成完整的 HTML + const url = option.type === "htmlmd" ? "/api/export/exportMdHTML" : "/api/export/exportHTML"; + fetchPost(url, { + id: option.id, + pdf: false, + removeAssets: false, + merge: true, + savePath: "" + }, async exportResponse => { + const html = await onExport(exportResponse, undefined, option); + fetchPost("/api/export/exportBrowserHTML", { + folder: exportResponse.data.folder, + html: html, + name: exportResponse.data.name + }, zipResponse => { + hideMessage(msgId); + if (zipResponse.code === -1) { + showMessage(window.siyuan.languages._kernel[14] + ": " + zipResponse.msg, 0, "error"); + return; + } + window.open(zipResponse.data.zip); + showMessage(window.siyuan.languages.exported); + }); + }); + return; + } + /// #else if (option.type === "pdf") { if (window.siyuan.config.appearance.mode === 1) { confirmDialog(window.siyuan.languages.pdfTip, window.siyuan.languages.pdfConfirm, () => { @@ -92,11 +127,12 @@ const getSnippetCSS = () => { /// #if !BROWSER const renderPDF = async (id: string) => { const localData = window.siyuan.storage[Constants.LOCAL_EXPORTPDF]; - const servePath = window.location.protocol + "//" + window.location.host; + const servePathWithoutTrailingSlash = window.location.protocol + "//" + window.location.host; + const servePath = servePathWithoutTrailingSlash + "/"; const isDefault = (window.siyuan.config.appearance.mode === 1 && window.siyuan.config.appearance.themeDark === "midnight") || (window.siyuan.config.appearance.mode === 0 && window.siyuan.config.appearance.themeLight === "daylight"); let themeStyle = ""; if (!isDefault) { - themeStyle = ``; + themeStyle = ``; } const currentWindowId = await ipcRenderer.invoke(Constants.SIYUAN_GET, { cmd: "getContentsId", @@ -105,15 +141,15 @@ const renderPDF = async (id: string) => { const html = `
-