Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
- Emitting TEXT_CHANGE is moved from Editor to Quill so Editor does not
  need to depend on Emitter.
- Cursor index is passed as parameter to Editor.update so Editor does
  not need to depend on Selection
- Editor.enable is moved into Scroll
- Quill top level modifcation APIs are all using modify now
  • Loading branch information
jhchen committed Oct 20, 2016
1 parent cad7490 commit ac63543
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 149 deletions.
5 changes: 5 additions & 0 deletions blots/scroll.js
Expand Up @@ -22,6 +22,7 @@ class Scroll extends Parchment.Scroll {
}, {}); }, {});
} }
this.optimize(); this.optimize();
this.enable();
} }


deleteAt(index, length) { deleteAt(index, length) {
Expand All @@ -40,6 +41,10 @@ class Scroll extends Parchment.Scroll {
this.optimize(); this.optimize();
} }


enable(enabled = true) {
this.domNode.setAttribute('contenteditable', enabled);
}

formatAt(index, length, format, value) { formatAt(index, length, format, value) {
if (this.whitelist != null && !this.whitelist[format]) return; if (this.whitelist != null && !this.whitelist[format]) return;
super.formatAt(index, length, format, value); super.formatAt(index, length, format, value);
Expand Down
54 changes: 18 additions & 36 deletions core/editor.js
@@ -1,6 +1,5 @@
import Delta from 'quill-delta'; import Delta from 'quill-delta';
import DeltaOp from 'quill-delta/lib/op'; import DeltaOp from 'quill-delta/lib/op';
import Emitter from './emitter';
import Parchment from 'parchment'; import Parchment from 'parchment';
import CodeBlock from '../formats/code'; import CodeBlock from '../formats/code';
import CursorBlot from '../blots/cursor'; import CursorBlot from '../blots/cursor';
Expand All @@ -11,16 +10,12 @@ import extend from 'extend';




class Editor { class Editor {
constructor(scroll, emitter, selection) { constructor(scroll) {
this.scroll = 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.delta = this.getDelta();
this.enable();
} }


applyDelta(delta, source = Emitter.sources.API) { applyDelta(delta) {
let consumeNextNewline = false; let consumeNextNewline = false;
this.scroll.update(); this.scroll.update();
let scrollLength = this.scroll.length(); let scrollLength = this.scroll.length();
Expand Down Expand Up @@ -68,19 +63,15 @@ class Editor {
}, 0); }, 0);
this.scroll.batch = false; this.scroll.batch = false;
this.scroll.optimize(); 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); 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) { formatLine(index, length, formats = {}) {
this.scroll.domNode.setAttribute('contenteditable', enabled);
}

formatLine(index, length, formats = {}, source = Emitter.sources.API) {
this.scroll.update(); this.scroll.update();
Object.keys(formats).forEach((format) => { Object.keys(formats).forEach((format) => {
let lines = this.scroll.lines(index, Math.max(length, 1)); let lines = this.scroll.lines(index, Math.max(length, 1));
Expand All @@ -98,14 +89,14 @@ class Editor {
}); });
}); });
this.scroll.optimize(); 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) => { Object.keys(formats).forEach((format) => {
this.scroll.formatAt(index, length, format, formats[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) { getContents(index, length) {
Expand Down Expand Up @@ -154,18 +145,18 @@ class Editor {
}).join(''); }).join('');
} }


insertEmbed(index, embed, value, source = Emitter.sources.API) { insertEmbed(index, embed, value) {
this.scroll.insertAt(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'); text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
this.scroll.insertAt(index, text); this.scroll.insertAt(index, text);
Object.keys(formats).forEach((format) => { Object.keys(formats).forEach((format) => {
this.scroll.formatAt(index, text.length, format, formats[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() { isBlank() {
Expand All @@ -175,7 +166,7 @@ class Editor {
return child.length() <= 1 && Object.keys(child.formats()).length == 0; return child.length() <= 1 && Object.keys(child.formats()).length == 0;
} }


removeFormat(index, length, source) { removeFormat(index, length) {
let text = this.getText(index, length); let text = this.getText(index, length);
let [line, offset] = this.scroll.line(index + length); let [line, offset] = this.scroll.line(index + length);
let suffixLength = 0, suffix = new Delta(); let suffixLength = 0, suffix = new Delta();
Expand All @@ -190,13 +181,11 @@ class Editor {
let contents = this.getContents(index, length + suffixLength); let contents = this.getContents(index, length + suffixLength);
let diff = contents.diff(new Delta().insert(text).concat(suffix)); let diff = contents.diff(new Delta().insert(text).concat(suffix));
let delta = new Delta().retain(index).concat(diff); 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 oldDelta = this.delta;
let range = this.selection.lastRange;
let index = range && range.length === 0 ? range.index : undefined;
if (mutations.length === 1 && if (mutations.length === 1 &&
mutations[0].type === 'characterData' && mutations[0].type === 'characterData' &&
Parchment.find(mutations[0].target)) { Parchment.find(mutations[0].target)) {
Expand All @@ -207,7 +196,7 @@ class Editor {
let oldValue = mutations[0].oldValue.replace(CursorBlot.CONTENTS, ''); let oldValue = mutations[0].oldValue.replace(CursorBlot.CONTENTS, '');
let oldText = new Delta().insert(oldValue); let oldText = new Delta().insert(oldValue);
let newText = new Delta().insert(textBlot.value()); 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) { change = diffDelta.reduce(function(delta, op) {
if (op.insert) { if (op.insert) {
return delta.insert(op.insert, formats); return delta.insert(op.insert, formats);
Expand All @@ -219,14 +208,7 @@ class Editor {
} else { } else {
this.delta = this.getDelta(); this.delta = this.getDelta();
if (!change || !equal(oldDelta.compose(change), this.delta)) { if (!change || !equal(oldDelta.compose(change), this.delta)) {
change = oldDelta.diff(this.delta, index); change = oldDelta.diff(this.delta, cursorIndex);
}
}
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);
} }
} }
return change; return change;
Expand Down
139 changes: 74 additions & 65 deletions core/quill.js
Expand Up @@ -65,8 +65,8 @@ class Quill {
emitter: this.emitter, emitter: this.emitter,
whitelist: this.options.formats whitelist: this.options.formats
}); });
this.editor = new Editor(this.scroll);
this.selection = new Selection(this.scroll, this.emitter); 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.theme = new this.options.theme(this, this.options);
this.keyboard = this.theme.addModule('keyboard'); this.keyboard = this.theme.addModule('keyboard');
this.clipboard = this.theme.addModule('clipboard'); this.clipboard = this.theme.addModule('clipboard');
Expand All @@ -77,6 +77,13 @@ class Quill {
this.root.classList.toggle('ql-blank', this.editor.isBlank()); 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(`<div class='ql-editor' style="white-space: normal;">${html}<p><br></p></div>`); let contents = this.clipboard.convert(`<div class='ql-editor' style="white-space: normal;">${html}<p><br></p></div>`);
this.setContents(contents); this.setContents(contents);
this.history.clear(); this.history.clear();
Expand Down Expand Up @@ -104,17 +111,17 @@ class Quill {


deleteText(index, length, source) { deleteText(index, length, source) {
[index, length, , source] = overload(index, length, source); [index, length, , source] = overload(index, length, source);
return modify.call(this, source, index, -1*length, () => { return modify.call(this, () => {
return this.editor.deleteText(index, length, source); return this.editor.deleteText(index, length);
}); }, source, index, -1*length);
} }


disable() { disable() {
this.enable(false); this.enable(false);
} }


enable(enabled = true) { enable(enabled = true) {
this.editor.enable(enabled); this.scroll.enable(enabled);
this.container.classList.toggle('ql-disabled', !enabled); this.container.classList.toggle('ql-disabled', !enabled);
if (!enabled) { if (!enabled) {
this.blur(); this.blur();
Expand All @@ -127,38 +134,38 @@ class Quill {
} }


format(name, value, source = Emitter.sources.API) { format(name, value, source = Emitter.sources.API) {
if (!this.options.strict && !this.isEnabled() && source === Emitter.sources.USER) { return modify.call(this, () => {
return new Delta(); let range = this.getSelection(true);
} let change = new Delta();
let range = this.getSelection(true); if (range == null) {
let change = new Delta(); return change;
if (range == null) return change; } else if (Parchment.query(name, Parchment.Scope.BLOCK)) {
if (Parchment.query(name, Parchment.Scope.BLOCK)) { change = this.editor.formatLine(range.index, range.length, { [name]: value });
change = this.formatLine(range, name, value, source); } else if (range.length === 0) {
} else if (range.length === 0) { this.selection.format(name, value);
this.selection.format(name, value); return change;
} else {
change = this.editor.formatText(range.index, range.length, { [name]: value });
}
this.setSelection(range, Emitter.sources.SILENT);
return change; return change;
} else { }, source);
change = this.formatText(range, name, value, source);
}
this.setSelection(range, Emitter.sources.SILENT);
return change;
} }


formatLine(index, length, name, value, source) { formatLine(index, length, name, value, source) {
let formats; let formats;
[index, length, formats, source] = overload(index, length, name, value, source); [index, length, formats, source] = overload(index, length, name, value, source);
return modify.call(this, source, index, 0, () => { return modify.call(this, () => {
return this.editor.formatLine(index, length, formats, source); return this.editor.formatLine(index, length, formats);
}); }, source, index, 0);
} }


formatText(index, length, name, value, source) { formatText(index, length, name, value, source) {
let formats; let formats;
[index, length, formats, source] = overload(index, length, name, value, source); [index, length, formats, source] = overload(index, length, name, value, source);
return modify.call(this, source, index, 0, () => { return modify.call(this, () => {
return this.editor.formatText(index, length, formats, source); return this.editor.formatText(index, length, formats);
}); }, source, index, 0);
} }


getBounds(index, length = 0) { getBounds(index, length = 0) {
Expand Down Expand Up @@ -206,17 +213,17 @@ class Quill {
} }


insertEmbed(index, embed, value, source = Quill.sources.API) { insertEmbed(index, embed, value, source = Quill.sources.API) {
return modify.call(this, source, index, null, () => { return modify.call(this, () => {
return this.editor.insertEmbed(index, embed, value, source); return this.editor.insertEmbed(index, embed, value);
}); }, source, index);
} }


insertText(index, text, name, value, source) { insertText(index, text, name, value, source) {
let formats; let formats;
[index, , formats, source] = overload(index, 0, name, value, source); [index, , formats, source] = overload(index, 0, name, value, source);
return modify.call(this, source, index, text.length, () => { return modify.call(this, () => {
return this.editor.insertText(index, text, formats, source); return this.editor.insertText(index, text, formats);
}); }, source, index, text.length);
} }


isEnabled() { isEnabled() {
Expand All @@ -241,23 +248,22 @@ class Quill {


removeFormat(index, length, source) { removeFormat(index, length, source) {
[index, length, , source] = overload(index, length, source); [index, length, , source] = overload(index, length, source);
return modify.call(this, source, index, null, () => { return modify.call(this, () => {
return this.editor.removeFormat(index, length, source); return this.editor.removeFormat(index, length);
}); }, source, index);
} }


setContents(delta, source = Emitter.sources.API) { setContents(delta, source = Emitter.sources.API) {
if (!this.options.strict && !this.isEnabled() && source === Emitter.sources.USER) { return modify.call(this, () => {
return new Delta(); delta = new Delta(delta).slice();
} let lastOp = delta.ops[delta.ops.length - 1];
delta = new Delta(delta).slice(); // Quill contents must always end with newline
let lastOp = delta.ops[delta.ops.length - 1]; if (lastOp == null || lastOp.insert[lastOp.insert.length-1] !== '\n') {
// Quill contents must always end with newline delta.insert('\n');
if (lastOp == null || lastOp.insert[lastOp.insert.length-1] !== '\n') { }
delta.insert('\n'); delta.delete(this.getLength());
} return this.editor.applyDelta(delta);
delta.delete(this.getLength()); }, source);
return this.editor.applyDelta(delta, source);
} }


setSelection(index, length, source) { setSelection(index, length, source) {
Expand All @@ -282,19 +288,12 @@ class Quill {
} }


updateContents(delta, source = Emitter.sources.API) { updateContents(delta, source = Emitter.sources.API) {
if (!this.options.strict && !this.isEnabled() && source === Emitter.sources.USER) { return modify.call(this, () => {
return new Delta(); if (Array.isArray(delta)) {
} delta = new Delta(delta.slice());
let range = this.getSelection(); }
if (Array.isArray(delta)) { return this.editor.applyDelta(delta, source);
delta = new Delta(delta.slice()); }, source, true);
}
let change = this.editor.applyDelta(delta, source);
if (range != null) {
range = shiftRange(range, change, source);
this.setSelection(range, Emitter.sources.SILENT);
}
return change;
} }
} }
Quill.DEFAULTS = { Quill.DEFAULTS = {
Expand Down Expand Up @@ -377,21 +376,31 @@ function expandConfig(container, userConfig) {
return userConfig; return userConfig;
} }


function modify(source, index, shift, modifier) { // Handle selection preservation and TEXT_CHANGE emission
let change = new Delta(); // common to modification APIs
function modify(modifier, source, index, shift) {
if (!this.options.strict && !this.isEnabled() && source === Emitter.sources.USER) { if (!this.options.strict && !this.isEnabled() && source === Emitter.sources.USER) {
return new Delta(); return new Delta();
} }
let range = this.getSelection(); let range = index == null ? null : this.getSelection();
change = modifier(); let oldDelta = this.editor.delta;
let change = modifier();
if (range != null) { if (range != null) {
if (shift === null) { if (index === true) index = range.index;
range = shiftRange(range, index, change, source); if (shift == null) {
range = shiftRange(range, change, source);
} else if (shift !== 0) { } else if (shift !== 0) {
range = shiftRange(range, index, shift, source); range = shiftRange(range, index, shift, source);
} }
this.setSelection(range, Emitter.sources.SILENT); this.setSelection(range, Emitter.sources.SILENT);
} }
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);
}
}
return change; return change;
} }


Expand Down

0 comments on commit ac63543

Please sign in to comment.