processSystem.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. import {Constants} from "../constants";
  2. import {fetchPost} from "../util/fetch";
  3. /// #if !MOBILE
  4. import {exportLayout} from "../layout/util";
  5. /// #endif
  6. /// #if !BROWSER
  7. import {ipcRenderer} from "electron";
  8. /// #endif
  9. import {hideMessage, showMessage} from "./message";
  10. import {Dialog} from "./index";
  11. import {isMobile} from "../util/functions";
  12. import {confirmDialog} from "./confirmDialog";
  13. import {escapeHtml} from "../util/escape";
  14. import {getWorkspaceName} from "../util/noRelyPCFunction";
  15. import {needSubscribe} from "../util/needSubscribe";
  16. import {redirectToCheckAuth, setNoteBook} from "../util/pathName";
  17. import {getAllModels} from "../layout/getAll";
  18. import {reloadProtyle} from "../protyle/util/reload";
  19. import {Tab} from "../layout/Tab";
  20. import {setEmpty} from "../mobile/util/setEmpty";
  21. import {hideAllElements, hideElements} from "../protyle/ui/hideElements";
  22. import {App} from "../index";
  23. import {saveScroll} from "../protyle/scroll/saveScroll";
  24. import {isInAndroid, isInIOS, setStorageVal} from "../protyle/util/compatibility";
  25. import {Plugin} from "../plugin";
  26. const updateTitle = (rootID: string, tab: Tab, protyle?: IProtyle) => {
  27. fetchPost("/api/block/getDocInfo", {
  28. id: rootID
  29. }, (response) => {
  30. tab.updateTitle(response.data.name);
  31. if (protyle && protyle.title) {
  32. protyle.title.setTitle(response.data.name);
  33. }
  34. });
  35. };
  36. export const reloadSync = (app: App, data: { upsertRootIDs: string[], removeRootIDs: string[] }) => {
  37. hideMessage();
  38. /// #if MOBILE
  39. if (window.siyuan.mobile.popEditor) {
  40. if (data.removeRootIDs.includes(window.siyuan.mobile.popEditor.protyle.block.rootID)) {
  41. hideElements(["dialog"]);
  42. } else {
  43. reloadProtyle(window.siyuan.mobile.popEditor.protyle, false);
  44. }
  45. }
  46. if (window.siyuan.mobile.editor) {
  47. if (data.removeRootIDs.includes(window.siyuan.mobile.editor.protyle.block.rootID)) {
  48. setEmpty(app);
  49. } else {
  50. reloadProtyle(window.siyuan.mobile.editor.protyle, false);
  51. fetchPost("/api/block/getDocInfo", {
  52. id: window.siyuan.mobile.editor.protyle.block.rootID
  53. }, (response) => {
  54. setTitle(response.data.name);
  55. (document.getElementById("toolbarName") as HTMLInputElement).value = response.data.name === "Untitled" ? "" : response.data.name;
  56. });
  57. }
  58. }
  59. setNoteBook(() => {
  60. window.siyuan.mobile.files.init(false);
  61. });
  62. /// #else
  63. const allModels = getAllModels();
  64. allModels.editor.forEach(item => {
  65. if (data.upsertRootIDs.includes(item.editor.protyle.block.rootID)) {
  66. reloadProtyle(item.editor.protyle, false);
  67. updateTitle(item.editor.protyle.block.rootID, item.parent, item.editor.protyle);
  68. } else if (data.removeRootIDs.includes(item.editor.protyle.block.rootID)) {
  69. item.parent.parent.removeTab(item.parent.id, false, false);
  70. delete window.siyuan.storage[Constants.LOCAL_FILEPOSITION][item.editor.protyle.block.rootID];
  71. setStorageVal(Constants.LOCAL_FILEPOSITION, window.siyuan.storage[Constants.LOCAL_FILEPOSITION]);
  72. }
  73. });
  74. allModels.graph.forEach(item => {
  75. if (item.type === "local" && data.removeRootIDs.includes(item.rootId)) {
  76. item.parent.parent.removeTab(item.parent.id, false, false);
  77. } else if (item.type !== "local" || data.upsertRootIDs.includes(item.rootId)) {
  78. item.searchGraph(false);
  79. if (item.type === "local") {
  80. updateTitle(item.rootId, item.parent);
  81. }
  82. }
  83. });
  84. allModels.outline.forEach(item => {
  85. if (item.type === "local" && data.removeRootIDs.includes(item.blockId)) {
  86. item.parent.parent.removeTab(item.parent.id, false, false);
  87. } else if (item.type !== "local" || data.upsertRootIDs.includes(item.blockId)) {
  88. fetchPost("/api/outline/getDocOutline", {
  89. id: item.blockId,
  90. }, response => {
  91. item.update(response);
  92. });
  93. if (item.type === "local") {
  94. updateTitle(item.blockId, item.parent);
  95. }
  96. }
  97. });
  98. allModels.backlink.forEach(item => {
  99. if (item.type === "local" && data.removeRootIDs.includes(item.rootId)) {
  100. item.parent.parent.removeTab(item.parent.id, false, false);
  101. } else {
  102. item.refresh();
  103. if (item.type === "local") {
  104. updateTitle(item.rootId, item.parent);
  105. }
  106. }
  107. });
  108. allModels.files.forEach(item => {
  109. setNoteBook(() => {
  110. item.init(false);
  111. });
  112. });
  113. allModels.bookmark.forEach(item => {
  114. item.update();
  115. });
  116. allModels.tag.forEach(item => {
  117. item.update();
  118. });
  119. // NOTE asset 无法获取推送地址,先不处理
  120. allModels.search.forEach(item => {
  121. item.parent.panelElement.querySelector("#searchInput").dispatchEvent(new CustomEvent("input"));
  122. });
  123. allModels.custom.forEach(item => {
  124. if (item.update) {
  125. item.update();
  126. }
  127. });
  128. /// #endif
  129. };
  130. export const lockScreen = (app: App) => {
  131. if (window.siyuan.config.readonly) {
  132. return;
  133. }
  134. app.plugins.forEach(item => {
  135. item.eventBus.emit("lock-screen");
  136. });
  137. /// #if BROWSER
  138. fetchPost("/api/system/logoutAuth", {}, () => {
  139. redirectToCheckAuth();
  140. });
  141. /// #else
  142. ipcRenderer.send(Constants.SIYUAN_SEND_WINDOWS, {cmd: "lockscreen"});
  143. /// #endif
  144. };
  145. export const kernelError = () => {
  146. if (document.querySelector("#errorLog")) {
  147. return;
  148. }
  149. let iosReStart = "";
  150. if (isInIOS()) {
  151. iosReStart = `<div class="fn__hr"></div><div class="fn__flex"><div class="fn__flex-1"></div><button class="b3-button">${window.siyuan.languages.retry}</button></div>`;
  152. }
  153. const dialog = new Dialog({
  154. disableClose: true,
  155. title: `💔 ${window.siyuan.languages.kernelFault0} <small>v${Constants.SIYUAN_VERSION}</small>`,
  156. width: isMobile() ? "92vw" : "520px",
  157. content: `<div class="b3-dialog__content">
  158. <div class="ft__breakword">
  159. <div>${window.siyuan.languages.kernelFault1}</div>
  160. <div class="fn__hr"></div>
  161. <div>${window.siyuan.languages.kernelFault2}</div>
  162. ${iosReStart}
  163. </div>
  164. </div>`
  165. });
  166. dialog.element.id = "errorLog";
  167. dialog.element.setAttribute("data-key", Constants.DIALOG_ERRORKERNELFAULT);
  168. const restartElement = dialog.element.querySelector(".b3-button");
  169. if (restartElement) {
  170. restartElement.addEventListener("click", () => {
  171. dialog.destroy();
  172. window.webkit.messageHandlers.startKernelFast.postMessage("startKernelFast");
  173. });
  174. }
  175. };
  176. export const exitSiYuan = () => {
  177. hideAllElements(["util"]);
  178. /// #if MOBILE
  179. if (window.siyuan.mobile.editor) {
  180. saveScroll(window.siyuan.mobile.editor.protyle);
  181. }
  182. /// #endif
  183. fetchPost("/api/system/exit", {force: false}, (response) => {
  184. if (response.code === 1) { // 同步执行失败
  185. const msgId = showMessage(response.msg, response.data.closeTimeout, "error");
  186. const buttonElement = document.querySelector(`#message [data-id="${msgId}"] button`);
  187. if (buttonElement) {
  188. buttonElement.addEventListener("click", () => {
  189. fetchPost("/api/system/exit", {force: true}, () => {
  190. /// #if !BROWSER
  191. ipcRenderer.send(Constants.SIYUAN_QUIT, location.port);
  192. /// #else
  193. if (isInIOS() || isInAndroid()) {
  194. window.location.href = "siyuan://api/system/exit";
  195. }
  196. /// #endif
  197. });
  198. });
  199. }
  200. } else if (response.code === 2) { // 提示新安装包
  201. hideMessage();
  202. confirmDialog(window.siyuan.languages.tip, response.msg, () => {
  203. fetchPost("/api/system/exit", {
  204. force: true,
  205. execInstallPkg: 2 // 0:默认检查新版本,1:不执行新版本安装,2:执行新版本安装
  206. }, () => {
  207. /// #if !BROWSER
  208. // 桌面端退出拉起更新安装时有时需要重启两次 https://github.com/siyuan-note/siyuan/issues/6544
  209. // 这里先将主界面隐藏
  210. setTimeout(() => {
  211. ipcRenderer.send(Constants.SIYUAN_CMD, "hide");
  212. }, 2000);
  213. // 然后等待一段时间后再退出,避免界面主进程退出以后内核子进程被杀死
  214. setTimeout(() => {
  215. ipcRenderer.send(Constants.SIYUAN_QUIT, location.port);
  216. }, 4000);
  217. /// #endif
  218. });
  219. }, () => {
  220. fetchPost("/api/system/exit", {
  221. force: true,
  222. execInstallPkg: 1 // 0:默认检查新版本,1:不执行新版本安装,2:执行新版本安装
  223. }, () => {
  224. /// #if !BROWSER
  225. ipcRenderer.send(Constants.SIYUAN_QUIT, location.port);
  226. /// #endif
  227. });
  228. });
  229. } else { // 正常退出
  230. /// #if !BROWSER
  231. ipcRenderer.send(Constants.SIYUAN_QUIT, location.port);
  232. /// #else
  233. if (isInIOS() || isInAndroid()) {
  234. window.location.href = "siyuan://api/system/exit";
  235. }
  236. /// #endif
  237. }
  238. });
  239. };
  240. export const transactionError = () => {
  241. if (document.getElementById("transactionError")) {
  242. return;
  243. }
  244. const dialog = new Dialog({
  245. disableClose: true,
  246. title: `${window.siyuan.languages.stateExcepted} v${Constants.SIYUAN_VERSION}`,
  247. content: `<div class="b3-dialog__content" id="transactionError">${window.siyuan.languages.rebuildIndexTip}</div>
  248. <div class="b3-dialog__action">
  249. <button class="b3-button b3-button--text">${window.siyuan.languages._kernel[97]}</button>
  250. <div class="fn__space"></div>
  251. <button class="b3-button">${window.siyuan.languages.rebuildIndex}</button>
  252. </div>`,
  253. width: isMobile() ? "92vw" : "520px",
  254. });
  255. dialog.element.setAttribute("data-key", Constants.DIALOG_ERRORSTATEEXCEPTED);
  256. const btnsElement = dialog.element.querySelectorAll(".b3-button");
  257. btnsElement[0].addEventListener("click", () => {
  258. /// #if MOBILE
  259. exitSiYuan();
  260. /// #else
  261. exportLayout({
  262. errorExit: true,
  263. cb: exitSiYuan
  264. });
  265. /// #endif
  266. });
  267. btnsElement[1].addEventListener("click", () => {
  268. fetchPost("/api/filetree/refreshFiletree", {});
  269. dialog.destroy();
  270. });
  271. };
  272. export const progressStatus = (data: IWebSocketData) => {
  273. const statusElement = document.querySelector("#status") as HTMLElement;
  274. if (!statusElement) {
  275. return;
  276. }
  277. if (isMobile()) {
  278. if (!document.querySelector("#keyboardToolbar").classList.contains("fn__none")) {
  279. return;
  280. }
  281. statusElement.innerHTML = data.msg;
  282. statusElement.classList.remove("status--hide");
  283. statusElement.style.bottom = "0";
  284. return;
  285. }
  286. const msgElement = statusElement.querySelector(".status__msg");
  287. if (msgElement) {
  288. msgElement.innerHTML = data.msg;
  289. }
  290. };
  291. export const progressLoading = (data: IWebSocketData) => {
  292. let progressElement = document.getElementById("progress");
  293. if (!progressElement) {
  294. document.body.insertAdjacentHTML("beforeend", `<div id="progress" style="z-index: ${++window.siyuan.zIndex}"></div>`);
  295. progressElement = document.getElementById("progress");
  296. }
  297. // code 0: 有进度;1: 无进度;2: 关闭
  298. if (data.code === 2) {
  299. progressElement.remove();
  300. return;
  301. }
  302. if (data.code === 0) {
  303. progressElement.innerHTML = `<div class="b3-dialog__scrim" style="opacity: 1"></div>
  304. <div style="position: fixed;top: 45vh;width: 70vw;left: 15vw;color:var(--b3-theme-on-surface);">
  305. <div style="text-align: right">${data.data.current}/${data.data.total}</div>
  306. <div style="margin: 8px 0;height: 8px;border-radius: var(--b3-border-radius);overflow: hidden;background-color:#fff;"><div style="width: ${data.data.current / data.data.total * 100}%;transition: var(--b3-transition);background-color: var(--b3-theme-primary);height: 8px;"></div></div>
  307. <div>${data.msg}</div>
  308. </div>`;
  309. } else if (data.code === 1) {
  310. if (progressElement.lastElementChild) {
  311. progressElement.lastElementChild.lastElementChild.innerHTML = data.msg;
  312. } else {
  313. progressElement.innerHTML = `<div class="b3-dialog__scrim" style="opacity: 1"></div>
  314. <div style="position: fixed;top: 45vh;width: 70vw;left: 15vw;color:var(--b3-theme-on-surface);">
  315. <div style="margin: 8px 0;height: 8px;border-radius: var(--b3-border-radius);overflow: hidden;background-color:#fff;"><div style="background-color: var(--b3-theme-primary);height: 8px;background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);animation: stripMove 450ms linear infinite;background-size: 50px 50px;"></div></div>
  316. <div>${data.msg}</div>
  317. </div>`;
  318. }
  319. }
  320. };
  321. export const progressBackgroundTask = (tasks: { action: string }[]) => {
  322. const backgroundTaskElement = document.querySelector(".status__backgroundtask");
  323. if (!backgroundTaskElement) {
  324. return;
  325. }
  326. if (tasks.length === 0) {
  327. backgroundTaskElement.classList.add("fn__none");
  328. if (!window.siyuan.menus.menu.element.classList.contains("fn__none") &&
  329. window.siyuan.menus.menu.element.getAttribute("data-name") === "statusBackgroundTask") {
  330. window.siyuan.menus.menu.remove();
  331. }
  332. } else {
  333. backgroundTaskElement.classList.remove("fn__none");
  334. backgroundTaskElement.setAttribute("data-tasks", JSON.stringify(tasks));
  335. backgroundTaskElement.innerHTML = tasks[0].action + "<div><div></div></div>";
  336. }
  337. };
  338. export const bootSync = () => {
  339. fetchPost("/api/sync/getBootSync", {}, response => {
  340. if (response.code === 1) {
  341. const dialog = new Dialog({
  342. width: isMobile() ? "92vw" : "50vw",
  343. title: "🌩️ " + window.siyuan.languages.bootSyncFailed,
  344. content: `<div class="b3-dialog__content">${response.msg}</div>
  345. <div class="b3-dialog__action">
  346. <button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
  347. <button class="b3-button b3-button--text">${window.siyuan.languages.syncNow}</button>
  348. </div>`
  349. });
  350. dialog.element.setAttribute("data-key", Constants.DIALOG_ERRORBOOTSYNCFAILED);
  351. const btnsElement = dialog.element.querySelectorAll(".b3-button");
  352. btnsElement[0].addEventListener("click", () => {
  353. dialog.destroy();
  354. });
  355. btnsElement[1].addEventListener("click", () => {
  356. if (btnsElement[1].getAttribute("disabled")) {
  357. return;
  358. }
  359. btnsElement[1].setAttribute("disabled", "disabled");
  360. fetchPost("/api/sync/performBootSync", {}, (syncResponse) => {
  361. if (syncResponse.code === 0) {
  362. dialog.destroy();
  363. }
  364. btnsElement[1].removeAttribute("disabled");
  365. });
  366. });
  367. }
  368. });
  369. };
  370. export const setTitle = (title: string) => {
  371. const dragElement = document.getElementById("drag");
  372. const workspaceName = getWorkspaceName();
  373. if (title === window.siyuan.languages.siyuanNote) {
  374. const versionTitle = `${workspaceName} - ${window.siyuan.languages.siyuanNote} v${Constants.SIYUAN_VERSION}`;
  375. document.title = versionTitle;
  376. if (dragElement) {
  377. dragElement.textContent = versionTitle;
  378. dragElement.setAttribute("title", versionTitle);
  379. }
  380. } else {
  381. title = title || "Untitled";
  382. document.title = `${title} - ${workspaceName} - ${window.siyuan.languages.siyuanNote} v${Constants.SIYUAN_VERSION}`;
  383. if (!dragElement) {
  384. return;
  385. }
  386. dragElement.setAttribute("title", title);
  387. dragElement.innerHTML = escapeHtml(title);
  388. }
  389. };
  390. export const downloadProgress = (data: { id: string, percent: number }) => {
  391. const bazzarSideElement = document.querySelector("#configBazaarReadme .item__side");
  392. if (!bazzarSideElement) {
  393. return;
  394. }
  395. if (data.id !== JSON.parse(bazzarSideElement.getAttribute("data-obj")).repoURL) {
  396. return;
  397. }
  398. const btnElement = bazzarSideElement.querySelector('[data-type="install"]') as HTMLElement;
  399. if (btnElement) {
  400. if (data.percent >= 1) {
  401. btnElement.parentElement.classList.add("fn__none");
  402. btnElement.parentElement.nextElementSibling.classList.add("fn__none");
  403. } else {
  404. btnElement.classList.add("b3-button--progress");
  405. btnElement.parentElement.nextElementSibling.firstElementChild.classList.add("b3-button--progress");
  406. btnElement.innerHTML = `<span style="width: ${data.percent * 100}%"></span>`;
  407. btnElement.parentElement.nextElementSibling.firstElementChild.innerHTML = `<span style="width: ${data.percent * 100}%"></span>`;
  408. }
  409. }
  410. };
  411. export const processSync = (data?: IWebSocketData, plugins?: Plugin[]) => {
  412. /// #if MOBILE
  413. const menuSyncUseElement = document.querySelector("#menuSyncNow use");
  414. const barSyncUseElement = document.querySelector("#toolbarSync use");
  415. if (!data) {
  416. if (!window.siyuan.config.sync.enabled || (0 === window.siyuan.config.sync.provider && needSubscribe(""))) {
  417. menuSyncUseElement?.setAttribute("xlink:href", "#iconCloudOff");
  418. barSyncUseElement.setAttribute("xlink:href", "#iconCloudOff");
  419. } else {
  420. menuSyncUseElement?.setAttribute("xlink:href", "#iconCloudSucc");
  421. barSyncUseElement.setAttribute("xlink:href", "#iconCloudSucc");
  422. }
  423. return;
  424. }
  425. menuSyncUseElement?.parentElement.classList.remove("fn__rotate");
  426. barSyncUseElement.parentElement.classList.remove("fn__rotate");
  427. if (data.code === 0) { // syncing
  428. menuSyncUseElement?.parentElement.classList.add("fn__rotate");
  429. barSyncUseElement.parentElement.classList.add("fn__rotate");
  430. menuSyncUseElement?.setAttribute("xlink:href", "#iconRefresh");
  431. barSyncUseElement.setAttribute("xlink:href", "#iconRefresh");
  432. } else if (data.code === 2) { // error
  433. menuSyncUseElement?.setAttribute("xlink:href", "#iconCloudError");
  434. barSyncUseElement.setAttribute("xlink:href", "#iconCloudError");
  435. } else if (data.code === 1) { // success
  436. menuSyncUseElement?.setAttribute("xlink:href", "#iconCloudSucc");
  437. barSyncUseElement.setAttribute("xlink:href", "#iconCloudSucc");
  438. }
  439. /// #else
  440. const iconElement = document.querySelector("#barSync");
  441. if (!iconElement) {
  442. return;
  443. }
  444. const useElement = iconElement.querySelector("use");
  445. if (!data) {
  446. iconElement.classList.remove("toolbar__item--active");
  447. if (!window.siyuan.config.sync.enabled || (0 === window.siyuan.config.sync.provider && needSubscribe(""))) {
  448. useElement.setAttribute("xlink:href", "#iconCloudOff");
  449. } else {
  450. useElement.setAttribute("xlink:href", "#iconCloudSucc");
  451. }
  452. return;
  453. }
  454. iconElement.firstElementChild.classList.remove("fn__rotate");
  455. if (data.code === 0) { // syncing
  456. iconElement.classList.add("toolbar__item--active");
  457. iconElement.firstElementChild.classList.add("fn__rotate");
  458. useElement.setAttribute("xlink:href", "#iconRefresh");
  459. } else if (data.code === 2) { // error
  460. iconElement.classList.remove("toolbar__item--active");
  461. useElement.setAttribute("xlink:href", "#iconCloudError");
  462. } else if (data.code === 1) { // success
  463. iconElement.classList.remove("toolbar__item--active");
  464. useElement.setAttribute("xlink:href", "#iconCloudSucc");
  465. }
  466. /// #endif
  467. plugins.forEach((item) => {
  468. if (data.code === 0) {
  469. item.eventBus.emit("sync-start", data);
  470. } else if (data.code === 1) {
  471. item.eventBus.emit("sync-end", data);
  472. } else if (data.code === 2) {
  473. item.eventBus.emit("sync-fail", data);
  474. }
  475. });
  476. };