A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1/// #if !BROWSER
2import {escapeHtml} from "../../util/escape";
3import * as path from "path";
4/// #endif
5import {hideMessage, showMessage} from "../../dialog/message";
6import {fetchPost} from "../../util/fetch";
7import {Dialog} from "../../dialog";
8import {addScript} from "../util/addScript";
9import {isMobile} from "../../util/functions";
10import {Constants} from "../../constants";
11import {highlightRender} from "../render/highlightRender";
12import {processRender} from "../util/processCode";
13import {isIPhone, isSafari, openByMobile, setStorageVal} from "../util/compatibility";
14import {useShell} from "../../util/pathName";
15
16export const afterExport = (exportPath: string, msgId: string) => {
17 /// #if !BROWSER
18 showMessage(`${window.siyuan.languages.exported} ${escapeHtml(exportPath)}
19<div class="fn__space"></div>
20<button class="b3-button b3-button--white">${window.siyuan.languages.showInFolder}</button>`, 6000, "info", msgId);
21 document.querySelector(`#message [data-id="${msgId}"] button`).addEventListener("click", () => {
22 useShell("showItemInFolder", path.join(exportPath));
23 hideMessage(msgId);
24 });
25 /// #endif
26};
27
28export const exportImage = (id: string) => {
29 const exportDialog = new Dialog({
30 title: window.siyuan.languages.exportAsImage,
31 content: `<div class="b3-dialog__content" style="${isMobile() ? "padding:8px;" : ""};background-color: var(--b3-theme-background)">
32 <div style="${isMobile() ? "margin: 8px 0" : "padding: 48px;margin: 8px 0"}" class="export-img">
33 <div ${isMobile() ? 'style="padding:8px"' : ""} class="protyle-wysiwyg${window.siyuan.config.editor.displayBookmarkIcon ? " protyle-wysiwyg--attr" : ""}"></div>
34 <div class="export-img__watermark"></div>
35 </div>
36</div>
37<div class="b3-dialog__action">
38 <label class="fn__flex">
39 ${window.siyuan.languages.exportPDF5}
40 <span class="fn__space"></span>
41 <input id="keepFold" class="b3-switch fn__flex-center" type="checkbox" ${window.siyuan.storage[Constants.LOCAL_EXPORTIMG].keepFold ? "checked" : ""}>
42 </label>
43 <label class="fn__flex" style="margin-left: 24px">
44 ${window.siyuan.languages.export30}
45 <span class="fn__space"></span>
46 <input id="watermark" class="b3-switch fn__flex-center" type="checkbox" ${window.siyuan.storage[Constants.LOCAL_EXPORTIMG].watermark ? "checked" : ""}>
47 </label>
48 <span class="fn__flex-1 export-img__space"></span>
49 <button disabled class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
50 <button disabled class="b3-button b3-button--text">${window.siyuan.languages.confirm}</button>
51</div>
52 <div class="fn__loading"><img height="128px" width="128px" src="stage/loading-pure.svg"></div>`,
53 width: isMobile() ? "92vw" : "990px",
54 height: "70vh"
55 });
56 exportDialog.element.setAttribute("data-key", Constants.DIALOG_EXPORTIMAGE);
57 const btnsElement = exportDialog.element.querySelectorAll(".b3-button");
58 btnsElement[0].addEventListener("click", () => {
59 exportDialog.destroy();
60 });
61 btnsElement[1].addEventListener("click", async () => {
62 const msgId = showMessage(window.siyuan.languages.exporting, 0);
63 const containerElement = exportDialog.element.querySelector(".b3-dialog__container") as HTMLElement;
64 containerElement.style.height = "";
65 /// #if MOBILE
66 containerElement.style.width = "100vw";
67 /// #endif
68 const contentElement = exportDialog.element.querySelector(".b3-dialog__content") as HTMLElement;
69 contentElement.style.overflow = "hidden";
70 setStorageVal(Constants.LOCAL_EXPORTIMG, window.siyuan.storage[Constants.LOCAL_EXPORTIMG]);
71 const plantumlElements = previewElement.querySelectorAll("[data-subtype='plantuml']");
72 for (let i = 0; i < plantumlElements.length; i++) {
73 const objectElement = plantumlElements[i].querySelector("object");
74 if (objectElement) {
75 const res = await fetch(objectElement.getAttribute("data"));
76 const response = await res.text();
77 objectElement.insertAdjacentHTML("beforebegin", response as string);
78 objectElement.remove();
79 }
80 }
81 previewElement.querySelectorAll(".protyle-linenumber__rows span").forEach((item, index) => {
82 item.textContent = (index + 1).toString();
83 });
84 setTimeout(() => {
85 addScript("/stage/protyle/js/html-to-image.min.js?v=1.11.13", "protyleHtml2image").then(async () => {
86 let blob = await window.htmlToImage.toBlob(exportDialog.element.querySelector(".b3-dialog__content"));
87 if (isIPhone() || isSafari()) {
88 await window.htmlToImage.toBlob(contentElement);
89 await window.htmlToImage.toBlob(contentElement);
90 await window.htmlToImage.toBlob(contentElement);
91 blob = await window.htmlToImage.toBlob(contentElement);
92 }
93 const formData = new FormData();
94 formData.append("file", blob, btnsElement[1].getAttribute("data-title"));
95 formData.append("type", "image/png");
96 fetchPost("/api/export/exportAsFile", formData, (response) => {
97 openByMobile(response.data.file);
98 });
99 hideMessage(msgId);
100 exportDialog.destroy();
101 });
102 }, Constants.TIMEOUT_LOAD);
103 });
104 const previewElement = exportDialog.element.querySelector(".protyle-wysiwyg") as HTMLElement;
105 const foldElement = (exportDialog.element.querySelector("#keepFold") as HTMLInputElement);
106 foldElement.addEventListener("change", () => {
107 btnsElement[0].setAttribute("disabled", "disabled");
108 btnsElement[1].setAttribute("disabled", "disabled");
109 btnsElement[1].parentElement.insertAdjacentHTML("afterend", '<div class="fn__loading"><img height="128px" width="128px" src="stage/loading-pure.svg"></div>');
110 window.siyuan.storage[Constants.LOCAL_EXPORTIMG].keepFold = foldElement.checked;
111 fetchPost("/api/export/exportPreviewHTML", {
112 id,
113 keepFold: foldElement.checked,
114 image: true,
115 }, (response) => {
116 refreshPreview(response);
117 });
118 });
119 const watermarkElement = (exportDialog.element.querySelector("#watermark") as HTMLInputElement);
120 watermarkElement.addEventListener("change", () => {
121 window.siyuan.storage[Constants.LOCAL_EXPORTIMG].watermark = watermarkElement.checked;
122 updateWatermark();
123 });
124 const updateWatermark = () => {
125 const watermarkPreviewElement = exportDialog.element.querySelector(".export-img__watermark") as HTMLElement;
126 watermarkPreviewElement.innerHTML = "";
127 if (watermarkElement.checked) {
128 if (window.siyuan.config.export.imageWatermarkDesc) {
129 watermarkPreviewElement.innerHTML = window.siyuan.config.export.imageWatermarkDesc;
130 } else if (window.siyuan.config.export.imageWatermarkStr) {
131 if (window.siyuan.config.export.imageWatermarkStr.startsWith("http")) {
132 watermarkPreviewElement.setAttribute("style", `background-image: url(${window.siyuan.config.export.imageWatermarkStr});background-repeat: repeat;position: absolute;top: 0;left: 0;width: 100%;height: 100%;border-radius: var(--b3-border-radius-b);`);
133 } else {
134 addScript("/stage/protyle/js/html-to-image.min.js?v=1.11.13", "protyleHtml2image").then(() => {
135 const width = Math.max(exportDialog.element.querySelector(".export-img").clientWidth / 3, 150);
136 watermarkPreviewElement.setAttribute("style", `width: ${width}px;height: ${width}px;display: flex;justify-content: center;align-items: center;color: var(--b3-border-color);font-size: 14px;`);
137 watermarkPreviewElement.innerHTML = `<div style="transform: rotate(-45deg)">${window.siyuan.config.export.imageWatermarkStr}</div>`;
138 window.htmlToImage.toCanvas(watermarkPreviewElement).then((canvas) => {
139 watermarkPreviewElement.innerHTML = "";
140 watermarkPreviewElement.setAttribute("style", `background-image: url(${canvas.toDataURL("image/png")});background-repeat: repeat;position: absolute;top: 0;left: 0;width: 100%;height: 100%;border-radius: var(--b3-border-radius-b);`);
141 });
142 });
143 }
144 }
145 } else {
146 watermarkPreviewElement.removeAttribute("style");
147 }
148 };
149 const refreshPreview = (response: IWebSocketData) => {
150 previewElement.innerHTML = response.data.content;
151 previewElement.setAttribute("data-doc-type", response.data.type || "NodeDocument");
152 Object.keys(response.data.attrs).forEach(key => {
153 previewElement.setAttribute(key, response.data.attrs[key]);
154 });
155 previewElement.querySelectorAll(".code-block").forEach(item => {
156 item.setAttribute("linewrap", "true");
157 });
158 processRender(previewElement);
159 highlightRender(previewElement);
160 previewElement.querySelectorAll("table").forEach((item: HTMLElement) => {
161 if (item.clientWidth > item.parentElement.clientWidth) {
162 item.setAttribute("style", `margin-bottom:${item.parentElement.clientWidth * item.clientHeight / item.clientWidth - item.parentElement.clientHeight + 1}px;transform: scale(${item.parentElement.clientWidth / item.clientWidth});transform-origin: top left;`);
163 item.parentElement.style.overflow = "hidden";
164 }
165 });
166
167 updateWatermark();
168 btnsElement[0].removeAttribute("disabled");
169 btnsElement[1].removeAttribute("disabled");
170 exportDialog.element.querySelector(".fn__loading").remove();
171 };
172 fetchPost("/api/export/exportPreviewHTML", {
173 id,
174 keepFold: foldElement.checked,
175 image: true,
176 }, (response) => {
177 refreshPreview(response);
178 btnsElement[1].setAttribute("data-title", response.data.name + ".png");
179 });
180};