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 authored and Tim McClure committed Dec 12, 2016
1 parent 97d3f22 commit c5eda7e
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Scroll extends Parchment.Scroll {
}, {});
}
this.optimize();
this.enable();
}

deleteAt(index, length) {
Expand All @@ -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);
Expand Down
54 changes: 18 additions & 36 deletions core/editor.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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();
Expand Down Expand Up @@ -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));
Expand All @@ -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) {
Expand Down Expand Up @@ -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() {
Expand All @@ -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();
Expand All @@ -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)) {
Expand All @@ -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);
Expand All @@ -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;
Expand Down
139 changes: 74 additions & 65 deletions core/quill.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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(`<div class='ql-editor' style="white-space: normal;">${html}<p><br></p></div>`);
this.setContents(contents);
this.history.clear();
Expand Down Expand Up @@ -104,17 +111,17 @@ class Quill {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Expand Down
Loading

0 comments on commit c5eda7e

Please sign in to comment.