1 | import { ContainerBlot, LeafBlot, Scope, ScrollBlot } from 'parchment';
|
2 | import Delta, { AttributeMap, Op } from 'quill-delta';
|
3 | import Emitter from '../core/emitter.js';
|
4 | import Block, { BlockEmbed, bubbleFormats } from './block.js';
|
5 | import Break from './break.js';
|
6 | import Container from './container.js';
|
7 | function isLine(blot) {
|
8 | return blot instanceof Block || blot instanceof BlockEmbed;
|
9 | }
|
10 | function isUpdatable(blot) {
|
11 | return typeof blot.updateContent === 'function';
|
12 | }
|
13 | class Scroll extends ScrollBlot {
|
14 | static blotName = 'scroll';
|
15 | static className = 'ql-editor';
|
16 | static tagName = 'DIV';
|
17 | static defaultChild = Block;
|
18 | static allowedChildren = [Block, BlockEmbed, Container];
|
19 | constructor(registry, domNode, _ref) {
|
20 | let {
|
21 | emitter
|
22 | } = _ref;
|
23 | super(registry, domNode);
|
24 | this.emitter = emitter;
|
25 | this.batch = false;
|
26 | this.optimize();
|
27 | this.enable();
|
28 | this.domNode.addEventListener('dragstart', e => this.handleDragStart(e));
|
29 | }
|
30 | batchStart() {
|
31 | if (!Array.isArray(this.batch)) {
|
32 | this.batch = [];
|
33 | }
|
34 | }
|
35 | batchEnd() {
|
36 | if (!this.batch) return;
|
37 | const mutations = this.batch;
|
38 | this.batch = false;
|
39 | this.update(mutations);
|
40 | }
|
41 | emitMount(blot) {
|
42 | this.emitter.emit(Emitter.events.SCROLL_BLOT_MOUNT, blot);
|
43 | }
|
44 | emitUnmount(blot) {
|
45 | this.emitter.emit(Emitter.events.SCROLL_BLOT_UNMOUNT, blot);
|
46 | }
|
47 | emitEmbedUpdate(blot, change) {
|
48 | this.emitter.emit(Emitter.events.SCROLL_EMBED_UPDATE, blot, change);
|
49 | }
|
50 | deleteAt(index, length) {
|
51 | const [first, offset] = this.line(index);
|
52 | const [last] = this.line(index + length);
|
53 | super.deleteAt(index, length);
|
54 | if (last != null && first !== last && offset > 0) {
|
55 | if (first instanceof BlockEmbed || last instanceof BlockEmbed) {
|
56 | this.optimize();
|
57 | return;
|
58 | }
|
59 | const ref = last.children.head instanceof Break ? null : last.children.head;
|
60 |
|
61 | first.moveChildren(last, ref);
|
62 |
|
63 | first.remove();
|
64 | }
|
65 | this.optimize();
|
66 | }
|
67 | enable() {
|
68 | let enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
69 | this.domNode.setAttribute('contenteditable', enabled ? 'true' : 'false');
|
70 | }
|
71 | formatAt(index, length, format, value) {
|
72 | super.formatAt(index, length, format, value);
|
73 | this.optimize();
|
74 | }
|
75 | insertAt(index, value, def) {
|
76 | if (index >= this.length()) {
|
77 | if (def == null || this.scroll.query(value, Scope.BLOCK) == null) {
|
78 | const blot = this.scroll.create(this.statics.defaultChild.blotName);
|
79 | this.appendChild(blot);
|
80 | if (def == null && value.endsWith('\n')) {
|
81 | blot.insertAt(0, value.slice(0, -1), def);
|
82 | } else {
|
83 | blot.insertAt(0, value, def);
|
84 | }
|
85 | } else {
|
86 | const embed = this.scroll.create(value, def);
|
87 | this.appendChild(embed);
|
88 | }
|
89 | } else {
|
90 | super.insertAt(index, value, def);
|
91 | }
|
92 | this.optimize();
|
93 | }
|
94 | insertBefore(blot, ref) {
|
95 | if (blot.statics.scope === Scope.INLINE_BLOT) {
|
96 | const wrapper = this.scroll.create(this.statics.defaultChild.blotName);
|
97 | wrapper.appendChild(blot);
|
98 | super.insertBefore(wrapper, ref);
|
99 | } else {
|
100 | super.insertBefore(blot, ref);
|
101 | }
|
102 | }
|
103 | insertContents(index, delta) {
|
104 | const renderBlocks = this.deltaToRenderBlocks(delta.concat(new Delta().insert('\n')));
|
105 | const last = renderBlocks.pop();
|
106 | if (last == null) return;
|
107 | this.batchStart();
|
108 | const first = renderBlocks.shift();
|
109 | if (first) {
|
110 | const shouldInsertNewlineChar = first.type === 'block' && (first.delta.length() === 0 || !this.descendant(BlockEmbed, index)[0] && index < this.length());
|
111 | const delta = first.type === 'block' ? first.delta : new Delta().insert({
|
112 | [first.key]: first.value
|
113 | });
|
114 | insertInlineContents(this, index, delta);
|
115 | const newlineCharLength = first.type === 'block' ? 1 : 0;
|
116 | const lineEndIndex = index + delta.length() + newlineCharLength;
|
117 | if (shouldInsertNewlineChar) {
|
118 | this.insertAt(lineEndIndex - 1, '\n');
|
119 | }
|
120 | const formats = bubbleFormats(this.line(index)[0]);
|
121 | const attributes = AttributeMap.diff(formats, first.attributes) || {};
|
122 | Object.keys(attributes).forEach(name => {
|
123 | this.formatAt(lineEndIndex - 1, 1, name, attributes[name]);
|
124 | });
|
125 | index = lineEndIndex;
|
126 | }
|
127 | let [refBlot, refBlotOffset] = this.children.find(index);
|
128 | if (renderBlocks.length) {
|
129 | if (refBlot) {
|
130 | refBlot = refBlot.split(refBlotOffset);
|
131 | refBlotOffset = 0;
|
132 | }
|
133 | renderBlocks.forEach(renderBlock => {
|
134 | if (renderBlock.type === 'block') {
|
135 | const block = this.createBlock(renderBlock.attributes, refBlot || undefined);
|
136 | insertInlineContents(block, 0, renderBlock.delta);
|
137 | } else {
|
138 | const blockEmbed = this.create(renderBlock.key, renderBlock.value);
|
139 | this.insertBefore(blockEmbed, refBlot || undefined);
|
140 | Object.keys(renderBlock.attributes).forEach(name => {
|
141 | blockEmbed.format(name, renderBlock.attributes[name]);
|
142 | });
|
143 | }
|
144 | });
|
145 | }
|
146 | if (last.type === 'block' && last.delta.length()) {
|
147 | const offset = refBlot ? refBlot.offset(refBlot.scroll) + refBlotOffset : this.length();
|
148 | insertInlineContents(this, offset, last.delta);
|
149 | }
|
150 | this.batchEnd();
|
151 | this.optimize();
|
152 | }
|
153 | isEnabled() {
|
154 | return this.domNode.getAttribute('contenteditable') === 'true';
|
155 | }
|
156 | leaf(index) {
|
157 | const last = this.path(index).pop();
|
158 | if (!last) {
|
159 | return [null, -1];
|
160 | }
|
161 | const [blot, offset] = last;
|
162 | return blot instanceof LeafBlot ? [blot, offset] : [null, -1];
|
163 | }
|
164 | line(index) {
|
165 | if (index === this.length()) {
|
166 | return this.line(index - 1);
|
167 | }
|
168 |
|
169 | return this.descendant(isLine, index);
|
170 | }
|
171 | lines() {
|
172 | let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
173 | let length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Number.MAX_VALUE;
|
174 | const getLines = (blot, blotIndex, blotLength) => {
|
175 | let lines = [];
|
176 | let lengthLeft = blotLength;
|
177 | blot.children.forEachAt(blotIndex, blotLength, (child, childIndex, childLength) => {
|
178 | if (isLine(child)) {
|
179 | lines.push(child);
|
180 | } else if (child instanceof ContainerBlot) {
|
181 | lines = lines.concat(getLines(child, childIndex, lengthLeft));
|
182 | }
|
183 | lengthLeft -= childLength;
|
184 | });
|
185 | return lines;
|
186 | };
|
187 | return getLines(this, index, length);
|
188 | }
|
189 | optimize() {
|
190 | let mutations = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
191 | let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
192 | if (this.batch) return;
|
193 | super.optimize(mutations, context);
|
194 | if (mutations.length > 0) {
|
195 | this.emitter.emit(Emitter.events.SCROLL_OPTIMIZE, mutations, context);
|
196 | }
|
197 | }
|
198 | path(index) {
|
199 | return super.path(index).slice(1);
|
200 | }
|
201 | remove() {
|
202 |
|
203 | }
|
204 | update(mutations) {
|
205 | if (this.batch) {
|
206 | if (Array.isArray(mutations)) {
|
207 | this.batch = this.batch.concat(mutations);
|
208 | }
|
209 | return;
|
210 | }
|
211 | let source = Emitter.sources.USER;
|
212 | if (typeof mutations === 'string') {
|
213 | source = mutations;
|
214 | }
|
215 | if (!Array.isArray(mutations)) {
|
216 | mutations = this.observer.takeRecords();
|
217 | }
|
218 | mutations = mutations.filter(_ref2 => {
|
219 | let {
|
220 | target
|
221 | } = _ref2;
|
222 | const blot = this.find(target, true);
|
223 | return blot && !isUpdatable(blot);
|
224 | });
|
225 | if (mutations.length > 0) {
|
226 | this.emitter.emit(Emitter.events.SCROLL_BEFORE_UPDATE, source, mutations);
|
227 | }
|
228 | super.update(mutations.concat([]));
|
229 | if (mutations.length > 0) {
|
230 | this.emitter.emit(Emitter.events.SCROLL_UPDATE, source, mutations);
|
231 | }
|
232 | }
|
233 | updateEmbedAt(index, key, change) {
|
234 |
|
235 |
|
236 | const [blot] = this.descendant(b => b instanceof BlockEmbed, index);
|
237 | if (blot && blot.statics.blotName === key && isUpdatable(blot)) {
|
238 | blot.updateContent(change);
|
239 | }
|
240 | }
|
241 | handleDragStart(event) {
|
242 | event.preventDefault();
|
243 | }
|
244 | deltaToRenderBlocks(delta) {
|
245 | const renderBlocks = [];
|
246 | let currentBlockDelta = new Delta();
|
247 | delta.forEach(op => {
|
248 | const insert = op?.insert;
|
249 | if (!insert) return;
|
250 | if (typeof insert === 'string') {
|
251 | const splitted = insert.split('\n');
|
252 | splitted.slice(0, -1).forEach(text => {
|
253 | currentBlockDelta.insert(text, op.attributes);
|
254 | renderBlocks.push({
|
255 | type: 'block',
|
256 | delta: currentBlockDelta,
|
257 | attributes: op.attributes ?? {}
|
258 | });
|
259 | currentBlockDelta = new Delta();
|
260 | });
|
261 | const last = splitted[splitted.length - 1];
|
262 | if (last) {
|
263 | currentBlockDelta.insert(last, op.attributes);
|
264 | }
|
265 | } else {
|
266 | const key = Object.keys(insert)[0];
|
267 | if (!key) return;
|
268 | if (this.query(key, Scope.INLINE)) {
|
269 | currentBlockDelta.push(op);
|
270 | } else {
|
271 | if (currentBlockDelta.length()) {
|
272 | renderBlocks.push({
|
273 | type: 'block',
|
274 | delta: currentBlockDelta,
|
275 | attributes: {}
|
276 | });
|
277 | }
|
278 | currentBlockDelta = new Delta();
|
279 | renderBlocks.push({
|
280 | type: 'blockEmbed',
|
281 | key,
|
282 | value: insert[key],
|
283 | attributes: op.attributes ?? {}
|
284 | });
|
285 | }
|
286 | }
|
287 | });
|
288 | if (currentBlockDelta.length()) {
|
289 | renderBlocks.push({
|
290 | type: 'block',
|
291 | delta: currentBlockDelta,
|
292 | attributes: {}
|
293 | });
|
294 | }
|
295 | return renderBlocks;
|
296 | }
|
297 | createBlock(attributes, refBlot) {
|
298 | let blotName;
|
299 | const formats = {};
|
300 | Object.entries(attributes).forEach(_ref3 => {
|
301 | let [key, value] = _ref3;
|
302 | const isBlockBlot = this.query(key, Scope.BLOCK & Scope.BLOT) != null;
|
303 | if (isBlockBlot) {
|
304 | blotName = key;
|
305 | } else {
|
306 | formats[key] = value;
|
307 | }
|
308 | });
|
309 | const block = this.create(blotName || this.statics.defaultChild.blotName, blotName ? attributes[blotName] : undefined);
|
310 | this.insertBefore(block, refBlot || undefined);
|
311 | const length = block.length();
|
312 | Object.entries(formats).forEach(_ref4 => {
|
313 | let [key, value] = _ref4;
|
314 | block.formatAt(0, length, key, value);
|
315 | });
|
316 | return block;
|
317 | }
|
318 | }
|
319 | function insertInlineContents(parent, index, inlineContents) {
|
320 | inlineContents.reduce((index, op) => {
|
321 | const length = Op.length(op);
|
322 | let attributes = op.attributes || {};
|
323 | if (op.insert != null) {
|
324 | if (typeof op.insert === 'string') {
|
325 | const text = op.insert;
|
326 | parent.insertAt(index, text);
|
327 | const [leaf] = parent.descendant(LeafBlot, index);
|
328 | const formats = bubbleFormats(leaf);
|
329 | attributes = AttributeMap.diff(formats, attributes) || {};
|
330 | } else if (typeof op.insert === 'object') {
|
331 | const key = Object.keys(op.insert)[0];
|
332 | if (key == null) return index;
|
333 | parent.insertAt(index, key, op.insert[key]);
|
334 | const isInlineEmbed = parent.scroll.query(key, Scope.INLINE) != null;
|
335 | if (isInlineEmbed) {
|
336 | const [leaf] = parent.descendant(LeafBlot, index);
|
337 | const formats = bubbleFormats(leaf);
|
338 | attributes = AttributeMap.diff(formats, attributes) || {};
|
339 | }
|
340 | }
|
341 | }
|
342 | Object.keys(attributes).forEach(key => {
|
343 | parent.formatAt(index, length, key, attributes[key]);
|
344 | });
|
345 | return index + length;
|
346 | }, index);
|
347 | }
|
348 | export default Scroll;
|
349 |
|
\ | No newline at end of file |