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",
|
icon: "iconPDF",
|
||||||
ignore: !isInAndroid() && !isInHarmony(),
|
ignore: !isInAndroid() && !isInHarmony(),
|
||||||
click: () => {
|
click: () => {
|
||||||
const msId = showMessage(window.siyuan.languages.exporting);
|
const msgId = showMessage(window.siyuan.languages.exporting);
|
||||||
const localData = window.siyuan.storage[Constants.LOCAL_EXPORTPDF];
|
const localData = window.siyuan.storage[Constants.LOCAL_EXPORTPDF];
|
||||||
fetchPost("/api/export/exportPreviewHTML", {
|
fetchPost("/api/export/exportPreviewHTML", {
|
||||||
id,
|
id,
|
||||||
|
|
@ -775,10 +775,25 @@ export const exportMd = (id: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
hideMessage(msId);
|
hideMessage(msgId);
|
||||||
}, 3000);
|
}, 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
|
/// #endif
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,43 @@ const getPluginStyle = async () => {
|
||||||
return css;
|
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) => {
|
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 (option.type === "pdf") {
|
||||||
if (window.siyuan.config.appearance.mode === 1) {
|
if (window.siyuan.config.appearance.mode === 1) {
|
||||||
confirmDialog(window.siyuan.languages.pdfTip, window.siyuan.languages.pdfConfirm, () => {
|
confirmDialog(window.siyuan.languages.pdfTip, window.siyuan.languages.pdfConfirm, () => {
|
||||||
|
|
@ -92,11 +127,12 @@ const getSnippetCSS = () => {
|
||||||
/// #if !BROWSER
|
/// #if !BROWSER
|
||||||
const renderPDF = async (id: string) => {
|
const renderPDF = async (id: string) => {
|
||||||
const localData = window.siyuan.storage[Constants.LOCAL_EXPORTPDF];
|
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");
|
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 = "";
|
let themeStyle = "";
|
||||||
if (!isDefault) {
|
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, {
|
const currentWindowId = await ipcRenderer.invoke(Constants.SIYUAN_GET, {
|
||||||
cmd: "getContentsId",
|
cmd: "getContentsId",
|
||||||
|
|
@ -105,15 +141,15 @@ const renderPDF = async (id: string) => {
|
||||||
const html = `<!DOCTYPE html>
|
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}">
|
<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>
|
<head>
|
||||||
<base href="${servePath}/">
|
<base href="${servePath}">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<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="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="mobile-web-app-capable" content="yes"/>
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<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="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}"/>
|
<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>
|
<script src="${servePath}stage/protyle/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||||
${themeStyle}
|
${themeStyle}
|
||||||
<title>${window.siyuan.languages.export} PDF</title>
|
<title>${window.siyuan.languages.export} PDF</title>
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -297,11 +333,11 @@ const renderPDF = async (id: string) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="zoom:${localData.scale || 1}" id="preview">
|
<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>
|
</div>
|
||||||
<script src="${servePath}/appearance/icons/${window.siyuan.config.appearance.icon}/icon.js?${Constants.SIYUAN_VERSION}"></script>
|
${getIconScript(servePath)}
|
||||||
<script src="${servePath}/stage/build/export/protyle-method.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>
|
<script src="${servePath}stage/protyle/js/lute/lute.min.js?${Constants.SIYUAN_VERSION}"></script>
|
||||||
<script>
|
<script>
|
||||||
const previewElement = document.getElementById('preview');
|
const previewElement = document.getElementById('preview');
|
||||||
const fixBlockWidth = () => {
|
const fixBlockWidth = () => {
|
||||||
|
|
@ -339,7 +375,7 @@ const renderPDF = async (id: string) => {
|
||||||
item.parentElement.style.width = Math.min(item.parentElement.clientWidth, width) + "px";
|
item.parentElement.style.width = Math.min(item.parentElement.clientWidth, width) + "px";
|
||||||
item.removeAttribute('data-render');
|
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) => {
|
previewElement.querySelectorAll('[data-type="NodeMathBlock"]').forEach((item) => {
|
||||||
// 超级块内不能移除 width https://github.com/siyuan-note/siyuan/issues/14318
|
// 超级块内不能移除 width https://github.com/siyuan-note/siyuan/issues/14318
|
||||||
item.removeAttribute('data-render');
|
item.removeAttribute('data-render');
|
||||||
|
|
@ -347,7 +383,7 @@ const renderPDF = async (id: string) => {
|
||||||
previewElement.querySelectorAll('[data-type="NodeCodeBlock"][data-subtype="mermaid"] svg').forEach((item) => {
|
previewElement.querySelectorAll('[data-type="NodeCodeBlock"][data-subtype="mermaid"] svg').forEach((item) => {
|
||||||
item.style.maxHeight = width * 1.414 + "px";
|
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 => {
|
previewElement.querySelectorAll("table").forEach(item => {
|
||||||
if (item.clientWidth > item.parentElement.clientWidth) {
|
if (item.clientWidth > item.parentElement.clientWidth) {
|
||||||
item.style.zoom = (item.parentElement.clientWidth / item.clientWidth).toFixed(2) - 0.01;
|
item.style.zoom = (item.parentElement.clientWidth / item.clientWidth).toFixed(2) - 0.01;
|
||||||
|
|
@ -404,7 +440,7 @@ const renderPDF = async (id: string) => {
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
const fetchPost = (url, data, cb) => {
|
const fetchPost = (url, data, cb) => {
|
||||||
fetch("${servePath}" + url, {
|
fetch("${servePathWithoutTrailingSlash}" + url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
|
|
@ -426,14 +462,14 @@ const renderPDF = async (id: string) => {
|
||||||
item.insertAdjacentHTML("beforeend", "<hr style='margin:0;border:0'>");
|
item.insertAdjacentHTML("beforeend", "<hr style='margin:0;border:0'>");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Protyle.mermaidRender(wysElement, "${servePath}/stage/protyle");
|
Protyle.mermaidRender(wysElement, "${servePath}stage/protyle");
|
||||||
Protyle.flowchartRender(wysElement, "${servePath}/stage/protyle");
|
Protyle.flowchartRender(wysElement, "${servePath}stage/protyle");
|
||||||
Protyle.graphvizRender(wysElement, "${servePath}/stage/protyle");
|
Protyle.graphvizRender(wysElement, "${servePath}stage/protyle");
|
||||||
Protyle.chartRender(wysElement, "${servePath}/stage/protyle");
|
Protyle.chartRender(wysElement, "${servePath}stage/protyle");
|
||||||
Protyle.mindmapRender(wysElement, "${servePath}/stage/protyle");
|
Protyle.mindmapRender(wysElement, "${servePath}stage/protyle");
|
||||||
Protyle.abcRender(wysElement, "${servePath}/stage/protyle");
|
Protyle.abcRender(wysElement, "${servePath}stage/protyle");
|
||||||
Protyle.htmlRender(wysElement);
|
Protyle.htmlRender(wysElement);
|
||||||
Protyle.plantumlRender(wysElement, "${servePath}/stage/protyle");
|
Protyle.plantumlRender(wysElement, "${servePath}stage/protyle");
|
||||||
}
|
}
|
||||||
fetchPost("/api/export/exportPreviewHTML", {
|
fetchPost("/api/export/exportPreviewHTML", {
|
||||||
id: "${id}",
|
id: "${id}",
|
||||||
|
|
@ -495,7 +531,7 @@ const renderPDF = async (id: string) => {
|
||||||
});
|
});
|
||||||
const watermarkElement = actionElement.querySelector('#watermark');
|
const watermarkElement = actionElement.querySelector('#watermark');
|
||||||
const refreshPreview = () => {
|
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", {
|
fetchPost("/api/export/exportPreviewHTML", {
|
||||||
id: "${id}",
|
id: "${id}",
|
||||||
keepFold: keepFoldElement.checked,
|
keepFold: keepFoldElement.checked,
|
||||||
|
|
@ -664,7 +700,8 @@ export const onExport = async (data: IWebSocketData, filePath: string, exportOpt
|
||||||
mode = 1;
|
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 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 = "";
|
let themeStyle = "";
|
||||||
if (!isDefault) {
|
if (!isDefault) {
|
||||||
themeStyle = `<link rel="stylesheet" type="text/css" id="themeStyle" href="${servePath}appearance/themes/${themeName}/theme.css?${Constants.SIYUAN_VERSION}"/>`;
|
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>
|
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}">
|
<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>
|
<head>
|
||||||
<base href="${servePath}/">
|
<base href="${servePath}">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<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="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="mobile-web-app-capable" content="yes"/>
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<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="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}"/>
|
<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>
|
<script src="${servePath}stage/protyle/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||||
${themeStyle}
|
${themeStyle}
|
||||||
<title>${data.data.name}</title>
|
<title>${data.data.name}</title>
|
||||||
<!-- Exported by SiYuan v${Constants.SIYUAN_VERSION} -->
|
<!-- Exported by SiYuan v${Constants.SIYUAN_VERSION} -->
|
||||||
|
|
@ -696,9 +733,9 @@ export const onExport = async (data: IWebSocketData, filePath: string, exportOpt
|
||||||
<body>
|
<body>
|
||||||
<div class="${["htmlmd", "word"].includes(exportOption.type) ? "b3-typography" : "protyle-wysiwyg" + (window.siyuan.config.editor.displayBookmarkIcon ? " protyle-wysiwyg--attr" : "")}"
|
<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>
|
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>
|
${getIconScript(servePath)}
|
||||||
<script src="${servePath}/stage/build/export/protyle-method.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>
|
<script src="${servePath}stage/protyle/js/lute/lute.min.js?v=${Constants.SIYUAN_VERSION}"></script>
|
||||||
<script>
|
<script>
|
||||||
${minWidthHtml};
|
${minWidthHtml};
|
||||||
window.siyuan = {
|
window.siyuan = {
|
||||||
|
|
@ -736,7 +773,7 @@ style="${isInAndroid() || isInHarmony() ? "margin: 0 16px;" : "max-width: 800px;
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
</script></body></html>`;
|
</script></body></html>`;
|
||||||
// 移动端导出 pdf
|
// 移动端导出 pdf、浏览器导出 HTML
|
||||||
if (typeof filePath === "undefined") {
|
if (typeof filePath === "undefined") {
|
||||||
return html;
|
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);
|
const height = Math.floor(window.siyuan.config.editor.fontSize * 1.625);
|
||||||
let style;
|
let style;
|
||||||
// Emojis Reset: 字体中包含了 emoji,需重置
|
// Emojis Reset: 字体中包含了 emoji,需重置
|
||||||
|
|
@ -206,7 +206,7 @@ export const setInlineStyle = async (set = true, servePath = "../../..") => {
|
||||||
if (isMac() || isIPad() || isIPhone()) {
|
if (isMac() || isIPad() || isIPhone()) {
|
||||||
style = `@font-face {
|
style = `@font-face {
|
||||||
font-family: "Emojis Additional";
|
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;
|
unicode-range: U+1fae9, U+1fac6, U+1fabe, U+1fadc, U+e50a, U+1fa89, U+1fadf, U+1f1e6-1f1ff, U+1fa8f;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
|
|
@ -232,7 +232,7 @@ export const setInlineStyle = async (set = true, servePath = "../../..") => {
|
||||||
if (isWin11Browser) {
|
if (isWin11Browser) {
|
||||||
style = `@font-face {
|
style = `@font-face {
|
||||||
font-family: "Emojis Additional";
|
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,
|
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;
|
U+e0065, U+e006e, U+e007f, U+e0073, U+e0063, U+e0074, U+e0077, U+e006c;
|
||||||
size-adjust: 85%;
|
size-adjust: 85%;
|
||||||
|
|
@ -254,7 +254,7 @@ export const setInlineStyle = async (set = true, servePath = "../../..") => {
|
||||||
} else {
|
} else {
|
||||||
style = `@font-face {
|
style = `@font-face {
|
||||||
font-family: "Emojis Reset";
|
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,
|
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+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,
|
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-face {
|
||||||
font-family: "Emojis";
|
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 Emoji"),
|
||||||
local("Segoe UI Symbol"),
|
local("Segoe UI Symbol"),
|
||||||
local("Apple Color Emoji"),
|
local("Apple Color Emoji"),
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -30,6 +31,7 @@ import (
|
||||||
"github.com/88250/lute/parse"
|
"github.com/88250/lute/parse"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/mssola/useragent"
|
"github.com/mssola/useragent"
|
||||||
|
"github.com/siyuan-note/filelock"
|
||||||
"github.com/siyuan-note/logging"
|
"github.com/siyuan-note/logging"
|
||||||
"github.com/siyuan-note/siyuan/kernel/model"
|
"github.com/siyuan-note/siyuan/kernel/model"
|
||||||
"github.com/siyuan-note/siyuan/kernel/util"
|
"github.com/siyuan-note/siyuan/kernel/util"
|
||||||
|
|
@ -489,6 +491,21 @@ func exportMdHTML(c *gin.Context) {
|
||||||
|
|
||||||
id := arg["id"].(string)
|
id := arg["id"].(string)
|
||||||
savePath := arg["savePath"].(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)
|
name, content := model.ExportMarkdownHTML(id, savePath, false, false)
|
||||||
ret.Data = map[string]interface{}{
|
ret.Data = map[string]interface{}{
|
||||||
"id": id,
|
"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) {
|
func exportPreviewHTML(c *gin.Context) {
|
||||||
ret := gulu.Ret.NewResult()
|
ret := gulu.Ret.NewResult()
|
||||||
defer c.JSON(http.StatusOK, ret)
|
defer c.JSON(http.StatusOK, ret)
|
||||||
|
|
@ -590,6 +663,21 @@ func exportHTML(c *gin.Context) {
|
||||||
if nil != arg["merge"] {
|
if nil != arg["merge"] {
|
||||||
merge = arg["merge"].(bool)
|
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)
|
name, content, _ := model.ExportHTML(id, savePath, pdf, false, keepFold, merge)
|
||||||
ret.Data = map[string]interface{}{
|
ret.Data = map[string]interface{}{
|
||||||
"id": id,
|
"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/exportData", model.CheckAuth, model.CheckAdminRole, exportData)
|
||||||
ginServer.Handle("POST", "/api/export/exportDataInFolder", model.CheckAuth, model.CheckAdminRole, exportDataInFolder)
|
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/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/export2Liandi", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, export2Liandi)
|
||||||
ginServer.Handle("POST", "/api/export/exportReStructuredText", model.CheckAuth, model.CheckAdminRole, exportReStructuredText)
|
ginServer.Handle("POST", "/api/export/exportReStructuredText", model.CheckAuth, model.CheckAdminRole, exportReStructuredText)
|
||||||
ginServer.Handle("POST", "/api/export/exportAsciiDoc", model.CheckAuth, model.CheckAdminRole, exportAsciiDoc)
|
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 {
|
if 1 == Conf.Appearance.Mode {
|
||||||
theme = Conf.Appearance.ThemeDark
|
theme = Conf.Appearance.ThemeDark
|
||||||
}
|
}
|
||||||
srcs = []string{"icons", "themes/" + theme}
|
// 复制主题文件夹
|
||||||
|
srcs = []string{"themes/" + theme}
|
||||||
appearancePath := util.AppearancePath
|
appearancePath := util.AppearancePath
|
||||||
if util.IsSymlinkPath(util.AppearancePath) {
|
if util.IsSymlinkPath(util.AppearancePath) {
|
||||||
// Support for symlinked theme folder when exporting HTML https://github.com/siyuan-note/siyuan/issues/9173
|
// 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)
|
emojis := emojisInTree(tree)
|
||||||
for _, emoji := range emojis {
|
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 {
|
if 1 == Conf.Appearance.Mode {
|
||||||
theme = Conf.Appearance.ThemeDark
|
theme = Conf.Appearance.ThemeDark
|
||||||
}
|
}
|
||||||
srcs = []string{"icons", "themes/" + theme}
|
// 复制主题文件夹
|
||||||
|
srcs = []string{"themes/" + theme}
|
||||||
appearancePath := util.AppearancePath
|
appearancePath := util.AppearancePath
|
||||||
if util.IsSymlinkPath(util.AppearancePath) {
|
if util.IsSymlinkPath(util.AppearancePath) {
|
||||||
// Support for symlinked theme folder when exporting HTML https://github.com/siyuan-note/siyuan/issues/9173
|
// 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)
|
emojis := emojisInTree(tree)
|
||||||
for _, emoji := range emojis {
|
for _, emoji := range emojis {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue