A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1import {adjustLayout, exportLayout, JSONToLayout, resetLayout, resizeTopBar} from "../layout/util";
2import {resizeTabs} from "../layout/tabUtil";
3import {setStorageVal} from "../protyle/util/compatibility";
4/// #if !BROWSER
5import {ipcRenderer, webFrame} from "electron";
6import * as fs from "fs";
7import * as path from "path";
8import {afterExport} from "../protyle/export/util";
9import {onWindowsMsg} from "../window/onWindowsMsg";
10import {initFocusFix} from "../protyle/util/compatibility";
11/// #endif
12import {Constants} from "../constants";
13import {appearance} from "../config/appearance";
14import {fetchPost, fetchSyncPost} from "../util/fetch";
15import {initAssets, setInlineStyle} from "../util/assets";
16import {renderSnippet} from "../config/util/snippets";
17import {openFile, openFileById} from "../editor/util";
18import {exitSiYuan} from "../dialog/processSystem";
19import {isWindow} from "../util/functions";
20import {initStatus} from "../layout/status";
21import {showMessage} from "../dialog/message";
22import {replaceLocalPath} from "../editor/rename";
23import {setTabPosition} from "../window/setHeader";
24import {initBar} from "../layout/topBar";
25import {openChangelog} from "./openChangelog";
26import {getIdFromSYProtocol, isSYProtocol} from "../util/pathName";
27import {App} from "../index";
28import {initWindowEvent} from "./globalEvent/event";
29import {sendGlobalShortcut} from "./globalEvent/keydown";
30import {closeWindow} from "../window/closeWin";
31import {checkFold} from "../util/noRelyPCFunction";
32import {correctHotkey} from "./globalEvent/commonHotkey";
33import {recordBeforeResizeTop} from "../protyle/util/resize";
34
35export const onGetConfig = (isStart: boolean, app: App) => {
36 correctHotkey(app);
37 /// #if !BROWSER
38 ipcRenderer.invoke(Constants.SIYUAN_INIT, {
39 languages: window.siyuan.languages["_trayMenu"],
40 workspaceDir: window.siyuan.config.system.workspaceDir,
41 port: location.port
42 });
43 webFrame.setZoomFactor(window.siyuan.storage[Constants.LOCAL_ZOOM]);
44 ipcRenderer.send(Constants.SIYUAN_CMD, {
45 cmd: "setTrafficLightPosition",
46 zoom: window.siyuan.storage[Constants.LOCAL_ZOOM],
47 position: Constants.SIZE_ZOOM.find((item) => item.zoom === window.siyuan.storage[Constants.LOCAL_ZOOM]).position
48 });
49 /// #endif
50 if (!window.siyuan.config.uiLayout || (window.siyuan.config.uiLayout && !window.siyuan.config.uiLayout.left)) {
51 window.siyuan.config.uiLayout = Constants.SIYUAN_EMPTY_LAYOUT;
52 }
53 initWindowEvent(app);
54 fetchPost("/api/system/getEmojiConf", {}, response => {
55 window.siyuan.emojis = response.data as IEmoji[];
56 try {
57 JSONToLayout(app, isStart);
58 setTimeout(() => {
59 adjustLayout();
60 }); // 等待 dock 中 !this.pin 的 setTimeout
61 /// #if !BROWSER
62 sendGlobalShortcut(app);
63 /// #endif
64 openChangelog();
65 } catch (e) {
66 resetLayout();
67 }
68 });
69 initBar(app);
70 initStatus();
71 initWindow(app);
72 /// #if !BROWSER
73 initFocusFix();
74 /// #endif
75 appearance.onSetAppearance(window.siyuan.config.appearance);
76 initAssets();
77 setInlineStyle();
78 renderSnippet();
79 let resizeTimeout = 0;
80 let firstResize = true;
81 window.addEventListener("resize", () => {
82 if (firstResize) {
83 recordBeforeResizeTop();
84 firstResize = false;
85 }
86 window.clearTimeout(resizeTimeout);
87 resizeTimeout = window.setTimeout(() => {
88 adjustLayout();
89 resizeTabs();
90 resizeTopBar();
91 firstResize = true;
92 }, 200);
93 });
94};
95
96const winOnMaxRestore = async () => {
97 /// #if !BROWSER
98 const maxBtnElement = document.getElementById("maxWindow");
99 const restoreBtnElement = document.getElementById("restoreWindow");
100 const isFullScreen = await ipcRenderer.invoke(Constants.SIYUAN_GET, {
101 cmd: "isFullScreen",
102 });
103 const isMaximized = await ipcRenderer.invoke(Constants.SIYUAN_GET, {
104 cmd: "isMaximized",
105 });
106 if (isMaximized || isFullScreen) {
107 restoreBtnElement.style.display = "flex";
108 maxBtnElement.style.display = "none";
109 } else {
110 restoreBtnElement.style.display = "none";
111 maxBtnElement.style.display = "flex";
112 }
113 /// #endif
114};
115
116export const initWindow = async (app: App) => {
117 /// #if !BROWSER
118 const winOnClose = (close = false) => {
119 exportLayout({
120 cb() {
121 if (window.siyuan.config.appearance.closeButtonBehavior === 1 && !close) {
122 // 最小化
123 if ("windows" === window.siyuan.config.system.os) {
124 ipcRenderer.send(Constants.SIYUAN_CONFIG_TRAY, {
125 languages: window.siyuan.languages["_trayMenu"],
126 });
127 } else {
128 ipcRenderer.send(Constants.SIYUAN_CMD, "closeButtonBehavior");
129 }
130 } else {
131 exitSiYuan();
132 }
133 },
134 errorExit: true
135 });
136 };
137
138 ipcRenderer.send(Constants.SIYUAN_EVENT);
139 ipcRenderer.on(Constants.SIYUAN_EVENT, (event, cmd) => {
140 if (cmd === "focus") {
141 // 由于 https://github.com/siyuan-note/siyuan/issues/10060 和新版 electron 应用切出再切进会保持光标,故移除 focus
142 window.siyuan.altIsPressed = false;
143 window.siyuan.ctrlIsPressed = false;
144 window.siyuan.shiftIsPressed = false;
145 document.body.classList.remove("body--blur");
146 } else if (cmd === "blur") {
147 document.body.classList.add("body--blur");
148 } else if (cmd === "enter-full-screen") {
149 if ("darwin" === window.siyuan.config.system.os) {
150 if (isWindow()) {
151 setTabPosition();
152 } else {
153 document.getElementById("toolbar").style.paddingLeft = "0";
154 }
155 } else {
156 winOnMaxRestore();
157 }
158 } else if (cmd === "leave-full-screen") {
159 if ("darwin" === window.siyuan.config.system.os) {
160 if (isWindow()) {
161 setTabPosition();
162 } else {
163 document.getElementById("toolbar").setAttribute("style", "");
164 }
165 } else {
166 winOnMaxRestore();
167 }
168 } else if (cmd === "maximize") {
169 winOnMaxRestore();
170 } else if (cmd === "unmaximize") {
171 winOnMaxRestore();
172 }
173 });
174 if (!isWindow()) {
175 ipcRenderer.on(Constants.SIYUAN_OPEN_URL, (event, url) => {
176 let urlObj: URL;
177 try {
178 urlObj = new URL(url);
179 if (urlObj.protocol !== "siyuan:") {
180 return;
181 }
182 } catch (error) {
183 return;
184 }
185 if (urlObj && urlObj.hostname === "plugins") {
186 const pluginNameType = urlObj.pathname.split("/")[1];
187 if (!pluginNameType) {
188 return;
189 }
190 app.plugins.find(plugin => {
191 if (pluginNameType.startsWith(plugin.name)) {
192 // siyuan://plugins/plugin-name/foo?bar=baz
193 plugin.eventBus.emit("open-siyuan-url-plugin", {url});
194
195 // https://github.com/siyuan-note/siyuan/pull/9256
196 if (pluginNameType.split("/")[0] !== plugin.name) {
197 // siyuan://plugins/plugin-samplecustom_tab?title=自定义页签&icon=iconFace&data={"text": "This is the custom plugin tab I opened via protocol."}
198 let data = urlObj.searchParams.get("data");
199 try {
200 data = JSON.parse(data || "{}");
201 } catch (e) {
202 console.log("Error open plugin tab with protocol:", e);
203 }
204 openFile({
205 app,
206 custom: {
207 title: urlObj.searchParams.get("title"),
208 icon: urlObj.searchParams.get("icon"),
209 data,
210 id: pluginNameType
211 },
212 });
213 }
214 return true;
215 }
216 });
217 return;
218 }
219 if (urlObj && isSYProtocol(url)) {
220 const id = getIdFromSYProtocol(url);
221 const focus = urlObj.searchParams.get("focus") === "1";
222 fetchPost("/api/block/checkBlockExist", {id}, existResponse => {
223 if (existResponse.data) {
224 checkFold(id, (zoomIn) => {
225 openFileById({
226 app,
227 id,
228 action: (zoomIn || focus) ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT, Constants.CB_GET_ROOTSCROLL],
229 zoomIn: zoomIn || focus
230 });
231 });
232 ipcRenderer.send(Constants.SIYUAN_CMD, "show");
233 }
234 app.plugins.forEach(plugin => {
235 plugin.eventBus.emit("open-siyuan-url-block", {
236 url,
237 id,
238 focus,
239 exist: existResponse.data,
240 });
241 });
242 });
243 return;
244 }
245 });
246 }
247 ipcRenderer.on(Constants.SIYUAN_OPEN_FILE, (event, data) => {
248 if (!data.app) {
249 data.app = app;
250 }
251 openFile(data);
252 });
253 ipcRenderer.on(Constants.SIYUAN_SAVE_CLOSE, (event, close) => {
254 if (isWindow()) {
255 closeWindow(app);
256 } else {
257 winOnClose(close);
258 }
259 });
260 ipcRenderer.on(Constants.SIYUAN_SEND_WINDOWS, (e, ipcData: IWebSocketData) => {
261 onWindowsMsg(ipcData);
262 });
263 ipcRenderer.on(Constants.SIYUAN_HOTKEY, (e, data) => {
264 let matchCommand = false;
265 app.plugins.find(item => {
266 item.commands.find(command => {
267 if (command.globalCallback && data.hotkey === command.customHotkey) {
268 matchCommand = true;
269 command.globalCallback();
270 return true;
271 }
272 });
273 if (matchCommand) {
274 return true;
275 }
276 });
277 });
278 ipcRenderer.on(Constants.SIYUAN_EXPORT_PDF, async (e, ipcData) => {
279 const msgId = showMessage(window.siyuan.languages.exporting, -1);
280 window.siyuan.storage[Constants.LOCAL_EXPORTPDF] = {
281 removeAssets: ipcData.removeAssets,
282 keepFold: ipcData.keepFold,
283 mergeSubdocs: ipcData.mergeSubdocs,
284 watermark: ipcData.watermark,
285 landscape: ipcData.pdfOptions.landscape,
286 marginType: ipcData.pdfOptions.marginType,
287 pageSize: ipcData.pdfOptions.pageSize,
288 scale: ipcData.pdfOptions.scale,
289 marginTop: ipcData.pdfOptions.margins.top,
290 marginRight: ipcData.pdfOptions.margins.right,
291 marginBottom: ipcData.pdfOptions.margins.bottom,
292 marginLeft: ipcData.pdfOptions.margins.left,
293 };
294 setStorageVal(Constants.LOCAL_EXPORTPDF, window.siyuan.storage[Constants.LOCAL_EXPORTPDF]);
295 try {
296 if (window.siyuan.config.export.pdfFooter.trim()) {
297 const response = await fetchSyncPost("/api/template/renderSprig", {template: window.siyuan.config.export.pdfFooter});
298 ipcData.pdfOptions.displayHeaderFooter = true;
299 ipcData.pdfOptions.headerTemplate = "<span></span>";
300 ipcData.pdfOptions.footerTemplate = `<div style="text-align:center;width:100%;font-size:10px;line-height:12px;">
301${response.data.replace("%pages", "<span class=totalPages></span>").replace("%page", "<span class=pageNumber></span>")}
302</div>`;
303 }
304 const pdfData = await ipcRenderer.invoke(Constants.SIYUAN_GET, {
305 cmd: "printToPDF",
306 pdfOptions: ipcData.pdfOptions,
307 webContentsId: ipcData.webContentsId
308 });
309 const savePath = ipcData.filePaths[0];
310 let pdfFilePath = path.join(savePath, replaceLocalPath(ipcData.rootTitle) + ".pdf");
311 const responseUnique = await fetchSyncPost("/api/file/getUniqueFilename", {path: pdfFilePath});
312 pdfFilePath = responseUnique.data.path;
313 fetchPost("/api/export/exportHTML", {
314 id: ipcData.rootId,
315 pdf: true,
316 removeAssets: ipcData.removeAssets,
317 merge: ipcData.mergeSubdocs,
318 savePath,
319 }, () => {
320 fs.writeFileSync(pdfFilePath, pdfData);
321 ipcRenderer.send(Constants.SIYUAN_CMD, {cmd: "destroy", webContentsId: ipcData.webContentsId});
322 fetchPost("/api/export/processPDF", {
323 id: ipcData.rootId,
324 merge: ipcData.mergeSubdocs,
325 path: pdfFilePath,
326 removeAssets: ipcData.removeAssets,
327 watermark: ipcData.watermark
328 }, async () => {
329 afterExport(pdfFilePath, msgId);
330 if (ipcData.removeAssets) {
331 const removePromise = (dir: string) => {
332 return new Promise(function (resolve) {
333 fs.stat(dir, function (err, stat) {
334 if (!stat) {
335 return;
336 }
337
338 if (stat.isDirectory()) {
339 fs.readdir(dir, function (err, files) {
340 files = files.map(file => path.join(dir, file)); // a/b a/m
341 Promise.all(files.map(file => removePromise(file))).then(function () {
342 fs.rm(dir, resolve);
343 });
344 });
345 } else {
346 fs.unlink(dir, resolve);
347 }
348 });
349 });
350 };
351
352 const assetsDir = path.join(savePath, "assets");
353 await removePromise(assetsDir);
354 if (1 > fs.readdirSync(assetsDir).length) {
355 fs.rmdirSync(assetsDir);
356 }
357 }
358 });
359 });
360 } catch (e) {
361 console.error(e);
362 showMessage(window.siyuan.languages.exportPDFLowMemory, 0, "error", msgId);
363 ipcRenderer.send(Constants.SIYUAN_CMD, {cmd: "destroy", webContentsId: ipcData.webContentsId});
364 }
365 ipcRenderer.send(Constants.SIYUAN_CMD, {cmd: "hide", webContentsId: ipcData.webContentsId});
366 });
367
368 if (isWindow()) {
369 document.body.insertAdjacentHTML("beforeend", `<div class="toolbar__window">
370<div class="toolbar__item ariaLabel" aria-label="${window.siyuan.languages.pin}" id="pinWindow">
371 <svg>
372 <use xlink:href="#iconPin"></use>
373 </svg>
374</div></div>`);
375 const pinElement = document.getElementById("pinWindow");
376 pinElement.addEventListener("click", () => {
377 if (pinElement.getAttribute("aria-label") === window.siyuan.languages.pin) {
378 pinElement.querySelector("use").setAttribute("xlink:href", "#iconUnpin");
379 pinElement.setAttribute("aria-label", window.siyuan.languages.unpin);
380 ipcRenderer.send(Constants.SIYUAN_CMD, "setAlwaysOnTopTrue");
381 } else {
382 pinElement.querySelector("use").setAttribute("xlink:href", "#iconPin");
383 pinElement.setAttribute("aria-label", window.siyuan.languages.pin);
384 ipcRenderer.send(Constants.SIYUAN_CMD, "setAlwaysOnTopFalse");
385 }
386 });
387 }
388 if ("darwin" !== window.siyuan.config.system.os) {
389 document.body.classList.add("body--win32");
390
391 // 添加窗口控件
392 const controlsHTML = `<div class="toolbar__item ariaLabel toolbar__item--win" aria-label="${window.siyuan.languages.min}" id="minWindow">
393 <svg>
394 <use xlink:href="#iconMin"></use>
395 </svg>
396</div>
397<div aria-label="${window.siyuan.languages.max}" class="ariaLabel toolbar__item toolbar__item--win" id="maxWindow">
398 <svg>
399 <use xlink:href="#iconMax"></use>
400 </svg>
401</div>
402<div aria-label="${window.siyuan.languages.restore}" class="ariaLabel toolbar__item toolbar__item--win" id="restoreWindow">
403 <svg>
404 <use xlink:href="#iconRestore"></use>
405 </svg>
406</div>
407<div aria-label="${window.siyuan.languages.close}" class="ariaLabel toolbar__item toolbar__item--close" id="closeWindow">
408 <svg>
409 <use xlink:href="#iconClose"></use>
410 </svg>
411</div>`;
412 if (isWindow()) {
413 document.querySelector(".toolbar__window").insertAdjacentHTML("beforeend", controlsHTML);
414 } else {
415 document.getElementById("windowControls").innerHTML = controlsHTML;
416 }
417 const maxBtnElement = document.getElementById("maxWindow");
418 const restoreBtnElement = document.getElementById("restoreWindow");
419
420 restoreBtnElement.addEventListener("click", () => {
421 ipcRenderer.send(Constants.SIYUAN_CMD, "restore");
422 });
423 maxBtnElement.addEventListener("click", () => {
424 ipcRenderer.send(Constants.SIYUAN_CMD, "maximize");
425 });
426
427 winOnMaxRestore();
428 const minBtnElement = document.getElementById("minWindow");
429 const closeBtnElement = document.getElementById("closeWindow");
430 minBtnElement.addEventListener("click", () => {
431 if (minBtnElement.classList.contains("window-controls__item--disabled")) {
432 return;
433 }
434 ipcRenderer.send(Constants.SIYUAN_CMD, "minimize");
435 });
436 closeBtnElement.addEventListener("click", () => {
437 if (isWindow()) {
438 closeWindow(app);
439 } else {
440 winOnClose();
441 }
442 });
443 } else {
444 const toolbarElement = document.getElementById("toolbar");
445 const isFullScreen = await ipcRenderer.invoke(Constants.SIYUAN_GET, {
446 cmd: "isFullScreen",
447 });
448 if (isFullScreen && !isWindow()) {
449 toolbarElement.style.paddingLeft = "0";
450 }
451 }
452 /// #else
453 if (!isWindow()) {
454 document.querySelector(".toolbar").classList.add("toolbar--browser");
455 }
456 /// #endif
457};