mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-16 14:40:12 +01:00
🎨 Improve exporting document HTML (#16219)
* 🎨 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
This commit is contained in:
parent
bca1f1eda6
commit
90a447f914
6 changed files with 241 additions and 40 deletions
|
|
@ -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
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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 ? "" : `<script src="${servePath}appearance/icons/material/icon.js?v=${Constants.SIYUAN_VERSION}"></script>`;
|
||||
return html + `<script src="${servePath}appearance/icons/${window.siyuan.config.appearance.icon}/icon.js?v=${Constants.SIYUAN_VERSION}"></script>`;
|
||||
}
|
||||
|
||||
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 = `<link rel="stylesheet" type="text/css" id="themeStyle" href="${servePath}/appearance/themes/${window.siyuan.config.appearance.themeLight}/theme.css?${Constants.SIYUAN_VERSION}"/>`;
|
||||
themeStyle = `<link rel="stylesheet" type="text/css" id="themeStyle" href="${servePath}appearance/themes/${window.siyuan.config.appearance.themeLight}/theme.css?${Constants.SIYUAN_VERSION}"/>`;
|
||||
}
|
||||
const currentWindowId = await ipcRenderer.invoke(Constants.SIYUAN_GET, {
|
||||
cmd: "getContentsId",
|
||||
|
|
@ -105,15 +141,15 @@ const renderPDF = async (id: string) => {
|
|||
const html = `<!DOCTYPE html>
|
||||
<html lang="${window.siyuan.config.appearance.lang}" data-theme-mode="light" data-light-theme="${window.siyuan.config.appearance.themeLight}" data-dark-theme="${window.siyuan.config.appearance.themeDark}">
|
||||
<head>
|
||||
<base href="${servePath}/">
|
||||
<base href="${servePath}">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
|
||||
<meta name="mobile-web-app-capable" content="yes"/>
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<link rel="stylesheet" type="text/css" id="baseStyle" href="${servePath}/stage/build/export/base.css?v=${Constants.SIYUAN_VERSION}"/>
|
||||
<link rel="stylesheet" type="text/css" id="themeDefaultStyle" href="${servePath}/appearance/themes/daylight/theme.css?v=${Constants.SIYUAN_VERSION}"/>
|
||||
<script src="${servePath}/stage/protyle/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||
<link rel="stylesheet" type="text/css" id="baseStyle" href="${servePath}stage/build/export/base.css?v=${Constants.SIYUAN_VERSION}"/>
|
||||
<link rel="stylesheet" type="text/css" id="themeDefaultStyle" href="${servePath}appearance/themes/daylight/theme.css?v=${Constants.SIYUAN_VERSION}"/>
|
||||
<script src="${servePath}stage/protyle/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||
${themeStyle}
|
||||
<title>${window.siyuan.languages.export} PDF</title>
|
||||
<style>
|
||||
|
|
@ -297,11 +333,11 @@ const renderPDF = async (id: string) => {
|
|||
</div>
|
||||
</div>
|
||||
<div style="zoom:${localData.scale || 1}" id="preview">
|
||||
<div class="fn__loading" style="left:0;height:100vh"><img width="48px" src="${servePath}/stage/loading-pure.svg"></div>
|
||||
<div class="fn__loading" style="left:0;height:100vh"><img width="48px" src="${servePath}stage/loading-pure.svg"></div>
|
||||
</div>
|
||||
<script src="${servePath}/appearance/icons/${window.siyuan.config.appearance.icon}/icon.js?${Constants.SIYUAN_VERSION}"></script>
|
||||
<script src="${servePath}/stage/build/export/protyle-method.js?${Constants.SIYUAN_VERSION}"></script>
|
||||
<script src="${servePath}/stage/protyle/js/lute/lute.min.js?${Constants.SIYUAN_VERSION}"></script>
|
||||
${getIconScript(servePath)}
|
||||
<script src="${servePath}stage/build/export/protyle-method.js?${Constants.SIYUAN_VERSION}"></script>
|
||||
<script src="${servePath}stage/protyle/js/lute/lute.min.js?${Constants.SIYUAN_VERSION}"></script>
|
||||
<script>
|
||||
const previewElement = document.getElementById('preview');
|
||||
const fixBlockWidth = () => {
|
||||
|
|
@ -339,7 +375,7 @@ const renderPDF = async (id: string) => {
|
|||
item.parentElement.style.width = Math.min(item.parentElement.clientWidth, width) + "px";
|
||||
item.removeAttribute('data-render');
|
||||
})
|
||||
Protyle.highlightRender(previewElement, "${servePath}/stage/protyle", document.querySelector("#scale").value);
|
||||
Protyle.highlightRender(previewElement, "${servePath}stage/protyle", document.querySelector("#scale").value);
|
||||
previewElement.querySelectorAll('[data-type="NodeMathBlock"]').forEach((item) => {
|
||||
// 超级块内不能移除 width https://github.com/siyuan-note/siyuan/issues/14318
|
||||
item.removeAttribute('data-render');
|
||||
|
|
@ -347,7 +383,7 @@ const renderPDF = async (id: string) => {
|
|||
previewElement.querySelectorAll('[data-type="NodeCodeBlock"][data-subtype="mermaid"] svg').forEach((item) => {
|
||||
item.style.maxHeight = width * 1.414 + "px";
|
||||
})
|
||||
Protyle.mathRender(previewElement, "${servePath}/stage/protyle", true);
|
||||
Protyle.mathRender(previewElement, "${servePath}stage/protyle", true);
|
||||
previewElement.querySelectorAll("table").forEach(item => {
|
||||
if (item.clientWidth > item.parentElement.clientWidth) {
|
||||
item.style.zoom = (item.parentElement.clientWidth / item.clientWidth).toFixed(2) - 0.01;
|
||||
|
|
@ -404,7 +440,7 @@ const renderPDF = async (id: string) => {
|
|||
}, 300);
|
||||
}
|
||||
const fetchPost = (url, data, cb) => {
|
||||
fetch("${servePath}" + url, {
|
||||
fetch("${servePathWithoutTrailingSlash}" + url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data)
|
||||
}).then((response) => {
|
||||
|
|
@ -426,14 +462,14 @@ const renderPDF = async (id: string) => {
|
|||
item.insertAdjacentHTML("beforeend", "<hr style='margin:0;border:0'>");
|
||||
}
|
||||
})
|
||||
Protyle.mermaidRender(wysElement, "${servePath}/stage/protyle");
|
||||
Protyle.flowchartRender(wysElement, "${servePath}/stage/protyle");
|
||||
Protyle.graphvizRender(wysElement, "${servePath}/stage/protyle");
|
||||
Protyle.chartRender(wysElement, "${servePath}/stage/protyle");
|
||||
Protyle.mindmapRender(wysElement, "${servePath}/stage/protyle");
|
||||
Protyle.abcRender(wysElement, "${servePath}/stage/protyle");
|
||||
Protyle.mermaidRender(wysElement, "${servePath}stage/protyle");
|
||||
Protyle.flowchartRender(wysElement, "${servePath}stage/protyle");
|
||||
Protyle.graphvizRender(wysElement, "${servePath}stage/protyle");
|
||||
Protyle.chartRender(wysElement, "${servePath}stage/protyle");
|
||||
Protyle.mindmapRender(wysElement, "${servePath}stage/protyle");
|
||||
Protyle.abcRender(wysElement, "${servePath}stage/protyle");
|
||||
Protyle.htmlRender(wysElement);
|
||||
Protyle.plantumlRender(wysElement, "${servePath}/stage/protyle");
|
||||
Protyle.plantumlRender(wysElement, "${servePath}stage/protyle");
|
||||
}
|
||||
fetchPost("/api/export/exportPreviewHTML", {
|
||||
id: "${id}",
|
||||
|
|
@ -495,7 +531,7 @@ const renderPDF = async (id: string) => {
|
|||
});
|
||||
const watermarkElement = actionElement.querySelector('#watermark');
|
||||
const refreshPreview = () => {
|
||||
previewElement.innerHTML = '<div class="fn__loading" style="left:0;height: 100vh"><img width="48px" src="${servePath}/stage/loading-pure.svg"></div>'
|
||||
previewElement.innerHTML = '<div class="fn__loading" style="left:0;height: 100vh"><img width="48px" src="${servePath}stage/loading-pure.svg"></div>'
|
||||
fetchPost("/api/export/exportPreviewHTML", {
|
||||
id: "${id}",
|
||||
keepFold: keepFoldElement.checked,
|
||||
|
|
@ -664,7 +700,8 @@ export const onExport = async (data: IWebSocketData, filePath: string, exportOpt
|
|||
mode = 1;
|
||||
}
|
||||
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");
|
||||
const servePath = window.location.protocol + "//" + window.location.host;
|
||||
const isLocalExport = typeof filePath !== "undefined";
|
||||
const servePath = isLocalExport ? "" : window.location.protocol + "//" + window.location.host + "/";
|
||||
let themeStyle = "";
|
||||
if (!isDefault) {
|
||||
themeStyle = `<link rel="stylesheet" type="text/css" id="themeStyle" href="${servePath}appearance/themes/${themeName}/theme.css?${Constants.SIYUAN_VERSION}"/>`;
|
||||
|
|
@ -674,15 +711,15 @@ export const onExport = async (data: IWebSocketData, filePath: string, exportOpt
|
|||
const html = `<!DOCTYPE html>
|
||||
<html lang="${window.siyuan.config.appearance.lang}" data-theme-mode="${getThemeMode()}" data-light-theme="${window.siyuan.config.appearance.themeLight}" data-dark-theme="${window.siyuan.config.appearance.themeDark}">
|
||||
<head>
|
||||
<base href="${servePath}/">
|
||||
<base href="${servePath}">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
|
||||
<meta name="mobile-web-app-capable" content="yes"/>
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<link rel="stylesheet" type="text/css" id="baseStyle" href="${servePath}/stage/build/export/base.css?v=${Constants.SIYUAN_VERSION}"/>
|
||||
<link rel="stylesheet" type="text/css" id="themeDefaultStyle" href="${servePath}/appearance/themes/${themeName}/theme.css?v=${Constants.SIYUAN_VERSION}"/>
|
||||
<script src="${servePath}/stage/protyle/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||
<link rel="stylesheet" type="text/css" id="baseStyle" href="${servePath}stage/build/export/base.css?v=${Constants.SIYUAN_VERSION}"/>
|
||||
<link rel="stylesheet" type="text/css" id="themeDefaultStyle" href="${servePath}appearance/themes/${themeName}/theme.css?v=${Constants.SIYUAN_VERSION}"/>
|
||||
<script src="${servePath}stage/protyle/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||
${themeStyle}
|
||||
<title>${data.data.name}</title>
|
||||
<!-- Exported by SiYuan v${Constants.SIYUAN_VERSION} -->
|
||||
|
|
@ -696,9 +733,9 @@ export const onExport = async (data: IWebSocketData, filePath: string, exportOpt
|
|||
<body>
|
||||
<div class="${["htmlmd", "word"].includes(exportOption.type) ? "b3-typography" : "protyle-wysiwyg" + (window.siyuan.config.editor.displayBookmarkIcon ? " protyle-wysiwyg--attr" : "")}"
|
||||
style="${isInAndroid() || isInHarmony() ? "margin: 0 16px;" : "max-width: 800px;margin: 0 auto;"}" id="preview">${data.data.content}</div>
|
||||
<script src="${servePath}/appearance/icons/${window.siyuan.config.appearance.icon}/icon.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||
<script src="${servePath}/stage/build/export/protyle-method.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||
<script src="${servePath}/stage/protyle/js/lute/lute.min.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||
${getIconScript(servePath)}
|
||||
<script src="${servePath}stage/build/export/protyle-method.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||
<script src="${servePath}stage/protyle/js/lute/lute.min.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||
<script>
|
||||
${minWidthHtml};
|
||||
window.siyuan = {
|
||||
|
|
@ -736,7 +773,7 @@ style="${isInAndroid() || isInHarmony() ? "margin: 0 16px;" : "max-width: 800px;
|
|||
})
|
||||
});
|
||||
</script></body></html>`;
|
||||
// 移动端导出 pdf
|
||||
// 移动端导出 pdf、浏览器导出 HTML
|
||||
if (typeof filePath === "undefined") {
|
||||
return html;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ export const initAssets = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const setInlineStyle = async (set = true, servePath = "../../..") => {
|
||||
export const setInlineStyle = async (set = true, servePath = "../../../") => {
|
||||
const height = Math.floor(window.siyuan.config.editor.fontSize * 1.625);
|
||||
let style;
|
||||
// Emojis Reset: 字体中包含了 emoji,需重置
|
||||
|
|
@ -206,7 +206,7 @@ export const setInlineStyle = async (set = true, servePath = "../../..") => {
|
|||
if (isMac() || isIPad() || isIPhone()) {
|
||||
style = `@font-face {
|
||||
font-family: "Emojis Additional";
|
||||
src: url(${servePath}/appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2");
|
||||
src: url(${servePath}appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2");
|
||||
unicode-range: U+1fae9, U+1fac6, U+1fabe, U+1fadc, U+e50a, U+1fa89, U+1fadf, U+1f1e6-1f1ff, U+1fa8f;
|
||||
}
|
||||
@font-face {
|
||||
|
|
@ -232,7 +232,7 @@ export const setInlineStyle = async (set = true, servePath = "../../..") => {
|
|||
if (isWin11Browser) {
|
||||
style = `@font-face {
|
||||
font-family: "Emojis Additional";
|
||||
src: url(${servePath}/appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2");
|
||||
src: url(${servePath}appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2");
|
||||
unicode-range: U+1fae9, U+1fac6, U+1fabe, U+1fadc, U+e50a, U+1fa89, U+1fadf, U+1f1e6-1f1ff, U+1f3f4, U+e0067, U+e0062,
|
||||
U+e0065, U+e006e, U+e007f, U+e0073, U+e0063, U+e0074, U+e0077, U+e006c;
|
||||
size-adjust: 85%;
|
||||
|
|
@ -254,7 +254,7 @@ export const setInlineStyle = async (set = true, servePath = "../../..") => {
|
|||
} else {
|
||||
style = `@font-face {
|
||||
font-family: "Emojis Reset";
|
||||
src: url(${servePath}/appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2");
|
||||
src: url(${servePath}appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2");
|
||||
unicode-range: U+1f170-1f171, U+1f17e, U+1f17f, U+1f21a, U+1f22f, U+1f232-1f23a, U+1f250, U+1f251, U+1f32b, U+1f3bc,
|
||||
U+1f411, U+1f42d, U+1f42e, U+1f431, U+1f435, U+1f441, U+1f4a8, U+1f4ab, U+1f525, U+1f600-1f60d, U+1f60f-1f623,
|
||||
U+1f625-1f62b, U+1f62d-1f63f, U+1F643, U+1F640, U+1f79, U+1f8f, U+1fa79, U+1fae4, U+1fae9, U+1fac6, U+1fabe, U+1fadf,
|
||||
|
|
@ -267,7 +267,7 @@ export const setInlineStyle = async (set = true, servePath = "../../..") => {
|
|||
}
|
||||
@font-face {
|
||||
font-family: "Emojis";
|
||||
src: url(${servePath}/appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2"),
|
||||
src: url(${servePath}appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2"),
|
||||
local("Segoe UI Emoji"),
|
||||
local("Segoe UI Symbol"),
|
||||
local("Apple Color Emoji"),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
|
@ -30,6 +31,7 @@ import (
|
|||
"github.com/88250/lute/parse"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mssola/useragent"
|
||||
"github.com/siyuan-note/filelock"
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/model"
|
||||
"github.com/siyuan-note/siyuan/kernel/util"
|
||||
|
|
@ -489,6 +491,21 @@ func exportMdHTML(c *gin.Context) {
|
|||
|
||||
id := arg["id"].(string)
|
||||
savePath := arg["savePath"].(string)
|
||||
|
||||
savePath = strings.TrimSpace(savePath)
|
||||
if savePath == "" {
|
||||
folderName := "htmlmd-" + id + "-" + util.CurrentTimeSecondsStr()
|
||||
tmpDir := filepath.Join(util.TempDir, "export", folderName)
|
||||
name, content := model.ExportMarkdownHTML(id, tmpDir, false, false)
|
||||
ret.Data = map[string]interface{}{
|
||||
"id": id,
|
||||
"name": name,
|
||||
"content": content,
|
||||
"folder": folderName,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
name, content := model.ExportMarkdownHTML(id, savePath, false, false)
|
||||
ret.Data = map[string]interface{}{
|
||||
"id": id,
|
||||
|
|
@ -527,6 +544,62 @@ func exportTempContent(c *gin.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func exportBrowserHTML(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
||||
arg, ok := util.JsonArg(c, ret)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
folder := arg["folder"].(string)
|
||||
htmlContent := arg["html"].(string)
|
||||
name := arg["name"].(string)
|
||||
|
||||
tmpDir := filepath.Join(util.TempDir, "export", folder)
|
||||
|
||||
htmlPath := filepath.Join(tmpDir, "index.html")
|
||||
if err := filelock.WriteFile(htmlPath, []byte(htmlContent)); err != nil {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
ret.Data = nil
|
||||
return
|
||||
}
|
||||
|
||||
zipFileName := util.FilterFileName(name) + ".zip"
|
||||
zipPath := filepath.Join(util.TempDir, "export", zipFileName)
|
||||
zip, err := gulu.Zip.Create(zipPath)
|
||||
if err != nil {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
ret.Data = nil
|
||||
return
|
||||
}
|
||||
|
||||
err = zip.AddDirectory("", tmpDir, func(string) {})
|
||||
if err != nil {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
ret.Data = nil
|
||||
return
|
||||
}
|
||||
|
||||
if err = zip.Close(); err != nil {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
ret.Data = nil
|
||||
return
|
||||
}
|
||||
|
||||
os.RemoveAll(tmpDir)
|
||||
|
||||
zipURL := "/export/" + url.PathEscape(filepath.Base(zipPath))
|
||||
ret.Data = map[string]interface{}{
|
||||
"zip": zipURL,
|
||||
}
|
||||
}
|
||||
|
||||
func exportPreviewHTML(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
|
@ -590,6 +663,21 @@ func exportHTML(c *gin.Context) {
|
|||
if nil != arg["merge"] {
|
||||
merge = arg["merge"].(bool)
|
||||
}
|
||||
|
||||
savePath = strings.TrimSpace(savePath)
|
||||
if savePath == "" {
|
||||
folderName := "html-" + id + "-" + util.CurrentTimeSecondsStr()
|
||||
tmpDir := filepath.Join(util.TempDir, "export", folderName)
|
||||
name, content, _ := model.ExportHTML(id, tmpDir, pdf, false, keepFold, merge)
|
||||
ret.Data = map[string]interface{}{
|
||||
"id": id,
|
||||
"name": name,
|
||||
"content": content,
|
||||
"folder": folderName,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
name, content, _ := model.ExportHTML(id, savePath, pdf, false, keepFold, merge)
|
||||
ret.Data = map[string]interface{}{
|
||||
"id": id,
|
||||
|
|
|
|||
|
|
@ -325,6 +325,7 @@ func ServeAPI(ginServer *gin.Engine) {
|
|||
ginServer.Handle("POST", "/api/export/exportData", model.CheckAuth, model.CheckAdminRole, exportData)
|
||||
ginServer.Handle("POST", "/api/export/exportDataInFolder", model.CheckAuth, model.CheckAdminRole, exportDataInFolder)
|
||||
ginServer.Handle("POST", "/api/export/exportTempContent", model.CheckAuth, model.CheckAdminRole, exportTempContent)
|
||||
ginServer.Handle("POST", "/api/export/exportBrowserHTML", model.CheckAuth, model.CheckAdminRole, exportBrowserHTML)
|
||||
ginServer.Handle("POST", "/api/export/export2Liandi", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, export2Liandi)
|
||||
ginServer.Handle("POST", "/api/export/exportReStructuredText", model.CheckAuth, model.CheckAdminRole, exportReStructuredText)
|
||||
ginServer.Handle("POST", "/api/export/exportAsciiDoc", model.CheckAuth, model.CheckAdminRole, exportAsciiDoc)
|
||||
|
|
|
|||
|
|
@ -766,7 +766,8 @@ func ExportMarkdownHTML(id, savePath string, docx, merge bool) (name, dom string
|
|||
if 1 == Conf.Appearance.Mode {
|
||||
theme = Conf.Appearance.ThemeDark
|
||||
}
|
||||
srcs = []string{"icons", "themes/" + theme}
|
||||
// 复制主题文件夹
|
||||
srcs = []string{"themes/" + theme}
|
||||
appearancePath := util.AppearancePath
|
||||
if util.IsSymlinkPath(util.AppearancePath) {
|
||||
// Support for symlinked theme folder when exporting HTML https://github.com/siyuan-note/siyuan/issues/9173
|
||||
|
|
@ -787,6 +788,35 @@ func ExportMarkdownHTML(id, savePath string, docx, merge bool) (name, dom string
|
|||
}
|
||||
}
|
||||
|
||||
// 只复制图标文件夹中的 icon.js 文件
|
||||
iconName := Conf.Appearance.Icon
|
||||
// 如果使用的不是内建图标(ant 或 material),需要复制 material 作为后备
|
||||
if iconName != "ant" && iconName != "material" && iconName != "" {
|
||||
srcIconFile := filepath.Join(appearancePath, "icons", "material", "icon.js")
|
||||
toIconDir := filepath.Join(savePath, "appearance", "icons", "material")
|
||||
if err := os.MkdirAll(toIconDir, 0755); err != nil {
|
||||
logging.LogErrorf("mkdir [%s] failed: %s", toIconDir, err)
|
||||
return
|
||||
}
|
||||
toIconFile := filepath.Join(toIconDir, "icon.js")
|
||||
if err := filelock.Copy(srcIconFile, toIconFile); err != nil {
|
||||
logging.LogWarnf("copy icon file from [%s] to [%s] failed: %s", srcIconFile, toIconFile, err)
|
||||
}
|
||||
}
|
||||
// 复制当前使用的图标文件
|
||||
if iconName != "" {
|
||||
srcIconFile := filepath.Join(appearancePath, "icons", iconName, "icon.js")
|
||||
toIconDir := filepath.Join(savePath, "appearance", "icons", iconName)
|
||||
if err := os.MkdirAll(toIconDir, 0755); err != nil {
|
||||
logging.LogErrorf("mkdir [%s] failed: %s", toIconDir, err)
|
||||
return
|
||||
}
|
||||
toIconFile := filepath.Join(toIconDir, "icon.js")
|
||||
if err := filelock.Copy(srcIconFile, toIconFile); err != nil {
|
||||
logging.LogWarnf("copy icon file from [%s] to [%s] failed: %s", srcIconFile, toIconFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 复制自定义表情图片
|
||||
emojis := emojisInTree(tree)
|
||||
for _, emoji := range emojis {
|
||||
|
|
@ -930,7 +960,8 @@ func ExportHTML(id, savePath string, pdf, image, keepFold, merge bool) (name, do
|
|||
if 1 == Conf.Appearance.Mode {
|
||||
theme = Conf.Appearance.ThemeDark
|
||||
}
|
||||
srcs = []string{"icons", "themes/" + theme}
|
||||
// 复制主题文件夹
|
||||
srcs = []string{"themes/" + theme}
|
||||
appearancePath := util.AppearancePath
|
||||
if util.IsSymlinkPath(util.AppearancePath) {
|
||||
// Support for symlinked theme folder when exporting HTML https://github.com/siyuan-note/siyuan/issues/9173
|
||||
|
|
@ -949,6 +980,35 @@ func ExportHTML(id, savePath string, pdf, image, keepFold, merge bool) (name, do
|
|||
}
|
||||
}
|
||||
|
||||
// 只复制图标文件夹中的 icon.js 文件
|
||||
iconName := Conf.Appearance.Icon
|
||||
// 如果使用的不是内建图标(ant 或 material),需要复制 material 作为后备
|
||||
if iconName != "ant" && iconName != "material" && iconName != "" {
|
||||
srcIconFile := filepath.Join(appearancePath, "icons", "material", "icon.js")
|
||||
toIconDir := filepath.Join(savePath, "appearance", "icons", "material")
|
||||
if err := os.MkdirAll(toIconDir, 0755); err != nil {
|
||||
logging.LogErrorf("mkdir [%s] failed: %s", toIconDir, err)
|
||||
return
|
||||
}
|
||||
toIconFile := filepath.Join(toIconDir, "icon.js")
|
||||
if err := filelock.Copy(srcIconFile, toIconFile); err != nil {
|
||||
logging.LogWarnf("copy icon file from [%s] to [%s] failed: %s", srcIconFile, toIconFile, err)
|
||||
}
|
||||
}
|
||||
// 复制当前使用的图标文件
|
||||
if iconName != "" {
|
||||
srcIconFile := filepath.Join(appearancePath, "icons", iconName, "icon.js")
|
||||
toIconDir := filepath.Join(savePath, "appearance", "icons", iconName)
|
||||
if err := os.MkdirAll(toIconDir, 0755); err != nil {
|
||||
logging.LogErrorf("mkdir [%s] failed: %s", toIconDir, err)
|
||||
return
|
||||
}
|
||||
toIconFile := filepath.Join(toIconDir, "icon.js")
|
||||
if err := filelock.Copy(srcIconFile, toIconFile); err != nil {
|
||||
logging.LogWarnf("copy icon file from [%s] to [%s] failed: %s", srcIconFile, toIconFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 复制自定义表情图片
|
||||
emojis := emojisInTree(tree)
|
||||
for _, emoji := range emojis {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue