processSystem.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. import {Constants} from "../constants";
  2. import {fetchPost} from "../util/fetch";
  3. /// #if !MOBILE
  4. import {exportLayout} from "../layout/util";
  5. /// #endif
  6. import {getAllEditor, getAllModels} from "../layout/getAll";
  7. import {getDockByType} from "../layout/tabUtil";
  8. import {Files} from "../layout/dock/Files";
  9. /// #if !BROWSER
  10. import {ipcRenderer} from "electron";
  11. /// #endif
  12. import {hideMessage, showMessage} from "./message";
  13. import {Dialog} from "./index";
  14. import {isMobile} from "../util/functions";
  15. import {confirmDialog} from "./confirmDialog";
  16. import {escapeHtml} from "../util/escape";
  17. import {getWorkspaceName} from "../util/noRelyPCFunction";
  18. import {needSubscribe} from "../util/needSubscribe";
  19. import {redirectToCheckAuth, setNoteBook} from "../util/pathName";
  20. import {reloadProtyle} from "../protyle/util/reload";
  21. import {Tab} from "../layout/Tab";
  22. import {setEmpty} from "../mobile/util/setEmpty";
  23. import {hideAllElements, hideElements} from "../protyle/ui/hideElements";
  24. import {App} from "../index";
  25. import {saveScroll} from "../protyle/scroll/saveScroll";
  26. import {isInAndroid, isInIOS, setStorageVal} from "../protyle/util/compatibility";
  27. import {Plugin} from "../plugin";
  28. const updateTitle = (rootID: string, tab: Tab, protyle?: IProtyle) => {
  29. fetchPost("/api/block/getDocInfo", {
  30. id: rootID
  31. }, (response) => {
  32. tab.updateTitle(response.data.name);
  33. if (protyle && protyle.title) {
  34. protyle.title.setTitle(response.data.name);
  35. }
  36. });
  37. };
  38. export const reloadSync = (
  39. app: App,
  40. data: { upsertRootIDs: string[], removeRootIDs: string[] },
  41. hideMsg = true,
  42. // 同步的时候需要更新只读状态 https://github.com/siyuan-note/siyuan/issues/11517
  43. // 调整大纲的时候需要使用现有状态 https://github.com/siyuan-note/siyuan/issues/11808
  44. updateReadonly = true
  45. ) => {
  46. if (hideMsg) {
  47. hideMessage();
  48. }
  49. /// #if MOBILE
  50. if (window.siyuan.mobile.popEditor) {
  51. if (data.removeRootIDs.includes(window.siyuan.mobile.popEditor.protyle.block.rootID)) {
  52. hideElements(["dialog"]);
  53. } else {
  54. reloadProtyle(window.siyuan.mobile.popEditor.protyle, false, updateReadonly);
  55. }
  56. }
  57. if (window.siyuan.mobile.editor) {
  58. if (data.removeRootIDs.includes(window.siyuan.mobile.editor.protyle.block.rootID)) {
  59. setEmpty(app);
  60. } else {
  61. reloadProtyle(window.siyuan.mobile.editor.protyle, false, updateReadonly);
  62. fetchPost("/api/block/getDocInfo", {
  63. id: window.siyuan.mobile.editor.protyle.block.rootID
  64. }, (response) => {
  65. setTitle(response.data.name);
  66. window.siyuan.mobile.editor.protyle.title.setTitle(response.data.name);
  67. });
  68. }
  69. }
  70. setNoteBook(() => {
  71. window.siyuan.mobile.files.init(false);
  72. });
  73. /// #else
  74. const allModels = getAllModels();
  75. allModels.editor.forEach(item => {
  76. if (data.upsertRootIDs.includes(item.editor.protyle.block.rootID)) {
  77. fetchPost("/api/block/getDocInfo", {
  78. id: item.editor.protyle.block.rootID,
  79. }, (response) => {
  80. item.editor.protyle.wysiwyg.renderCustom(response.data.ial);
  81. reloadProtyle(item.editor.protyle, false, updateReadonly);
  82. updateTitle(item.editor.protyle.block.rootID, item.parent, item.editor.protyle);
  83. });
  84. } else if (data.removeRootIDs.includes(item.editor.protyle.block.rootID)) {
  85. item.parent.parent.removeTab(item.parent.id, false, false);
  86. delete window.siyuan.storage[Constants.LOCAL_FILEPOSITION][item.editor.protyle.block.rootID];
  87. setStorageVal(Constants.LOCAL_FILEPOSITION, window.siyuan.storage[Constants.LOCAL_FILEPOSITION]);
  88. }
  89. });
  90. allModels.graph.forEach(item => {
  91. if (item.type === "local" && data.removeRootIDs.includes(item.rootId)) {
  92. item.parent.parent.removeTab(item.parent.id, false, false);
  93. } else if (item.type !== "local" || data.upsertRootIDs.includes(item.rootId)) {
  94. item.searchGraph(false);
  95. if (item.type === "local") {
  96. updateTitle(item.rootId, item.parent);
  97. }
  98. }
  99. });
  100. allModels.outline.forEach(item => {
  101. if (item.type === "local" && data.removeRootIDs.includes(item.blockId)) {
  102. item.parent.parent.removeTab(item.parent.id, false, false);
  103. } else if (item.type !== "local" || data.upsertRootIDs.includes(item.blockId)) {
  104. fetchPost("/api/outline/getDocOutline", {
  105. id: item.blockId,
  106. }, response => {
  107. item.update(response);
  108. });
  109. if (item.type === "local") {
  110. updateTitle(item.blockId, item.parent);
  111. }
  112. }
  113. });
  114. allModels.backlink.forEach(item => {
  115. if (item.type === "local" && data.removeRootIDs.includes(item.rootId)) {
  116. item.parent.parent.removeTab(item.parent.id, false, false);
  117. } else {
  118. item.refresh();
  119. if (item.type === "local") {
  120. updateTitle(item.rootId, item.parent);
  121. }
  122. }
  123. });
  124. allModels.files.forEach(item => {
  125. setNoteBook(() => {
  126. item.init(false);
  127. });
  128. });
  129. allModels.bookmark.forEach(item => {
  130. item.update();
  131. });
  132. allModels.tag.forEach(item => {
  133. item.update();
  134. });
  135. // NOTE asset 无法获取推送地址,先不处理
  136. allModels.search.forEach(item => {
  137. item.parent.panelElement.querySelector("#searchInput").dispatchEvent(new CustomEvent("input"));
  138. });
  139. allModels.custom.forEach(item => {
  140. if (item.update) {
  141. item.update();
  142. }
  143. });
  144. /// #endif
  145. };
  146. export const setRefDynamicText = (data: {
  147. "blockID": string,
  148. "defBlockID": string,
  149. "refText": string,
  150. "rootID": string
  151. }) => {
  152. getAllEditor().forEach(item => {
  153. // 不能对比 rootId,否则潜入块中的锚文本无法更新
  154. item.protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${data.blockID}"] span[data-type="block-ref"][data-subtype="d"][data-id="${data.defBlockID}"]`).forEach(item => {
  155. item.innerHTML = data.refText;
  156. });
  157. })
  158. }
  159. export const setDefRefCount = (data: {
  160. "blockID": string,
  161. "refCount": number,
  162. "rootRefCount": number,
  163. "rootID": string
  164. refIDs: string[]
  165. }) => {
  166. getAllEditor().forEach(item => {
  167. if (data.rootID === data.blockID && item.protyle.block.rootID === data.rootID) {
  168. const attrElement = item.protyle.title.element.querySelector(".protyle-attr")
  169. const countElement = attrElement.querySelector('.popover__block')
  170. if (countElement) {
  171. if (data.refCount === 0) {
  172. countElement.remove()
  173. } else {
  174. countElement.textContent = data.refCount.toString();
  175. countElement.setAttribute("data-id", data.refIDs.toString())
  176. }
  177. } else if (data.refCount > 0) {
  178. attrElement.insertAdjacentHTML("beforeend", `<div class="protyle-attr--refcount popover__block" data-defids="[&quot;${data.blockID}&quot;]" data-id="${data.refIDs.toString()}" style="">${data.refCount}</div>`)
  179. }
  180. return;
  181. }
  182. // 不能对比 rootId,否则潜入块中的锚文本无法更新
  183. item.protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${data.blockID}"]`).forEach(item => {
  184. const countElement = item.querySelector('.protyle-attr--refcount')
  185. if (countElement) {
  186. if (data.refCount === 0) {
  187. countElement.remove()
  188. } else {
  189. countElement.textContent = data.refCount.toString();
  190. }
  191. } else if (data.refCount > 0) {
  192. const attrElement = item.querySelector('.protyle-attr')
  193. if (attrElement.childElementCount > 0) {
  194. attrElement.lastElementChild.insertAdjacentHTML("afterend", `<div class="protyle-attr--refcount popover__block">${data.refCount}</div>`)
  195. } else {
  196. attrElement.innerHTML = `<div class="protyle-attr--refcount popover__block">${data.refCount}</div>${Constants.ZWSP}`
  197. }
  198. }
  199. });
  200. })
  201. let liElement;
  202. /// #if MOBILE
  203. liElement = window.siyuan.mobile.files.element.querySelector(`li[data-node-id="${data.rootID}"]`)
  204. /// #else
  205. liElement = (getDockByType("file").data.file as Files).element.querySelector(`li[data-node-id="${data.rootID}"]`)
  206. /// #endif
  207. if (liElement) {
  208. const counterElement = liElement.querySelector(".counter")
  209. if (counterElement) {
  210. if (data.rootRefCount === 0) {
  211. counterElement.remove()
  212. } else {
  213. counterElement.textContent = data.rootRefCount.toString()
  214. }
  215. } else if (data.rootRefCount > 0) {
  216. liElement.insertAdjacentHTML("beforeend", `<span class="popover__block counter b3-tooltips b3-tooltips__nw" aria-label="${window.siyuan.languages.ref}">${data.rootRefCount}</span>`)
  217. }
  218. }
  219. }
  220. export const lockScreen = (app: App) => {
  221. if (window.siyuan.config.readonly) {
  222. return;
  223. }
  224. app.plugins.forEach(item => {
  225. item.eventBus.emit("lock-screen");
  226. });
  227. /// #if BROWSER
  228. fetchPost("/api/system/logoutAuth", {}, () => {
  229. redirectToCheckAuth();
  230. });
  231. /// #else
  232. ipcRenderer.send(Constants.SIYUAN_SEND_WINDOWS, {cmd: "lockscreen"});
  233. /// #endif
  234. };
  235. export const kernelError = () => {
  236. if (document.querySelector("#errorLog")) {
  237. return;
  238. }
  239. let iosReStart = "";
  240. if (isInIOS()) {
  241. 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>`;
  242. }
  243. const dialog = new Dialog({
  244. disableClose: true,
  245. title: `💔 ${window.siyuan.languages.kernelFault0} <small>v${Constants.SIYUAN_VERSION}</small>`,
  246. width: isMobile() ? "92vw" : "520px",
  247. content: `<div class="b3-dialog__content">
  248. <div class="ft__breakword">
  249. <div>${window.siyuan.languages.kernelFault1}</div>
  250. <div class="fn__hr"></div>
  251. <div>${window.siyuan.languages.kernelFault2}</div>
  252. ${iosReStart}
  253. </div>
  254. </div>`
  255. });
  256. dialog.element.id = "errorLog";
  257. dialog.element.setAttribute("data-key", Constants.DIALOG_KERNELFAULT);
  258. const restartElement = dialog.element.querySelector(".b3-button");
  259. if (restartElement) {
  260. restartElement.addEventListener("click", () => {
  261. dialog.destroy();
  262. window.webkit.messageHandlers.startKernelFast.postMessage("startKernelFast");
  263. });
  264. }
  265. };
  266. export const exitSiYuan = () => {
  267. hideAllElements(["util"]);
  268. /// #if MOBILE
  269. if (window.siyuan.mobile.editor) {
  270. saveScroll(window.siyuan.mobile.editor.protyle);
  271. }
  272. /// #endif
  273. fetchPost("/api/system/exit", {force: false}, (response) => {
  274. if (response.code === 1) { // 同步执行失败
  275. const msgId = showMessage(response.msg, response.data.closeTimeout, "error");
  276. const buttonElement = document.querySelector(`#message [data-id="${msgId}"] button`);
  277. if (buttonElement) {
  278. buttonElement.addEventListener("click", () => {
  279. fetchPost("/api/system/exit", {force: true}, () => {
  280. /// #if !BROWSER
  281. ipcRenderer.send(Constants.SIYUAN_QUIT, location.port);
  282. /// #else
  283. if (isInIOS() || isInAndroid()) {
  284. window.location.href = "siyuan://api/system/exit";
  285. }
  286. /// #endif
  287. });
  288. });
  289. }
  290. } else if (response.code === 2) { // 提示新安装包
  291. hideMessage();
  292. confirmDialog(window.siyuan.languages.tip, response.msg, () => {
  293. fetchPost("/api/system/exit", {
  294. force: true,
  295. execInstallPkg: 2 // 0:默认检查新版本,1:不执行新版本安装,2:执行新版本安装
  296. }, () => {
  297. /// #if !BROWSER
  298. // 桌面端退出拉起更新安装时有时需要重启两次 https://github.com/siyuan-note/siyuan/issues/6544
  299. // 这里先将主界面隐藏
  300. setTimeout(() => {
  301. ipcRenderer.send(Constants.SIYUAN_CMD, "hide");
  302. }, 2000);
  303. // 然后等待一段时间后再退出,避免界面主进程退出以后内核子进程被杀死
  304. setTimeout(() => {
  305. ipcRenderer.send(Constants.SIYUAN_QUIT, location.port);
  306. }, 4000);
  307. /// #endif
  308. });
  309. }, () => {
  310. fetchPost("/api/system/exit", {
  311. force: true,
  312. execInstallPkg: 1 // 0:默认检查新版本,1:不执行新版本安装,2:执行新版本安装
  313. }, () => {
  314. /// #if !BROWSER
  315. ipcRenderer.send(Constants.SIYUAN_QUIT, location.port);
  316. /// #endif
  317. });
  318. });
  319. } else { // 正常退出
  320. /// #if !BROWSER
  321. ipcRenderer.send(Constants.SIYUAN_QUIT, location.port);
  322. /// #else
  323. if (isInIOS() || isInAndroid()) {
  324. window.location.href = "siyuan://api/system/exit";
  325. }
  326. /// #endif
  327. }
  328. });
  329. };
  330. export const transactionError = () => {
  331. if (document.getElementById("transactionError")) {
  332. return;
  333. }
  334. const dialog = new Dialog({
  335. disableClose: true,
  336. title: `${window.siyuan.languages.stateExcepted} v${Constants.SIYUAN_VERSION}`,
  337. content: `<div class="b3-dialog__content" id="transactionError">${window.siyuan.languages.rebuildIndexTip}</div>
  338. <div class="b3-dialog__action">
  339. <button class="b3-button b3-button--text">${window.siyuan.languages._kernel[97]}</button>
  340. <div class="fn__space"></div>
  341. <button class="b3-button">${window.siyuan.languages.rebuildIndex}</button>
  342. </div>`,
  343. width: isMobile() ? "92vw" : "520px",
  344. });
  345. dialog.element.setAttribute("data-key", Constants.DIALOG_STATEEXCEPTED);
  346. const btnsElement = dialog.element.querySelectorAll(".b3-button");
  347. btnsElement[0].addEventListener("click", () => {
  348. /// #if MOBILE
  349. exitSiYuan();
  350. /// #else
  351. exportLayout({
  352. errorExit: true,
  353. cb: exitSiYuan
  354. });
  355. /// #endif
  356. });
  357. btnsElement[1].addEventListener("click", () => {
  358. refreshFileTree();
  359. dialog.destroy();
  360. });
  361. };
  362. export const refreshFileTree = (cb?: () => void) => {
  363. window.siyuan.storage[Constants.LOCAL_FILEPOSITION] = {};
  364. setStorageVal(Constants.LOCAL_FILEPOSITION, window.siyuan.storage[Constants.LOCAL_FILEPOSITION]);
  365. fetchPost("/api/filetree/refreshFiletree", {}, () => {
  366. if (cb) {
  367. cb();
  368. }
  369. });
  370. };
  371. let statusTimeout: number;
  372. export const progressStatus = (data: IWebSocketData) => {
  373. const statusElement = document.querySelector("#status") as HTMLElement;
  374. if (!statusElement) {
  375. return;
  376. }
  377. if (isMobile()) {
  378. if (!document.querySelector("#keyboardToolbar").classList.contains("fn__none")) {
  379. return;
  380. }
  381. clearTimeout(statusTimeout);
  382. statusElement.innerHTML = data.msg;
  383. statusElement.style.bottom = "0";
  384. statusTimeout = window.setTimeout(() => {
  385. statusElement.style.bottom = "";
  386. }, 7000);
  387. } else {
  388. const msgElement = statusElement.querySelector(".status__msg");
  389. if (msgElement) {
  390. clearTimeout(statusTimeout);
  391. msgElement.innerHTML = data.msg;
  392. statusTimeout = window.setTimeout(() => {
  393. msgElement.innerHTML = "";
  394. }, 7000);
  395. }
  396. }
  397. };
  398. export const progressLoading = (data: IWebSocketData) => {
  399. let progressElement = document.getElementById("progress");
  400. if (!progressElement) {
  401. document.body.insertAdjacentHTML("beforeend", `<div id="progress" style="z-index: ${++window.siyuan.zIndex}"></div>`);
  402. progressElement = document.getElementById("progress");
  403. }
  404. // code 0: 有进度;1: 无进度;2: 关闭
  405. if (data.code === 2) {
  406. progressElement.remove();
  407. return;
  408. }
  409. if (data.code === 0) {
  410. progressElement.innerHTML = `<div class="b3-dialog__scrim" style="opacity: 1"></div>
  411. <div class="b3-dialog__loading">
  412. <div style="text-align: right">${data.data.current}/${data.data.total}</div>
  413. <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>
  414. <div>${data.msg}</div>
  415. </div>`;
  416. } else if (data.code === 1) {
  417. if (progressElement.lastElementChild) {
  418. progressElement.lastElementChild.lastElementChild.innerHTML = data.msg;
  419. } else {
  420. progressElement.innerHTML = `<div class="b3-dialog__scrim" style="opacity: 1"></div>
  421. <div class="b3-dialog__loading">
  422. <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>
  423. <div>${data.msg}</div>
  424. </div>`;
  425. }
  426. }
  427. };
  428. export const progressBackgroundTask = (tasks: { action: string }[]) => {
  429. const backgroundTaskElement = document.querySelector(".status__backgroundtask");
  430. if (!backgroundTaskElement) {
  431. return;
  432. }
  433. if (tasks.length === 0) {
  434. backgroundTaskElement.classList.add("fn__none");
  435. if (!window.siyuan.menus.menu.element.classList.contains("fn__none") &&
  436. window.siyuan.menus.menu.element.getAttribute("data-name") === "statusBackgroundTask") {
  437. window.siyuan.menus.menu.remove();
  438. }
  439. } else {
  440. backgroundTaskElement.classList.remove("fn__none");
  441. backgroundTaskElement.setAttribute("data-tasks", JSON.stringify(tasks));
  442. backgroundTaskElement.innerHTML = tasks[0].action + "<div><div></div></div>";
  443. }
  444. };
  445. export const bootSync = () => {
  446. fetchPost("/api/sync/getBootSync", {}, response => {
  447. if (response.code === 1) {
  448. const dialog = new Dialog({
  449. width: isMobile() ? "92vw" : "50vw",
  450. title: "🌩️ " + window.siyuan.languages.bootSyncFailed,
  451. content: `<div class="b3-dialog__content">${response.msg}</div>
  452. <div class="b3-dialog__action">
  453. <button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
  454. <button class="b3-button b3-button--text">${window.siyuan.languages.syncNow}</button>
  455. </div>`
  456. });
  457. dialog.element.setAttribute("data-key", Constants.DIALOG_BOOTSYNCFAILED);
  458. const btnsElement = dialog.element.querySelectorAll(".b3-button");
  459. btnsElement[0].addEventListener("click", () => {
  460. dialog.destroy();
  461. });
  462. btnsElement[1].addEventListener("click", () => {
  463. if (btnsElement[1].getAttribute("disabled")) {
  464. return;
  465. }
  466. btnsElement[1].setAttribute("disabled", "disabled");
  467. fetchPost("/api/sync/performBootSync", {}, (syncResponse) => {
  468. if (syncResponse.code === 0) {
  469. dialog.destroy();
  470. }
  471. btnsElement[1].removeAttribute("disabled");
  472. });
  473. });
  474. }
  475. });
  476. };
  477. export const setTitle = (title: string) => {
  478. const dragElement = document.getElementById("drag");
  479. const workspaceName = getWorkspaceName();
  480. if (title === window.siyuan.languages.siyuanNote) {
  481. const versionTitle = `${workspaceName} - ${window.siyuan.languages.siyuanNote} v${Constants.SIYUAN_VERSION}`;
  482. document.title = versionTitle;
  483. if (dragElement) {
  484. dragElement.textContent = versionTitle;
  485. dragElement.setAttribute("title", versionTitle);
  486. }
  487. } else {
  488. title = title || window.siyuan.languages.untitled;
  489. document.title = `${title} - ${workspaceName} - ${window.siyuan.languages.siyuanNote} v${Constants.SIYUAN_VERSION}`;
  490. if (!dragElement) {
  491. return;
  492. }
  493. dragElement.setAttribute("title", title);
  494. dragElement.innerHTML = escapeHtml(title);
  495. }
  496. };
  497. export const downloadProgress = (data: { id: string, percent: number }) => {
  498. const bazzarSideElement = document.querySelector("#configBazaarReadme .item__side");
  499. if (!bazzarSideElement) {
  500. return;
  501. }
  502. if (data.id !== JSON.parse(bazzarSideElement.getAttribute("data-obj")).repoURL) {
  503. return;
  504. }
  505. const btnElement = bazzarSideElement.querySelector('[data-type="install"]') as HTMLElement;
  506. if (btnElement) {
  507. if (data.percent >= 1) {
  508. btnElement.parentElement.classList.add("fn__none");
  509. btnElement.parentElement.nextElementSibling.classList.add("fn__none");
  510. } else {
  511. btnElement.classList.add("b3-button--progress");
  512. btnElement.parentElement.nextElementSibling.firstElementChild.classList.add("b3-button--progress");
  513. btnElement.innerHTML = `<span style="width: ${data.percent * 100}%"></span>`;
  514. btnElement.parentElement.nextElementSibling.firstElementChild.innerHTML = `<span style="width: ${data.percent * 100}%"></span>`;
  515. }
  516. }
  517. };
  518. export const processSync = (data?: IWebSocketData, plugins?: Plugin[]) => {
  519. /// #if MOBILE
  520. const menuSyncUseElement = document.querySelector("#menuSyncNow use");
  521. const barSyncUseElement = document.querySelector("#toolbarSync use");
  522. if (!data) {
  523. if (!window.siyuan.config.sync.enabled || (0 === window.siyuan.config.sync.provider && needSubscribe(""))) {
  524. menuSyncUseElement?.setAttribute("xlink:href", "#iconCloudOff");
  525. barSyncUseElement.setAttribute("xlink:href", "#iconCloudOff");
  526. } else {
  527. menuSyncUseElement?.setAttribute("xlink:href", "#iconCloudSucc");
  528. barSyncUseElement.setAttribute("xlink:href", "#iconCloudSucc");
  529. }
  530. return;
  531. }
  532. menuSyncUseElement?.parentElement.classList.remove("fn__rotate");
  533. barSyncUseElement.parentElement.classList.remove("fn__rotate");
  534. if (data.code === 0) { // syncing
  535. menuSyncUseElement?.parentElement.classList.add("fn__rotate");
  536. barSyncUseElement.parentElement.classList.add("fn__rotate");
  537. menuSyncUseElement?.setAttribute("xlink:href", "#iconRefresh");
  538. barSyncUseElement.setAttribute("xlink:href", "#iconRefresh");
  539. } else if (data.code === 2) { // error
  540. menuSyncUseElement?.setAttribute("xlink:href", "#iconCloudError");
  541. barSyncUseElement.setAttribute("xlink:href", "#iconCloudError");
  542. } else if (data.code === 1) { // success
  543. menuSyncUseElement?.setAttribute("xlink:href", "#iconCloudSucc");
  544. barSyncUseElement.setAttribute("xlink:href", "#iconCloudSucc");
  545. }
  546. /// #else
  547. const iconElement = document.querySelector("#barSync");
  548. if (!iconElement) {
  549. return;
  550. }
  551. const useElement = iconElement.querySelector("use");
  552. if (!data) {
  553. iconElement.classList.remove("toolbar__item--active");
  554. if (!window.siyuan.config.sync.enabled || (0 === window.siyuan.config.sync.provider && needSubscribe(""))) {
  555. useElement.setAttribute("xlink:href", "#iconCloudOff");
  556. } else {
  557. useElement.setAttribute("xlink:href", "#iconCloudSucc");
  558. }
  559. return;
  560. }
  561. iconElement.firstElementChild.classList.remove("fn__rotate");
  562. if (data.code === 0) { // syncing
  563. iconElement.classList.add("toolbar__item--active");
  564. iconElement.firstElementChild.classList.add("fn__rotate");
  565. useElement.setAttribute("xlink:href", "#iconRefresh");
  566. } else if (data.code === 2) { // error
  567. iconElement.classList.remove("toolbar__item--active");
  568. useElement.setAttribute("xlink:href", "#iconCloudError");
  569. } else if (data.code === 1) { // success
  570. iconElement.classList.remove("toolbar__item--active");
  571. useElement.setAttribute("xlink:href", "#iconCloudSucc");
  572. }
  573. /// #endif
  574. plugins.forEach((item) => {
  575. if (data.code === 0) {
  576. item.eventBus.emit("sync-start", data);
  577. } else if (data.code === 1) {
  578. item.eventBus.emit("sync-end", data);
  579. } else if (data.code === 2) {
  580. item.eventBus.emit("sync-fail", data);
  581. }
  582. });
  583. };