not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

137 lines
3.6 KiB

  1. import { Scope } from 'parchment';
  2. import Quill from '../core/quill';
  3. import Module from '../core/module';
  4. class History extends Module {
  5. constructor(quill, options) {
  6. super(quill, options);
  7. this.lastRecorded = 0;
  8. this.ignoreChange = false;
  9. this.clear();
  10. this.quill.on(
  11. Quill.events.EDITOR_CHANGE,
  12. (eventName, delta, oldDelta, source) => {
  13. if (eventName !== Quill.events.TEXT_CHANGE || this.ignoreChange) return;
  14. if (!this.options.userOnly || source === Quill.sources.USER) {
  15. this.record(delta, oldDelta);
  16. } else {
  17. this.transform(delta);
  18. }
  19. },
  20. );
  21. this.quill.keyboard.addBinding(
  22. { key: 'z', shortKey: true },
  23. this.undo.bind(this),
  24. );
  25. this.quill.keyboard.addBinding(
  26. { key: 'z', shortKey: true, shiftKey: true },
  27. this.redo.bind(this),
  28. );
  29. if (/Win/i.test(navigator.platform)) {
  30. this.quill.keyboard.addBinding(
  31. { key: 'y', shortKey: true },
  32. this.redo.bind(this),
  33. );
  34. }
  35. }
  36. change(source, dest) {
  37. if (this.stack[source].length === 0) return;
  38. const delta = this.stack[source].pop();
  39. const base = this.quill.getContents();
  40. const inverseDelta = delta.invert(base);
  41. this.stack[dest].push(inverseDelta);
  42. this.lastRecorded = 0;
  43. this.ignoreChange = true;
  44. this.quill.updateContents(delta, Quill.sources.USER);
  45. this.ignoreChange = false;
  46. const index = getLastChangeIndex(this.quill.scroll, delta);
  47. this.quill.setSelection(index);
  48. }
  49. clear() {
  50. this.stack = { undo: [], redo: [] };
  51. }
  52. cutoff() {
  53. this.lastRecorded = 0;
  54. }
  55. record(changeDelta, oldDelta) {
  56. if (changeDelta.ops.length === 0) return;
  57. this.stack.redo = [];
  58. let undoDelta = changeDelta.invert(oldDelta);
  59. const timestamp = Date.now();
  60. if (
  61. this.lastRecorded + this.options.delay > timestamp &&
  62. this.stack.undo.length > 0
  63. ) {
  64. const delta = this.stack.undo.pop();
  65. undoDelta = undoDelta.compose(delta);
  66. } else {
  67. this.lastRecorded = timestamp;
  68. }
  69. if (undoDelta.length() === 0) return;
  70. this.stack.undo.push(undoDelta);
  71. if (this.stack.undo.length > this.options.maxStack) {
  72. this.stack.undo.shift();
  73. }
  74. }
  75. redo() {
  76. this.change('redo', 'undo');
  77. }
  78. transform(delta) {
  79. transformStack(this.stack.undo, delta);
  80. transformStack(this.stack.redo, delta);
  81. }
  82. undo() {
  83. this.change('undo', 'redo');
  84. }
  85. }
  86. History.DEFAULTS = {
  87. delay: 1000,
  88. maxStack: 100,
  89. userOnly: false,
  90. };
  91. function transformStack(stack, delta) {
  92. let remoteDelta = delta;
  93. for (let i = stack.length - 1; i >= 0; i -= 1) {
  94. const oldDelta = stack[i];
  95. stack[i] = remoteDelta.transform(oldDelta, true);
  96. remoteDelta = oldDelta.transform(remoteDelta);
  97. if (stack[i].length() === 0) {
  98. stack.splice(i, 1);
  99. }
  100. }
  101. }
  102. function endsWithNewlineChange(scroll, delta) {
  103. const lastOp = delta.ops[delta.ops.length - 1];
  104. if (lastOp == null) return false;
  105. if (lastOp.insert != null) {
  106. return typeof lastOp.insert === 'string' && lastOp.insert.endsWith('\n');
  107. }
  108. if (lastOp.attributes != null) {
  109. return Object.keys(lastOp.attributes).some(attr => {
  110. return scroll.query(attr, Scope.BLOCK) != null;
  111. });
  112. }
  113. return false;
  114. }
  115. function getLastChangeIndex(scroll, delta) {
  116. const deleteLength = delta.reduce((length, op) => {
  117. return length + (op.delete || 0);
  118. }, 0);
  119. let changeIndex = delta.length() - deleteLength;
  120. if (endsWithNewlineChange(scroll, delta)) {
  121. changeIndex -= 1;
  122. }
  123. return changeIndex;
  124. }
  125. export { History as default, getLastChangeIndex };