A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 457 lines 19 kB view raw
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};