From 7f1fa0df86b5614fd51a614db4ef9e7d01bbb0c8 Mon Sep 17 00:00:00 2001 From: Jason Chen Date: Mon, 11 Jul 2016 22:04:05 -0700 Subject: [PATCH] Save selection on api change methods Includes insertText, insertEmbed, deleteText, formatText, formatLine, removeformat, updateContents Closes #731 --- core/editor.js | 15 ++++---- core/quill.js | 87 +++++++++++++++++++++++++++++++++++---------- modules/keyboard.js | 4 --- modules/toolbar.js | 6 ---- themes/base.js | 1 - ui/link-tooltip.js | 2 -- 6 files changed, 76 insertions(+), 39 deletions(-) diff --git a/core/editor.js b/core/editor.js index f7a60cf5be..d082ddfd30 100644 --- a/core/editor.js +++ b/core/editor.js @@ -63,12 +63,12 @@ class Editor { return index + length; }, 0); this.updating = false; - this.update(delta, source); + return this.update(delta, source); } deleteText(index, length, source = Emitter.sources.API) { this.scroll.deleteAt(index, length); - this.update(new Delta().retain(index).delete(length), source); + return this.update(new Delta().retain(index).delete(length), source); } enable(enabled = true) { @@ -90,14 +90,14 @@ class Editor { }); }); this.scroll.optimize(); - this.update(new Delta().retain(index).retain(length, clone(formats)), source); + return this.update(new Delta().retain(index).retain(length, clone(formats)), source); } formatText(index, length, formats = {}, source = Emitter.sources.API) { Object.keys(formats).forEach((format) => { this.scroll.formatAt(index, length, format, formats[format]); }); - this.update(new Delta().retain(index).retain(length, clone(formats)), source); + return this.update(new Delta().retain(index).retain(length, clone(formats)), source); } getContents(index, length) { @@ -146,7 +146,7 @@ class Editor { insertEmbed(index, embed, value, source = Emitter.sources.API) { this.scroll.insertAt(index, embed, value); - this.update(new Delta().retain(index).insert({ [embed]: value }), source); + return this.update(new Delta().retain(index).insert({ [embed]: value }), source); } insertText(index, text, formats = {}, source = Emitter.sources.API) { @@ -155,7 +155,7 @@ class Editor { Object.keys(formats).forEach((format) => { this.scroll.formatAt(index, text.length, format, formats[format]); }); - this.update(new Delta().retain(index).insert(text, clone(formats)), source) + return this.update(new Delta().retain(index).insert(text, clone(formats)), source) } isBlank() { @@ -176,7 +176,7 @@ 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); - this.applyDelta(delta, source); + return this.applyDelta(delta, source); } update(change, source = Emitter.sources.USER) { @@ -193,6 +193,7 @@ class Editor { this.emitter.emit(...args); } } + return change; } } diff --git a/core/quill.js b/core/quill.js index ca00de2414..ea32d5a378 100644 --- a/core/quill.js +++ b/core/quill.js @@ -13,8 +13,7 @@ let debug = logger('quill'); class Quill { - static debug(limit) { - logger.level(limit); + static debug(limit) {3 } static import(name) { @@ -107,7 +106,11 @@ class Quill { deleteText(index, length, source) { [index, length, , source] = overload(index, length, source); - this.editor.deleteText(index, length, source); + let range = this.getSelection(); + let change = this.editor.deleteText(index, length, source); + range = shiftRange(range, index, -1*length, source); + this.setSelection(range, Emitter.sources.SILENT); + return change; } disable() { @@ -125,27 +128,36 @@ class Quill { format(name, value, source = Emitter.sources.API) { let range = this.getSelection(); - if (range == null) return; + let change = new Delta(); + if (range == null) return change; if (Parchment.query(name, Parchment.Scope.BLOCK)) { - this.formatLine(range, name, value, source); + change = this.formatLine(range, name, value, source); } else if (range.length === 0) { - return this.selection.format(name, value); + this.selection.format(name, value); + return change; } else { - this.formatText(range, name, value, source); + change = this.formatText(range, name, value, source); } this.setSelection(range, Emitter.sources.SILENT); + return change; } formatLine(index, length, name, value, source) { let formats; [index, length, formats, source] = overload(index, length, name, value, source); - this.editor.formatLine(index, length, formats, source); + let range = this.getSelection(); + let change = this.editor.formatLine(index, length, formats, source); + this.setSelection(range, Emitter.sources.SILENT); + return change; } formatText(index, length, name, value, source) { let formats; [index, length, formats, source] = overload(index, length, name, value, source); - this.editor.formatText(index, length, formats, source); + let range = this.getSelection(); + let change = this.editor.formatText(index, length, formats, source); + this.setSelection(range, Emitter.sources.SILENT); + return change; } getBounds(index, length = 0) { @@ -193,13 +205,20 @@ class Quill { } insertEmbed(index, embed, value, source) { - this.editor.insertEmbed(index, embed, value, source); + let range = this.getSelection(); + let change = this.editor.insertEmbed(index, embed, value, source); + range = shiftRange(range, change, source); + this.setSelection(range, Emitter.sources.SILENT); + return change; } insertText(index, text, name, value, source) { - let formats; + let formats, range = this.getSelection(); [index, , formats, source] = overload(index, 0, name, value, source); - this.editor.insertText(index, text, formats, source); + let change = this.editor.insertText(index, text, formats, source); + range = shiftRange(range, index, text.length, source); + this.setSelection(range, Emitter.sources.SILENT); + return change; } off() { @@ -216,16 +235,20 @@ class Quill { pasteHTML(index, html, source = Emitter.sources.API) { if (typeof index === 'string') { - this.setContents(this.clipboard.convert(index), html); + return this.setContents(this.clipboard.convert(index), html); } else { let paste = this.clipboard.convert(html); - this.updateContents(new Delta().retain(index).concat(paste), source); + return this.updateContents(new Delta().retain(index).concat(paste), source); } } removeFormat(index, length, source) { + let range = this.getSelection(); [index, length, , source] = overload(index, length, source); - this.editor.removeFormat(index, length, source); + let change = this.editor.removeFormat(index, length, source); + range = shiftRange(range, index, change.length(), source); + this.setSelection(range, Emitter.sources.SILENT); + return change; } setContents(delta, source = Emitter.sources.API) { @@ -236,7 +259,7 @@ class Quill { delta.insert('\n'); } delta.delete(this.getLength()); - this.editor.applyDelta(delta, source); + return this.editor.applyDelta(delta, source); } setSelection(index, length, source) { @@ -251,19 +274,24 @@ class Quill { setText(text, source = Emitter.sources.API) { let delta = new Delta().insert(text); - this.setContents(delta, source); + return this.setContents(delta, source); } update(source = Emitter.sources.USER) { - this.scroll.update(source); // Will update selection before selection.update() does if text changes + let change = this.scroll.update(source); // Will update selection before selection.update() does if text changes this.selection.update(source); + return change; } updateContents(delta, source = Emitter.sources.API) { + let range = this.getSelection(); if (Array.isArray(delta)) { delta = new Delta(delta.slice()); } - this.editor.applyDelta(delta, source); + let change = this.editor.applyDelta(delta, source); + range = shiftRange(range, change, source); + this.setSelection(range, Emitter.sources.SILENT); + return change; } } Quill.DEFAULTS = { @@ -314,5 +342,26 @@ function overload(index, length, name, value, source) { return [index, length, formats, source]; } +function shiftRange(range, index, length, source) { + if (range == null) return null; + let start, end; + if (index instanceof Delta) { + [start, end] = [range.index, range.index + range.length].map(function(pos) { + return index.transformPosition(pos, source === Emitter.sources.USER); + }); + } else { + if (source === Emitter.sources.USER) index -= 1; + [start, end] = [range.index, range.index + range.length].map(function(pos) { + if (index > pos) return pos; + if (length >= 0) { + return pos + length; + } else { + return Math.max(index, pos + length); + } + }); + } + return new Range(start, end - start); +} + export { overload, Quill as default }; diff --git a/modules/keyboard.js b/modules/keyboard.js index 85d8182fbe..91d772ffc0 100644 --- a/modules/keyboard.js +++ b/modules/keyboard.js @@ -41,13 +41,11 @@ class Keyboard extends Module { this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true, prefix: /^$/ }, function(range) { if (range.index === 0) return; this.quill.deleteText(range.index - 1, 1, Quill.sources.USER); - this.quill.setSelection(range.index - 1, Quill.sources.SILENT); this.quill.selection.scrollIntoView(); }); this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true, suffix: /^$/ }, function(range) { if (range.index >= this.quill.getLength() - 1) return; this.quill.deleteText(range.index, 1, Quill.sources.USER); - this.quill.setSelection(range.index, Quill.sources.SILENT); }); this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: false }, handleDelete); this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: false }, handleDelete); @@ -181,7 +179,6 @@ Keyboard.DEFAULTS = { this.quill.scroll.deleteAt(range.index, range.length); } this.quill.insertText(range.index, '\t', Quill.sources.USER); - this.quill.setSelection(range.index + 1, Quill.sources.SILENT); } ], 'list empty enter': [ @@ -235,7 +232,6 @@ function handleEnter(range, context) { return lineFormats; }, {}); this.quill.insertText(range.index, '\n', lineFormats, Quill.sources.USER); - this.quill.setSelection(range.index + 1, Quill.sources.SILENT); this.quill.selection.scrollIntoView(); Object.keys(context.format).forEach((name) => { if (lineFormats[name] != null) return; diff --git a/modules/toolbar.js b/modules/toolbar.js index bdae84d163..61a9335ad3 100644 --- a/modules/toolbar.js +++ b/modules/toolbar.js @@ -91,8 +91,6 @@ class Toolbar extends Module { .delete(range.length) .insert({ [format]: true }) , Quill.sources.USER); - range = new Range(range.index + 1, 0); - this.quill.setSelection(range, Quill.sources.SILENT); } else { this.quill.format(format, value, Quill.sources.USER); } @@ -197,11 +195,7 @@ Toolbar.DEFAULTS = { } }); } else { - let startLength = this.quill.getLength(); this.quill.removeFormat(range, Quill.sources.USER); - let endLength = this.quill.getLength(); - // account for embed removals - this.quill.setSelection(range.index, range.length - (startLength-endLength), Quill.sources.SILENT); } }, direction: function(value) { diff --git a/themes/base.js b/themes/base.js index f71d5ba388..31f13949f7 100644 --- a/themes/base.js +++ b/themes/base.js @@ -137,7 +137,6 @@ BaseTheme.DEFAULTS = { .delete(range.length) .insert({ image: e.target.result }) , Emitter.sources.USER); - quill.setSelection(range.index + 1, Emitter.sources.SILENT); fileInput.value = ""; } reader.readAsDataURL(this.files[0]); diff --git a/ui/link-tooltip.js b/ui/link-tooltip.js index 7194f301c7..974fdc180f 100644 --- a/ui/link-tooltip.js +++ b/ui/link-tooltip.js @@ -74,7 +74,6 @@ class LinkTooltip { remove() { this.quill.formatText(this.range, 'link', false, Quill.sources.USER); - this.quill.setSelection(this.range, Quill.sources.SILENT); this.hide(); } @@ -82,7 +81,6 @@ class LinkTooltip { let url = this.textbox.value; let scrollTop = this.quill.root.scrollTop; this.quill.formatText(this.range, 'link', url, Quill.sources.USER); - this.quill.setSelection(this.range, Quill.sources.SILENT); this.quill.root.scrollTop = scrollTop; this.hide(); }