1 | import { EmbedBlot, Scope } from 'parchment';
|
2 | import TextBlot from './text.js';
|
3 | class Cursor extends EmbedBlot {
|
4 | static blotName = 'cursor';
|
5 | static className = 'ql-cursor';
|
6 | static tagName = 'span';
|
7 | static CONTENTS = '\uFEFF';
|
8 |
|
9 | static value() {
|
10 | return undefined;
|
11 | }
|
12 | constructor(scroll, domNode, selection) {
|
13 | super(scroll, domNode);
|
14 | this.selection = selection;
|
15 | this.textNode = document.createTextNode(Cursor.CONTENTS);
|
16 | this.domNode.appendChild(this.textNode);
|
17 | this.savedLength = 0;
|
18 | }
|
19 | detach() {
|
20 |
|
21 | if (this.parent != null) this.parent.removeChild(this);
|
22 | }
|
23 | format(name, value) {
|
24 | if (this.savedLength !== 0) {
|
25 | super.format(name, value);
|
26 | return;
|
27 | }
|
28 |
|
29 |
|
30 | let target = this;
|
31 | let index = 0;
|
32 | while (target != null && target.statics.scope !== Scope.BLOCK_BLOT) {
|
33 | index += target.offset(target.parent);
|
34 | target = target.parent;
|
35 | }
|
36 | if (target != null) {
|
37 | this.savedLength = Cursor.CONTENTS.length;
|
38 |
|
39 | target.optimize();
|
40 | target.formatAt(index, Cursor.CONTENTS.length, name, value);
|
41 | this.savedLength = 0;
|
42 | }
|
43 | }
|
44 | index(node, offset) {
|
45 | if (node === this.textNode) return 0;
|
46 | return super.index(node, offset);
|
47 | }
|
48 | length() {
|
49 | return this.savedLength;
|
50 | }
|
51 | position() {
|
52 | return [this.textNode, this.textNode.data.length];
|
53 | }
|
54 | remove() {
|
55 | super.remove();
|
56 |
|
57 | this.parent = null;
|
58 | }
|
59 | restore() {
|
60 | if (this.selection.composing || this.parent == null) return null;
|
61 | const range = this.selection.getNativeRange();
|
62 |
|
63 |
|
64 | while (this.domNode.lastChild != null && this.domNode.lastChild !== this.textNode) {
|
65 |
|
66 | this.domNode.parentNode.insertBefore(this.domNode.lastChild, this.domNode);
|
67 | }
|
68 | const prevTextBlot = this.prev instanceof TextBlot ? this.prev : null;
|
69 | const prevTextLength = prevTextBlot ? prevTextBlot.length() : 0;
|
70 | const nextTextBlot = this.next instanceof TextBlot ? this.next : null;
|
71 |
|
72 | const nextText = nextTextBlot ? nextTextBlot.text : '';
|
73 | const {
|
74 | textNode
|
75 | } = this;
|
76 |
|
77 | const newText = textNode.data.split(Cursor.CONTENTS).join('');
|
78 | textNode.data = Cursor.CONTENTS;
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 | let mergedTextBlot;
|
85 | if (prevTextBlot) {
|
86 | mergedTextBlot = prevTextBlot;
|
87 | if (newText || nextTextBlot) {
|
88 | prevTextBlot.insertAt(prevTextBlot.length(), newText + nextText);
|
89 | if (nextTextBlot) {
|
90 | nextTextBlot.remove();
|
91 | }
|
92 | }
|
93 | } else if (nextTextBlot) {
|
94 | mergedTextBlot = nextTextBlot;
|
95 | nextTextBlot.insertAt(0, newText);
|
96 | } else {
|
97 | const newTextNode = document.createTextNode(newText);
|
98 | mergedTextBlot = this.scroll.create(newTextNode);
|
99 | this.parent.insertBefore(mergedTextBlot, this);
|
100 | }
|
101 | this.remove();
|
102 | if (range) {
|
103 |
|
104 | const remapOffset = (node, offset) => {
|
105 | if (prevTextBlot && node === prevTextBlot.domNode) {
|
106 | return offset;
|
107 | }
|
108 | if (node === textNode) {
|
109 | return prevTextLength + offset - 1;
|
110 | }
|
111 | if (nextTextBlot && node === nextTextBlot.domNode) {
|
112 | return prevTextLength + newText.length + offset;
|
113 | }
|
114 | return null;
|
115 | };
|
116 | const start = remapOffset(range.start.node, range.start.offset);
|
117 | const end = remapOffset(range.end.node, range.end.offset);
|
118 | if (start !== null && end !== null) {
|
119 | return {
|
120 | startNode: mergedTextBlot.domNode,
|
121 | startOffset: start,
|
122 | endNode: mergedTextBlot.domNode,
|
123 | endOffset: end
|
124 | };
|
125 | }
|
126 | }
|
127 | return null;
|
128 | }
|
129 | update(mutations, context) {
|
130 | if (mutations.some(mutation => {
|
131 | return mutation.type === 'characterData' && mutation.target === this.textNode;
|
132 | })) {
|
133 | const range = this.restore();
|
134 | if (range) context.range = range;
|
135 | }
|
136 | }
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | optimize(context) {
|
149 |
|
150 | super.optimize(context);
|
151 | let {
|
152 | parent
|
153 | } = this;
|
154 | while (parent) {
|
155 | if (parent.domNode.tagName === 'A') {
|
156 | this.savedLength = Cursor.CONTENTS.length;
|
157 |
|
158 | parent.isolate(this.offset(parent), this.length()).unwrap();
|
159 | this.savedLength = 0;
|
160 | break;
|
161 | }
|
162 | parent = parent.parent;
|
163 | }
|
164 | }
|
165 | value() {
|
166 | return '';
|
167 | }
|
168 | }
|
169 | export default Cursor;
|
170 |
|
\ | No newline at end of file |