diff --git a/blots/scroll.js b/blots/scroll.js index cabcbfb6a0..e9accc9aa7 100644 --- a/blots/scroll.js +++ b/blots/scroll.js @@ -22,6 +22,7 @@ class Scroll extends Parchment.Scroll { }, {}); } this.optimize(); + this.enable(); } deleteAt(index, length) { @@ -40,6 +41,10 @@ class Scroll extends Parchment.Scroll { this.optimize(); } + enable(enabled = true) { + this.domNode.setAttribute('contenteditable', enabled); + } + formatAt(index, length, format, value) { if (this.whitelist != null && !this.whitelist[format]) return; super.formatAt(index, length, format, value); diff --git a/core/editor.js b/core/editor.js index b771e789ae..ea598b6f17 100644 --- a/core/editor.js +++ b/core/editor.js @@ -1,6 +1,5 @@ import Delta from 'quill-delta'; import DeltaOp from 'quill-delta/lib/op'; -import Emitter from './emitter'; import Parchment from 'parchment'; import CodeBlock from '../formats/code'; import CursorBlot from '../blots/cursor'; @@ -11,16 +10,12 @@ import extend from 'extend'; class Editor { - constructor(scroll, emitter, selection) { + constructor(scroll) { this.scroll = scroll; - this.selection = selection; - this.emitter = emitter; - this.emitter.on(Emitter.events.SCROLL_UPDATE, this.update.bind(this, null)); this.delta = this.getDelta(); - this.enable(); } - applyDelta(delta, source = Emitter.sources.API) { + applyDelta(delta) { let consumeNextNewline = false; this.scroll.update(); let scrollLength = this.scroll.length(); @@ -68,19 +63,15 @@ class Editor { }, 0); this.scroll.batch = false; this.scroll.optimize(); - return this.update(delta, source); + return this.update(delta); } - deleteText(index, length, source = Emitter.sources.API) { + deleteText(index, length) { this.scroll.deleteAt(index, length); - return this.update(new Delta().retain(index).delete(length), source); + return this.update(new Delta().retain(index).delete(length)); } - enable(enabled = true) { - this.scroll.domNode.setAttribute('contenteditable', enabled); - } - - formatLine(index, length, formats = {}, source = Emitter.sources.API) { + formatLine(index, length, formats = {}) { this.scroll.update(); Object.keys(formats).forEach((format) => { let lines = this.scroll.lines(index, Math.max(length, 1)); @@ -98,14 +89,14 @@ class Editor { }); }); this.scroll.optimize(); - return this.update(new Delta().retain(index).retain(length, clone(formats)), source); + return this.update(new Delta().retain(index).retain(length, clone(formats))); } - formatText(index, length, formats = {}, source = Emitter.sources.API) { + formatText(index, length, formats = {}) { Object.keys(formats).forEach((format) => { this.scroll.formatAt(index, length, format, formats[format]); }); - return this.update(new Delta().retain(index).retain(length, clone(formats)), source); + return this.update(new Delta().retain(index).retain(length, clone(formats))); } getContents(index, length) { @@ -154,18 +145,18 @@ class Editor { }).join(''); } - insertEmbed(index, embed, value, source = Emitter.sources.API) { + insertEmbed(index, embed, value) { this.scroll.insertAt(index, embed, value); - return this.update(new Delta().retain(index).insert({ [embed]: value }), source); + return this.update(new Delta().retain(index).insert({ [embed]: value })); } - insertText(index, text, formats = {}, source = Emitter.sources.API) { + insertText(index, text, formats = {}) { text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); this.scroll.insertAt(index, text); Object.keys(formats).forEach((format) => { this.scroll.formatAt(index, text.length, format, formats[format]); }); - return this.update(new Delta().retain(index).insert(text, clone(formats)), source) + return this.update(new Delta().retain(index).insert(text, clone(formats))); } isBlank() { @@ -175,7 +166,7 @@ class Editor { return child.length() <= 1 && Object.keys(child.formats()).length == 0; } - removeFormat(index, length, source) { + removeFormat(index, length) { let text = this.getText(index, length); let [line, offset] = this.scroll.line(index + length); let suffixLength = 0, suffix = new Delta(); @@ -190,13 +181,11 @@ class Editor { let contents = this.getContents(index, length + suffixLength); let diff = contents.diff(new Delta().insert(text).concat(suffix)); let delta = new Delta().retain(index).concat(diff); - return this.applyDelta(delta, source); + return this.applyDelta(delta); } - update(change, source = Emitter.sources.USER, mutations = []) { + update(change, mutations = [], cursorIndex = undefined) { let oldDelta = this.delta; - let range = this.selection.lastRange; - let index = range && range.length === 0 ? range.index : undefined; if (mutations.length === 1 && mutations[0].type === 'characterData' && Parchment.find(mutations[0].target)) { @@ -207,7 +196,7 @@ class Editor { let oldValue = mutations[0].oldValue.replace(CursorBlot.CONTENTS, ''); let oldText = new Delta().insert(oldValue); let newText = new Delta().insert(textBlot.value()); - let diffDelta = new Delta().retain(index).concat(oldText.diff(newText, index)); + let diffDelta = new Delta().retain(index).concat(oldText.diff(newText, cursorIndex)); change = diffDelta.reduce(function(delta, op) { if (op.insert) { return delta.insert(op.insert, formats); @@ -219,14 +208,7 @@ class Editor { } else { this.delta = this.getDelta(); if (!change || !equal(oldDelta.compose(change), this.delta)) { - change = oldDelta.diff(this.delta, index); - } - } - if (change.length() > 0) { - let args = [Emitter.events.TEXT_CHANGE, change, oldDelta, source]; - this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args); - if (source !== Emitter.sources.SILENT) { - this.emitter.emit(...args); + change = oldDelta.diff(this.delta, cursorIndex); } } return change; diff --git a/core/quill.js b/core/quill.js index 67ba84cc03..50a97c701e 100644 --- a/core/quill.js +++ b/core/quill.js @@ -65,8 +65,8 @@ class Quill { emitter: this.emitter, whitelist: this.options.formats }); + this.editor = new Editor(this.scroll); this.selection = new Selection(this.scroll, this.emitter); - this.editor = new Editor(this.scroll, this.emitter, this.selection); this.theme = new this.options.theme(this, this.options); this.keyboard = this.theme.addModule('keyboard'); this.clipboard = this.theme.addModule('clipboard'); @@ -77,6 +77,13 @@ class Quill { this.root.classList.toggle('ql-blank', this.editor.isBlank()); } }); + this.emitter.on(Emitter.events.SCROLL_UPDATE, (source, mutations) => { + let range = this.selection.lastRange; + let index = range && range.length === 0 ? range.index : undefined; + modify.call(this, () => { + return this.editor.update(null, mutations, index); + }, source); + }); let contents = this.clipboard.convert(`
0123
'); - editor.update(); - spyOn(editor.emitter, 'emit').and.callThrough(); - let old = editor.getDelta(); - editor.insertText(2, '!'); - let delta = new Delta().retain(2).insert('!'); - expect(editor.emitter.emit).toHaveBeenCalledWith(Emitter.events.TEXT_CHANGE, delta, old, Emitter.sources.API); - }); - - it('user text insert', function(done) { - let editor = this.initialize(Editor, '0123
'); - editor.update(); - spyOn(editor.emitter, 'emit').and.callThrough(); - let old = editor.getDelta(); - this.container.firstChild.firstChild.data = '01!23'; - let delta = new Delta().retain(2).insert('!'); - setTimeout(() => { - expect(editor.emitter.emit).toHaveBeenCalledWith(Emitter.events.TEXT_CHANGE, delta, old, Emitter.sources.USER); - done(); - }, 1); - }); - - it('insert same character', function(done) { - let [editor, selection] = this.initialize([Editor, Selection], 'aaaa
'); - selection.setRange(new Range(2, 0)); - editor.update(); - spyOn(editor.emitter, 'emit').and.callThrough(); - let old = editor.getDelta(); - let textNode = this.container.firstChild.firstChild - textNode.data = 'aaaaa'; - selection.setNativeRange(textNode.data, 3); - let delta = new Delta().retain(2).insert('a'); - setTimeout(() => { - let args = editor.emitter.emit.calls.mostRecent().args; - expect(args).toEqual([Emitter.events.TEXT_CHANGE, delta, old, Emitter.sources.USER]); - done(); - }, 1); - }); - }); }); diff --git a/test/unit/core/quill.js b/test/unit/core/quill.js index e3f395ffb8..b5dd2d021b 100644 --- a/test/unit/core/quill.js +++ b/test/unit/core/quill.js @@ -154,6 +154,49 @@ describe('Quill', function() { }); }); + describe('events', function() { + beforeEach(function() { + this.quill = this.initialize(Quill, '0123
'); + this.quill.update(); + spyOn(this.quill.emitter, 'emit').and.callThrough(); + this.oldDelta = this.quill.getContents(); + }); + + it('api text insert', function() { + this.quill.insertText(2, '!'); + let delta = new Delta().retain(2).insert('!'); + expect(this.quill.emitter.emit) + .toHaveBeenCalledWith(Emitter.events.TEXT_CHANGE, delta, this.oldDelta, Emitter.sources.API); + }); + + it('user text insert', function(done) { + this.container.firstChild.firstChild.firstChild.data = '01!23'; + let delta = new Delta().retain(2).insert('!'); + setTimeout(() => { + expect(this.quill.emitter.emit) + .toHaveBeenCalledWith(Emitter.events.TEXT_CHANGE, delta, this.oldDelta, Emitter.sources.USER); + done(); + }, 1); + }); + + it('insert same character', function(done) { + this.quill.setText('aaaa\n'); + this.quill.setSelection(2); + this.quill.update(); + let old = this.quill.getContents(); + let textNode = this.container.firstChild.firstChild.firstChild; + textNode.data = 'aaaaa'; + this.quill.selection.setNativeRange(textNode.data, 3); + // this.quill.selection.update(Emitter.sources.SILENT); + let delta = new Delta().retain(2).insert('a'); + setTimeout(() => { + let args = this.quill.emitter.emit.calls.mostRecent().args; + expect(args).toEqual([Emitter.events.TEXT_CHANGE, delta, old, Emitter.sources.USER]); + done(); + }, 1); + }); + }); + describe('setContents()', function() { it('empty', function() { let quill = this.initialize(Quill, '');