scroll.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import { Scope, ScrollBlot, ContainerBlot } from 'parchment';
  2. import Emitter from '../core/emitter';
  3. import Block, { BlockEmbed } from './block';
  4. import Break from './break';
  5. import Container from './container';
  6. function isLine(blot) {
  7. return blot instanceof Block || blot instanceof BlockEmbed;
  8. }
  9. class Scroll extends ScrollBlot {
  10. constructor(registry, domNode, { emitter }) {
  11. super(registry, domNode);
  12. this.emitter = emitter;
  13. this.batch = false;
  14. this.optimize();
  15. this.enable();
  16. this.domNode.addEventListener('dragstart', e => this.handleDragStart(e));
  17. }
  18. batchStart() {
  19. if (!Array.isArray(this.batch)) {
  20. this.batch = [];
  21. }
  22. }
  23. batchEnd() {
  24. const mutations = this.batch;
  25. this.batch = false;
  26. this.update(mutations);
  27. }
  28. emitMount(blot) {
  29. this.emitter.emit(Emitter.events.SCROLL_BLOT_MOUNT, blot);
  30. }
  31. emitUnmount(blot) {
  32. this.emitter.emit(Emitter.events.SCROLL_BLOT_UNMOUNT, blot);
  33. }
  34. deleteAt(index, length) {
  35. const [first, offset] = this.line(index);
  36. const [last] = this.line(index + length);
  37. super.deleteAt(index, length);
  38. if (last != null && first !== last && offset > 0) {
  39. if (first instanceof BlockEmbed || last instanceof BlockEmbed) {
  40. this.optimize();
  41. return;
  42. }
  43. const ref =
  44. last.children.head instanceof Break ? null : last.children.head;
  45. first.moveChildren(last, ref);
  46. first.remove();
  47. }
  48. this.optimize();
  49. }
  50. enable(enabled = true) {
  51. this.domNode.setAttribute('contenteditable', enabled);
  52. }
  53. formatAt(index, length, format, value) {
  54. super.formatAt(index, length, format, value);
  55. this.optimize();
  56. }
  57. handleDragStart(event) {
  58. event.preventDefault();
  59. }
  60. insertAt(index, value, def) {
  61. if (index >= this.length()) {
  62. if (def == null || this.scroll.query(value, Scope.BLOCK) == null) {
  63. const blot = this.scroll.create(this.statics.defaultChild.blotName);
  64. this.appendChild(blot);
  65. if (def == null && value.endsWith('\n')) {
  66. blot.insertAt(0, value.slice(0, -1), def);
  67. } else {
  68. blot.insertAt(0, value, def);
  69. }
  70. } else {
  71. const embed = this.scroll.create(value, def);
  72. this.appendChild(embed);
  73. }
  74. } else {
  75. super.insertAt(index, value, def);
  76. }
  77. this.optimize();
  78. }
  79. insertBefore(blot, ref) {
  80. if (blot.statics.scope === Scope.INLINE_BLOT) {
  81. const wrapper = this.scroll.create(this.statics.defaultChild.blotName);
  82. wrapper.appendChild(blot);
  83. super.insertBefore(wrapper, ref);
  84. } else {
  85. super.insertBefore(blot, ref);
  86. }
  87. }
  88. isEnabled() {
  89. return this.domNode.getAttribute('contenteditable') === 'true';
  90. }
  91. leaf(index) {
  92. return this.path(index).pop() || [null, -1];
  93. }
  94. line(index) {
  95. if (index === this.length()) {
  96. return this.line(index - 1);
  97. }
  98. return this.descendant(isLine, index);
  99. }
  100. lines(index = 0, length = Number.MAX_VALUE) {
  101. const getLines = (blot, blotIndex, blotLength) => {
  102. let lines = [];
  103. let lengthLeft = blotLength;
  104. blot.children.forEachAt(
  105. blotIndex,
  106. blotLength,
  107. (child, childIndex, childLength) => {
  108. if (isLine(child)) {
  109. lines.push(child);
  110. } else if (child instanceof ContainerBlot) {
  111. lines = lines.concat(getLines(child, childIndex, lengthLeft));
  112. }
  113. lengthLeft -= childLength;
  114. },
  115. );
  116. return lines;
  117. };
  118. return getLines(this, index, length);
  119. }
  120. optimize(mutations = [], context = {}) {
  121. if (this.batch) return;
  122. super.optimize(mutations, context);
  123. if (mutations.length > 0) {
  124. this.emitter.emit(Emitter.events.SCROLL_OPTIMIZE, mutations, context);
  125. }
  126. }
  127. path(index) {
  128. return super.path(index).slice(1); // Exclude self
  129. }
  130. remove() {
  131. // Never remove self
  132. }
  133. update(mutations) {
  134. if (this.batch) {
  135. if (Array.isArray(mutations)) {
  136. this.batch = this.batch.concat(mutations);
  137. }
  138. return;
  139. }
  140. let source = Emitter.sources.USER;
  141. if (typeof mutations === 'string') {
  142. source = mutations;
  143. }
  144. if (!Array.isArray(mutations)) {
  145. mutations = this.observer.takeRecords();
  146. }
  147. if (mutations.length > 0) {
  148. this.emitter.emit(Emitter.events.SCROLL_BEFORE_UPDATE, source, mutations);
  149. }
  150. super.update(mutations.concat([])); // pass copy
  151. if (mutations.length > 0) {
  152. this.emitter.emit(Emitter.events.SCROLL_UPDATE, source, mutations);
  153. }
  154. }
  155. }
  156. Scroll.blotName = 'scroll';
  157. Scroll.className = 'ql-editor';
  158. Scroll.tagName = 'DIV';
  159. Scroll.defaultChild = Block;
  160. Scroll.allowedChildren = [Block, BlockEmbed, Container];
  161. export default Scroll;