A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1import {onTransaction, transaction} from "../wysiwyg/transaction";
2import {preventScroll} from "../scroll/preventScroll";
3import {Constants} from "../../constants";
4import {hideElements} from "../ui/hideElements";
5import {scrollCenter} from "../../util/highlightById";
6import {matchHotKey} from "../util/hotKey";
7import {ipcRenderer} from "electron";
8
9interface IOperations {
10 doOperations: IOperation[],
11 undoOperations: IOperation[]
12}
13
14export class Undo {
15 private hasUndo = false;
16 public redoStack: IOperations[];
17 public undoStack: IOperations[];
18
19 constructor() {
20 this.redoStack = [];
21 this.undoStack = [];
22 }
23
24 public undo(protyle: IProtyle) {
25 if (protyle.disabled) {
26 return;
27 }
28 if (this.undoStack.length === 0) {
29 return;
30 }
31 const state = this.undoStack.pop();
32 this.render(protyle, state, false);
33 this.hasUndo = true;
34 this.redoStack.push(state);
35 if (protyle.breadcrumb) {
36 const undoElement = protyle.breadcrumb.element.parentElement.querySelector('[data-type="undo"]');
37 if (undoElement) {
38 if (this.undoStack.length === 0) {
39 undoElement.setAttribute("disabled", "true");
40 }
41 protyle.breadcrumb.element.parentElement.querySelector('[data-type="redo"]').removeAttribute("disabled");
42 }
43 }
44 }
45
46 public redo(protyle: IProtyle) {
47 if (protyle.disabled) {
48 return;
49 }
50 if (this.redoStack.length === 0) {
51 return;
52 }
53 const state = this.redoStack.pop();
54 this.render(protyle, state, true);
55 this.undoStack.push(state);
56 if (protyle.breadcrumb) {
57 const redoElement = protyle.breadcrumb.element.parentElement.querySelector('[data-type="redo"]');
58 if (redoElement) {
59 protyle.breadcrumb.element.parentElement.querySelector('[data-type="undo"]').removeAttribute("disabled");
60 if (this.redoStack.length === 0) {
61 redoElement.setAttribute("disabled", "true");
62 }
63 }
64 }
65 }
66
67 private render(protyle: IProtyle, state: IOperations, redo: boolean) {
68 hideElements(["hint", "gutter"], protyle);
69 protyle.wysiwyg.lastHTMLs = {};
70 if (!redo) {
71 state.undoOperations.forEach(item => {
72 onTransaction(protyle, item, true);
73 });
74 transaction(protyle, state.undoOperations);
75 } else {
76 state.doOperations.forEach(item => {
77 onTransaction(protyle, item, true);
78 });
79 transaction(protyle, state.doOperations);
80 }
81 document.querySelector(".av__panel")?.remove();
82 preventScroll(protyle);
83 scrollCenter(protyle);
84 }
85
86 public replace(doOperations: IOperation[], protyle: IProtyle) {
87 // undo 引发 replace 导致 stack 错误 https://github.com/siyuan-note/siyuan/issues/9178
88 if (this.hasUndo && this.redoStack.length > 0) {
89 this.undoStack.push(this.redoStack.pop());
90 this.redoStack = [];
91 this.hasUndo = false;
92 if (protyle.breadcrumb) {
93 const redoElement = protyle.breadcrumb.element.parentElement.querySelector('[data-type="redo"]');
94 if (redoElement) {
95 redoElement.setAttribute("disabled", "true");
96 }
97 }
98 }
99 if (this.undoStack.length > 0) {
100 this.undoStack[this.undoStack.length - 1].doOperations = doOperations;
101 }
102 }
103
104 public add(doOperations: IOperation[], undoOperations: IOperation[], protyle: IProtyle) {
105 this.undoStack.push({undoOperations, doOperations});
106 if (this.undoStack.length > Constants.SIZE_UNDO) {
107 this.undoStack.shift();
108 }
109 if (this.hasUndo) {
110 this.redoStack = [];
111 this.hasUndo = false;
112 }
113 if (protyle.breadcrumb) {
114 const undoElement = protyle.breadcrumb.element.parentElement.querySelector('[data-type="undo"]');
115 if (undoElement) {
116 undoElement.removeAttribute("disabled");
117 }
118 }
119 }
120
121 public clear() {
122 this.undoStack = [];
123 this.redoStack = [];
124 }
125}
126
127export const electronUndo = (event: KeyboardEvent) => {
128 /// #if !BROWSER
129 if (matchHotKey(window.siyuan.config.keymap.editor.general.undo.custom, event)) {
130 ipcRenderer.send(Constants.SIYUAN_CMD, "undo");
131 event.preventDefault();
132 event.stopPropagation();
133 return true;
134 }
135 if (matchHotKey(window.siyuan.config.keymap.editor.general.redo.custom, event)) {
136 ipcRenderer.send(Constants.SIYUAN_CMD, "redo");
137 event.preventDefault();
138 event.stopPropagation();
139 return true;
140 }
141 /// #endif
142 return false;
143};