123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- import { EmbedBlot, Scope } from 'parchment';
- import TextBlot from './text';
- class Cursor extends EmbedBlot {
- static value() {
- return undefined;
- }
- constructor(scroll, domNode, selection) {
- super(scroll, domNode);
- this.selection = selection;
- this.textNode = document.createTextNode(Cursor.CONTENTS);
- this.domNode.appendChild(this.textNode);
- this.savedLength = 0;
- }
- detach() {
- // super.detach() will also clear domNode.__blot
- if (this.parent != null) this.parent.removeChild(this);
- }
- format(name, value) {
- if (this.savedLength !== 0) {
- super.format(name, value);
- return;
- }
- let target = this;
- let index = 0;
- while (target != null && target.statics.scope !== Scope.BLOCK_BLOT) {
- index += target.offset(target.parent);
- target = target.parent;
- }
- if (target != null) {
- this.savedLength = Cursor.CONTENTS.length;
- target.optimize();
- target.formatAt(index, Cursor.CONTENTS.length, name, value);
- this.savedLength = 0;
- }
- }
- index(node, offset) {
- if (node === this.textNode) return 0;
- return super.index(node, offset);
- }
- length() {
- return this.savedLength;
- }
- position() {
- return [this.textNode, this.textNode.data.length];
- }
- remove() {
- super.remove();
- this.parent = null;
- }
- restore() {
- if (this.selection.composing || this.parent == null) return null;
- const range = this.selection.getNativeRange();
- // Link format will insert text outside of anchor tag
- while (
- this.domNode.lastChild != null &&
- this.domNode.lastChild !== this.textNode
- ) {
- this.domNode.parentNode.insertBefore(
- this.domNode.lastChild,
- this.domNode,
- );
- }
- const prevTextBlot = this.prev instanceof TextBlot ? this.prev : null;
- const prevTextLength = prevTextBlot ? prevTextBlot.length() : 0;
- const nextTextBlot = this.next instanceof TextBlot ? this.next : null;
- const nextText = nextTextBlot ? nextTextBlot.text : '';
- const { textNode } = this;
- // take text from inside this blot and reset it
- const newText = textNode.data.split(Cursor.CONTENTS).join('');
- textNode.data = Cursor.CONTENTS;
- // proactively merge TextBlots around cursor so that optimization
- // doesn't lose the cursor. the reason we are here in cursor.restore
- // could be that the user clicked in prevTextBlot or nextTextBlot, or
- // the user typed something.
- let mergedTextBlot;
- if (prevTextBlot) {
- mergedTextBlot = prevTextBlot;
- if (newText || nextTextBlot) {
- prevTextBlot.insertAt(prevTextBlot.length(), newText + nextText);
- if (nextTextBlot) {
- nextTextBlot.remove();
- }
- }
- } else if (nextTextBlot) {
- mergedTextBlot = nextTextBlot;
- nextTextBlot.insertAt(0, newText);
- } else {
- const newTextNode = document.createTextNode(newText);
- mergedTextBlot = this.scroll.create(newTextNode);
- this.parent.insertBefore(mergedTextBlot, this);
- }
- this.remove();
- if (range) {
- // calculate selection to restore
- const remapOffset = (node, offset) => {
- if (prevTextBlot && node === prevTextBlot.domNode) {
- return offset;
- }
- if (node === textNode) {
- return prevTextLength + offset - 1;
- }
- if (nextTextBlot && node === nextTextBlot.domNode) {
- return prevTextLength + newText.length + offset;
- }
- return null;
- };
- const start = remapOffset(range.start.node, range.start.offset);
- const end = remapOffset(range.end.node, range.end.offset);
- if (start !== null && end !== null) {
- return {
- startNode: mergedTextBlot.domNode,
- startOffset: start,
- endNode: mergedTextBlot.domNode,
- endOffset: end,
- };
- }
- }
- return null;
- }
- update(mutations, context) {
- if (
- mutations.some(mutation => {
- return (
- mutation.type === 'characterData' && mutation.target === this.textNode
- );
- })
- ) {
- const range = this.restore();
- if (range) context.range = range;
- }
- }
- value() {
- return '';
- }
- }
- Cursor.blotName = 'cursor';
- Cursor.className = 'ql-cursor';
- Cursor.tagName = 'span';
- Cursor.CONTENTS = '\uFEFF'; // Zero width no break space
- export default Cursor;
|