From 78c38ee62bebe82c56223453f9fa30418a4b5bbf Mon Sep 17 00:00:00 2001 From: Jacob Wright Date: Mon, 16 Apr 2018 18:00:14 -0600 Subject: [PATCH] Rewrite typewriter from scratch using delta and vdom --- .gitignore | 1 - LICENSE | 2 +- README.md | 31 +- dist/index.js | 3923 ++++++++++++++++++++++++ dist/index.js.map | 1 + gulpfile.js | 164 - index.html | 12 + karma.conf.js | 79 - package.json | 59 +- rollup.config.dev.js | 26 + rollup.config.js | 20 + src/defaultDom.js | 105 + src/dev.js | 46 + src/document.js | 232 ++ src/dom.js | 233 ++ src/editor.js | 349 +++ src/editor/README.md | 38 - src/editor/arrayfind-polyfill.js | 23 - src/editor/blocks/block.js | 77 - src/editor/blocks/header.js | 22 - src/editor/blocks/image.js | 35 - src/editor/blocks/list.js | 13 - src/editor/blocks/preformatted.js | 13 - src/editor/command.js | 26 - src/editor/commands/composite.js | 40 - src/editor/commands/delete-block.js | 28 - src/editor/commands/index.js | 4 - src/editor/commands/insert-block.js | 30 - src/editor/commands/update-block.js | 33 - src/editor/editor.js | 402 --- src/editor/editor.less | 89 - src/editor/history.js | 184 -- src/editor/interactions/backspace.js | 121 - src/editor/interactions/enter.js | 92 - src/editor/interactions/index.js | 5 - src/editor/interactions/paste.js | 252 -- src/editor/interactions/text-entry.js | 116 - src/editor/interactions/undo.js | 40 - src/editor/interactions/utils.js | 185 -- src/editor/mapping.js | 366 --- src/editor/markups/markup.js | 31 - src/editor/menu.js | 129 - src/editor/menu/index.js | 0 src/editor/platform.js | 4 - src/editor/range.js | 102 - src/editor/rect.js | 10 - src/editor/schema/default.js | 34 - src/editor/schema/schema.js | 95 - src/editor/selection-rect.js | 29 - src/editor/selection.js | 508 --- src/editor/selectionchange-polyfill.js | 30 - src/editor/selectors.js | 158 - src/eventdispatcher.js | 36 + src/html-view.js | 167 + src/index.js | 10 + src/modules/history.js | 116 + src/modules/input.js | 72 + src/modules/key-shortcuts.js | 41 + src/modules/placeholder.js | 18 + src/selection.js | 122 + test/editor/test-history.js | 125 - test/editor/test-mapping.js | 136 - 62 files changed, 5578 insertions(+), 3912 deletions(-) create mode 100644 dist/index.js create mode 100644 dist/index.js.map delete mode 100644 gulpfile.js create mode 100644 index.html delete mode 100644 karma.conf.js create mode 100644 rollup.config.dev.js create mode 100644 rollup.config.js create mode 100644 src/defaultDom.js create mode 100644 src/dev.js create mode 100644 src/document.js create mode 100644 src/dom.js create mode 100644 src/editor.js delete mode 100644 src/editor/README.md delete mode 100644 src/editor/arrayfind-polyfill.js delete mode 100644 src/editor/blocks/block.js delete mode 100644 src/editor/blocks/header.js delete mode 100644 src/editor/blocks/image.js delete mode 100644 src/editor/blocks/list.js delete mode 100644 src/editor/blocks/preformatted.js delete mode 100644 src/editor/command.js delete mode 100644 src/editor/commands/composite.js delete mode 100644 src/editor/commands/delete-block.js delete mode 100644 src/editor/commands/index.js delete mode 100644 src/editor/commands/insert-block.js delete mode 100644 src/editor/commands/update-block.js delete mode 100644 src/editor/editor.js delete mode 100644 src/editor/editor.less delete mode 100644 src/editor/history.js delete mode 100644 src/editor/interactions/backspace.js delete mode 100644 src/editor/interactions/enter.js delete mode 100644 src/editor/interactions/index.js delete mode 100644 src/editor/interactions/paste.js delete mode 100644 src/editor/interactions/text-entry.js delete mode 100644 src/editor/interactions/undo.js delete mode 100644 src/editor/interactions/utils.js delete mode 100644 src/editor/mapping.js delete mode 100644 src/editor/markups/markup.js delete mode 100644 src/editor/menu.js delete mode 100644 src/editor/menu/index.js delete mode 100644 src/editor/platform.js delete mode 100644 src/editor/range.js delete mode 100644 src/editor/rect.js delete mode 100644 src/editor/schema/default.js delete mode 100644 src/editor/schema/schema.js delete mode 100644 src/editor/selection-rect.js delete mode 100644 src/editor/selection.js delete mode 100644 src/editor/selectionchange-polyfill.js delete mode 100644 src/editor/selectors.js create mode 100644 src/eventdispatcher.js create mode 100644 src/html-view.js create mode 100644 src/index.js create mode 100644 src/modules/history.js create mode 100644 src/modules/input.js create mode 100644 src/modules/key-shortcuts.js create mode 100644 src/modules/placeholder.js create mode 100644 src/selection.js delete mode 100644 test/editor/test-history.js delete mode 100644 test/editor/test-mapping.js diff --git a/.gitignore b/.gitignore index 7d84607..1533896 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ .* node_modules/ -build/ diff --git a/LICENSE b/LICENSE index 4a4f9fd..cf8d0f0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2016 Jacob Wright +Copyright (c) 2018 Jacob Wright Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.md b/README.md index cf67588..45b8d43 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,30 @@ # Typewriter -A browser editor patterened after medium.com's editor described here in https://medium.com/medium-eng/why-contenteditable-is-terrible-122d8a40e480. +A Quill.js clone that uses the [Delta](https://github.com/quilljs/delta/) data format for content but manages the DOM +with a virtual DOM like React and similar frameworks use. Typewriter uses the tiny +[Ultradom](https://github.com/jorgebucaran/ultradom/) for this. This allows decorators—temporary markup which is visible +to the user but does not get merged into the editor contents or sent over the wire to collaborators. -Typewriter keeps an object model representing the text of the document ensuring it will always contain the supported HTML and nothing more. +## Benefits over Quill.js -This was pulled from another project and likely needs cleaning up to be used. There are still things needing to be finished such as: -* cleaning paste operations from other sources (such as MS Word) -* supporting OL and UL items as blocks +* The biggest is the decorators feature which I don't believe will be trivial to add to Quill +* I believe it could be faster, but this needs benchmarking +* Lists are handled correctly like normal HTML lists should be +* Allowed `
` tags when Shift+Enter is used +* Paragraphs are treated normal, with margins, unless of course you want to remove them in your own stylesheet +* No required stylesheet to make it work +* Undo breaks correctly when actions change—follows the native OS behavior of Mac and Windows + +### TODO + +* Add list handling, fix indent when deleting in the middle of a list (unindent as necessary) +* Paste handling (should be pretty easy with deltaFromDom) +* Code comments +* Testing +* More modules +* Optional UI, toolbars, image handling, etc. +* Benchmarking for good feels + +## Contributing + +Submit PRs, will get a gitter up diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..dd37833 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,3923 @@ +(function(l, i, v, e) { v = l.createElement(i); v.async = 1; v.src = '//' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; e = l.getElementsByTagName(i)[0]; e.parentNode.insertBefore(v, e)})(document, 'script'); +(function () { + 'use strict'; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + + var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + }; + + var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + + var defineProperty = function (obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; + }; + + var _extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; + }; + + var inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + }; + + var possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; + }; + + var slicedToArray = function () { + function sliceIterator(arr, i) { + var _arr = []; + var _n = true; + var _d = false; + var _e = undefined; + + try { + for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value); + + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"]) _i["return"](); + } finally { + if (_d) throw _e; + } + } + + return _arr; + } + + return function (arr, i) { + if (Array.isArray(arr)) { + return arr; + } else if (Symbol.iterator in Object(arr)) { + return sliceIterator(arr, i); + } else { + throw new TypeError("Invalid attempt to destructure non-iterable instance"); + } + }; + }(); + + var dispatcherEvents = new WeakMap(); + + var EventDispatcher = function () { + function EventDispatcher() { + classCallCheck(this, EventDispatcher); + } + + createClass(EventDispatcher, [{ + key: "on", + value: function on(type, listener) { + getEventListeners(this, type).add(listener); + } + }, { + key: "off", + value: function off(type, listener) { + getEventListeners(this, type).delete(listener); + } + }, { + key: "once", + value: function once(type, listener) { + function once() { + this.off(type, once); + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + listener.apply(this, args); + } + this.on(type, once); + } + }, { + key: "fire", + value: function fire(type) { + var _this = this; + + for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + args[_key2 - 1] = arguments[_key2]; + } + + var uncanceled = true; + getEventListeners(this, type).forEach(function (listener) { + uncanceled && listener.apply(_this, args) !== false || (uncanceled = false); + }); + return uncanceled; + } + }]); + return EventDispatcher; + }(); + + + function getEventListeners(obj, type) { + var events = dispatcherEvents.get(obj); + if (!events) dispatcherEvents.set(obj, events = Object.create(null)); + return events[type] || (events[type] = new Set()); + } + + /** + * This library modifies the diff-patch-match library by Neil Fraser + * by removing the patch and match functionality and certain advanced + * options in the diff function. The original license is as follows: + * + * === + * + * Diff Match and Patch + * + * Copyright 2006 Google Inc. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + /** + * The data structure representing a diff is an array of tuples: + * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] + * which means: delete 'Hello', add 'Goodbye' and keep ' world.' + */ + var DIFF_DELETE = -1; + var DIFF_INSERT = 1; + var DIFF_EQUAL = 0; + + + /** + * Find the differences between two texts. Simplifies the problem by stripping + * any common prefix or suffix off the texts before diffing. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {Int} cursor_pos Expected edit position in text1 (optional) + * @return {Array} Array of diff tuples. + */ + function diff_main(text1, text2, cursor_pos) { + // Check for equality (speedup). + if (text1 == text2) { + if (text1) { + return [[DIFF_EQUAL, text1]]; + } + return []; + } + + // Check cursor_pos within bounds + if (cursor_pos < 0 || text1.length < cursor_pos) { + cursor_pos = null; + } + + // Trim off common prefix (speedup). + var commonlength = diff_commonPrefix(text1, text2); + var commonprefix = text1.substring(0, commonlength); + text1 = text1.substring(commonlength); + text2 = text2.substring(commonlength); + + // Trim off common suffix (speedup). + commonlength = diff_commonSuffix(text1, text2); + var commonsuffix = text1.substring(text1.length - commonlength); + text1 = text1.substring(0, text1.length - commonlength); + text2 = text2.substring(0, text2.length - commonlength); + + // Compute the diff on the middle block. + var diffs = diff_compute_(text1, text2); + + // Restore the prefix and suffix. + if (commonprefix) { + diffs.unshift([DIFF_EQUAL, commonprefix]); + } + if (commonsuffix) { + diffs.push([DIFF_EQUAL, commonsuffix]); + } + diff_cleanupMerge(diffs); + if (cursor_pos != null) { + diffs = fix_cursor(diffs, cursor_pos); + } + diffs = fix_emoji(diffs); + return diffs; + } + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @return {Array} Array of diff tuples. + */ + function diff_compute_(text1, text2) { + var diffs; + + if (!text1) { + // Just add some text (speedup). + return [[DIFF_INSERT, text2]]; + } + + if (!text2) { + // Just delete some text (speedup). + return [[DIFF_DELETE, text1]]; + } + + var longtext = text1.length > text2.length ? text1 : text2; + var shorttext = text1.length > text2.length ? text2 : text1; + var i = longtext.indexOf(shorttext); + if (i != -1) { + // Shorter text is inside the longer text (speedup). + diffs = [[DIFF_INSERT, longtext.substring(0, i)], + [DIFF_EQUAL, shorttext], + [DIFF_INSERT, longtext.substring(i + shorttext.length)]]; + // Swap insertions for deletions if diff is reversed. + if (text1.length > text2.length) { + diffs[0][0] = diffs[2][0] = DIFF_DELETE; + } + return diffs; + } + + if (shorttext.length == 1) { + // Single character string. + // After the previous speedup, the character can't be an equality. + return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; + } + + // Check to see if the problem can be split in two. + var hm = diff_halfMatch_(text1, text2); + if (hm) { + // A half-match was found, sort out the return data. + var text1_a = hm[0]; + var text1_b = hm[1]; + var text2_a = hm[2]; + var text2_b = hm[3]; + var mid_common = hm[4]; + // Send both pairs off for separate processing. + var diffs_a = diff_main(text1_a, text2_a); + var diffs_b = diff_main(text1_b, text2_b); + // Merge the results. + return diffs_a.concat([[DIFF_EQUAL, mid_common]], diffs_b); + } + + return diff_bisect_(text1, text2); + } + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @return {Array} Array of diff tuples. + * @private + */ + function diff_bisect_(text1, text2) { + // Cache the text lengths to prevent multiple calls. + var text1_length = text1.length; + var text2_length = text2.length; + var max_d = Math.ceil((text1_length + text2_length) / 2); + var v_offset = max_d; + var v_length = 2 * max_d; + var v1 = new Array(v_length); + var v2 = new Array(v_length); + // Setting all elements to -1 is faster in Chrome & Firefox than mixing + // integers and undefined. + for (var x = 0; x < v_length; x++) { + v1[x] = -1; + v2[x] = -1; + } + v1[v_offset + 1] = 0; + v2[v_offset + 1] = 0; + var delta = text1_length - text2_length; + // If the total number of characters is odd, then the front path will collide + // with the reverse path. + var front = (delta % 2 != 0); + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + var k1start = 0; + var k1end = 0; + var k2start = 0; + var k2end = 0; + for (var d = 0; d < max_d; d++) { + // Walk the front path one step. + for (var k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + var k1_offset = v_offset + k1; + var x1; + if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { + x1 = v1[k1_offset + 1]; + } else { + x1 = v1[k1_offset - 1] + 1; + } + var y1 = x1 - k1; + while (x1 < text1_length && y1 < text2_length && + text1.charAt(x1) == text2.charAt(y1)) { + x1++; + y1++; + } + v1[k1_offset] = x1; + if (x1 > text1_length) { + // Ran off the right of the graph. + k1end += 2; + } else if (y1 > text2_length) { + // Ran off the bottom of the graph. + k1start += 2; + } else if (front) { + var k2_offset = v_offset + delta - k1; + if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { + // Mirror x2 onto top-left coordinate system. + var x2 = text1_length - v2[k2_offset]; + if (x1 >= x2) { + // Overlap detected. + return diff_bisectSplit_(text1, text2, x1, y1); + } + } + } + } + + // Walk the reverse path one step. + for (var k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + var k2_offset = v_offset + k2; + var x2; + if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { + x2 = v2[k2_offset + 1]; + } else { + x2 = v2[k2_offset - 1] + 1; + } + var y2 = x2 - k2; + while (x2 < text1_length && y2 < text2_length && + text1.charAt(text1_length - x2 - 1) == + text2.charAt(text2_length - y2 - 1)) { + x2++; + y2++; + } + v2[k2_offset] = x2; + if (x2 > text1_length) { + // Ran off the left of the graph. + k2end += 2; + } else if (y2 > text2_length) { + // Ran off the top of the graph. + k2start += 2; + } else if (!front) { + var k1_offset = v_offset + delta - k2; + if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { + var x1 = v1[k1_offset]; + var y1 = v_offset + x1 - k1_offset; + // Mirror x2 onto top-left coordinate system. + x2 = text1_length - x2; + if (x1 >= x2) { + // Overlap detected. + return diff_bisectSplit_(text1, text2, x1, y1); + } + } + } + } + } + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; + } + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} x Index of split point in text1. + * @param {number} y Index of split point in text2. + * @return {Array} Array of diff tuples. + */ + function diff_bisectSplit_(text1, text2, x, y) { + var text1a = text1.substring(0, x); + var text2a = text2.substring(0, y); + var text1b = text1.substring(x); + var text2b = text2.substring(y); + + // Compute both diffs serially. + var diffs = diff_main(text1a, text2a); + var diffsb = diff_main(text1b, text2b); + + return diffs.concat(diffsb); + } + + /** + * Determine the common prefix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the start of each + * string. + */ + function diff_commonPrefix(text1, text2) { + // Quick check for common null cases. + if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) { + return 0; + } + // Binary search. + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + var pointermin = 0; + var pointermax = Math.min(text1.length, text2.length); + var pointermid = pointermax; + var pointerstart = 0; + while (pointermin < pointermid) { + if (text1.substring(pointerstart, pointermid) == + text2.substring(pointerstart, pointermid)) { + pointermin = pointermid; + pointerstart = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); + } + return pointermid; + } + + /** + * Determine the common suffix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of each string. + */ + function diff_commonSuffix(text1, text2) { + // Quick check for common null cases. + if (!text1 || !text2 || + text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) { + return 0; + } + // Binary search. + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + var pointermin = 0; + var pointermax = Math.min(text1.length, text2.length); + var pointermid = pointermax; + var pointerend = 0; + while (pointermin < pointermid) { + if (text1.substring(text1.length - pointermid, text1.length - pointerend) == + text2.substring(text2.length - pointermid, text2.length - pointerend)) { + pointermin = pointermid; + pointerend = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); + } + return pointermid; + } + + /** + * Do the two texts share a substring which is at least half the length of the + * longer text? + * This speedup can produce non-minimal diffs. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {Array.} Five element Array, containing the prefix of + * text1, the suffix of text1, the prefix of text2, the suffix of + * text2 and the common middle. Or null if there was no match. + */ + function diff_halfMatch_(text1, text2) { + var longtext = text1.length > text2.length ? text1 : text2; + var shorttext = text1.length > text2.length ? text2 : text1; + if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { + return null; // Pointless. + } + + /** + * Does a substring of shorttext exist within longtext such that the substring + * is at least half the length of longtext? + * Closure, but does not reference any external variables. + * @param {string} longtext Longer string. + * @param {string} shorttext Shorter string. + * @param {number} i Start index of quarter length substring within longtext. + * @return {Array.} Five element Array, containing the prefix of + * longtext, the suffix of longtext, the prefix of shorttext, the suffix + * of shorttext and the common middle. Or null if there was no match. + * @private + */ + function diff_halfMatchI_(longtext, shorttext, i) { + // Start with a 1/4 length substring at position i as a seed. + var seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); + var j = -1; + var best_common = ''; + var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b; + while ((j = shorttext.indexOf(seed, j + 1)) != -1) { + var prefixLength = diff_commonPrefix(longtext.substring(i), + shorttext.substring(j)); + var suffixLength = diff_commonSuffix(longtext.substring(0, i), + shorttext.substring(0, j)); + if (best_common.length < suffixLength + prefixLength) { + best_common = shorttext.substring(j - suffixLength, j) + + shorttext.substring(j, j + prefixLength); + best_longtext_a = longtext.substring(0, i - suffixLength); + best_longtext_b = longtext.substring(i + prefixLength); + best_shorttext_a = shorttext.substring(0, j - suffixLength); + best_shorttext_b = shorttext.substring(j + prefixLength); + } + } + if (best_common.length * 2 >= longtext.length) { + return [best_longtext_a, best_longtext_b, + best_shorttext_a, best_shorttext_b, best_common]; + } else { + return null; + } + } + + // First check if the second quarter is the seed for a half-match. + var hm1 = diff_halfMatchI_(longtext, shorttext, + Math.ceil(longtext.length / 4)); + // Check again based on the third quarter. + var hm2 = diff_halfMatchI_(longtext, shorttext, + Math.ceil(longtext.length / 2)); + var hm; + if (!hm1 && !hm2) { + return null; + } else if (!hm2) { + hm = hm1; + } else if (!hm1) { + hm = hm2; + } else { + // Both matched. Select the longest. + hm = hm1[4].length > hm2[4].length ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + var text1_a, text1_b, text2_a, text2_b; + if (text1.length > text2.length) { + text1_a = hm[0]; + text1_b = hm[1]; + text2_a = hm[2]; + text2_b = hm[3]; + } else { + text2_a = hm[0]; + text2_b = hm[1]; + text1_a = hm[2]; + text1_b = hm[3]; + } + var mid_common = hm[4]; + return [text1_a, text1_b, text2_a, text2_b, mid_common]; + } + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param {Array} diffs Array of diff tuples. + */ + function diff_cleanupMerge(diffs) { + diffs.push([DIFF_EQUAL, '']); // Add a dummy entry at the end. + var pointer = 0; + var count_delete = 0; + var count_insert = 0; + var text_delete = ''; + var text_insert = ''; + var commonlength; + while (pointer < diffs.length) { + switch (diffs[pointer][0]) { + case DIFF_INSERT: + count_insert++; + text_insert += diffs[pointer][1]; + pointer++; + break; + case DIFF_DELETE: + count_delete++; + text_delete += diffs[pointer][1]; + pointer++; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete + count_insert > 1) { + if (count_delete !== 0 && count_insert !== 0) { + // Factor out any common prefixies. + commonlength = diff_commonPrefix(text_insert, text_delete); + if (commonlength !== 0) { + if ((pointer - count_delete - count_insert) > 0 && + diffs[pointer - count_delete - count_insert - 1][0] == + DIFF_EQUAL) { + diffs[pointer - count_delete - count_insert - 1][1] += + text_insert.substring(0, commonlength); + } else { + diffs.splice(0, 0, [DIFF_EQUAL, + text_insert.substring(0, commonlength)]); + pointer++; + } + text_insert = text_insert.substring(commonlength); + text_delete = text_delete.substring(commonlength); + } + // Factor out any common suffixies. + commonlength = diff_commonSuffix(text_insert, text_delete); + if (commonlength !== 0) { + diffs[pointer][1] = text_insert.substring(text_insert.length - + commonlength) + diffs[pointer][1]; + text_insert = text_insert.substring(0, text_insert.length - + commonlength); + text_delete = text_delete.substring(0, text_delete.length - + commonlength); + } + } + // Delete the offending records and add the merged ones. + if (count_delete === 0) { + diffs.splice(pointer - count_insert, + count_delete + count_insert, [DIFF_INSERT, text_insert]); + } else if (count_insert === 0) { + diffs.splice(pointer - count_delete, + count_delete + count_insert, [DIFF_DELETE, text_delete]); + } else { + diffs.splice(pointer - count_delete - count_insert, + count_delete + count_insert, [DIFF_DELETE, text_delete], + [DIFF_INSERT, text_insert]); + } + pointer = pointer - count_delete - count_insert + + (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1; + } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) { + // Merge this equality with the previous one. + diffs[pointer - 1][1] += diffs[pointer][1]; + diffs.splice(pointer, 1); + } else { + pointer++; + } + count_insert = 0; + count_delete = 0; + text_delete = ''; + text_insert = ''; + break; + } + } + if (diffs[diffs.length - 1][1] === '') { + diffs.pop(); // Remove the dummy entry at the end. + } + + // Second pass: look for single edits surrounded on both sides by equalities + // which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + var changes = false; + pointer = 1; + // Intentionally ignore the first and last element (don't need checking). + while (pointer < diffs.length - 1) { + if (diffs[pointer - 1][0] == DIFF_EQUAL && + diffs[pointer + 1][0] == DIFF_EQUAL) { + // This is a single edit surrounded by equalities. + if (diffs[pointer][1].substring(diffs[pointer][1].length - + diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) { + // Shift the edit over the previous equality. + diffs[pointer][1] = diffs[pointer - 1][1] + + diffs[pointer][1].substring(0, diffs[pointer][1].length - + diffs[pointer - 1][1].length); + diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; + diffs.splice(pointer - 1, 1); + changes = true; + } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) == + diffs[pointer + 1][1]) { + // Shift the edit over the next equality. + diffs[pointer - 1][1] += diffs[pointer + 1][1]; + diffs[pointer][1] = + diffs[pointer][1].substring(diffs[pointer + 1][1].length) + + diffs[pointer + 1][1]; + diffs.splice(pointer + 1, 1); + changes = true; + } + } + pointer++; + } + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) { + diff_cleanupMerge(diffs); + } + } + + var diff = diff_main; + diff.INSERT = DIFF_INSERT; + diff.DELETE = DIFF_DELETE; + diff.EQUAL = DIFF_EQUAL; + + var diff_1 = diff; + + /* + * Modify a diff such that the cursor position points to the start of a change: + * E.g. + * cursor_normalize_diff([[DIFF_EQUAL, 'abc']], 1) + * => [1, [[DIFF_EQUAL, 'a'], [DIFF_EQUAL, 'bc']]] + * cursor_normalize_diff([[DIFF_INSERT, 'new'], [DIFF_DELETE, 'xyz']], 2) + * => [2, [[DIFF_INSERT, 'new'], [DIFF_DELETE, 'xy'], [DIFF_DELETE, 'z']]] + * + * @param {Array} diffs Array of diff tuples + * @param {Int} cursor_pos Suggested edit position. Must not be out of bounds! + * @return {Array} A tuple [cursor location in the modified diff, modified diff] + */ + function cursor_normalize_diff (diffs, cursor_pos) { + if (cursor_pos === 0) { + return [DIFF_EQUAL, diffs]; + } + for (var current_pos = 0, i = 0; i < diffs.length; i++) { + var d = diffs[i]; + if (d[0] === DIFF_DELETE || d[0] === DIFF_EQUAL) { + var next_pos = current_pos + d[1].length; + if (cursor_pos === next_pos) { + return [i + 1, diffs]; + } else if (cursor_pos < next_pos) { + // copy to prevent side effects + diffs = diffs.slice(); + // split d into two diff changes + var split_pos = cursor_pos - current_pos; + var d_left = [d[0], d[1].slice(0, split_pos)]; + var d_right = [d[0], d[1].slice(split_pos)]; + diffs.splice(i, 1, d_left, d_right); + return [i + 1, diffs]; + } else { + current_pos = next_pos; + } + } + } + throw new Error('cursor_pos is out of bounds!') + } + + /* + * Modify a diff such that the edit position is "shifted" to the proposed edit location (cursor_position). + * + * Case 1) + * Check if a naive shift is possible: + * [0, X], [ 1, Y] -> [ 1, Y], [0, X] (if X + Y === Y + X) + * [0, X], [-1, Y] -> [-1, Y], [0, X] (if X + Y === Y + X) - holds same result + * Case 2) + * Check if the following shifts are possible: + * [0, 'pre'], [ 1, 'prefix'] -> [ 1, 'pre'], [0, 'pre'], [ 1, 'fix'] + * [0, 'pre'], [-1, 'prefix'] -> [-1, 'pre'], [0, 'pre'], [-1, 'fix'] + * ^ ^ + * d d_next + * + * @param {Array} diffs Array of diff tuples + * @param {Int} cursor_pos Suggested edit position. Must not be out of bounds! + * @return {Array} Array of diff tuples + */ + function fix_cursor (diffs, cursor_pos) { + var norm = cursor_normalize_diff(diffs, cursor_pos); + var ndiffs = norm[1]; + var cursor_pointer = norm[0]; + var d = ndiffs[cursor_pointer]; + var d_next = ndiffs[cursor_pointer + 1]; + + if (d == null) { + // Text was deleted from end of original string, + // cursor is now out of bounds in new string + return diffs; + } else if (d[0] !== DIFF_EQUAL) { + // A modification happened at the cursor location. + // This is the expected outcome, so we can return the original diff. + return diffs; + } else { + if (d_next != null && d[1] + d_next[1] === d_next[1] + d[1]) { + // Case 1) + // It is possible to perform a naive shift + ndiffs.splice(cursor_pointer, 2, d_next, d); + return merge_tuples(ndiffs, cursor_pointer, 2) + } else if (d_next != null && d_next[1].indexOf(d[1]) === 0) { + // Case 2) + // d[1] is a prefix of d_next[1] + // We can assume that d_next[0] !== 0, since d[0] === 0 + // Shift edit locations.. + ndiffs.splice(cursor_pointer, 2, [d_next[0], d[1]], [0, d[1]]); + var suffix = d_next[1].slice(d[1].length); + if (suffix.length > 0) { + ndiffs.splice(cursor_pointer + 2, 0, [d_next[0], suffix]); + } + return merge_tuples(ndiffs, cursor_pointer, 3) + } else { + // Not possible to perform any modification + return diffs; + } + } + } + + /* + * Check diff did not split surrogate pairs. + * Ex. [0, '\uD83D'], [-1, '\uDC36'], [1, '\uDC2F'] -> [-1, '\uD83D\uDC36'], [1, '\uD83D\uDC2F'] + * '\uD83D\uDC36' === '🐶', '\uD83D\uDC2F' === '🐯' + * + * @param {Array} diffs Array of diff tuples + * @return {Array} Array of diff tuples + */ + function fix_emoji (diffs) { + var compact = false; + var starts_with_pair_end = function(str) { + return str.charCodeAt(0) >= 0xDC00 && str.charCodeAt(0) <= 0xDFFF; + }; + var ends_with_pair_start = function(str) { + return str.charCodeAt(str.length-1) >= 0xD800 && str.charCodeAt(str.length-1) <= 0xDBFF; + }; + for (var i = 2; i < diffs.length; i += 1) { + if (diffs[i-2][0] === DIFF_EQUAL && ends_with_pair_start(diffs[i-2][1]) && + diffs[i-1][0] === DIFF_DELETE && starts_with_pair_end(diffs[i-1][1]) && + diffs[i][0] === DIFF_INSERT && starts_with_pair_end(diffs[i][1])) { + compact = true; + + diffs[i-1][1] = diffs[i-2][1].slice(-1) + diffs[i-1][1]; + diffs[i][1] = diffs[i-2][1].slice(-1) + diffs[i][1]; + + diffs[i-2][1] = diffs[i-2][1].slice(0, -1); + } + } + if (!compact) { + return diffs; + } + var fixed_diffs = []; + for (var i = 0; i < diffs.length; i += 1) { + if (diffs[i][1].length > 0) { + fixed_diffs.push(diffs[i]); + } + } + return fixed_diffs; + } + + /* + * Try to merge tuples with their neigbors in a given range. + * E.g. [0, 'a'], [0, 'b'] -> [0, 'ab'] + * + * @param {Array} diffs Array of diff tuples. + * @param {Int} start Position of the first element to merge (diffs[start] is also merged with diffs[start - 1]). + * @param {Int} length Number of consecutive elements to check. + * @return {Array} Array of merged diff tuples. + */ + function merge_tuples (diffs, start, length) { + // Check from (start-1) to (start+length). + for (var i = start + length - 1; i >= 0 && i >= start - 1; i--) { + if (i + 1 < diffs.length) { + var left_d = diffs[i]; + var right_d = diffs[i+1]; + if (left_d[0] === right_d[1]) { + diffs.splice(i, 2, [left_d[0], left_d[1] + right_d[1]]); + } + } + } + return diffs; + } + + function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; + } + + var keys = createCommonjsModule(function (module, exports) { + exports = module.exports = typeof Object.keys === 'function' + ? Object.keys : shim; + + exports.shim = shim; + function shim (obj) { + var keys = []; + for (var key in obj) keys.push(key); + return keys; + } + }); + var keys_1 = keys.shim; + + var is_arguments = createCommonjsModule(function (module, exports) { + var supportsArgumentsClass = (function(){ + return Object.prototype.toString.call(arguments) + })() == '[object Arguments]'; + + exports = module.exports = supportsArgumentsClass ? supported : unsupported; + + exports.supported = supported; + function supported(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; + } + exports.unsupported = unsupported; + function unsupported(object){ + return object && + typeof object == 'object' && + typeof object.length == 'number' && + Object.prototype.hasOwnProperty.call(object, 'callee') && + !Object.prototype.propertyIsEnumerable.call(object, 'callee') || + false; + }}); + var is_arguments_1 = is_arguments.supported; + var is_arguments_2 = is_arguments.unsupported; + + var deepEqual_1 = createCommonjsModule(function (module) { + var pSlice = Array.prototype.slice; + + + + var deepEqual = module.exports = function (actual, expected, opts) { + if (!opts) opts = {}; + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (actual instanceof Date && expected instanceof Date) { + return actual.getTime() === expected.getTime(); + + // 7.3. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (!actual || !expected || typeof actual != 'object' && typeof expected != 'object') { + return opts.strict ? actual === expected : actual == expected; + + // 7.4. For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected, opts); + } + }; + + function isUndefinedOrNull(value) { + return value === null || value === undefined; + } + + function isBuffer (x) { + if (!x || typeof x !== 'object' || typeof x.length !== 'number') return false; + if (typeof x.copy !== 'function' || typeof x.slice !== 'function') { + return false; + } + if (x.length > 0 && typeof x[0] !== 'number') return false; + return true; + } + + function objEquiv(a, b, opts) { + var i, key; + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if (is_arguments(a)) { + if (!is_arguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return deepEqual(a, b, opts); + } + if (isBuffer(a)) { + if (!isBuffer(b)) { + return false; + } + if (a.length !== b.length) return false; + for (i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; + } + try { + var ka = keys(a), + kb = keys(b); + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!deepEqual(a[key], b[key], opts)) return false; + } + return typeof a === typeof b; + } + }); + + var hasOwn = Object.prototype.hasOwnProperty; + var toStr = Object.prototype.toString; + + var isArray = function isArray(arr) { + if (typeof Array.isArray === 'function') { + return Array.isArray(arr); + } + + return toStr.call(arr) === '[object Array]'; + }; + + var isPlainObject = function isPlainObject(obj) { + if (!obj || toStr.call(obj) !== '[object Object]') { + return false; + } + + var hasOwnConstructor = hasOwn.call(obj, 'constructor'); + var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf'); + // Not own constructor property must be Object + if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + var key; + for (key in obj) { /**/ } + + return typeof key === 'undefined' || hasOwn.call(obj, key); + }; + + var extend = function extend() { + var options, name, src, copy, copyIsArray, clone; + var target = arguments[0]; + var i = 1; + var length = arguments.length; + var deep = false; + + // Handle a deep copy situation + if (typeof target === 'boolean') { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + if (target == null || (typeof target !== 'object' && typeof target !== 'function')) { + target = {}; + } + + for (; i < length; ++i) { + options = arguments[i]; + // Only deal with non-null/undefined values + if (options != null) { + // Extend the base object + for (name in options) { + src = target[name]; + copy = options[name]; + + // Prevent never-ending loop + if (target !== copy) { + // Recurse if we're merging plain objects or arrays + if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) { + if (copyIsArray) { + copyIsArray = false; + clone = src && isArray(src) ? src : []; + } else { + clone = src && isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[name] = extend(deep, clone, copy); + + // Don't bring in undefined values + } else if (typeof copy !== 'undefined') { + target[name] = copy; + } + } + } + } + } + + // Return the modified object + return target; + }; + + var lib = { + attributes: { + compose: function (a, b, keepNull) { + if (typeof a !== 'object') a = {}; + if (typeof b !== 'object') b = {}; + var attributes = extend(true, {}, b); + if (!keepNull) { + attributes = Object.keys(attributes).reduce(function (copy, key) { + if (attributes[key] != null) { + copy[key] = attributes[key]; + } + return copy; + }, {}); + } + for (var key in a) { + if (a[key] !== undefined && b[key] === undefined) { + attributes[key] = a[key]; + } + } + return Object.keys(attributes).length > 0 ? attributes : undefined; + }, + + diff: function(a, b) { + if (typeof a !== 'object') a = {}; + if (typeof b !== 'object') b = {}; + var attributes = Object.keys(a).concat(Object.keys(b)).reduce(function (attributes, key) { + if (!deepEqual_1(a[key], b[key])) { + attributes[key] = b[key] === undefined ? null : b[key]; + } + return attributes; + }, {}); + return Object.keys(attributes).length > 0 ? attributes : undefined; + }, + + transform: function (a, b, priority) { + if (typeof a !== 'object') return b; + if (typeof b !== 'object') return undefined; + if (!priority) return b; // b simply overwrites us without priority + var attributes = Object.keys(b).reduce(function (attributes, key) { + if (a[key] === undefined) attributes[key] = b[key]; // null is a valid value + return attributes; + }, {}); + return Object.keys(attributes).length > 0 ? attributes : undefined; + } + }, + + iterator: function (ops) { + return new Iterator(ops); + }, + + length: function (op) { + if (typeof op['delete'] === 'number') { + return op['delete']; + } else if (typeof op.retain === 'number') { + return op.retain; + } else { + return typeof op.insert === 'string' ? op.insert.length : 1; + } + } + }; + + + function Iterator(ops) { + this.ops = ops; + this.index = 0; + this.offset = 0; + } + Iterator.prototype.hasNext = function () { + return this.peekLength() < Infinity; + }; + + Iterator.prototype.next = function (length) { + if (!length) length = Infinity; + var nextOp = this.ops[this.index]; + if (nextOp) { + var offset = this.offset; + var opLength = lib.length(nextOp); + if (length >= opLength - offset) { + length = opLength - offset; + this.index += 1; + this.offset = 0; + } else { + this.offset += length; + } + if (typeof nextOp['delete'] === 'number') { + return { 'delete': length }; + } else { + var retOp = {}; + if (nextOp.attributes) { + retOp.attributes = nextOp.attributes; + } + if (typeof nextOp.retain === 'number') { + retOp.retain = length; + } else if (typeof nextOp.insert === 'string') { + retOp.insert = nextOp.insert.substr(offset, length); + } else { + // offset should === 0, length should === 1 + retOp.insert = nextOp.insert; + } + return retOp; + } + } else { + return { retain: Infinity }; + } + }; + + Iterator.prototype.peek = function () { + return this.ops[this.index]; + }; + + Iterator.prototype.peekLength = function () { + if (this.ops[this.index]) { + // Should never return 0 if our index is being managed correctly + return lib.length(this.ops[this.index]) - this.offset; + } else { + return Infinity; + } + }; + + Iterator.prototype.peekType = function () { + if (this.ops[this.index]) { + if (typeof this.ops[this.index]['delete'] === 'number') { + return 'delete'; + } else if (typeof this.ops[this.index].retain === 'number') { + return 'retain'; + } else { + return 'insert'; + } + } + return 'retain'; + }; + + + var op = lib; + + var NULL_CHARACTER = String.fromCharCode(0); // Placeholder char for embed in diff() + + + var Delta = function (ops) { + // Assume we are given a well formed ops + if (Array.isArray(ops)) { + this.ops = ops; + } else if (ops != null && Array.isArray(ops.ops)) { + this.ops = ops.ops; + } else { + this.ops = []; + } + }; + + + Delta.prototype.insert = function (text, attributes) { + var newOp = {}; + if (text.length === 0) return this; + newOp.insert = text; + if (attributes != null && typeof attributes === 'object' && Object.keys(attributes).length > 0) { + newOp.attributes = attributes; + } + return this.push(newOp); + }; + + Delta.prototype['delete'] = function (length) { + if (length <= 0) return this; + return this.push({ 'delete': length }); + }; + + Delta.prototype.retain = function (length, attributes) { + if (length <= 0) return this; + var newOp = { retain: length }; + if (attributes != null && typeof attributes === 'object' && Object.keys(attributes).length > 0) { + newOp.attributes = attributes; + } + return this.push(newOp); + }; + + Delta.prototype.push = function (newOp) { + var index = this.ops.length; + var lastOp = this.ops[index - 1]; + newOp = extend(true, {}, newOp); + if (typeof lastOp === 'object') { + if (typeof newOp['delete'] === 'number' && typeof lastOp['delete'] === 'number') { + this.ops[index - 1] = { 'delete': lastOp['delete'] + newOp['delete'] }; + return this; + } + // Since it does not matter if we insert before or after deleting at the same index, + // always prefer to insert first + if (typeof lastOp['delete'] === 'number' && newOp.insert != null) { + index -= 1; + lastOp = this.ops[index - 1]; + if (typeof lastOp !== 'object') { + this.ops.unshift(newOp); + return this; + } + } + if (deepEqual_1(newOp.attributes, lastOp.attributes)) { + if (typeof newOp.insert === 'string' && typeof lastOp.insert === 'string') { + this.ops[index - 1] = { insert: lastOp.insert + newOp.insert }; + if (typeof newOp.attributes === 'object') this.ops[index - 1].attributes = newOp.attributes; + return this; + } else if (typeof newOp.retain === 'number' && typeof lastOp.retain === 'number') { + this.ops[index - 1] = { retain: lastOp.retain + newOp.retain }; + if (typeof newOp.attributes === 'object') this.ops[index - 1].attributes = newOp.attributes; + return this; + } + } + } + if (index === this.ops.length) { + this.ops.push(newOp); + } else { + this.ops.splice(index, 0, newOp); + } + return this; + }; + + Delta.prototype.chop = function () { + var lastOp = this.ops[this.ops.length - 1]; + if (lastOp && lastOp.retain && !lastOp.attributes) { + this.ops.pop(); + } + return this; + }; + + Delta.prototype.filter = function (predicate) { + return this.ops.filter(predicate); + }; + + Delta.prototype.forEach = function (predicate) { + this.ops.forEach(predicate); + }; + + Delta.prototype.map = function (predicate) { + return this.ops.map(predicate); + }; + + Delta.prototype.partition = function (predicate) { + var passed = [], failed = []; + this.forEach(function(op$$1) { + var target = predicate(op$$1) ? passed : failed; + target.push(op$$1); + }); + return [passed, failed]; + }; + + Delta.prototype.reduce = function (predicate, initial) { + return this.ops.reduce(predicate, initial); + }; + + Delta.prototype.changeLength = function () { + return this.reduce(function (length, elem) { + if (elem.insert) { + return length + op.length(elem); + } else if (elem.delete) { + return length - elem.delete; + } + return length; + }, 0); + }; + + Delta.prototype.length = function () { + return this.reduce(function (length, elem) { + return length + op.length(elem); + }, 0); + }; + + Delta.prototype.slice = function (start, end) { + start = start || 0; + if (typeof end !== 'number') end = Infinity; + var ops = []; + var iter = op.iterator(this.ops); + var index = 0; + while (index < end && iter.hasNext()) { + var nextOp; + if (index < start) { + nextOp = iter.next(start - index); + } else { + nextOp = iter.next(end - index); + ops.push(nextOp); + } + index += op.length(nextOp); + } + return new Delta(ops); + }; + + + Delta.prototype.compose = function (other) { + var thisIter = op.iterator(this.ops); + var otherIter = op.iterator(other.ops); + var delta = new Delta(); + while (thisIter.hasNext() || otherIter.hasNext()) { + if (otherIter.peekType() === 'insert') { + delta.push(otherIter.next()); + } else if (thisIter.peekType() === 'delete') { + delta.push(thisIter.next()); + } else { + var length = Math.min(thisIter.peekLength(), otherIter.peekLength()); + var thisOp = thisIter.next(length); + var otherOp = otherIter.next(length); + if (typeof otherOp.retain === 'number') { + var newOp = {}; + if (typeof thisOp.retain === 'number') { + newOp.retain = length; + } else { + newOp.insert = thisOp.insert; + } + // Preserve null when composing with a retain, otherwise remove it for inserts + var attributes = op.attributes.compose(thisOp.attributes, otherOp.attributes, typeof thisOp.retain === 'number'); + if (attributes) newOp.attributes = attributes; + delta.push(newOp); + // Other op should be delete, we could be an insert or retain + // Insert + delete cancels out + } else if (typeof otherOp['delete'] === 'number' && typeof thisOp.retain === 'number') { + delta.push(otherOp); + } + } + } + return delta.chop(); + }; + + Delta.prototype.concat = function (other) { + var delta = new Delta(this.ops.slice()); + if (other.ops.length > 0) { + delta.push(other.ops[0]); + delta.ops = delta.ops.concat(other.ops.slice(1)); + } + return delta; + }; + + Delta.prototype.diff = function (other, index) { + if (this.ops === other.ops) { + return new Delta(); + } + var strings = [this, other].map(function (delta) { + return delta.map(function (op$$1) { + if (op$$1.insert != null) { + return typeof op$$1.insert === 'string' ? op$$1.insert : NULL_CHARACTER; + } + var prep = (delta === other) ? 'on' : 'with'; + throw new Error('diff() called ' + prep + ' non-document'); + }).join(''); + }); + var delta = new Delta(); + var diffResult = diff_1(strings[0], strings[1], index); + var thisIter = op.iterator(this.ops); + var otherIter = op.iterator(other.ops); + diffResult.forEach(function (component) { + var length = component[1].length; + while (length > 0) { + var opLength = 0; + switch (component[0]) { + case diff_1.INSERT: + opLength = Math.min(otherIter.peekLength(), length); + delta.push(otherIter.next(opLength)); + break; + case diff_1.DELETE: + opLength = Math.min(length, thisIter.peekLength()); + thisIter.next(opLength); + delta['delete'](opLength); + break; + case diff_1.EQUAL: + opLength = Math.min(thisIter.peekLength(), otherIter.peekLength(), length); + var thisOp = thisIter.next(opLength); + var otherOp = otherIter.next(opLength); + if (deepEqual_1(thisOp.insert, otherOp.insert)) { + delta.retain(opLength, op.attributes.diff(thisOp.attributes, otherOp.attributes)); + } else { + delta.push(otherOp)['delete'](opLength); + } + break; + } + length -= opLength; + } + }); + return delta.chop(); + }; + + Delta.prototype.eachLine = function (predicate, newline) { + newline = newline || '\n'; + var iter = op.iterator(this.ops); + var line = new Delta(); + var i = 0; + while (iter.hasNext()) { + if (iter.peekType() !== 'insert') return; + var thisOp = iter.peek(); + var start = op.length(thisOp) - iter.peekLength(); + var index = typeof thisOp.insert === 'string' ? + thisOp.insert.indexOf(newline, start) - start : -1; + if (index < 0) { + line.push(iter.next()); + } else if (index > 0) { + line.push(iter.next(index)); + } else { + if (predicate(line, iter.next(1).attributes || {}, i) === false) { + return; + } + i += 1; + line = new Delta(); + } + } + if (line.length() > 0) { + predicate(line, {}, i); + } + }; + + Delta.prototype.transform = function (other, priority) { + priority = !!priority; + if (typeof other === 'number') { + return this.transformPosition(other, priority); + } + var thisIter = op.iterator(this.ops); + var otherIter = op.iterator(other.ops); + var delta = new Delta(); + while (thisIter.hasNext() || otherIter.hasNext()) { + if (thisIter.peekType() === 'insert' && (priority || otherIter.peekType() !== 'insert')) { + delta.retain(op.length(thisIter.next())); + } else if (otherIter.peekType() === 'insert') { + delta.push(otherIter.next()); + } else { + var length = Math.min(thisIter.peekLength(), otherIter.peekLength()); + var thisOp = thisIter.next(length); + var otherOp = otherIter.next(length); + if (thisOp['delete']) { + // Our delete either makes their delete redundant or removes their retain + continue; + } else if (otherOp['delete']) { + delta.push(otherOp); + } else { + // We retain either their retain or insert + delta.retain(length, op.attributes.transform(thisOp.attributes, otherOp.attributes, priority)); + } + } + } + return delta.chop(); + }; + + Delta.prototype.transformPosition = function (index, priority) { + priority = !!priority; + var thisIter = op.iterator(this.ops); + var offset = 0; + while (thisIter.hasNext() && offset <= index) { + var length = thisIter.peekLength(); + var nextType = thisIter.peekType(); + thisIter.next(); + if (nextType === 'delete') { + index -= Math.min(length, index - offset); + continue; + } else if (nextType === 'insert' && (offset < index || !priority)) { + index += length; + } + offset += length; + } + return index; + }; + + + var delta = Delta; + + var createIsSameValueZero = function createIsSameValueZero() { + /** + * @function isSameValueZero + * + * @description + * are the objects passed strictly equal or both NaN + * + * @param {*} objectA the object to compare against + * @param {*} objectB the object to test + * @returns {boolean} are the objects equal by the SameValueZero principle + */ + return function (objectA, objectB) { + return objectA === objectB || objectA !== objectA && objectB !== objectB; + }; + }; + + /** + * @function toPairs + * + * @param {Map|Set} iterable the iterable to convert to [key, value] pairs (entries) + * @returns {{keys: Array<*>, values: Array<*>}} the [key, value] pairs + */ + var toPairs = function toPairs(iterable) { + var pairs = { keys: new Array(iterable.size), values: new Array(iterable.size) }; + + var index = 0; + + iterable.forEach(function (value, key) { + pairs.keys[index] = key; + pairs.values[index++] = value; + }); + + return pairs; + }; + + /** + * @function areIterablesEqual + * + * @description + * determine if the iterables are equivalent in value + * + * @param {Map|Set} objectA the object to test + * @param {Map|Set} objectB the object to test against + * @param {function} comparator the comparator to determine deep equality + * @param {boolean} shouldCompareKeys should the keys be tested in the equality comparison + * @returns {boolean} are the objects equal in value + */ + var areIterablesEqual = function areIterablesEqual(objectA, objectB, comparator, shouldCompareKeys) { + if (objectA.size !== objectB.size) { + return false; + } + + var pairsA = toPairs(objectA); + var pairsB = toPairs(objectB); + + return shouldCompareKeys ? comparator(pairsA.keys, pairsB.keys) && comparator(pairsA.values, pairsB.values) : comparator(pairsA.values, pairsB.values); + }; + + // utils + + var HAS_MAP_SUPPORT = typeof Map === 'function'; + var HAS_SET_SUPPORT = typeof Set === 'function'; + + var isSameValueZero = createIsSameValueZero(); + + var createComparator = function createComparator(createIsEqual) { + var isEqual = typeof createIsEqual === 'function' ? createIsEqual(comparator) : comparator; // eslint-disable-line + + /** + * @function comparator + * + * @description + * compare the value of the two objects and return true if they are equivalent in values + * + * @param {*} objectA the object to test against + * @param {*} objectB the object to test + * @returns {boolean} are objectA and objectB equivalent in value + */ + function comparator(objectA, objectB) { + if (isSameValueZero(objectA, objectB)) { + return true; + } + + var typeOfA = typeof objectA; + + if (typeOfA !== typeof objectB) { + return false; + } + + if (typeOfA === 'object' && objectA && objectB) { + var arrayA = Array.isArray(objectA); + var arrayB = Array.isArray(objectB); + + var index = void 0; + + if (arrayA || arrayB) { + if (arrayA !== arrayB || objectA.length !== objectB.length) { + return false; + } + + for (index = 0; index < objectA.length; index++) { + if (!isEqual(objectA[index], objectB[index])) { + return false; + } + } + + return true; + } + + var dateA = objectA instanceof Date; + var dateB = objectB instanceof Date; + + if (dateA || dateB) { + return dateA === dateB && isSameValueZero(objectA.getTime(), objectB.getTime()); + } + + var regexpA = objectA instanceof RegExp; + var regexpB = objectB instanceof RegExp; + + if (regexpA || regexpB) { + return regexpA === regexpB && objectA.source === objectB.source && objectA.global === objectB.global && objectA.ignoreCase === objectB.ignoreCase && objectA.multiline === objectB.multiline; + } + + if (HAS_MAP_SUPPORT) { + var mapA = objectA instanceof Map; + var mapB = objectB instanceof Map; + + if (mapA || mapB) { + return mapA === mapB && areIterablesEqual(objectA, objectB, comparator, true); + } + } + + if (HAS_SET_SUPPORT) { + var setA = objectA instanceof Set; + var setB = objectB instanceof Set; + + if (setA || setB) { + return setA === setB && areIterablesEqual(objectA, objectB, comparator, false); + } + } + + var keysA = Object.keys(objectA); + + if (keysA.length !== Object.keys(objectB).length) { + return false; + } + + var key = void 0; + + for (index = 0; index < keysA.length; index++) { + key = keysA[index]; + + if (!Object.prototype.hasOwnProperty.call(objectB, key) || !isEqual(objectA[key], objectB[key])) { + return false; + } + } + + return true; + } + + return false; + } + + return comparator; + }; + + // comparator + + var deepEqual = createComparator(); + var shallowEqual = createComparator(createIsSameValueZero); + + var SOURCE_USER = 'user'; + var SOURCE_SILENT = 'silent'; + var empty = {}; + + var Editor = function (_EventDispatcher) { + inherits(Editor, _EventDispatcher); + + function Editor() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, Editor); + + var _this = possibleConstructorReturn(this, (Editor.__proto__ || Object.getPrototypeOf(Editor)).call(this)); + + _this.selection = null; + _this.activeFormats = empty; + setContents(_this, options.contents || _this.delta().insert('\n')); + if (options.modules) options.modules.forEach(function (module) { + return module(_this); + }); + return _this; + } + + createClass(Editor, [{ + key: 'delta', + value: function delta$$1(ops) { + return new delta(ops); + } + }, { + key: 'getContents', + value: function getContents() { + var from = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + var to = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.length; + + var _normalizeArguments2 = this._normalizeArguments(from, to); + + var _normalizeArguments3 = slicedToArray(_normalizeArguments2, 2); + + from = _normalizeArguments3[0]; + to = _normalizeArguments3[1]; + + return this.contents.slice(from, to); + } + }, { + key: 'getText', + value: function getText(from, to) { + return this.getContents(from, to).filter(function (op$$1) { + return typeof op$$1.insert === 'string'; + }).map(function (op$$1) { + return op$$1.insert; + }).join('').slice(0, -1); // remove the trailing newline + } + }, { + key: 'getChange', + value: function getChange(producer) { + var change = this.delta(); + this.updateContents = function (singleChange) { + return change = change.compose(singleChange); + }; + producer(); + delete this.updateContents; + return change; + } + }, { + key: 'setContents', + value: function setContents(newContents, source, selection) { + var change = this.contents.diff(newContents); + return this.updateContents(change, source, selection); + } + }, { + key: 'setText', + value: function setText(text, source, selection) { + return this.setContents(this.delta().insert(text + '\n'), source, selection); + } + }, { + key: 'insertText', + value: function insertText(from, to, text, formats, source, selection) { + var _normalizeArguments4 = this._normalizeArguments(from, to, text, formats, source, selection); + + var _normalizeArguments5 = slicedToArray(_normalizeArguments4, 6); + + from = _normalizeArguments5[0]; + to = _normalizeArguments5[1]; + text = _normalizeArguments5[2]; + formats = _normalizeArguments5[3]; + source = _normalizeArguments5[4]; + selection = _normalizeArguments5[5]; + + if (selection == null) selection = from + text.length; + var change = this.delta().retain(from).delete(to - from); + var lineFormat = from === to && text.indexOf('\n') === -1 ? null : this.getLineFormat(from); + text.split('\n').forEach(function (line, i) { + if (i) change.insert('\n', lineFormat); + line.length && change.insert(line, formats); + }); + + change = cleanDelete(this, from, to, change); + return this.updateContents(change, source, selection); + } + }, { + key: 'insertEmbed', + value: function insertEmbed(from, to, embed, value, source, selection) { + var _normalizeArguments6 = this._normalizeArguments(from, to, embed, value, source, selection); + + var _normalizeArguments7 = slicedToArray(_normalizeArguments6, 6); + + from = _normalizeArguments7[0]; + to = _normalizeArguments7[1]; + embed = _normalizeArguments7[2]; + value = _normalizeArguments7[3]; + source = _normalizeArguments7[4]; + selection = _normalizeArguments7[5]; + + if (selection == null) selection = from + 1; + var change = this.delta().retain(index).delete(to - from).insert(defineProperty({}, embed, value)); + change = cleanDelete(this, from, to, change); + return this.updateContents(change, source, selection); + } + }, { + key: 'deleteText', + value: function deleteText(from, to, source, selection) { + var _normalizeArguments8 = this._normalizeArguments(from, to, source, selection); + + var _normalizeArguments9 = slicedToArray(_normalizeArguments8, 4); + + from = _normalizeArguments9[0]; + to = _normalizeArguments9[1]; + source = _normalizeArguments9[2]; + selection = _normalizeArguments9[3]; + + if (selection == null) selection = from; + var change = this.delta().retain(from).delete(to - from); + change = cleanDelete(this, from, to, change); + return this.updateContents(change, source, from); + } + }, { + key: 'getLineFormat', + value: function getLineFormat(from, to) { + var _normalizeArguments10 = this._normalizeArguments(from, to); + + var _normalizeArguments11 = slicedToArray(_normalizeArguments10, 2); + + from = _normalizeArguments11[0]; + to = _normalizeArguments11[1]; + + var formats = void 0; + + this.contents.getLines(from, to).forEach(function (line) { + if (!line.attributes) formats = {};else if (!formats) formats = _extends({}, line.attributes);else formats = combineFormats(formats, line.attributes); + }); + + return formats; + } + }, { + key: 'getTextFormat', + value: function getTextFormat(from, to) { + var _this2 = this; + + var _normalizeArguments12 = this._normalizeArguments(from, to); + + var _normalizeArguments13 = slicedToArray(_normalizeArguments12, 2); + + from = _normalizeArguments13[0]; + to = _normalizeArguments13[1]; + + var formats = void 0; + + this.contents.getOps(from, to).forEach(function (_ref) { + var op$$1 = _ref.op; + + if (!op$$1.attributes) formats = {};else if (!formats) formats = _extends({}, op$$1.attributes);else formats = combineFormats(formats, op$$1.attributes); + }); + + if (!formats) formats = {}; + + if (this.activeFormats !== empty) { + Object.keys(this.activeFormats).forEach(function (name) { + var value = _this2.activeFormats[name]; + if (value === null) delete formats[name];else formats[name] = value; + }); + } + + return formats; + } + }, { + key: 'getFormat', + value: function getFormat(from, to) { + return _extends({}, this.getTextFormat(from, to), this.getLineFormat(from, to)); + } + }, { + key: 'formatLine', + value: function formatLine(from, to, formats, source) { + var _normalizeArguments14 = this._normalizeArguments(from, to, formats, source); + + var _normalizeArguments15 = slicedToArray(_normalizeArguments14, 4); + + from = _normalizeArguments15[0]; + to = _normalizeArguments15[1]; + formats = _normalizeArguments15[2]; + source = _normalizeArguments15[3]; + + var change = this.delta(); + + this.contents.getLines(from, to).forEach(function (line) { + if (!change.ops.length) change.retain(line.endIndex - 1);else change.retain(line.endIndex - line.startIndex - 1); + // Clear out old formats on the line + Object.keys(line.attributes).forEach(function (name) { + return !formats[name] && (formats[name] = null); + }); + change.retain(1, formats); + }); + + return change.ops.length ? this.updateContents(change, source) : this.contents; + } + }, { + key: 'formatText', + value: function formatText(from, to, formats, source) { + var _this3 = this; + + var _normalizeArguments16 = this._normalizeArguments(from, to, formats, source); + + var _normalizeArguments17 = slicedToArray(_normalizeArguments16, 4); + + from = _normalizeArguments17[0]; + to = _normalizeArguments17[1]; + formats = _normalizeArguments17[2]; + source = _normalizeArguments17[3]; + + if (from === to) { + if (this.activeFormats === empty) this.activeFormats = {}; + Object.keys(formats).forEach(function (key) { + return _this3.activeFormats[key] = formats[key]; + }); + return; + } + Object.keys(formats).forEach(function (name) { + return formats[name] === false && (formats[name] = null); + }); + var text = this.getText(); + var change = this.delta().retain(from); + text.slice(from, to).split('\n').forEach(function (line) { + line.length && change.retain(line.length, formats).retain(1); + }); + + return this.updateContents(change, source); + } + }, { + key: 'toggleLineFormat', + value: function toggleLineFormat(from, to, format, source) { + var _normalizeArguments18 = this._normalizeArguments(from, to, format, source); + + var _normalizeArguments19 = slicedToArray(_normalizeArguments18, 4); + + from = _normalizeArguments19[0]; + to = _normalizeArguments19[1]; + format = _normalizeArguments19[2]; + source = _normalizeArguments19[3]; + + var existing = this.getLineFormat(from, to); + if (deepEqual(existing, format)) { + Object.keys(format).forEach(function (key) { + return format[key] = null; + }); + } + return this.formatLine(from, to, format, source); + } + }, { + key: 'toggleTextFormat', + value: function toggleTextFormat(from, to, format, source) { + var _normalizeArguments20 = this._normalizeArguments(from, to, format, source); + + var _normalizeArguments21 = slicedToArray(_normalizeArguments20, 4); + + from = _normalizeArguments21[0]; + to = _normalizeArguments21[1]; + format = _normalizeArguments21[2]; + source = _normalizeArguments21[3]; + + var existing = this.getTextFormat(from, to); + var isSame = Object.keys(format).every(function (key) { + return format[key] === existing[key]; + }); + if (isSame) { + Object.keys(format).forEach(function (key) { + return format[key] = null; + }); + } + return this.formatText(from, to, format, source); + } + }, { + key: 'removeFormat', + value: function removeFormat(from, to, source) { + var _this4 = this; + + var _normalizeArguments22 = this._normalizeArguments(from, to, source); + + var _normalizeArguments23 = slicedToArray(_normalizeArguments22, 3); + + from = _normalizeArguments23[0]; + to = _normalizeArguments23[1]; + source = _normalizeArguments23[2]; + + var formats = {}; + + this.contents.getOps(from, to).forEach(function (_ref2) { + var op$$1 = _ref2.op; + + op$$1.attributes && Object.keys(op$$1.attributes).forEach(function (key) { + return formats[key] = null; + }); + }); + + var change = this.delta().retain(from).retain(to - from, formats); + + // If the last block was not captured be sure to clear that too + this.contents.getLines(from, to).forEach(function (line) { + var formats = {}; + Object.keys(line.attributes).forEach(function (key) { + return formats[key] = null; + }); + change = change.compose(_this4.delta().retain(line.endIndex - 1).retain(1, formats)); + }); + + return this.updateContents(change, source); + } + }, { + key: 'updateContents', + value: function updateContents(change) { + var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : SOURCE_USER; + var selection = arguments[2]; + + var oldContents = this.contents; + var contents = normalizeContents(oldContents.compose(change)); + var length = contents.length(); + var oldSelection = this.selection; + if (!selection) selection = this.selection ? this.selection.map(function (i) { + return change.transform(i); + }) : oldSelection; + selection = selection && this.getSelectedRange(selection, length - 1); + + var changeEvent = { contents: contents, oldContents: oldContents, change: change, selection: selection, oldSelection: oldSelection, source: source }; + var selectionEvent = shallowEqual(oldSelection, selection) ? null : { selection: selection, oldSelection: oldSelection, source: source }; + + if (change.ops.length && this.fire('text-changing', changeEvent)) { + setContents(this, contents); + if (selection) this.selection = selection; + + if (source !== SOURCE_SILENT) { + this.fire('text-change', changeEvent); + if (selectionEvent) this.fire('selection-change', selectionEvent); + } + this.fire('editor-change', changeEvent); + } + + return this.contents; + } + }, { + key: 'setSelection', + value: function setSelection(selection) { + var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : SOURCE_USER; + + var oldSelection = this.selection; + selection = this.getSelectedRange(selection); + this.activeFormats = empty; + + if (shallowEqual(oldSelection, selection)) return false; + + this.selection = selection; + var event = { selection: selection, oldSelection: oldSelection, source: source }; + + if (source !== SOURCE_SILENT) this.fire('selection-change', event); + this.fire('editor-change', event); + return true; + } + }, { + key: 'getSelectedRange', + value: function getSelectedRange() { + var range = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.selection; + var max = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.length - 1; + + if (range == null) return range; + if (typeof range === 'number') range = [range, range]; + if (range[0] > range[1]) { + var _ref3 = [range[1], range[0]]; + range[0] = _ref3[0]; + range[1] = _ref3[1]; + }return range.map(function (index) { + return Math.max(0, Math.min(max, index)); + }); + } + + /** + * Normalizes range values to a proper range if it is not already. A range is a `from` and a `to` index, e.g. 0, 4. + * This will ensure the lower index is first. Example usage: + * editor._normalizeArguments(5); // [5, 5] + * editor._normalizeArguments(-4, 100); // for a doc with length 10, [0, 10] + * editor._normalizeArguments(25, 18); // [18, 25] + * editor._normalizeArguments([12, 13]); // [12, 13] + * editor._normalizeArguments(5, { bold: true }); // [5, 5, { bold: true }] + */ + + }, { + key: '_normalizeArguments', + value: function _normalizeArguments(from, to) { + for (var _len = arguments.length, rest = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + rest[_key - 2] = arguments[_key]; + } + + if (Array.isArray(from)) { + if (to !== undefined || rest.length) rest.unshift(to); + var _from = from; + + var _from2 = slicedToArray(_from, 2); + + from = _from2[0]; + to = _from2[1]; + + if (to === undefined) to = from; + } else if (typeof from !== 'number') { + if (to !== undefined || rest.length) rest.unshift(to); + if (from !== undefined || rest.length) rest.unshift(from); + from = to = 0; + } else if (typeof to !== 'number') { + if (to !== undefined || rest.length) rest.unshift(to); + to = from; + } + from = Math.max(0, Math.min(this.length, ~~from)); + to = Math.max(0, Math.min(this.length, ~~to)); + if (from > to) { + var _ref4 = [to, from]; + from = _ref4[0]; + to = _ref4[1]; + } + return [from, to].concat(rest); + } + }]); + return Editor; + }(EventDispatcher); + + + function cleanDelete(editor, from, to, change) { + if (from !== to) { + var lineFormat = editor.getLineFormat(from); + if (!deepEqual(lineFormat, editor.getLineFormat(to))) { + var lineChange = editor.getChange(function () { + return editor.formatLine(to, lineFormat); + }); + change = change.compose(change.transform(lineChange)); + } + } + return change; + } + + function normalizeContents(contents) { + if (!contents.ops.length || contents.ops[contents.ops.length - 1].insert.slice(-1) !== '\n') contents.insert('\n'); + return contents; + } + + function setContents(editor, contents) { + normalizeContents(contents); + contents.push = function () { + return this; + }; // freeze from modification + editor.contents = contents; + editor.length = contents.length(); + } + + function combineFormats(formats, combined) { + return Object.keys(combined).reduce(function (merged, name) { + if (formats[name] == null) return merged; + if (combined[name] === formats[name]) { + merged[name] = combined[name]; + } else if (Array.isArray(combined[name])) { + if (combined[name].indexOf(formats[name]) < 0) { + merged[name] = combined[name].concat([formats[name]]); + } + } else { + merged[name] = [combined[name], formats[name]]; + } + return merged; + }, {}); + } + + delta.prototype.getLines = function (from, to, predicate) { + var startIndex = 0; + var lines = []; + this.eachLine(function (contents, attributes, number) { + if (startIndex >= to) return false; + var endIndex = startIndex + contents.length() + 1; + if (endIndex > from || from === to && endIndex === to) { + lines.push({ contents: contents, attributes: attributes, number: number, startIndex: startIndex, endIndex: endIndex }); + } + startIndex = endIndex; + }); + return lines; + }; + + delta.prototype.getLine = function (at) { + return this.getLines(at, at)[0]; + }; + + delta.prototype.getOps = function (from, to) { + var startIndex = 0; + var ops = []; + this.ops.some(function (op$$1) { + if (startIndex >= to) return true; + var endIndex = startIndex + op.length(op$$1); + if (endIndex > from || from === to && endIndex === to) { + ops.push({ op: op$$1, startIndex: startIndex, endIndex: endIndex }); + } + startIndex = endIndex; + }); + return ops; + }; + + delta.prototype.getOp = function (from) { + return this.getOps(from, from)[0]; + }; + + function h(name, attributes) { + var rest = []; + var children = []; + var length = arguments.length; + + while (length-- > 2) rest.push(arguments[length]); + + while (rest.length) { + var node = rest.pop(); + if (node && node.pop) { + for (length = node.length; length--; ) { + rest.push(node[length]); + } + } else if (node != null && node !== true && node !== false) { + children.push(node); + } + } + + return typeof name === "function" + ? name(attributes || {}, children) // h(Component) + : { + nodeName: name, + attributes: attributes || {}, + children: children, + key: attributes && attributes.key + } + } + + function recycleElement(element, map) { + return { + nodeName: element.nodeName.toLowerCase(), + attributes: {}, + children: map.call(element.childNodes, function(element) { + return element.nodeType === 3 // Node.TEXT_NODE + ? element.nodeValue + : recycleElement(element, map) + }) + } + } + + function clone(target, source) { + var obj = {}; + + for (var i in target) obj[i] = target[i]; + for (var i in source) obj[i] = source[i]; + + return obj + } + + function eventListener(event) { + return event.currentTarget.events[event.type](event) + } + + function updateAttribute(element, name, value, oldValue, isSVG) { + if (name === "key") { + } else if (name === "style") { + for (var i in clone(oldValue, value)) { + var style = value == null || value[i] == null ? "" : value[i]; + if (i[0] === "-") { + element[name].setProperty(i, style); + } else { + element[name][i] = style; + } + } + } else { + if (name[0] === "o" && name[1] === "n") { + name = name.slice(2); + + if (element.events) { + if (!oldValue) oldValue = element.events[name]; + } else { + element.events = {}; + } + + element.events[name] = value; + + if (value) { + if (!oldValue) { + element.addEventListener(name, eventListener); + } + } else { + element.removeEventListener(name, eventListener); + } + } else if (name in element && name !== "list" && !isSVG) { + element[name] = value == null ? "" : value; + } else if (value != null && value !== false) { + element.setAttribute(name, value); + } + + if (value == null || value === false) { + element.removeAttribute(name); + } + } + } + + function createElement(node, lifecycle, isSVG) { + var element = + typeof node === "string" || typeof node === "number" + ? document.createTextNode(node) + : (isSVG = isSVG || node.nodeName === "svg") + ? document.createElementNS("http://www.w3.org/2000/svg", node.nodeName) + : document.createElement(node.nodeName); + + var attributes = node.attributes; + if (attributes) { + if (attributes.oncreate) { + lifecycle.push(function() { + attributes.oncreate(element); + }); + } + + for (var i = 0; i < node.children.length; i++) { + element.appendChild(createElement(node.children[i], lifecycle, isSVG)); + } + + for (var name in attributes) { + updateAttribute(element, name, attributes[name], null, isSVG); + } + } + + return element + } + + function removeChildren(element, node) { + var attributes = node.attributes; + if (attributes) { + for (var i = 0; i < node.children.length; i++) { + removeChildren(element.childNodes[i], node.children[i]); + } + + if (attributes.ondestroy) { + attributes.ondestroy(element); + } + } + return element + } + + function removeElement(parent, element, node) { + function done() { + parent.removeChild(removeChildren(element, node)); + } + + var cb = node.attributes && node.attributes.onremove; + if (cb) { + cb(element, done); + } else { + done(); + } + } + + function updateElement( + element, + oldAttributes, + attributes, + lifecycle, + isRecycling, + isSVG + ) { + for (var name in clone(oldAttributes, attributes)) { + if ( + attributes[name] !== + (name === "value" || name === "checked" + ? element[name] + : oldAttributes[name]) + ) { + updateAttribute( + element, + name, + attributes[name], + oldAttributes[name], + isSVG + ); + } + } + + var cb = isRecycling ? attributes.oncreate : attributes.onupdate; + if (cb) { + lifecycle.push(function() { + cb(element, oldAttributes); + }); + } + } + + function getKey(node) { + return node ? node.key : null + } + + function patchElement( + parent, + element, + oldNode, + node, + lifecycle, + isRecycling, + isSVG + ) { + if (node === oldNode) { + } else if (oldNode == null || oldNode.nodeName !== node.nodeName) { + var newElement = createElement(node, lifecycle, isSVG); + if (parent) { + parent.insertBefore(newElement, element); + if (oldNode != null) { + removeElement(parent, element, oldNode); + } + } + element = newElement; + } else if (oldNode.nodeName == null) { + element.nodeValue = node; + } else { + updateElement( + element, + oldNode.attributes, + node.attributes, + lifecycle, + isRecycling, + (isSVG = isSVG || node.nodeName === "svg") + ); + + var oldKeyed = {}; + var newKeyed = {}; + var oldElements = []; + var oldChildren = oldNode.children; + var children = node.children; + + for (var i = 0; i < oldChildren.length; i++) { + oldElements[i] = element.childNodes[i]; + + var oldKey = getKey(oldChildren[i]); + if (oldKey != null) { + oldKeyed[oldKey] = [oldElements[i], oldChildren[i]]; + } + } + + var i = 0; + var k = 0; + + while (k < children.length) { + var oldKey = getKey(oldChildren[i]); + var newKey = getKey(children[k]); + + if (newKeyed[oldKey]) { + i++; + continue + } + + if (newKey == null || isRecycling) { + if (oldKey == null) { + patchElement( + element, + oldElements[i], + oldChildren[i], + children[k], + lifecycle, + isRecycling, + isSVG + ); + k++; + } + i++; + } else { + var keyedNode = oldKeyed[newKey] || []; + + if (oldKey === newKey) { + patchElement( + element, + keyedNode[0], + keyedNode[1], + children[k], + lifecycle, + isRecycling, + isSVG + ); + i++; + } else if (keyedNode[0]) { + patchElement( + element, + element.insertBefore(keyedNode[0], oldElements[i]), + keyedNode[1], + children[k], + lifecycle, + isRecycling, + isSVG + ); + } else { + patchElement( + element, + oldElements[i], + null, + children[k], + lifecycle, + isRecycling, + isSVG + ); + } + + newKeyed[newKey] = children[k]; + k++; + } + } + + while (i < oldChildren.length) { + if (getKey(oldChildren[i]) == null) { + removeElement(element, oldElements[i], oldChildren[i]); + } + i++; + } + + for (var i in oldKeyed) { + if (!newKeyed[i]) { + removeElement(element, oldKeyed[i][0], oldKeyed[i][1]); + } + } + } + return element + } + + function patch(node, element) { + var lifecycle = []; + + element = element + ? patchElement( + element.parentNode, + element, + element.node == null ? recycleElement(element, [].map) : element.node, + node, + lifecycle, + element.node == null // isRecycling + ) + : patchElement(null, null, null, node, lifecycle); + + element.node = node; + + while (lifecycle.length) lifecycle.pop()(); + + return element + } + + var paragraph = { + name: 'paragraph', + selector: 'p', + vdom: function vdom(children) { + return h( + 'p', + null, + children + ); + } + }; + + var header = { + name: 'header', + selector: 'h1, h2, h3, h4, h5, h6', + attr: function attr(node) { + return { header: parseInt(node.nodeName.replace('H', '')) }; + }, + vdom: function vdom(children, attr) { + var H = 'h' + attr.header; + return h( + H, + null, + children + ); + } + }; + + var list = { + name: 'list', + selector: 'ul > li, ol > li', + optimize: true, + attr: function attr(node) { + var indent = -1, + parent = node.parentNode; + var list = parent.nodeName === 'OL' ? 'ordered' : 'bullet'; + while (parent) { + if (/^UL|OL$/.test(parent.nodeName)) indent++;else if (parent.nodeName !== 'LI') break; + parent = parent.parentNode; + } + return indent ? { list: list, indent: indent } : { list: list }; + }, + vdom: function vdom(lists) { + var topLevelChildren = []; + var levels = []; + // e.g. levels = [ul, li, ul, li] + + lists.forEach(function (_ref) { + var _ref2 = slicedToArray(_ref, 2), + children = _ref2[0], + attr = _ref2[1]; + + var List = attr.list === 'ordered' ? 'ol' : 'ul'; + var index = (attr.indent || 0) * 2; + var item = h( + 'li', + null, + children + ); + var list = levels[index]; + if (list && list.nodeName === List) { + list.children.push(item); + } else { + list = h( + List, + null, + item + ); + var childrenArray = index ? levels[index - 1].children : topLevelChildren; + childrenArray.push(list); + levels[index] = list; + } + levels[index + 1] = item; + levels.length = index + 2; + }); + + return topLevelChildren; + } + }; + + var container = { + name: 'container', + selector: 'div', + vdom: function vdom(children, attr) { + return h( + 'div', + { contenteditable: attr.enabled }, + children && children.length && children || paragraph.vdom() + ); + } + }; + + var bold = { + name: 'bold', + selector: 'strong, b', + vdom: function vdom(children) { + return h( + 'strong', + null, + children + ); + } + }; + + var italics = { + name: 'italics', + selector: 'em, i', + vdom: function vdom(children) { + return h( + 'em', + null, + children + ); + } + }; + + var link = { + name: 'link', + selector: 'a[href]', + attr: function attr(node) { + return node.href; + }, + vdom: function vdom(children, attr) { + return h( + 'a', + { href: attr.link, target: '_blank' }, + children + ); + } + }; + + var image = { + name: 'image', + selector: 'img', + attr: function attr(node) { + return node.src; + }, + vdom: function vdom(children, attr) { + return h('img', { src: attr.image }); + } + }; + + var defaultDom = { + blocks: [paragraph, header, list, container], + markups: [bold, italics, link], + embeds: [image] + }; + + var indexOf = [].indexOf; + + // Get the range (a tuple of indexes) for this view from the browser selection + function getSelection(view) { + var root = view.root; + var selection = root.ownerDocument.defaultView.getSelection(); + + if (!root.contains(selection.anchorNode)) { + return null; + } else { + var anchorIndex = getNodeIndex(view, selection.anchorNode); + var focusIndex = selection.anchorNode === selection.focusNode ? anchorIndex : getNodeIndex(view, selection.focusNode); + + return [anchorIndex + selection.anchorOffset, focusIndex + selection.focusOffset]; + } + } + + // Set the browser selection to the range (a tuple of indexes) of this view + function setSelection(view, range) { + var root = view.root; + var selection = root.ownerDocument.defaultView.getSelection(); + var hasFocus = root.contains(root.ownerDocument.activeElement); + + if (range == null) { + if (hasFocus) { + root.blur(); + selection.setBaseAndExtent(null, 0, null, 0); + } + } else { + var _getNodesForRange = getNodesForRange(view, range), + _getNodesForRange2 = slicedToArray(_getNodesForRange, 4), + anchorNode = _getNodesForRange2[0], + anchorOffset = _getNodesForRange2[1], + focusNode = _getNodesForRange2[2], + focusOffset = _getNodesForRange2[3]; + + selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset); + if (!hasFocus) root.focus(); + } + } + + // Get a browser range object for the given editor range tuple + function getBrowserRange(view, range) { + if (range[0] > range[1]) range = [range[1], range[0]]; + + var _getNodesForRange3 = getNodesForRange(view, range), + _getNodesForRange4 = slicedToArray(_getNodesForRange3, 4), + anchorNode = _getNodesForRange4[0], + anchorOffset = _getNodesForRange4[1], + focusNode = _getNodesForRange4[2], + focusOffset = _getNodesForRange4[3]; + + var browserRange = document.createRange(); + browserRange.setStart(anchorNode, anchorOffset); + browserRange.setEnd(focusNode, focusOffset); + return browserRange; + } + + // Get the browser nodes and offsets for the range (a tuple of indexes) of this view + function getNodesForRange(view, range) { + if (range == null) { + return [null, 0, null, 0]; + } else { + var _getNodeAndOffset = getNodeAndOffset(view, range[0]), + _getNodeAndOffset2 = slicedToArray(_getNodeAndOffset, 2), + anchorNode = _getNodeAndOffset2[0], + anchorOffset = _getNodeAndOffset2[1]; + + var _ref = range[0] === range[1] ? [anchorNode, anchorOffset] : getNodeAndOffset(view, range[1]), + _ref2 = slicedToArray(_ref, 2), + focusNode = _ref2[0], + focusOffset = _ref2[1]; + + return [anchorNode, anchorOffset, focusNode, focusOffset]; + } + } + + function getNodeAndOffset(view, index) { + var root = view.root; + var blocksSelector = view.dom.blocks.selector; + var walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, { + acceptNode: function acceptNode(node) { + return (node.nodeType === Node.TEXT_NODE || node.offsetParent) && NodeFilter.FILTER_ACCEPT || NodeFilter.FILTER_REJECT; + } + }); + + var count = 0, + node = void 0, + firstBlockSeen = false; + walker.currentNode = root; + while (node = walker.nextNode()) { + if (node.nodeType === Node.TEXT_NODE) { + var size = node.nodeValue.length; + if (index <= count + size) return [node, index - count]; + count += size; + } else if (node.matches(blocksSelector)) { + if (firstBlockSeen) count += 1;else firstBlockSeen = true; + + // If the selection lands at the beginning of a block, and the first node isn't a text node, place the selection + if (count === index && (!node.firstChild || node.firstChild.nodeType !== Node.TEXT_NODE)) { + return [node, 0]; + } + } else if (node.nodeName === 'BR' && node.parentNode.lastChild !== node) { + count += 1; + // If the selection lands after this br, and the next node isn't a text node, place the selection + if (count === index && (!node.nextSibling || node.nextSibling.nodeType !== Node.TEXT_NODE)) { + return [node.parentNode, indexOf.call(node.parentNode.childNodes, node) + 1]; + } + } + } + return [null, 0]; + } + + // Get the index the node starts at in the content + function getNodeIndex(view, node) { + var root = view.root; + var blocksSelector = view.dom.blocks.selector; + var walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, { + acceptNode: function acceptNode(node) { + return (node.nodeType === Node.TEXT_NODE || node.offsetParent) && NodeFilter.FILTER_ACCEPT || NodeFilter.FILTER_REJECT; + } + }); + + walker.currentNode = node; + var index = node.nodeType === Node.ELEMENT_NODE ? 0 : -1; + while (node = walker.previousNode()) { + if (node.nodeType === Node.TEXT_NODE) index += node.nodeValue.length;else if (node.nodeName === 'BR' && node.parentNode.lastChild !== node) index++;else if (node !== root && node.matches(blocksSelector)) index++; + } + return index; + } + + /*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */ + + /** + * Module variables. + * @private + */ + + var matchHtmlRegExp = /["'&<>]/; + + /** + * Module exports. + * @public + */ + + var escapeHtml_1 = escapeHtml; + + /** + * Escape special characters in the given string of html. + * + * @param {string} string The string to escape for inserting into HTML + * @return {string} + * @public + */ + + function escapeHtml(string) { + var str = '' + string; + var match = matchHtmlRegExp.exec(str); + + if (!match) { + return str; + } + + var escape; + var html = ''; + var index = 0; + var lastIndex = 0; + + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: // " + escape = '"'; + break; + case 38: // & + escape = '&'; + break; + case 39: // ' + escape = '''; + break; + case 60: // < + escape = '<'; + break; + case 62: // > + escape = '>'; + break; + default: + continue; + } + + if (lastIndex !== index) { + html += str.substring(lastIndex, index); + } + + lastIndex = index + 1; + html += escape; + } + + return lastIndex !== index + ? html + str.substring(lastIndex, index) + : html; + } + + var br = h('br', null); + var voidElements = { + area: true, base: true, br: true, col: true, embed: true, hr: true, img: true, input: true, + link: true, meta: true, param: true, source: true, track: true, wbr: true + }; + + var DOM = function DOM(types) { + var _this = this; + + classCallCheck(this, DOM); + + this.blocks = new DOMTypes(); + this.markups = new DOMTypes(); + if (types && types.blocks) types.blocks.forEach(function (block) { + return _this.blocks.add(block); + }); + if (types && types.markups) types.markups.forEach(function (markup) { + return _this.markups.add(markup); + }); + }; + + var DOMTypes = function () { + function DOMTypes() { + classCallCheck(this, DOMTypes); + + this.selector = ''; + this.domTypes = {}; + this.array = []; + this.priorities = {}; + } + + createClass(DOMTypes, [{ + key: 'add', + value: function add(definition, index) { + var _this2 = this; + + if (!definition.name || !definition.selector || !definition.vdom) { + throw new Error('DOMType definitions must include a name, selector, and vdom function'); + } + if (this.domTypes[definition.name]) this.remove(definition.name); + this.selector += (this.selector ? ', ' : '') + definition.selector; + this.domTypes[definition.name] = definition; + if (typeof index !== 'number') { + this.priorities[name] = this.array.length; + this.array.push(definition); + } else { + this.array.splice(i, 0, definition); + this.array.forEach(function (_ref, i) { + var name = _ref.name; + return _this2.priorities[name] = i; + }); + } + } + }, { + key: 'remove', + value: function remove(name) { + var _this3 = this; + + if (!this.domTypes[name]) return; + delete this.domTypes[name]; + this.array = this.array.filter(function (domType) { + return domType.name === name; + }); + this.array.forEach(function (_ref2, i) { + var name = _ref2.name; + return _this3.priorities[name] = i; + }); + this.selector = this.array.map(function (type) { + return type.selector; + }).join(', '); + } + }, { + key: 'get', + value: function get$$1(name) { + return this.domTypes[name]; + } + }, { + key: 'priority', + value: function priority(name) { + return this.priorities[name]; + } + }, { + key: 'getDefault', + value: function getDefault() { + return this.array[0]; + } + }, { + key: 'matches', + value: function matches(node) { + return node.matches(this.selector); + } + }, { + key: 'find', + value: function find(nodeOrAttr) { + var _this4 = this; + + if (nodeOrAttr instanceof Node) { + return this.array.find(function (domType) { + return nodeOrAttr.matches(domType.selector); + }); + } else if (nodeOrAttr && (typeof nodeOrAttr === 'undefined' ? 'undefined' : _typeof(nodeOrAttr)) === 'object') { + var domType = void 0; + Object.keys(nodeOrAttr).some(function (name) { + return domType = _this4.get(name); + }); + return domType; + } + } + }]); + return DOMTypes; + }(); + + function deltaToVdom(view, delta$$1) { + var _view$dom = view.dom, + blocks = _view$dom.blocks, + markups = _view$dom.markups; + + var blockData = []; + + delta$$1.eachLine(function (line, attr) { + var inlineChildren = []; + + // Collect block children + line.forEach(function (op) { + var children = []; + op.insert.split(/\r/).forEach(function (child, i) { + if (i !== 0) children.push(br); + child && children.push(child.replace(/ /g, '\xA0 ').replace(/ +$/, '\xA0')); + }); + + if (op.attributes) { + // Sort them by the order found in markups and be efficient + Object.keys(op.attributes).sort(function (a, b) { + return markups.priority(b) - markups.priority(a); + }).forEach(function (name) { + var markup = markups.get(name); + if (markup) { + var node = markup.vdom.call(view.dom, children, op.attributes); + node.markup = markup; + children = [node]; + } + }); + } + inlineChildren = inlineChildren.concat(children); + }); + + // Merge markups to optimize + inlineChildren = mergeChildren(inlineChildren); + if (!inlineChildren.length || inlineChildren[inlineChildren.length - 1] === br) { + inlineChildren.push(br); + } + + var block = blocks.find(attr); + if (!block) block = blocks.getDefault(); + + blockData.push([block, inlineChildren, attr]); + }); + + // If a block has optimize=true on it, vdom will be called with all sibling nodes of the same block + var blockChildren = []; + var collect = []; + blockData.forEach(function (data, i) { + var _data = slicedToArray(data, 3), + block = _data[0], + children = _data[1], + attr = _data[2]; + + if (block.optimize) { + collect.push([children, attr]); + var next = blockData[i + 1]; + if (!next || next[0] !== block) { + blockChildren = blockChildren.concat(block.vdom.call(view.dom, collect)); + collect = []; + } + } else { + blockChildren.push(block.vdom.call(view.dom, children, attr)); + } + }); + + return blocks.get('container').vdom.call(view.dom, blockChildren, view); + } + + function deltaFromDom(view) { + var root = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : view.root; + var _view$dom2 = view.dom, + blocks = _view$dom2.blocks, + markups = _view$dom2.markups; + + + var walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, { + acceptNode: function acceptNode(node) { + return (node.nodeType === Node.TEXT_NODE || node.offsetParent) && NodeFilter.FILTER_ACCEPT || NodeFilter.FILTER_REJECT; + } + }); + var delta$$1 = new delta(); + var currentBlock = void 0, + firstBlockSeen = false, + node = void 0; + + walker.currentNode = root; + + while (node = walker.nextNode()) { + var isBr = node.nodeName === 'BR' && node.parentNode.lastChild !== node; + + if (node.nodeType === Node.TEXT_NODE || isBr) { + var text = isBr ? '\r' : node.nodeValue.replace(/\xA0/g, ' '); + var parent = node.parentNode, + attr = {}; + + while (parent && !blocks.matches(parent) && parent !== root) { + if (markups.matches(parent)) { + var markup = markups.find(parent); + attr[markup.name] = markup.attr ? markup.attr(parent) : true; + } + parent = parent.parentNode; + } + delta$$1.insert(text, attr); + } else if (blocks.matches(node)) { + if (firstBlockSeen) delta$$1.insert('\n', currentBlock);else firstBlockSeen = true; + var block = blocks.find(node); + if (block !== blocks.getDefault()) { + currentBlock = block.attr ? block.attr(node) : defineProperty({}, block.name, true); + } else { + currentBlock = undefined; + } + } + } + delta$$1.insert('\n', currentBlock); + return delta$$1; + } + + function deltaToHTML(view, delta$$1) { + return childrenToHTML(deltaToVdom(view, delta$$1).children); + } + + // Join adjacent blocks + function mergeChildren(oldChildren) { + var children = []; + oldChildren.forEach(function (next, i) { + var prev = children[children.length - 1]; + + if (prev && typeof prev !== 'string' && typeof next !== 'string' && prev.markup && prev.markup === next.markup && deepEqual(prev.attributes, next.attributes)) { + prev.children = prev.children.concat(next.children); + } else { + children.push(next); + } + }); + return children; + } + + function elementToHTML(node) { + var attr = Object.keys(node.attributes).reduce(function (attr, name) { + return attr + ' ' + escapeHtml_1(name) + '="' + escapeHtml_1(node.attributes[name]) + '"'; + }, ''); + var children = childrenToHTML(node.children); + var closingTag = children || !voidElements[node.nodeName] ? '' : ''; + return '<' + node.nodeName + attr + '>' + children + closingTag; + } + + function childrenToHTML(children) { + if (!children || !children.length) return ''; + return children.reduce(function (html, child) { + return html + (child.nodeName ? elementToHTML(child) : escapeHtml_1(child)); + }, ''); + } + + var keyboardeventKeyPolyfill = createCommonjsModule(function (module, exports) { + /* global define, KeyboardEvent, module */ + + (function () { + + var keyboardeventKeyPolyfill = { + polyfill: polyfill, + keys: { + 3: 'Cancel', + 6: 'Help', + 8: 'Backspace', + 9: 'Tab', + 12: 'Clear', + 13: 'Enter', + 16: 'Shift', + 17: 'Control', + 18: 'Alt', + 19: 'Pause', + 20: 'CapsLock', + 27: 'Escape', + 28: 'Convert', + 29: 'NonConvert', + 30: 'Accept', + 31: 'ModeChange', + 32: ' ', + 33: 'PageUp', + 34: 'PageDown', + 35: 'End', + 36: 'Home', + 37: 'ArrowLeft', + 38: 'ArrowUp', + 39: 'ArrowRight', + 40: 'ArrowDown', + 41: 'Select', + 42: 'Print', + 43: 'Execute', + 44: 'PrintScreen', + 45: 'Insert', + 46: 'Delete', + 48: ['0', ')'], + 49: ['1', '!'], + 50: ['2', '@'], + 51: ['3', '#'], + 52: ['4', '$'], + 53: ['5', '%'], + 54: ['6', '^'], + 55: ['7', '&'], + 56: ['8', '*'], + 57: ['9', '('], + 91: 'OS', + 93: 'ContextMenu', + 144: 'NumLock', + 145: 'ScrollLock', + 181: 'VolumeMute', + 182: 'VolumeDown', + 183: 'VolumeUp', + 186: [';', ':'], + 187: ['=', '+'], + 188: [',', '<'], + 189: ['-', '_'], + 190: ['.', '>'], + 191: ['/', '?'], + 192: ['`', '~'], + 219: ['[', '{'], + 220: ['\\', '|'], + 221: [']', '}'], + 222: ["'", '"'], + 224: 'Meta', + 225: 'AltGraph', + 246: 'Attn', + 247: 'CrSel', + 248: 'ExSel', + 249: 'EraseEof', + 250: 'Play', + 251: 'ZoomOut' + } + }; + + // Function keys (F1-24). + var i; + for (i = 1; i < 25; i++) { + keyboardeventKeyPolyfill.keys[111 + i] = 'F' + i; + } + + // Printable ASCII characters. + var letter = ''; + for (i = 65; i < 91; i++) { + letter = String.fromCharCode(i); + keyboardeventKeyPolyfill.keys[i] = [letter.toLowerCase(), letter.toUpperCase()]; + } + + function polyfill () { + if (!('KeyboardEvent' in window) || + 'key' in KeyboardEvent.prototype) { + return false; + } + + // Polyfill `key` on `KeyboardEvent`. + var proto = { + get: function (x) { + var key = keyboardeventKeyPolyfill.keys[this.which || this.keyCode]; + + if (Array.isArray(key)) { + key = key[+this.shiftKey]; + } + + return key; + } + }; + Object.defineProperty(KeyboardEvent.prototype, 'key', proto); + return proto; + } + + if (typeof undefined === 'function' && undefined.amd) { + undefined('keyboardevent-key-polyfill', keyboardeventKeyPolyfill); + } else { + module.exports = keyboardeventKeyPolyfill; + } + + })(); + }); + + keyboardeventKeyPolyfill.polyfill(); + + var modifierKeys = { + Control: true, + Meta: true, + Shift: true, + Alt: true + }; + + + /** + * Returns the textual representation of a shortcut given a keyboard event. Examples of shortcuts: + * Cmd+L + * Cmd+Shift+M + * Ctrl+O + * Backspace + * T + * Right + * Shift+Down + * Shift+F1 + * + */ + var fromEvent = function(event) { + var shortcutArray = []; + var key = event.key; + + if (event.metaKey) shortcutArray.push('Cmd'); + if (event.ctrlKey) shortcutArray.push('Ctrl'); + if (event.altKey) shortcutArray.push('Alt'); + if (event.shiftKey) shortcutArray.push('Shift'); + + if (!modifierKeys[key]) { + // a and A, b and B, should be the same shortcut + if (key.length === 1) key = key.toUpperCase(); + shortcutArray.push(key); + } + + return shortcutArray.join('+'); + }; + + var shortcutString = { + fromEvent: fromEvent + }; + + var isMac = navigator.userAgent.indexOf('Macintosh') !== -1; + + var HTMLView = function (_EventDispatcher) { + inherits(HTMLView, _EventDispatcher); + + function HTMLView(editor) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + classCallCheck(this, HTMLView); + + var _this = possibleConstructorReturn(this, (HTMLView.__proto__ || Object.getPrototypeOf(HTMLView)).call(this)); + + if (!editor) throw new Error('Editor view requires an editor'); + _this.editor = editor; + _this.root = null; + _this.dom = new DOM(options.dom || defaultDom); + _this.enabled = true; + _this.isMac = isMac; + _this._settingEditorSelection = false; + _this._settingBrowserSelection = false; + + if (options.modules) options.modules.forEach(function (module) { + return module(_this); + }); + + _this.editor.on('text-change', function () { + return _this.update(); + }); + _this.editor.on('selection-change', function () { + return !_this._settingEditorSelection && _this.updateBrowserSelection(); + }); + return _this; + } + + createClass(HTMLView, [{ + key: 'hasFocus', + value: function hasFocus() { + return this.root.contains(this.root.ownerDocument.activeElement); + } + }, { + key: 'focus', + value: function focus() { + this.root.focus(); + } + }, { + key: 'blur', + value: function blur() { + this.root.blur(); + } + }, { + key: 'disable', + value: function disable() { + this.enable(false); + } + }, { + key: 'enable', + value: function enable() { + var enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + + this.enabled = enabled; + this.update(); + } + }, { + key: 'getBounds', + value: function getBounds(range) { + range = this.editor.normalizeRange(range, this.editor.length - 1); + var browserRange = getBrowserRange(this, range); + if (browserRange.endContainer.nodeType === Node.ELEMENT_NODE) { + browserRange.setEnd(browserRange.endContainer, browserRange.endOffset + 1); + } + return browserRange.getBoundingClientRect(); + } + }, { + key: 'getHTML', + value: function getHTML() { + return deltaToHTML(this, this.editor.contents); + } + }, { + key: 'setHTML', + value: function setHTML(html) { + this.editor.setContents(deltaFromHTML(this, html)); + } + }, { + key: 'update', + value: function update() { + var _this2 = this; + + var contents = this.editor.contents; + var viewEditor = new Editor({ contents: contents }); + this.decorations = viewEditor.getChange(function () { + return _this2.fire('decorate', viewEditor); + }); + if (this.root && this.decorations.ops.length) this.root.node = null; + var vdom = deltaToVdom(this, contents.compose(this.decorations)); + this.root = patch(vdom, this.root); + this.updateBrowserSelection(); + this.fire('update'); + } + }, { + key: 'updateBrowserSelection', + value: function updateBrowserSelection() { + var _this3 = this; + + if (this._settingEditorSelection) return; + this._settingBrowserSelection = true; + setSelection(this, this.editor.selection); + setTimeout(function () { + return _this3._settingBrowserSelection = false; + }, 20); + } + }, { + key: 'updateEditorSelection', + value: function updateEditorSelection() { + if (this._settingBrowserSelection) return this._settingBrowserSelection = false; + var range = getSelection(this); + this._settingEditorSelection = true; + this.editor.setSelection(range); + this._settingEditorSelection = false; + if (!shallowEqual(range, this.editor.selection)) this.updateBrowserSelection(); + } + }, { + key: 'mount', + value: function mount(container$$1) { + var _this4 = this; + + this.update(); + container$$1.appendChild(this.root); + this.root.ownerDocument.execCommand('defaultParagraphSeparator', false, 'p'); + + var onKeyDown = function onKeyDown(event) { + var shortcut = shortcutString.fromEvent(event); + _this4.fire('shortcut:' + shortcut, event, shortcut); + _this4.fire('shortcut', event, shortcut); + }; + + // TODO this was added to replace the mutation observer, however, it does not accurately capture changes that + // occur with native changes such as spell-check replacements, cut or delete using the app menus, etc. Paste should + // be handled elsewhere (probably?). + var onInput = function onInput() { + if (!_this4.editor.selection) throw new Error('How did an input event occur without a selection?'); + + var _editor$getSelectedRa = _this4.editor.getSelectedRange(), + _editor$getSelectedRa2 = slicedToArray(_editor$getSelectedRa, 2), + from = _editor$getSelectedRa2[0], + to = _editor$getSelectedRa2[1]; + + var _getNodeAndOffset = getNodeAndOffset(_this4, from), + _getNodeAndOffset2 = slicedToArray(_getNodeAndOffset, 2), + node = _getNodeAndOffset2[0], + offset = _getNodeAndOffset2[1]; + + if (!node || node.nodeType !== Node.TEXT_NODE && node.nodeName !== 'BR') { + _this4.root.node = null; + return _this4.update(); + //throw new Error('Text entry should always result in a text node'); + } + if (from !== to || Object.keys(_this4.editor.activeFormats).length) { + _this4.root.node = null; // The DOM may have (or will be) changing, refresh from scratch + } + var text = node.nodeValue.slice(offset, offset + 1).replace(/\xA0/g, ' '); + _this4.editor.insertText(_this4.editor.selection, text, _this4.editor.getTextFormat(from)); + }; + + var onSelectionChange = function onSelectionChange() { + _this4.updateEditorSelection(); + }; + + // Use mutation tracking during development to catch errors + // TODO delete mutation observer + var checking = 0; + var onMutate = function onMutate(list$$1) { + if (checking) clearTimeout(checking); + checking = setTimeout(function () { + checking = 0; + var diff = editor.contents.compose(_this4.decorations).diff(deltaFromDom(view)); + if (diff.length()) { + console.error('Delta out of sync with DOM:', diff); + } + }, 20); + }; + + this.root.addEventListener('keydown', onKeyDown); + this.root.addEventListener('input', onInput); + container$$1.ownerDocument.addEventListener('selectionchange', onSelectionChange); + + var observer = new MutationObserver(onMutate); + observer.observe(this.root, { characterData: true, characterDataOldValue: true, childList: true, attributes: true, subtree: true }); + + this.unmount = function () { + observer.disconnect(); + _this4.root.removeEventListener('keydown', onKeyDown); + _this4.root.removeEventListener('input', onInput); + _this4.root.ownerDocument.removeEventListener('selectionchange', onSelectionChange); + _this4.root.remove(); + _this4.unmount = function () {}; + }; + } + }, { + key: 'unmount', + value: function unmount() {} + }]); + return HTMLView; + }(EventDispatcher); + + var lastWord = /\w+[^\w]*$/; + var firstWord = /^[^\w]*\w+/; + var lastLine = /[^\n]*$/; + + // Basic text input module. Prevent any actions other than typing characters and handle with the API. + function input(view) { + var editor = view.editor; + + function onEnter(event, shortcut) { + if (event.defaultPrevented) return; + event.preventDefault(); + + var _editor$getSelectedRa = editor.getSelectedRange(), + _editor$getSelectedRa2 = slicedToArray(_editor$getSelectedRa, 2), + from = _editor$getSelectedRa2[0], + to = _editor$getSelectedRa2[1]; + + if (shortcut === 'Shift+Enter') { + editor.insertText(from, to, '\r'); + } else { + var line = editor.contents.getLine(from); + editor.insertText([from, to], '\n', line.attributes); + } + } + + function onBackspace(event, shortcut) { + if (event.defaultPrevented) return; + event.preventDefault(); + + var _editor$selection = slicedToArray(editor.selection, 2), + from = _editor$selection[0], + to = _editor$selection[1]; + + if (from + to === 0) { + editor.formatLine(0, {}); + } else { + // The "from" block needs to stay the same. The "to" block gets merged into it + if (from === to) { + if (shortcut === 'Alt+Backspace' && view.isMac) { + var match = editor.getText().slice(0, from).match(lastWord); + if (match) from -= match[0].length; + } else if (shortcut === 'Cmd+Backspace' && view.isMac) { + var _match = editor.getText().slice(0, from).match(lastLine); + if (_match) from -= _match[0].length; + } else { + from--; + } + } + editor.deleteText([from, to]); + } + } + + function onDelete(event, shortcut) { + if (event.defaultPrevented) return; + event.preventDefault(); + + var _editor$selection2 = slicedToArray(editor.selection, 2), + from = _editor$selection2[0], + to = _editor$selection2[1]; + + if (from === to && from === editor.length) return; + + if (from === to) { + if (shortcut === 'Alt+Delete' && view.isMac) { + var match = editor.getText().slice(from).match(firstWord); + if (match) to += match[0].length; + } else { + to++; + } + } + editor.deleteText([from, to]); + } + + view.on('shortcut:Enter', onEnter); + view.on('shortcut:Shift+Enter', onEnter); + view.on('shortcut:Backspace', onBackspace); + view.on('shortcut:Alt+Backspace', onBackspace); + view.on('shortcut:Cmd+Backspace', onBackspace); + view.on('shortcut:Delete', onDelete); + view.on('shortcut:Alt+Delete', onDelete); + } + + var keymap = { + 'Ctrl+B': function CtrlB(editor) { + return editor.toggleTextFormat(editor.selection, { bold: true }); + }, + 'Ctrl+I': function CtrlI(editor) { + return editor.toggleTextFormat(editor.selection, { italics: true }); + }, + 'Ctrl+1': function Ctrl1(editor) { + return editor.toggleLineFormat(editor.selection, { header: 1 }); + }, + 'Ctrl+2': function Ctrl2(editor) { + return editor.toggleLineFormat(editor.selection, { header: 2 }); + }, + 'Ctrl+3': function Ctrl3(editor) { + return editor.toggleLineFormat(editor.selection, { header: 3 }); + }, + 'Ctrl+4': function Ctrl4(editor) { + return editor.toggleLineFormat(editor.selection, { header: 4 }); + }, + 'Ctrl+5': function Ctrl5(editor) { + return editor.toggleLineFormat(editor.selection, { header: 5 }); + }, + 'Ctrl+6': function Ctrl6(editor) { + return editor.toggleLineFormat(editor.selection, { header: 6 }); + }, + 'Ctrl+0': function Ctrl0(editor) { + return editor.formatLine(editor.selection, {}); + } + }; + + var macKeymap = { + 'Cmd+B': keymap['Ctrl+B'], + 'Cmd+I': keymap['Ctrl+I'], + 'Cmd+1': keymap['Ctrl+1'], + 'Cmd+2': keymap['Ctrl+2'], + 'Cmd+3': keymap['Ctrl+3'], + 'Cmd+4': keymap['Ctrl+4'], + 'Cmd+5': keymap['Ctrl+5'], + 'Cmd+6': keymap['Ctrl+6'], + 'Cmd+0': keymap['Ctrl+0'] + }; + + // Basic text input module. Prevent any actions other than typing characters and handle with the API. + function keyShortcuts(view) { + var editor = view.editor; + + view.on('shortcut', function (event, shortcut) { + if (event.defaultPrevented) return; + var map = view.isMac ? macKeymap : keymap; + + if (shortcut in map) { + event.preventDefault(); + map[shortcut](editor); + } + }); + } + + var SOURCE_USER$3 = 'user'; + var defaultSettings = { + delay: 4000, + maxStack: 500 + }; + + function history(view) { + var settings = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultSettings; + + var editor = view.editor; + + var stack = { + undo: [], + redo: [] + }; + var lastRecorded = 0; + var lastAction = void 0; + var ignoreChange = false; + + function undo(event) { + action(event, 'undo', 'redo'); + } + + function redo() { + action(event, 'redo', 'undo'); + } + + function action(event, source, dest) { + if (event.defaultPrevented) return; + event.preventDefault(); + if (stack[source].length === 0) return; + var entry = stack[source].pop(); + stack[dest].push(entry); + lastRecorded = 0; + ignoreChange = true; + editor.updateContents(entry[source], SOURCE_USER$3, entry[source + 'Selection']); + ignoreChange = false; + } + + function record(change, contents, oldContents, selection, oldSelection) { + var timestamp = Date.now(); + var action = getAction(change); + stack.redo.length = 0; + + var undoChange = contents.diff(oldContents); + // Break combining if actions are different (e.g. a delete then an insert should break it) + if (!action || lastAction !== action) lastRecorded = 0; + lastAction = action; + + if (lastRecorded + settings.delay > timestamp && stack.undo.length > 0) { + // Combine with the last change + var entry = stack.undo.pop(); + oldSelection = entry.undoSelection; + undoChange = undoChange.compose(entry.undo); + change = entry.redo.compose(change); + } else { + lastRecorded = timestamp; + } + + stack.undo.push({ + redo: change, + undo: undoChange, + redoSelection: selection, + undoSelection: oldSelection + }); + + if (stack.undo.length > settings.maxStack) { + stack.undo.shift(); + } + } + + function transform(change) { + stack.undo.forEach(function (entry) { + entry.undo = change.transform(entry.undo, true); + entry.redo = change.transform(entry.redo, true); + }); + stack.redo.forEach(function (entry) { + entry.undo = change.transform(entry.undo, true); + entry.redo = change.transform(entry.redo, true); + }); + } + + editor.on('editor-change', function (_ref) { + var change = _ref.change, + contents = _ref.contents, + oldContents = _ref.oldContents, + selection = _ref.selection, + oldSelection = _ref.oldSelection, + source = _ref.source; + + if (ignoreChange) return; + if (!change) { + // Break the history merging when selection changes + lastRecorded = 0; + return; + } + if (source === SOURCE_USER$3) { + record(change, contents, oldContents, selection, oldSelection); + } else { + transform(change); + } + }); + + editor.on('selection'); + + if (view.isMac) { + view.on('shortcut:Cmd+Z', undo); + view.on('shortcut:Cmd+Shift+Z', redo); + } else { + view.on('shortcut:Ctrl+Z', undo); + view.on('shortcut:Cmd+Y', redo); + } + } + + function getAction(change) { + if (change.ops.length === 1 || change.ops.length === 2 && change.ops[0].retain && !change.ops[0].attributes) { + var changeOp = change.ops[change.ops.length - 1]; + if (changeOp.delete) return 'delete'; + if (changeOp.insert === '\n') return 'newline'; + if (changeOp.insert) return 'insert'; + } + return ''; + } + + var defaultViewModules = [input, keyShortcuts, history]; + + var editor$1 = new Editor(); + var view$1 = new HTMLView(editor$1, { modules: defaultViewModules }); + + window.editor = editor$1; + window.view = view$1; + + // ***** Search feature + var style = document.createElement('style'); + style.textContent = 'span.search { background: yellow }'; + document.head.appendChild(style); + + var searchInput = document.createElement('input'); + var searchString = ''; + searchInput.type = 'search'; + document.body.appendChild(searchInput); + searchInput.addEventListener('input', function () { + searchString = searchInput.value.toLowerCase().trim(); + view$1.update(); + }); + + view$1.dom.markups.add({ + name: 'search', + selector: 'span.search', + vdom: function vdom(children) { + return h( + 'span', + { 'class': 'search' }, + children + ); + } + }); + + view$1.on('decorate', function (editor) { + if (!searchString) return; + var text = editor.getText().toLowerCase(); + var lastIndex = 0, + index = void 0; + while ((index = text.indexOf(searchString, lastIndex)) !== -1) { + lastIndex = index + searchString.length; + editor.formatText(index, lastIndex, { search: true }); + } + }); + // ***** Search feature + + + editor$1.setText('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur quis sagittis libero. Etiam egestas rhoncus risus, sed accumsan nisi laoreet a. Praesent pulvinar porttitor lorem, vel tempor est vulputate nec. Duis magna justo, ultrices at ullamcorper a, sagittis quis mi. Duis id libero non augue faucibus faucibus sed nec sapien. Vivamus pulvinar justo nec metus dapibus, quis tincidunt justo fermentum. Aliquam erat volutpat. Nam hendrerit libero ut nunc rutrum pellentesque. Nulla erat eros, molestie ac nibh non, consectetur luctus lorem. Mauris vel egestas nisi.\nMauris sed mi cursus urna pretium posuere sit amet id lorem. Maecenas tristique commodo diam at elementum. Maecenas dapibus risus at mauris consequat, ac semper justo commodo. Sed tempor mattis nisi, in accumsan felis gravida non. In dignissim pellentesque ornare. Mauris lorem sem, consectetur eu ornare at, laoreet sed dui. Nam gravida justo tempus ligula pharetra, sit amet vestibulum lorem sagittis. In mauris purus, cursus vitae tempus at, tincidunt et arcu. Etiam sed libero ac mi fermentum hendrerit. Cras vel cursus urna, sed pretium nisl. Mauris sodales tempor ex nec iaculis. Nulla ac erat ac nunc malesuada viverra. Pellentesque nec ipsum in arcu consectetur elementum a ut metus. Integer sit amet eleifend nulla. Morbi ac felis malesuada, dapibus libero eget, posuere neque. Cras porta ut metus sed vulputate.'); + + view$1.mount(document.body); + +}()); +//# sourceMappingURL=index.js.map diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..2cdd9eb --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sources":["../src/eventdispatcher.js","../node_modules/fast-diff/diff.js","../node_modules/deep-equal/lib/keys.js","../node_modules/deep-equal/lib/is_arguments.js","../node_modules/deep-equal/index.js","../node_modules/extend/index.js","../node_modules/quill-delta/lib/op.js","../node_modules/quill-delta/lib/delta.js","../node_modules/fast-equals/es/utils.js","../node_modules/fast-equals/es/comparator.js","../node_modules/fast-equals/es/index.js","../src/editor.js","../node_modules/ultradom/src/h.js","../node_modules/ultradom/src/recycleElement.js","../node_modules/ultradom/src/clone.js","../node_modules/ultradom/src/eventListener.js","../node_modules/ultradom/src/updateAttribute.js","../node_modules/ultradom/src/createElement.js","../node_modules/ultradom/src/removeChildren.js","../node_modules/ultradom/src/removeElement.js","../node_modules/ultradom/src/updateElement.js","../node_modules/ultradom/src/getKey.js","../node_modules/ultradom/src/patchElement.js","../node_modules/ultradom/src/patch.js","../src/defaultDom.js","../src/selection.js","../node_modules/escape-html/index.js","../src/dom.js","../node_modules/keyboardevent-key-polyfill/index.js","../node_modules/shortcut-string/index.js","../src/html-view.js","../src/modules/input.js","../src/modules/key-shortcuts.js","../src/modules/history.js","../src/index.js","../src/dev.js"],"sourcesContent":["const dispatcherEvents = new WeakMap();\n\n\nexport default class EventDispatcher {\n\n on(type, listener) {\n getEventListeners(this, type).add(listener);\n }\n\n off(type, listener) {\n getEventListeners(this, type).delete(listener);\n }\n\n once(type, listener) {\n function once(...args) {\n this.off(type, once);\n listener.apply(this, args);\n }\n this.on(type, once);\n }\n\n fire(type, ...args) {\n let uncanceled = true;\n getEventListeners(this, type).forEach(listener => {\n uncanceled && listener.apply(this, args) !== false || (uncanceled = false);\n });\n return uncanceled;\n }\n}\n\n\nfunction getEventListeners(obj, type) {\n let events = dispatcherEvents.get(obj);\n if (!events) dispatcherEvents.set(obj, events = Object.create(null));\n return events[type] || (events[type] = new Set());\n}\n","/**\n * This library modifies the diff-patch-match library by Neil Fraser\n * by removing the patch and match functionality and certain advanced\n * options in the diff function. The original license is as follows:\n *\n * ===\n *\n * Diff Match and Patch\n *\n * Copyright 2006 Google Inc.\n * http://code.google.com/p/google-diff-match-patch/\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n/**\n * The data structure representing a diff is an array of tuples:\n * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]\n * which means: delete 'Hello', add 'Goodbye' and keep ' world.'\n */\nvar DIFF_DELETE = -1;\nvar DIFF_INSERT = 1;\nvar DIFF_EQUAL = 0;\n\n\n/**\n * Find the differences between two texts. Simplifies the problem by stripping\n * any common prefix or suffix off the texts before diffing.\n * @param {string} text1 Old string to be diffed.\n * @param {string} text2 New string to be diffed.\n * @param {Int} cursor_pos Expected edit position in text1 (optional)\n * @return {Array} Array of diff tuples.\n */\nfunction diff_main(text1, text2, cursor_pos) {\n // Check for equality (speedup).\n if (text1 == text2) {\n if (text1) {\n return [[DIFF_EQUAL, text1]];\n }\n return [];\n }\n\n // Check cursor_pos within bounds\n if (cursor_pos < 0 || text1.length < cursor_pos) {\n cursor_pos = null;\n }\n\n // Trim off common prefix (speedup).\n var commonlength = diff_commonPrefix(text1, text2);\n var commonprefix = text1.substring(0, commonlength);\n text1 = text1.substring(commonlength);\n text2 = text2.substring(commonlength);\n\n // Trim off common suffix (speedup).\n commonlength = diff_commonSuffix(text1, text2);\n var commonsuffix = text1.substring(text1.length - commonlength);\n text1 = text1.substring(0, text1.length - commonlength);\n text2 = text2.substring(0, text2.length - commonlength);\n\n // Compute the diff on the middle block.\n var diffs = diff_compute_(text1, text2);\n\n // Restore the prefix and suffix.\n if (commonprefix) {\n diffs.unshift([DIFF_EQUAL, commonprefix]);\n }\n if (commonsuffix) {\n diffs.push([DIFF_EQUAL, commonsuffix]);\n }\n diff_cleanupMerge(diffs);\n if (cursor_pos != null) {\n diffs = fix_cursor(diffs, cursor_pos);\n }\n diffs = fix_emoji(diffs);\n return diffs;\n};\n\n\n/**\n * Find the differences between two texts. Assumes that the texts do not\n * have any common prefix or suffix.\n * @param {string} text1 Old string to be diffed.\n * @param {string} text2 New string to be diffed.\n * @return {Array} Array of diff tuples.\n */\nfunction diff_compute_(text1, text2) {\n var diffs;\n\n if (!text1) {\n // Just add some text (speedup).\n return [[DIFF_INSERT, text2]];\n }\n\n if (!text2) {\n // Just delete some text (speedup).\n return [[DIFF_DELETE, text1]];\n }\n\n var longtext = text1.length > text2.length ? text1 : text2;\n var shorttext = text1.length > text2.length ? text2 : text1;\n var i = longtext.indexOf(shorttext);\n if (i != -1) {\n // Shorter text is inside the longer text (speedup).\n diffs = [[DIFF_INSERT, longtext.substring(0, i)],\n [DIFF_EQUAL, shorttext],\n [DIFF_INSERT, longtext.substring(i + shorttext.length)]];\n // Swap insertions for deletions if diff is reversed.\n if (text1.length > text2.length) {\n diffs[0][0] = diffs[2][0] = DIFF_DELETE;\n }\n return diffs;\n }\n\n if (shorttext.length == 1) {\n // Single character string.\n // After the previous speedup, the character can't be an equality.\n return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];\n }\n\n // Check to see if the problem can be split in two.\n var hm = diff_halfMatch_(text1, text2);\n if (hm) {\n // A half-match was found, sort out the return data.\n var text1_a = hm[0];\n var text1_b = hm[1];\n var text2_a = hm[2];\n var text2_b = hm[3];\n var mid_common = hm[4];\n // Send both pairs off for separate processing.\n var diffs_a = diff_main(text1_a, text2_a);\n var diffs_b = diff_main(text1_b, text2_b);\n // Merge the results.\n return diffs_a.concat([[DIFF_EQUAL, mid_common]], diffs_b);\n }\n\n return diff_bisect_(text1, text2);\n};\n\n\n/**\n * Find the 'middle snake' of a diff, split the problem in two\n * and return the recursively constructed diff.\n * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.\n * @param {string} text1 Old string to be diffed.\n * @param {string} text2 New string to be diffed.\n * @return {Array} Array of diff tuples.\n * @private\n */\nfunction diff_bisect_(text1, text2) {\n // Cache the text lengths to prevent multiple calls.\n var text1_length = text1.length;\n var text2_length = text2.length;\n var max_d = Math.ceil((text1_length + text2_length) / 2);\n var v_offset = max_d;\n var v_length = 2 * max_d;\n var v1 = new Array(v_length);\n var v2 = new Array(v_length);\n // Setting all elements to -1 is faster in Chrome & Firefox than mixing\n // integers and undefined.\n for (var x = 0; x < v_length; x++) {\n v1[x] = -1;\n v2[x] = -1;\n }\n v1[v_offset + 1] = 0;\n v2[v_offset + 1] = 0;\n var delta = text1_length - text2_length;\n // If the total number of characters is odd, then the front path will collide\n // with the reverse path.\n var front = (delta % 2 != 0);\n // Offsets for start and end of k loop.\n // Prevents mapping of space beyond the grid.\n var k1start = 0;\n var k1end = 0;\n var k2start = 0;\n var k2end = 0;\n for (var d = 0; d < max_d; d++) {\n // Walk the front path one step.\n for (var k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {\n var k1_offset = v_offset + k1;\n var x1;\n if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) {\n x1 = v1[k1_offset + 1];\n } else {\n x1 = v1[k1_offset - 1] + 1;\n }\n var y1 = x1 - k1;\n while (x1 < text1_length && y1 < text2_length &&\n text1.charAt(x1) == text2.charAt(y1)) {\n x1++;\n y1++;\n }\n v1[k1_offset] = x1;\n if (x1 > text1_length) {\n // Ran off the right of the graph.\n k1end += 2;\n } else if (y1 > text2_length) {\n // Ran off the bottom of the graph.\n k1start += 2;\n } else if (front) {\n var k2_offset = v_offset + delta - k1;\n if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) {\n // Mirror x2 onto top-left coordinate system.\n var x2 = text1_length - v2[k2_offset];\n if (x1 >= x2) {\n // Overlap detected.\n return diff_bisectSplit_(text1, text2, x1, y1);\n }\n }\n }\n }\n\n // Walk the reverse path one step.\n for (var k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {\n var k2_offset = v_offset + k2;\n var x2;\n if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) {\n x2 = v2[k2_offset + 1];\n } else {\n x2 = v2[k2_offset - 1] + 1;\n }\n var y2 = x2 - k2;\n while (x2 < text1_length && y2 < text2_length &&\n text1.charAt(text1_length - x2 - 1) ==\n text2.charAt(text2_length - y2 - 1)) {\n x2++;\n y2++;\n }\n v2[k2_offset] = x2;\n if (x2 > text1_length) {\n // Ran off the left of the graph.\n k2end += 2;\n } else if (y2 > text2_length) {\n // Ran off the top of the graph.\n k2start += 2;\n } else if (!front) {\n var k1_offset = v_offset + delta - k2;\n if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) {\n var x1 = v1[k1_offset];\n var y1 = v_offset + x1 - k1_offset;\n // Mirror x2 onto top-left coordinate system.\n x2 = text1_length - x2;\n if (x1 >= x2) {\n // Overlap detected.\n return diff_bisectSplit_(text1, text2, x1, y1);\n }\n }\n }\n }\n }\n // Diff took too long and hit the deadline or\n // number of diffs equals number of characters, no commonality at all.\n return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];\n};\n\n\n/**\n * Given the location of the 'middle snake', split the diff in two parts\n * and recurse.\n * @param {string} text1 Old string to be diffed.\n * @param {string} text2 New string to be diffed.\n * @param {number} x Index of split point in text1.\n * @param {number} y Index of split point in text2.\n * @return {Array} Array of diff tuples.\n */\nfunction diff_bisectSplit_(text1, text2, x, y) {\n var text1a = text1.substring(0, x);\n var text2a = text2.substring(0, y);\n var text1b = text1.substring(x);\n var text2b = text2.substring(y);\n\n // Compute both diffs serially.\n var diffs = diff_main(text1a, text2a);\n var diffsb = diff_main(text1b, text2b);\n\n return diffs.concat(diffsb);\n};\n\n\n/**\n * Determine the common prefix of two strings.\n * @param {string} text1 First string.\n * @param {string} text2 Second string.\n * @return {number} The number of characters common to the start of each\n * string.\n */\nfunction diff_commonPrefix(text1, text2) {\n // Quick check for common null cases.\n if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) {\n return 0;\n }\n // Binary search.\n // Performance analysis: http://neil.fraser.name/news/2007/10/09/\n var pointermin = 0;\n var pointermax = Math.min(text1.length, text2.length);\n var pointermid = pointermax;\n var pointerstart = 0;\n while (pointermin < pointermid) {\n if (text1.substring(pointerstart, pointermid) ==\n text2.substring(pointerstart, pointermid)) {\n pointermin = pointermid;\n pointerstart = pointermin;\n } else {\n pointermax = pointermid;\n }\n pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);\n }\n return pointermid;\n};\n\n\n/**\n * Determine the common suffix of two strings.\n * @param {string} text1 First string.\n * @param {string} text2 Second string.\n * @return {number} The number of characters common to the end of each string.\n */\nfunction diff_commonSuffix(text1, text2) {\n // Quick check for common null cases.\n if (!text1 || !text2 ||\n text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) {\n return 0;\n }\n // Binary search.\n // Performance analysis: http://neil.fraser.name/news/2007/10/09/\n var pointermin = 0;\n var pointermax = Math.min(text1.length, text2.length);\n var pointermid = pointermax;\n var pointerend = 0;\n while (pointermin < pointermid) {\n if (text1.substring(text1.length - pointermid, text1.length - pointerend) ==\n text2.substring(text2.length - pointermid, text2.length - pointerend)) {\n pointermin = pointermid;\n pointerend = pointermin;\n } else {\n pointermax = pointermid;\n }\n pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);\n }\n return pointermid;\n};\n\n\n/**\n * Do the two texts share a substring which is at least half the length of the\n * longer text?\n * This speedup can produce non-minimal diffs.\n * @param {string} text1 First string.\n * @param {string} text2 Second string.\n * @return {Array.} Five element Array, containing the prefix of\n * text1, the suffix of text1, the prefix of text2, the suffix of\n * text2 and the common middle. Or null if there was no match.\n */\nfunction diff_halfMatch_(text1, text2) {\n var longtext = text1.length > text2.length ? text1 : text2;\n var shorttext = text1.length > text2.length ? text2 : text1;\n if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {\n return null; // Pointless.\n }\n\n /**\n * Does a substring of shorttext exist within longtext such that the substring\n * is at least half the length of longtext?\n * Closure, but does not reference any external variables.\n * @param {string} longtext Longer string.\n * @param {string} shorttext Shorter string.\n * @param {number} i Start index of quarter length substring within longtext.\n * @return {Array.} Five element Array, containing the prefix of\n * longtext, the suffix of longtext, the prefix of shorttext, the suffix\n * of shorttext and the common middle. Or null if there was no match.\n * @private\n */\n function diff_halfMatchI_(longtext, shorttext, i) {\n // Start with a 1/4 length substring at position i as a seed.\n var seed = longtext.substring(i, i + Math.floor(longtext.length / 4));\n var j = -1;\n var best_common = '';\n var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b;\n while ((j = shorttext.indexOf(seed, j + 1)) != -1) {\n var prefixLength = diff_commonPrefix(longtext.substring(i),\n shorttext.substring(j));\n var suffixLength = diff_commonSuffix(longtext.substring(0, i),\n shorttext.substring(0, j));\n if (best_common.length < suffixLength + prefixLength) {\n best_common = shorttext.substring(j - suffixLength, j) +\n shorttext.substring(j, j + prefixLength);\n best_longtext_a = longtext.substring(0, i - suffixLength);\n best_longtext_b = longtext.substring(i + prefixLength);\n best_shorttext_a = shorttext.substring(0, j - suffixLength);\n best_shorttext_b = shorttext.substring(j + prefixLength);\n }\n }\n if (best_common.length * 2 >= longtext.length) {\n return [best_longtext_a, best_longtext_b,\n best_shorttext_a, best_shorttext_b, best_common];\n } else {\n return null;\n }\n }\n\n // First check if the second quarter is the seed for a half-match.\n var hm1 = diff_halfMatchI_(longtext, shorttext,\n Math.ceil(longtext.length / 4));\n // Check again based on the third quarter.\n var hm2 = diff_halfMatchI_(longtext, shorttext,\n Math.ceil(longtext.length / 2));\n var hm;\n if (!hm1 && !hm2) {\n return null;\n } else if (!hm2) {\n hm = hm1;\n } else if (!hm1) {\n hm = hm2;\n } else {\n // Both matched. Select the longest.\n hm = hm1[4].length > hm2[4].length ? hm1 : hm2;\n }\n\n // A half-match was found, sort out the return data.\n var text1_a, text1_b, text2_a, text2_b;\n if (text1.length > text2.length) {\n text1_a = hm[0];\n text1_b = hm[1];\n text2_a = hm[2];\n text2_b = hm[3];\n } else {\n text2_a = hm[0];\n text2_b = hm[1];\n text1_a = hm[2];\n text1_b = hm[3];\n }\n var mid_common = hm[4];\n return [text1_a, text1_b, text2_a, text2_b, mid_common];\n};\n\n\n/**\n * Reorder and merge like edit sections. Merge equalities.\n * Any edit section can move as long as it doesn't cross an equality.\n * @param {Array} diffs Array of diff tuples.\n */\nfunction diff_cleanupMerge(diffs) {\n diffs.push([DIFF_EQUAL, '']); // Add a dummy entry at the end.\n var pointer = 0;\n var count_delete = 0;\n var count_insert = 0;\n var text_delete = '';\n var text_insert = '';\n var commonlength;\n while (pointer < diffs.length) {\n switch (diffs[pointer][0]) {\n case DIFF_INSERT:\n count_insert++;\n text_insert += diffs[pointer][1];\n pointer++;\n break;\n case DIFF_DELETE:\n count_delete++;\n text_delete += diffs[pointer][1];\n pointer++;\n break;\n case DIFF_EQUAL:\n // Upon reaching an equality, check for prior redundancies.\n if (count_delete + count_insert > 1) {\n if (count_delete !== 0 && count_insert !== 0) {\n // Factor out any common prefixies.\n commonlength = diff_commonPrefix(text_insert, text_delete);\n if (commonlength !== 0) {\n if ((pointer - count_delete - count_insert) > 0 &&\n diffs[pointer - count_delete - count_insert - 1][0] ==\n DIFF_EQUAL) {\n diffs[pointer - count_delete - count_insert - 1][1] +=\n text_insert.substring(0, commonlength);\n } else {\n diffs.splice(0, 0, [DIFF_EQUAL,\n text_insert.substring(0, commonlength)]);\n pointer++;\n }\n text_insert = text_insert.substring(commonlength);\n text_delete = text_delete.substring(commonlength);\n }\n // Factor out any common suffixies.\n commonlength = diff_commonSuffix(text_insert, text_delete);\n if (commonlength !== 0) {\n diffs[pointer][1] = text_insert.substring(text_insert.length -\n commonlength) + diffs[pointer][1];\n text_insert = text_insert.substring(0, text_insert.length -\n commonlength);\n text_delete = text_delete.substring(0, text_delete.length -\n commonlength);\n }\n }\n // Delete the offending records and add the merged ones.\n if (count_delete === 0) {\n diffs.splice(pointer - count_insert,\n count_delete + count_insert, [DIFF_INSERT, text_insert]);\n } else if (count_insert === 0) {\n diffs.splice(pointer - count_delete,\n count_delete + count_insert, [DIFF_DELETE, text_delete]);\n } else {\n diffs.splice(pointer - count_delete - count_insert,\n count_delete + count_insert, [DIFF_DELETE, text_delete],\n [DIFF_INSERT, text_insert]);\n }\n pointer = pointer - count_delete - count_insert +\n (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1;\n } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) {\n // Merge this equality with the previous one.\n diffs[pointer - 1][1] += diffs[pointer][1];\n diffs.splice(pointer, 1);\n } else {\n pointer++;\n }\n count_insert = 0;\n count_delete = 0;\n text_delete = '';\n text_insert = '';\n break;\n }\n }\n if (diffs[diffs.length - 1][1] === '') {\n diffs.pop(); // Remove the dummy entry at the end.\n }\n\n // Second pass: look for single edits surrounded on both sides by equalities\n // which can be shifted sideways to eliminate an equality.\n // e.g: ABAC -> ABAC\n var changes = false;\n pointer = 1;\n // Intentionally ignore the first and last element (don't need checking).\n while (pointer < diffs.length - 1) {\n if (diffs[pointer - 1][0] == DIFF_EQUAL &&\n diffs[pointer + 1][0] == DIFF_EQUAL) {\n // This is a single edit surrounded by equalities.\n if (diffs[pointer][1].substring(diffs[pointer][1].length -\n diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) {\n // Shift the edit over the previous equality.\n diffs[pointer][1] = diffs[pointer - 1][1] +\n diffs[pointer][1].substring(0, diffs[pointer][1].length -\n diffs[pointer - 1][1].length);\n diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];\n diffs.splice(pointer - 1, 1);\n changes = true;\n } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) ==\n diffs[pointer + 1][1]) {\n // Shift the edit over the next equality.\n diffs[pointer - 1][1] += diffs[pointer + 1][1];\n diffs[pointer][1] =\n diffs[pointer][1].substring(diffs[pointer + 1][1].length) +\n diffs[pointer + 1][1];\n diffs.splice(pointer + 1, 1);\n changes = true;\n }\n }\n pointer++;\n }\n // If shifts were made, the diff needs reordering and another shift sweep.\n if (changes) {\n diff_cleanupMerge(diffs);\n }\n};\n\n\nvar diff = diff_main;\ndiff.INSERT = DIFF_INSERT;\ndiff.DELETE = DIFF_DELETE;\ndiff.EQUAL = DIFF_EQUAL;\n\nmodule.exports = diff;\n\n/*\n * Modify a diff such that the cursor position points to the start of a change:\n * E.g.\n * cursor_normalize_diff([[DIFF_EQUAL, 'abc']], 1)\n * => [1, [[DIFF_EQUAL, 'a'], [DIFF_EQUAL, 'bc']]]\n * cursor_normalize_diff([[DIFF_INSERT, 'new'], [DIFF_DELETE, 'xyz']], 2)\n * => [2, [[DIFF_INSERT, 'new'], [DIFF_DELETE, 'xy'], [DIFF_DELETE, 'z']]]\n *\n * @param {Array} diffs Array of diff tuples\n * @param {Int} cursor_pos Suggested edit position. Must not be out of bounds!\n * @return {Array} A tuple [cursor location in the modified diff, modified diff]\n */\nfunction cursor_normalize_diff (diffs, cursor_pos) {\n if (cursor_pos === 0) {\n return [DIFF_EQUAL, diffs];\n }\n for (var current_pos = 0, i = 0; i < diffs.length; i++) {\n var d = diffs[i];\n if (d[0] === DIFF_DELETE || d[0] === DIFF_EQUAL) {\n var next_pos = current_pos + d[1].length;\n if (cursor_pos === next_pos) {\n return [i + 1, diffs];\n } else if (cursor_pos < next_pos) {\n // copy to prevent side effects\n diffs = diffs.slice();\n // split d into two diff changes\n var split_pos = cursor_pos - current_pos;\n var d_left = [d[0], d[1].slice(0, split_pos)];\n var d_right = [d[0], d[1].slice(split_pos)];\n diffs.splice(i, 1, d_left, d_right);\n return [i + 1, diffs];\n } else {\n current_pos = next_pos;\n }\n }\n }\n throw new Error('cursor_pos is out of bounds!')\n}\n\n/*\n * Modify a diff such that the edit position is \"shifted\" to the proposed edit location (cursor_position).\n *\n * Case 1)\n * Check if a naive shift is possible:\n * [0, X], [ 1, Y] -> [ 1, Y], [0, X] (if X + Y === Y + X)\n * [0, X], [-1, Y] -> [-1, Y], [0, X] (if X + Y === Y + X) - holds same result\n * Case 2)\n * Check if the following shifts are possible:\n * [0, 'pre'], [ 1, 'prefix'] -> [ 1, 'pre'], [0, 'pre'], [ 1, 'fix']\n * [0, 'pre'], [-1, 'prefix'] -> [-1, 'pre'], [0, 'pre'], [-1, 'fix']\n * ^ ^\n * d d_next\n *\n * @param {Array} diffs Array of diff tuples\n * @param {Int} cursor_pos Suggested edit position. Must not be out of bounds!\n * @return {Array} Array of diff tuples\n */\nfunction fix_cursor (diffs, cursor_pos) {\n var norm = cursor_normalize_diff(diffs, cursor_pos);\n var ndiffs = norm[1];\n var cursor_pointer = norm[0];\n var d = ndiffs[cursor_pointer];\n var d_next = ndiffs[cursor_pointer + 1];\n\n if (d == null) {\n // Text was deleted from end of original string,\n // cursor is now out of bounds in new string\n return diffs;\n } else if (d[0] !== DIFF_EQUAL) {\n // A modification happened at the cursor location.\n // This is the expected outcome, so we can return the original diff.\n return diffs;\n } else {\n if (d_next != null && d[1] + d_next[1] === d_next[1] + d[1]) {\n // Case 1)\n // It is possible to perform a naive shift\n ndiffs.splice(cursor_pointer, 2, d_next, d)\n return merge_tuples(ndiffs, cursor_pointer, 2)\n } else if (d_next != null && d_next[1].indexOf(d[1]) === 0) {\n // Case 2)\n // d[1] is a prefix of d_next[1]\n // We can assume that d_next[0] !== 0, since d[0] === 0\n // Shift edit locations..\n ndiffs.splice(cursor_pointer, 2, [d_next[0], d[1]], [0, d[1]]);\n var suffix = d_next[1].slice(d[1].length);\n if (suffix.length > 0) {\n ndiffs.splice(cursor_pointer + 2, 0, [d_next[0], suffix]);\n }\n return merge_tuples(ndiffs, cursor_pointer, 3)\n } else {\n // Not possible to perform any modification\n return diffs;\n }\n }\n}\n\n/*\n * Check diff did not split surrogate pairs.\n * Ex. [0, '\\uD83D'], [-1, '\\uDC36'], [1, '\\uDC2F'] -> [-1, '\\uD83D\\uDC36'], [1, '\\uD83D\\uDC2F']\n * '\\uD83D\\uDC36' === '🐶', '\\uD83D\\uDC2F' === '🐯'\n *\n * @param {Array} diffs Array of diff tuples\n * @return {Array} Array of diff tuples\n */\nfunction fix_emoji (diffs) {\n var compact = false;\n var starts_with_pair_end = function(str) {\n return str.charCodeAt(0) >= 0xDC00 && str.charCodeAt(0) <= 0xDFFF;\n }\n var ends_with_pair_start = function(str) {\n return str.charCodeAt(str.length-1) >= 0xD800 && str.charCodeAt(str.length-1) <= 0xDBFF;\n }\n for (var i = 2; i < diffs.length; i += 1) {\n if (diffs[i-2][0] === DIFF_EQUAL && ends_with_pair_start(diffs[i-2][1]) &&\n diffs[i-1][0] === DIFF_DELETE && starts_with_pair_end(diffs[i-1][1]) &&\n diffs[i][0] === DIFF_INSERT && starts_with_pair_end(diffs[i][1])) {\n compact = true;\n\n diffs[i-1][1] = diffs[i-2][1].slice(-1) + diffs[i-1][1];\n diffs[i][1] = diffs[i-2][1].slice(-1) + diffs[i][1];\n\n diffs[i-2][1] = diffs[i-2][1].slice(0, -1);\n }\n }\n if (!compact) {\n return diffs;\n }\n var fixed_diffs = [];\n for (var i = 0; i < diffs.length; i += 1) {\n if (diffs[i][1].length > 0) {\n fixed_diffs.push(diffs[i]);\n }\n }\n return fixed_diffs;\n}\n\n/*\n * Try to merge tuples with their neigbors in a given range.\n * E.g. [0, 'a'], [0, 'b'] -> [0, 'ab']\n *\n * @param {Array} diffs Array of diff tuples.\n * @param {Int} start Position of the first element to merge (diffs[start] is also merged with diffs[start - 1]).\n * @param {Int} length Number of consecutive elements to check.\n * @return {Array} Array of merged diff tuples.\n */\nfunction merge_tuples (diffs, start, length) {\n // Check from (start-1) to (start+length).\n for (var i = start + length - 1; i >= 0 && i >= start - 1; i--) {\n if (i + 1 < diffs.length) {\n var left_d = diffs[i];\n var right_d = diffs[i+1];\n if (left_d[0] === right_d[1]) {\n diffs.splice(i, 2, [left_d[0], left_d[1] + right_d[1]]);\n }\n }\n }\n return diffs;\n}\n","exports = module.exports = typeof Object.keys === 'function'\n ? Object.keys : shim;\n\nexports.shim = shim;\nfunction shim (obj) {\n var keys = [];\n for (var key in obj) keys.push(key);\n return keys;\n}\n","var supportsArgumentsClass = (function(){\n return Object.prototype.toString.call(arguments)\n})() == '[object Arguments]';\n\nexports = module.exports = supportsArgumentsClass ? supported : unsupported;\n\nexports.supported = supported;\nfunction supported(object) {\n return Object.prototype.toString.call(object) == '[object Arguments]';\n};\n\nexports.unsupported = unsupported;\nfunction unsupported(object){\n return object &&\n typeof object == 'object' &&\n typeof object.length == 'number' &&\n Object.prototype.hasOwnProperty.call(object, 'callee') &&\n !Object.prototype.propertyIsEnumerable.call(object, 'callee') ||\n false;\n};\n","var pSlice = Array.prototype.slice;\nvar objectKeys = require('./lib/keys.js');\nvar isArguments = require('./lib/is_arguments.js');\n\nvar deepEqual = module.exports = function (actual, expected, opts) {\n if (!opts) opts = {};\n // 7.1. All identical values are equivalent, as determined by ===.\n if (actual === expected) {\n return true;\n\n } else if (actual instanceof Date && expected instanceof Date) {\n return actual.getTime() === expected.getTime();\n\n // 7.3. Other pairs that do not both pass typeof value == 'object',\n // equivalence is determined by ==.\n } else if (!actual || !expected || typeof actual != 'object' && typeof expected != 'object') {\n return opts.strict ? actual === expected : actual == expected;\n\n // 7.4. For all other Object pairs, including Array objects, equivalence is\n // determined by having the same number of owned properties (as verified\n // with Object.prototype.hasOwnProperty.call), the same set of keys\n // (although not necessarily the same order), equivalent values for every\n // corresponding key, and an identical 'prototype' property. Note: this\n // accounts for both named and indexed properties on Arrays.\n } else {\n return objEquiv(actual, expected, opts);\n }\n}\n\nfunction isUndefinedOrNull(value) {\n return value === null || value === undefined;\n}\n\nfunction isBuffer (x) {\n if (!x || typeof x !== 'object' || typeof x.length !== 'number') return false;\n if (typeof x.copy !== 'function' || typeof x.slice !== 'function') {\n return false;\n }\n if (x.length > 0 && typeof x[0] !== 'number') return false;\n return true;\n}\n\nfunction objEquiv(a, b, opts) {\n var i, key;\n if (isUndefinedOrNull(a) || isUndefinedOrNull(b))\n return false;\n // an identical 'prototype' property.\n if (a.prototype !== b.prototype) return false;\n //~~~I've managed to break Object.keys through screwy arguments passing.\n // Converting to array solves the problem.\n if (isArguments(a)) {\n if (!isArguments(b)) {\n return false;\n }\n a = pSlice.call(a);\n b = pSlice.call(b);\n return deepEqual(a, b, opts);\n }\n if (isBuffer(a)) {\n if (!isBuffer(b)) {\n return false;\n }\n if (a.length !== b.length) return false;\n for (i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n }\n try {\n var ka = objectKeys(a),\n kb = objectKeys(b);\n } catch (e) {//happens when one is a string literal and the other isn't\n return false;\n }\n // having the same number of owned properties (keys incorporates\n // hasOwnProperty)\n if (ka.length != kb.length)\n return false;\n //the same set of keys (although not necessarily the same order),\n ka.sort();\n kb.sort();\n //~~~cheap key test\n for (i = ka.length - 1; i >= 0; i--) {\n if (ka[i] != kb[i])\n return false;\n }\n //equivalent values for every corresponding key, and\n //~~~possibly expensive deep test\n for (i = ka.length - 1; i >= 0; i--) {\n key = ka[i];\n if (!deepEqual(a[key], b[key], opts)) return false;\n }\n return typeof a === typeof b;\n}\n","'use strict';\n\nvar hasOwn = Object.prototype.hasOwnProperty;\nvar toStr = Object.prototype.toString;\n\nvar isArray = function isArray(arr) {\n\tif (typeof Array.isArray === 'function') {\n\t\treturn Array.isArray(arr);\n\t}\n\n\treturn toStr.call(arr) === '[object Array]';\n};\n\nvar isPlainObject = function isPlainObject(obj) {\n\tif (!obj || toStr.call(obj) !== '[object Object]') {\n\t\treturn false;\n\t}\n\n\tvar hasOwnConstructor = hasOwn.call(obj, 'constructor');\n\tvar hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');\n\t// Not own constructor property must be Object\n\tif (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {\n\t\treturn false;\n\t}\n\n\t// Own properties are enumerated firstly, so to speed up,\n\t// if last one is own, then all properties are own.\n\tvar key;\n\tfor (key in obj) { /**/ }\n\n\treturn typeof key === 'undefined' || hasOwn.call(obj, key);\n};\n\nmodule.exports = function extend() {\n\tvar options, name, src, copy, copyIsArray, clone;\n\tvar target = arguments[0];\n\tvar i = 1;\n\tvar length = arguments.length;\n\tvar deep = false;\n\n\t// Handle a deep copy situation\n\tif (typeof target === 'boolean') {\n\t\tdeep = target;\n\t\ttarget = arguments[1] || {};\n\t\t// skip the boolean and the target\n\t\ti = 2;\n\t}\n\tif (target == null || (typeof target !== 'object' && typeof target !== 'function')) {\n\t\ttarget = {};\n\t}\n\n\tfor (; i < length; ++i) {\n\t\toptions = arguments[i];\n\t\t// Only deal with non-null/undefined values\n\t\tif (options != null) {\n\t\t\t// Extend the base object\n\t\t\tfor (name in options) {\n\t\t\t\tsrc = target[name];\n\t\t\t\tcopy = options[name];\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif (target !== copy) {\n\t\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\t\tif (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {\n\t\t\t\t\t\tif (copyIsArray) {\n\t\t\t\t\t\t\tcopyIsArray = false;\n\t\t\t\t\t\t\tclone = src && isArray(src) ? src : [];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tclone = src && isPlainObject(src) ? src : {};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\t\ttarget[name] = extend(deep, clone, copy);\n\n\t\t\t\t\t// Don't bring in undefined values\n\t\t\t\t\t} else if (typeof copy !== 'undefined') {\n\t\t\t\t\t\ttarget[name] = copy;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n","var equal = require('deep-equal');\nvar extend = require('extend');\n\n\nvar lib = {\n attributes: {\n compose: function (a, b, keepNull) {\n if (typeof a !== 'object') a = {};\n if (typeof b !== 'object') b = {};\n var attributes = extend(true, {}, b);\n if (!keepNull) {\n attributes = Object.keys(attributes).reduce(function (copy, key) {\n if (attributes[key] != null) {\n copy[key] = attributes[key];\n }\n return copy;\n }, {});\n }\n for (var key in a) {\n if (a[key] !== undefined && b[key] === undefined) {\n attributes[key] = a[key];\n }\n }\n return Object.keys(attributes).length > 0 ? attributes : undefined;\n },\n\n diff: function(a, b) {\n if (typeof a !== 'object') a = {};\n if (typeof b !== 'object') b = {};\n var attributes = Object.keys(a).concat(Object.keys(b)).reduce(function (attributes, key) {\n if (!equal(a[key], b[key])) {\n attributes[key] = b[key] === undefined ? null : b[key];\n }\n return attributes;\n }, {});\n return Object.keys(attributes).length > 0 ? attributes : undefined;\n },\n\n transform: function (a, b, priority) {\n if (typeof a !== 'object') return b;\n if (typeof b !== 'object') return undefined;\n if (!priority) return b; // b simply overwrites us without priority\n var attributes = Object.keys(b).reduce(function (attributes, key) {\n if (a[key] === undefined) attributes[key] = b[key]; // null is a valid value\n return attributes;\n }, {});\n return Object.keys(attributes).length > 0 ? attributes : undefined;\n }\n },\n\n iterator: function (ops) {\n return new Iterator(ops);\n },\n\n length: function (op) {\n if (typeof op['delete'] === 'number') {\n return op['delete'];\n } else if (typeof op.retain === 'number') {\n return op.retain;\n } else {\n return typeof op.insert === 'string' ? op.insert.length : 1;\n }\n }\n};\n\n\nfunction Iterator(ops) {\n this.ops = ops;\n this.index = 0;\n this.offset = 0;\n};\n\nIterator.prototype.hasNext = function () {\n return this.peekLength() < Infinity;\n};\n\nIterator.prototype.next = function (length) {\n if (!length) length = Infinity;\n var nextOp = this.ops[this.index];\n if (nextOp) {\n var offset = this.offset;\n var opLength = lib.length(nextOp)\n if (length >= opLength - offset) {\n length = opLength - offset;\n this.index += 1;\n this.offset = 0;\n } else {\n this.offset += length;\n }\n if (typeof nextOp['delete'] === 'number') {\n return { 'delete': length };\n } else {\n var retOp = {};\n if (nextOp.attributes) {\n retOp.attributes = nextOp.attributes;\n }\n if (typeof nextOp.retain === 'number') {\n retOp.retain = length;\n } else if (typeof nextOp.insert === 'string') {\n retOp.insert = nextOp.insert.substr(offset, length);\n } else {\n // offset should === 0, length should === 1\n retOp.insert = nextOp.insert;\n }\n return retOp;\n }\n } else {\n return { retain: Infinity };\n }\n};\n\nIterator.prototype.peek = function () {\n return this.ops[this.index];\n};\n\nIterator.prototype.peekLength = function () {\n if (this.ops[this.index]) {\n // Should never return 0 if our index is being managed correctly\n return lib.length(this.ops[this.index]) - this.offset;\n } else {\n return Infinity;\n }\n};\n\nIterator.prototype.peekType = function () {\n if (this.ops[this.index]) {\n if (typeof this.ops[this.index]['delete'] === 'number') {\n return 'delete';\n } else if (typeof this.ops[this.index].retain === 'number') {\n return 'retain';\n } else {\n return 'insert';\n }\n }\n return 'retain';\n};\n\n\nmodule.exports = lib;\n","var diff = require('fast-diff');\nvar equal = require('deep-equal');\nvar extend = require('extend');\nvar op = require('./op');\n\n\nvar NULL_CHARACTER = String.fromCharCode(0); // Placeholder char for embed in diff()\n\n\nvar Delta = function (ops) {\n // Assume we are given a well formed ops\n if (Array.isArray(ops)) {\n this.ops = ops;\n } else if (ops != null && Array.isArray(ops.ops)) {\n this.ops = ops.ops;\n } else {\n this.ops = [];\n }\n};\n\n\nDelta.prototype.insert = function (text, attributes) {\n var newOp = {};\n if (text.length === 0) return this;\n newOp.insert = text;\n if (attributes != null && typeof attributes === 'object' && Object.keys(attributes).length > 0) {\n newOp.attributes = attributes;\n }\n return this.push(newOp);\n};\n\nDelta.prototype['delete'] = function (length) {\n if (length <= 0) return this;\n return this.push({ 'delete': length });\n};\n\nDelta.prototype.retain = function (length, attributes) {\n if (length <= 0) return this;\n var newOp = { retain: length };\n if (attributes != null && typeof attributes === 'object' && Object.keys(attributes).length > 0) {\n newOp.attributes = attributes;\n }\n return this.push(newOp);\n};\n\nDelta.prototype.push = function (newOp) {\n var index = this.ops.length;\n var lastOp = this.ops[index - 1];\n newOp = extend(true, {}, newOp);\n if (typeof lastOp === 'object') {\n if (typeof newOp['delete'] === 'number' && typeof lastOp['delete'] === 'number') {\n this.ops[index - 1] = { 'delete': lastOp['delete'] + newOp['delete'] };\n return this;\n }\n // Since it does not matter if we insert before or after deleting at the same index,\n // always prefer to insert first\n if (typeof lastOp['delete'] === 'number' && newOp.insert != null) {\n index -= 1;\n lastOp = this.ops[index - 1];\n if (typeof lastOp !== 'object') {\n this.ops.unshift(newOp);\n return this;\n }\n }\n if (equal(newOp.attributes, lastOp.attributes)) {\n if (typeof newOp.insert === 'string' && typeof lastOp.insert === 'string') {\n this.ops[index - 1] = { insert: lastOp.insert + newOp.insert };\n if (typeof newOp.attributes === 'object') this.ops[index - 1].attributes = newOp.attributes\n return this;\n } else if (typeof newOp.retain === 'number' && typeof lastOp.retain === 'number') {\n this.ops[index - 1] = { retain: lastOp.retain + newOp.retain };\n if (typeof newOp.attributes === 'object') this.ops[index - 1].attributes = newOp.attributes\n return this;\n }\n }\n }\n if (index === this.ops.length) {\n this.ops.push(newOp);\n } else {\n this.ops.splice(index, 0, newOp);\n }\n return this;\n};\n\nDelta.prototype.chop = function () {\n var lastOp = this.ops[this.ops.length - 1];\n if (lastOp && lastOp.retain && !lastOp.attributes) {\n this.ops.pop();\n }\n return this;\n};\n\nDelta.prototype.filter = function (predicate) {\n return this.ops.filter(predicate);\n};\n\nDelta.prototype.forEach = function (predicate) {\n this.ops.forEach(predicate);\n};\n\nDelta.prototype.map = function (predicate) {\n return this.ops.map(predicate);\n};\n\nDelta.prototype.partition = function (predicate) {\n var passed = [], failed = [];\n this.forEach(function(op) {\n var target = predicate(op) ? passed : failed;\n target.push(op);\n });\n return [passed, failed];\n};\n\nDelta.prototype.reduce = function (predicate, initial) {\n return this.ops.reduce(predicate, initial);\n};\n\nDelta.prototype.changeLength = function () {\n return this.reduce(function (length, elem) {\n if (elem.insert) {\n return length + op.length(elem);\n } else if (elem.delete) {\n return length - elem.delete;\n }\n return length;\n }, 0);\n};\n\nDelta.prototype.length = function () {\n return this.reduce(function (length, elem) {\n return length + op.length(elem);\n }, 0);\n};\n\nDelta.prototype.slice = function (start, end) {\n start = start || 0;\n if (typeof end !== 'number') end = Infinity;\n var ops = [];\n var iter = op.iterator(this.ops);\n var index = 0;\n while (index < end && iter.hasNext()) {\n var nextOp;\n if (index < start) {\n nextOp = iter.next(start - index);\n } else {\n nextOp = iter.next(end - index);\n ops.push(nextOp);\n }\n index += op.length(nextOp);\n }\n return new Delta(ops);\n};\n\n\nDelta.prototype.compose = function (other) {\n var thisIter = op.iterator(this.ops);\n var otherIter = op.iterator(other.ops);\n var delta = new Delta();\n while (thisIter.hasNext() || otherIter.hasNext()) {\n if (otherIter.peekType() === 'insert') {\n delta.push(otherIter.next());\n } else if (thisIter.peekType() === 'delete') {\n delta.push(thisIter.next());\n } else {\n var length = Math.min(thisIter.peekLength(), otherIter.peekLength());\n var thisOp = thisIter.next(length);\n var otherOp = otherIter.next(length);\n if (typeof otherOp.retain === 'number') {\n var newOp = {};\n if (typeof thisOp.retain === 'number') {\n newOp.retain = length;\n } else {\n newOp.insert = thisOp.insert;\n }\n // Preserve null when composing with a retain, otherwise remove it for inserts\n var attributes = op.attributes.compose(thisOp.attributes, otherOp.attributes, typeof thisOp.retain === 'number');\n if (attributes) newOp.attributes = attributes;\n delta.push(newOp);\n // Other op should be delete, we could be an insert or retain\n // Insert + delete cancels out\n } else if (typeof otherOp['delete'] === 'number' && typeof thisOp.retain === 'number') {\n delta.push(otherOp);\n }\n }\n }\n return delta.chop();\n};\n\nDelta.prototype.concat = function (other) {\n var delta = new Delta(this.ops.slice());\n if (other.ops.length > 0) {\n delta.push(other.ops[0]);\n delta.ops = delta.ops.concat(other.ops.slice(1));\n }\n return delta;\n};\n\nDelta.prototype.diff = function (other, index) {\n if (this.ops === other.ops) {\n return new Delta();\n }\n var strings = [this, other].map(function (delta) {\n return delta.map(function (op) {\n if (op.insert != null) {\n return typeof op.insert === 'string' ? op.insert : NULL_CHARACTER;\n }\n var prep = (delta === other) ? 'on' : 'with';\n throw new Error('diff() called ' + prep + ' non-document');\n }).join('');\n });\n var delta = new Delta();\n var diffResult = diff(strings[0], strings[1], index);\n var thisIter = op.iterator(this.ops);\n var otherIter = op.iterator(other.ops);\n diffResult.forEach(function (component) {\n var length = component[1].length;\n while (length > 0) {\n var opLength = 0;\n switch (component[0]) {\n case diff.INSERT:\n opLength = Math.min(otherIter.peekLength(), length);\n delta.push(otherIter.next(opLength));\n break;\n case diff.DELETE:\n opLength = Math.min(length, thisIter.peekLength());\n thisIter.next(opLength);\n delta['delete'](opLength);\n break;\n case diff.EQUAL:\n opLength = Math.min(thisIter.peekLength(), otherIter.peekLength(), length);\n var thisOp = thisIter.next(opLength);\n var otherOp = otherIter.next(opLength);\n if (equal(thisOp.insert, otherOp.insert)) {\n delta.retain(opLength, op.attributes.diff(thisOp.attributes, otherOp.attributes));\n } else {\n delta.push(otherOp)['delete'](opLength);\n }\n break;\n }\n length -= opLength;\n }\n });\n return delta.chop();\n};\n\nDelta.prototype.eachLine = function (predicate, newline) {\n newline = newline || '\\n';\n var iter = op.iterator(this.ops);\n var line = new Delta();\n var i = 0;\n while (iter.hasNext()) {\n if (iter.peekType() !== 'insert') return;\n var thisOp = iter.peek();\n var start = op.length(thisOp) - iter.peekLength();\n var index = typeof thisOp.insert === 'string' ?\n thisOp.insert.indexOf(newline, start) - start : -1;\n if (index < 0) {\n line.push(iter.next());\n } else if (index > 0) {\n line.push(iter.next(index));\n } else {\n if (predicate(line, iter.next(1).attributes || {}, i) === false) {\n return;\n }\n i += 1;\n line = new Delta();\n }\n }\n if (line.length() > 0) {\n predicate(line, {}, i);\n }\n};\n\nDelta.prototype.transform = function (other, priority) {\n priority = !!priority;\n if (typeof other === 'number') {\n return this.transformPosition(other, priority);\n }\n var thisIter = op.iterator(this.ops);\n var otherIter = op.iterator(other.ops);\n var delta = new Delta();\n while (thisIter.hasNext() || otherIter.hasNext()) {\n if (thisIter.peekType() === 'insert' && (priority || otherIter.peekType() !== 'insert')) {\n delta.retain(op.length(thisIter.next()));\n } else if (otherIter.peekType() === 'insert') {\n delta.push(otherIter.next());\n } else {\n var length = Math.min(thisIter.peekLength(), otherIter.peekLength());\n var thisOp = thisIter.next(length);\n var otherOp = otherIter.next(length);\n if (thisOp['delete']) {\n // Our delete either makes their delete redundant or removes their retain\n continue;\n } else if (otherOp['delete']) {\n delta.push(otherOp);\n } else {\n // We retain either their retain or insert\n delta.retain(length, op.attributes.transform(thisOp.attributes, otherOp.attributes, priority));\n }\n }\n }\n return delta.chop();\n};\n\nDelta.prototype.transformPosition = function (index, priority) {\n priority = !!priority;\n var thisIter = op.iterator(this.ops);\n var offset = 0;\n while (thisIter.hasNext() && offset <= index) {\n var length = thisIter.peekLength();\n var nextType = thisIter.peekType();\n thisIter.next();\n if (nextType === 'delete') {\n index -= Math.min(length, index - offset);\n continue;\n } else if (nextType === 'insert' && (offset < index || !priority)) {\n index += length;\n }\n offset += length;\n }\n return index;\n};\n\n\nmodule.exports = Delta;\n","export var createIsSameValueZero = function createIsSameValueZero() {\n /**\n * @function isSameValueZero\n *\n * @description\n * are the objects passed strictly equal or both NaN\n *\n * @param {*} objectA the object to compare against\n * @param {*} objectB the object to test\n * @returns {boolean} are the objects equal by the SameValueZero principle\n */\n return function (objectA, objectB) {\n return objectA === objectB || objectA !== objectA && objectB !== objectB;\n };\n};\n\n/**\n * @function toPairs\n *\n * @param {Map|Set} iterable the iterable to convert to [key, value] pairs (entries)\n * @returns {{keys: Array<*>, values: Array<*>}} the [key, value] pairs\n */\nexport var toPairs = function toPairs(iterable) {\n var pairs = { keys: new Array(iterable.size), values: new Array(iterable.size) };\n\n var index = 0;\n\n iterable.forEach(function (value, key) {\n pairs.keys[index] = key;\n pairs.values[index++] = value;\n });\n\n return pairs;\n};\n\n/**\n * @function areIterablesEqual\n *\n * @description\n * determine if the iterables are equivalent in value\n *\n * @param {Map|Set} objectA the object to test\n * @param {Map|Set} objectB the object to test against\n * @param {function} comparator the comparator to determine deep equality\n * @param {boolean} shouldCompareKeys should the keys be tested in the equality comparison\n * @returns {boolean} are the objects equal in value\n */\nexport var areIterablesEqual = function areIterablesEqual(objectA, objectB, comparator, shouldCompareKeys) {\n if (objectA.size !== objectB.size) {\n return false;\n }\n\n var pairsA = toPairs(objectA);\n var pairsB = toPairs(objectB);\n\n return shouldCompareKeys ? comparator(pairsA.keys, pairsB.keys) && comparator(pairsA.values, pairsB.values) : comparator(pairsA.values, pairsB.values);\n};","// utils\nimport { areIterablesEqual, createIsSameValueZero } from './utils';\n\nvar HAS_MAP_SUPPORT = typeof Map === 'function';\nvar HAS_SET_SUPPORT = typeof Set === 'function';\n\nvar isSameValueZero = createIsSameValueZero();\n\nvar createComparator = function createComparator(createIsEqual) {\n var isEqual = typeof createIsEqual === 'function' ? createIsEqual(comparator) : comparator; // eslint-disable-line\n\n /**\n * @function comparator\n *\n * @description\n * compare the value of the two objects and return true if they are equivalent in values\n *\n * @param {*} objectA the object to test against\n * @param {*} objectB the object to test\n * @returns {boolean} are objectA and objectB equivalent in value\n */\n function comparator(objectA, objectB) {\n if (isSameValueZero(objectA, objectB)) {\n return true;\n }\n\n var typeOfA = typeof objectA;\n\n if (typeOfA !== typeof objectB) {\n return false;\n }\n\n if (typeOfA === 'object' && objectA && objectB) {\n var arrayA = Array.isArray(objectA);\n var arrayB = Array.isArray(objectB);\n\n var index = void 0;\n\n if (arrayA || arrayB) {\n if (arrayA !== arrayB || objectA.length !== objectB.length) {\n return false;\n }\n\n for (index = 0; index < objectA.length; index++) {\n if (!isEqual(objectA[index], objectB[index])) {\n return false;\n }\n }\n\n return true;\n }\n\n var dateA = objectA instanceof Date;\n var dateB = objectB instanceof Date;\n\n if (dateA || dateB) {\n return dateA === dateB && isSameValueZero(objectA.getTime(), objectB.getTime());\n }\n\n var regexpA = objectA instanceof RegExp;\n var regexpB = objectB instanceof RegExp;\n\n if (regexpA || regexpB) {\n return regexpA === regexpB && objectA.source === objectB.source && objectA.global === objectB.global && objectA.ignoreCase === objectB.ignoreCase && objectA.multiline === objectB.multiline;\n }\n\n if (HAS_MAP_SUPPORT) {\n var mapA = objectA instanceof Map;\n var mapB = objectB instanceof Map;\n\n if (mapA || mapB) {\n return mapA === mapB && areIterablesEqual(objectA, objectB, comparator, true);\n }\n }\n\n if (HAS_SET_SUPPORT) {\n var setA = objectA instanceof Set;\n var setB = objectB instanceof Set;\n\n if (setA || setB) {\n return setA === setB && areIterablesEqual(objectA, objectB, comparator, false);\n }\n }\n\n var keysA = Object.keys(objectA);\n\n if (keysA.length !== Object.keys(objectB).length) {\n return false;\n }\n\n var key = void 0;\n\n for (index = 0; index < keysA.length; index++) {\n key = keysA[index];\n\n if (!Object.prototype.hasOwnProperty.call(objectB, key) || !isEqual(objectA[key], objectB[key])) {\n return false;\n }\n }\n\n return true;\n }\n\n return false;\n }\n\n return comparator;\n};\n\nexport default createComparator;","// comparator\nimport createComparator from './comparator';\n\n// utils\nimport { createIsSameValueZero } from './utils';\n\nexport var createCustomEqual = createComparator;\n\nexport var deepEqual = createComparator();\nexport var sameValueZeroEqual = createIsSameValueZero();\nexport var shallowEqual = createComparator(createIsSameValueZero);\n\nexport default {\n createCustom: createCustomEqual,\n deep: deepEqual,\n sameValueZero: sameValueZeroEqual,\n shallow: shallowEqual\n};","import EventDispatcher from './eventdispatcher';\nimport Delta from 'quill-delta';\nimport deltaOp from 'quill-delta/lib/op';\nimport { shallowEqual, deepEqual } from 'fast-equals';\n\nconst SOURCE_API = 'api';\nconst SOURCE_USER = 'user';\nconst SOURCE_SILENT = 'silent';\nconst empty = {};\n\nexport default class Editor extends EventDispatcher {\n\n constructor(options = {}) {\n super();\n this.selection = null;\n this.activeFormats = empty;\n setContents(this, options.contents || this.delta().insert('\\n'));\n if (options.modules) options.modules.forEach(module => module(this));\n }\n\n delta(ops) {\n return new Delta(ops);\n }\n\n getContents(from = 0, to = this.length) {\n [ from, to ] = this._normalizeArguments(from, to);\n return this.contents.slice(from, to);\n }\n\n getText(from, to) {\n return this.getContents(from, to)\n .filter(op => typeof op.insert === 'string')\n .map(op => op.insert)\n .join('')\n .slice(0, -1); // remove the trailing newline\n }\n\n getChange(producer) {\n let change = this.delta();\n this.updateContents = singleChange => change = change.compose(singleChange);\n producer();\n delete this.updateContents;\n return change;\n }\n\n setContents(newContents, source, selection) {\n const change = this.contents.diff(newContents);\n return this.updateContents(change, source, selection);\n }\n\n setText(text, source, selection) {\n return this.setContents(this.delta().insert(text + '\\n'), source, selection);\n }\n\n insertText(from, to, text, formats, source, selection) {\n [ from, to, text, formats, source, selection ] =\n this._normalizeArguments(from, to, text, formats, source, selection);\n if (selection == null) selection = from + text.length;\n let change = this.delta().retain(from).delete(to - from);\n const lineFormat = from === to && text.indexOf('\\n') === -1 ? null : this.getLineFormat(from);\n text.split('\\n').forEach((line, i) => {\n if (i) change.insert('\\n', lineFormat);\n line.length && change.insert(line, formats);\n });\n\n change = cleanDelete(this, from, to, change);\n return this.updateContents(change, source, selection);\n }\n\n insertEmbed(from, to, embed, value, source, selection) {\n [ from, to, embed, value, source, selection ] =\n this._normalizeArguments(from, to, embed, value, source, selection);\n if (selection == null) selection = from + 1;\n let change = this.delta().retain(index).delete(to - from).insert({ [embed]: value });\n change = cleanDelete(this, from, to, change);\n return this.updateContents(change, source, selection);\n }\n\n deleteText(from, to, source, selection) {\n [ from, to, source, selection ] = this._normalizeArguments(from, to, source, selection);\n if (selection == null) selection = from;\n let change = this.delta().retain(from).delete(to - from);\n change = cleanDelete(this, from, to, change);\n return this.updateContents(change, source, from);\n }\n\n getLineFormat(from, to) {\n [ from, to ] = this._normalizeArguments(from, to);\n let formats;\n\n this.contents.getLines(from, to).forEach(line => {\n if (!line.attributes) formats = {};\n else if (!formats) formats = { ...line.attributes };\n else formats = combineFormats(formats, line.attributes);\n });\n\n return formats;\n }\n\n getTextFormat(from, to) {\n [ from, to ] = this._normalizeArguments(from, to);\n let formats;\n\n this.contents.getOps(from, to).forEach(({ op }) => {\n if (!op.attributes) formats = {};\n else if (!formats) formats = { ...op.attributes };\n else formats = combineFormats(formats, op.attributes);\n });\n\n if (!formats) formats = {};\n\n if (this.activeFormats !== empty) {\n Object.keys(this.activeFormats).forEach(name => {\n const value = this.activeFormats[name];\n if (value === null) delete formats[name];\n else formats[name] = value;\n });\n }\n\n return formats;\n }\n\n getFormat(from, to) {\n return { ...this.getTextFormat(from, to), ...this.getLineFormat(from, to) };\n }\n\n formatLine(from, to, formats, source) {\n [ from, to, formats, source ] = this._normalizeArguments(from, to, formats, source);\n const change = this.delta();\n\n this.contents.getLines(from, to).forEach(line => {\n if (!change.ops.length) change.retain(line.endIndex - 1);\n else change.retain(line.endIndex - line.startIndex - 1);\n // Clear out old formats on the line\n Object.keys(line.attributes).forEach(name => !formats[name] && (formats[name] = null));\n change.retain(1, formats);\n });\n\n return change.ops.length ? this.updateContents(change, source) : this.contents;\n }\n\n formatText(from, to, formats, source) {\n [ from, to, formats, source ] = this._normalizeArguments(from, to, formats, source);\n if (from === to) {\n if (this.activeFormats === empty) this.activeFormats = {};\n Object.keys(formats).forEach(key => this.activeFormats[key] = formats[key]);\n return;\n }\n Object.keys(formats).forEach(name => formats[name] === false && (formats[name] = null));\n const text = this.getText();\n const change = this.delta().retain(from);\n text.slice(from, to).split('\\n').forEach(line => {\n line.length && change.retain(line.length, formats).retain(1);\n });\n\n return this.updateContents(change, source);\n }\n\n toggleLineFormat(from, to, format, source) {\n [ from, to, format, source ] = this._normalizeArguments(from, to, format, source);\n const existing = this.getLineFormat(from, to);\n if (deepEqual(existing, format)) {\n Object.keys(format).forEach(key => format[key] = null);\n }\n return this.formatLine(from, to, format, source);\n }\n\n toggleTextFormat(from, to, format, source) {\n [ from, to, format, source ] = this._normalizeArguments(from, to, format, source);\n const existing = this.getTextFormat(from, to);\n const isSame = Object.keys(format).every(key => format[key] === existing[key]);\n if (isSame) {\n Object.keys(format).forEach(key => format[key] = null);\n }\n return this.formatText(from, to, format, source);\n }\n\n removeFormat(from, to, source) {\n [ from, to, source ] = this._normalizeArguments(from, to, source);\n const formats = {};\n\n this.contents.getOps(from, to).forEach(({ op }) => {\n op.attributes && Object.keys(op.attributes).forEach(key => formats[key] = null);\n });\n\n let change = this.delta().retain(from).retain(to - from, formats);\n\n // If the last block was not captured be sure to clear that too\n this.contents.getLines(from, to).forEach(line => {\n const formats = {};\n Object.keys(line.attributes).forEach(key => formats[key] = null);\n change = change.compose(this.delta().retain(line.endIndex - 1).retain(1, formats));\n });\n\n return this.updateContents(change, source);\n }\n\n updateContents(change, source = SOURCE_USER, selection) {\n const oldContents = this.contents;\n const contents = normalizeContents(oldContents.compose(change));\n const length = contents.length();\n const oldSelection = this.selection;\n if (!selection) selection = this.selection ? this.selection.map(i => change.transform(i)) : oldSelection;\n selection = selection && this.getSelectedRange(selection, length - 1);\n\n const changeEvent = { contents, oldContents, change, selection, oldSelection, source };\n const selectionEvent = shallowEqual(oldSelection, selection) ? null : { selection, oldSelection, source };\n\n if (change.ops.length && this.fire('text-changing', changeEvent)) {\n setContents(this, contents);\n if (selection) this.selection = selection;\n\n if (source !== SOURCE_SILENT) {\n this.fire('text-change', changeEvent);\n if (selectionEvent) this.fire('selection-change', selectionEvent);\n }\n this.fire('editor-change', changeEvent);\n }\n\n return this.contents;\n }\n\n setSelection(selection, source = SOURCE_USER) {\n const oldSelection = this.selection;\n selection = this.getSelectedRange(selection);\n this.activeFormats = empty;\n\n if (shallowEqual(oldSelection, selection)) return false;\n\n this.selection = selection;\n const event = { selection, oldSelection, source };\n\n if (source !== SOURCE_SILENT) this.fire('selection-change', event);\n this.fire('editor-change', event);\n return true;\n }\n\n getSelectedRange(range = this.selection, max = this.length - 1) {\n if (range == null) return range;\n if (typeof range === 'number') range = [ range, range ];\n if (range[0] > range[1]) [range[0], range[1]] = [range[1], range[0]];\n return range.map(index => Math.max(0, Math.min(max, index)));\n }\n\n /**\n * Normalizes range values to a proper range if it is not already. A range is a `from` and a `to` index, e.g. 0, 4.\n * This will ensure the lower index is first. Example usage:\n * editor._normalizeArguments(5); // [5, 5]\n * editor._normalizeArguments(-4, 100); // for a doc with length 10, [0, 10]\n * editor._normalizeArguments(25, 18); // [18, 25]\n * editor._normalizeArguments([12, 13]); // [12, 13]\n * editor._normalizeArguments(5, { bold: true }); // [5, 5, { bold: true }]\n */\n _normalizeArguments(from, to, ...rest) {\n if (Array.isArray(from)) {\n if (to !== undefined || rest.length) rest.unshift(to);\n [from, to] = from;\n if (to === undefined) to = from;\n } else if (typeof from !== 'number') {\n if (to !== undefined || rest.length) rest.unshift(to);\n if (from !== undefined || rest.length) rest.unshift(from);\n from = to = 0;\n } else if (typeof to !== 'number') {\n if (to !== undefined || rest.length) rest.unshift(to);\n to = from;\n }\n from = Math.max(0, Math.min(this.length, ~~from));\n to = Math.max(0, Math.min(this.length, ~~to));\n if (from > to) {\n [from, to] = [to, from];\n }\n return [from, to].concat(rest);\n }\n}\n\nfunction cleanDelete(editor, from, to, change) {\n if (from !== to) {\n const lineFormat = editor.getLineFormat(from);\n if (!deepEqual(lineFormat, editor.getLineFormat(to))) {\n const lineChange = editor.getChange(() => editor.formatLine(to, lineFormat))\n change = change.compose(change.transform(lineChange));\n }\n }\n return change;\n}\n\nfunction normalizeContents(contents) {\n if (!contents.ops.length || contents.ops[contents.ops.length - 1].insert.slice(-1) !== '\\n') contents.insert('\\n');\n return contents;\n}\n\nfunction setContents(editor, contents) {\n normalizeContents(contents);\n contents.push = function() { return this; } // freeze from modification\n editor.contents = contents;\n editor.length = contents.length();\n}\n\nfunction combineFormats(formats, combined) {\n return Object.keys(combined).reduce(function(merged, name) {\n if (formats[name] == null) return merged;\n if (combined[name] === formats[name]) {\n merged[name] = combined[name];\n } else if (Array.isArray(combined[name])) {\n if (combined[name].indexOf(formats[name]) < 0) {\n merged[name] = combined[name].concat([formats[name]]);\n }\n } else {\n merged[name] = [combined[name], formats[name]];\n }\n return merged;\n }, {});\n}\n\nDelta.prototype.getLines = function(from, to, predicate) {\n let startIndex = 0;\n const lines = [];\n this.eachLine((contents, attributes, number) => {\n if (startIndex >= to) return false;\n const endIndex = startIndex + contents.length() + 1;\n if (endIndex > from || (from === to && endIndex === to)) {\n lines.push({ contents, attributes, number, startIndex, endIndex });\n }\n startIndex = endIndex;\n });\n return lines;\n}\n\nDelta.prototype.getLine = function(at) {\n return this.getLines(at, at)[0];\n}\n\nDelta.prototype.getOps = function(from, to) {\n let startIndex = 0;\n const ops = [];\n this.ops.some(op => {\n if (startIndex >= to) return true;\n const endIndex = startIndex + deltaOp.length(op);\n if (endIndex > from || (from === to && endIndex === to)) {\n ops.push({ op, startIndex, endIndex });\n }\n startIndex = endIndex;\n });\n return ops;\n}\n\nDelta.prototype.getOp = function(from) {\n return this.getOps(from, from)[0];\n}\n","export function h(name, attributes) {\n var rest = []\n var children = []\n var length = arguments.length\n\n while (length-- > 2) rest.push(arguments[length])\n\n while (rest.length) {\n var node = rest.pop()\n if (node && node.pop) {\n for (length = node.length; length--; ) {\n rest.push(node[length])\n }\n } else if (node != null && node !== true && node !== false) {\n children.push(node)\n }\n }\n\n return typeof name === \"function\"\n ? name(attributes || {}, children) // h(Component)\n : {\n nodeName: name,\n attributes: attributes || {},\n children: children,\n key: attributes && attributes.key\n }\n}\n","export function recycleElement(element, map) {\n return {\n nodeName: element.nodeName.toLowerCase(),\n attributes: {},\n children: map.call(element.childNodes, function(element) {\n return element.nodeType === 3 // Node.TEXT_NODE\n ? element.nodeValue\n : recycleElement(element, map)\n })\n }\n}\n","export function clone(target, source) {\n var obj = {}\n\n for (var i in target) obj[i] = target[i]\n for (var i in source) obj[i] = source[i]\n\n return obj\n}\n","export function eventListener(event) {\n return event.currentTarget.events[event.type](event)\n}\n","import { clone } from \"./clone\"\nimport { eventListener } from \"./eventListener\"\n\nexport function updateAttribute(element, name, value, oldValue, isSVG) {\n if (name === \"key\") {\n } else if (name === \"style\") {\n for (var i in clone(oldValue, value)) {\n var style = value == null || value[i] == null ? \"\" : value[i]\n if (i[0] === \"-\") {\n element[name].setProperty(i, style)\n } else {\n element[name][i] = style\n }\n }\n } else {\n if (name[0] === \"o\" && name[1] === \"n\") {\n name = name.slice(2)\n\n if (element.events) {\n if (!oldValue) oldValue = element.events[name]\n } else {\n element.events = {}\n }\n\n element.events[name] = value\n\n if (value) {\n if (!oldValue) {\n element.addEventListener(name, eventListener)\n }\n } else {\n element.removeEventListener(name, eventListener)\n }\n } else if (name in element && name !== \"list\" && !isSVG) {\n element[name] = value == null ? \"\" : value\n } else if (value != null && value !== false) {\n element.setAttribute(name, value)\n }\n\n if (value == null || value === false) {\n element.removeAttribute(name)\n }\n }\n}\n","import { updateAttribute } from \"./updateAttribute\"\n\nexport function createElement(node, lifecycle, isSVG) {\n var element =\n typeof node === \"string\" || typeof node === \"number\"\n ? document.createTextNode(node)\n : (isSVG = isSVG || node.nodeName === \"svg\")\n ? document.createElementNS(\"http://www.w3.org/2000/svg\", node.nodeName)\n : document.createElement(node.nodeName)\n\n var attributes = node.attributes\n if (attributes) {\n if (attributes.oncreate) {\n lifecycle.push(function() {\n attributes.oncreate(element)\n })\n }\n\n for (var i = 0; i < node.children.length; i++) {\n element.appendChild(createElement(node.children[i], lifecycle, isSVG))\n }\n\n for (var name in attributes) {\n updateAttribute(element, name, attributes[name], null, isSVG)\n }\n }\n\n return element\n}\n","export function removeChildren(element, node) {\n var attributes = node.attributes\n if (attributes) {\n for (var i = 0; i < node.children.length; i++) {\n removeChildren(element.childNodes[i], node.children[i])\n }\n\n if (attributes.ondestroy) {\n attributes.ondestroy(element)\n }\n }\n return element\n}\n","import { removeChildren } from \"./removeChildren\"\n\nexport function removeElement(parent, element, node) {\n function done() {\n parent.removeChild(removeChildren(element, node))\n }\n\n var cb = node.attributes && node.attributes.onremove\n if (cb) {\n cb(element, done)\n } else {\n done()\n }\n}\n","import { clone } from \"./clone\"\nimport { updateAttribute } from \"./updateAttribute\"\n\nexport function updateElement(\n element,\n oldAttributes,\n attributes,\n lifecycle,\n isRecycling,\n isSVG\n) {\n for (var name in clone(oldAttributes, attributes)) {\n if (\n attributes[name] !==\n (name === \"value\" || name === \"checked\"\n ? element[name]\n : oldAttributes[name])\n ) {\n updateAttribute(\n element,\n name,\n attributes[name],\n oldAttributes[name],\n isSVG\n )\n }\n }\n\n var cb = isRecycling ? attributes.oncreate : attributes.onupdate\n if (cb) {\n lifecycle.push(function() {\n cb(element, oldAttributes)\n })\n }\n}\n","export function getKey(node) {\n return node ? node.key : null\n}\n","import { createElement } from \"./createElement\"\nimport { removeElement } from \"./removeElement\"\nimport { updateElement } from \"./updateElement\"\nimport { getKey } from \"./getKey\"\n\nexport function patchElement(\n parent,\n element,\n oldNode,\n node,\n lifecycle,\n isRecycling,\n isSVG\n) {\n if (node === oldNode) {\n } else if (oldNode == null || oldNode.nodeName !== node.nodeName) {\n var newElement = createElement(node, lifecycle, isSVG)\n if (parent) {\n parent.insertBefore(newElement, element)\n if (oldNode != null) {\n removeElement(parent, element, oldNode)\n }\n }\n element = newElement\n } else if (oldNode.nodeName == null) {\n element.nodeValue = node\n } else {\n updateElement(\n element,\n oldNode.attributes,\n node.attributes,\n lifecycle,\n isRecycling,\n (isSVG = isSVG || node.nodeName === \"svg\")\n )\n\n var oldKeyed = {}\n var newKeyed = {}\n var oldElements = []\n var oldChildren = oldNode.children\n var children = node.children\n\n for (var i = 0; i < oldChildren.length; i++) {\n oldElements[i] = element.childNodes[i]\n\n var oldKey = getKey(oldChildren[i])\n if (oldKey != null) {\n oldKeyed[oldKey] = [oldElements[i], oldChildren[i]]\n }\n }\n\n var i = 0\n var k = 0\n\n while (k < children.length) {\n var oldKey = getKey(oldChildren[i])\n var newKey = getKey(children[k])\n\n if (newKeyed[oldKey]) {\n i++\n continue\n }\n\n if (newKey == null || isRecycling) {\n if (oldKey == null) {\n patchElement(\n element,\n oldElements[i],\n oldChildren[i],\n children[k],\n lifecycle,\n isRecycling,\n isSVG\n )\n k++\n }\n i++\n } else {\n var keyedNode = oldKeyed[newKey] || []\n\n if (oldKey === newKey) {\n patchElement(\n element,\n keyedNode[0],\n keyedNode[1],\n children[k],\n lifecycle,\n isRecycling,\n isSVG\n )\n i++\n } else if (keyedNode[0]) {\n patchElement(\n element,\n element.insertBefore(keyedNode[0], oldElements[i]),\n keyedNode[1],\n children[k],\n lifecycle,\n isRecycling,\n isSVG\n )\n } else {\n patchElement(\n element,\n oldElements[i],\n null,\n children[k],\n lifecycle,\n isRecycling,\n isSVG\n )\n }\n\n newKeyed[newKey] = children[k]\n k++\n }\n }\n\n while (i < oldChildren.length) {\n if (getKey(oldChildren[i]) == null) {\n removeElement(element, oldElements[i], oldChildren[i])\n }\n i++\n }\n\n for (var i in oldKeyed) {\n if (!newKeyed[i]) {\n removeElement(element, oldKeyed[i][0], oldKeyed[i][1])\n }\n }\n }\n return element\n}\n","import { recycleElement } from \"./recycleElement\"\nimport { patchElement } from \"./patchElement\"\n\nexport function patch(node, element) {\n var lifecycle = []\n\n element = element\n ? patchElement(\n element.parentNode,\n element,\n element.node == null ? recycleElement(element, [].map) : element.node,\n node,\n lifecycle,\n element.node == null // isRecycling\n )\n : patchElement(null, null, null, node, lifecycle)\n\n element.node = node\n\n while (lifecycle.length) lifecycle.pop()()\n\n return element\n}\n","import { h } from 'ultradom';\n\nexport const paragraph = {\n name: 'paragraph',\n selector: 'p',\n vdom: children =>

{children}

,\n};\n\n\nexport const header = {\n name: 'header',\n selector: 'h1, h2, h3, h4, h5, h6',\n attr: node => ({ header: parseInt(node.nodeName.replace('H', '')) }),\n vdom: (children, attr) => {\n const H = `h${attr.header}`;\n return {children};\n },\n};\n\n\nexport const list = {\n name: 'list',\n selector: 'ul > li, ol > li',\n optimize: true,\n attr: node => {\n let indent = -1, parent = node.parentNode;\n const list = parent.nodeName === 'OL' ? 'ordered' : 'bullet';\n while (parent) {\n if (/^UL|OL$/.test(parent.nodeName)) indent++;\n else if (parent.nodeName !== 'LI') break;\n parent = parent.parentNode;\n }\n return indent ? { list, indent } : { list };\n },\n vdom: lists => {\n const topLevelChildren = [];\n let levels = [];\n // e.g. levels = [ul, li, ul, li]\n\n lists.forEach(([children, attr]) => {\n const List = attr.list === 'ordered' ? 'ol' : 'ul';\n const index = (attr.indent || 0) * 2;\n const item =
  • {children}
  • ;\n let list = levels[index];\n if (list && list.nodeName === List) {\n list.children.push(item);\n } else {\n list = {item};\n const childrenArray = index ? levels[index - 1].children : topLevelChildren;\n childrenArray.push(list);\n levels[index] = list;\n }\n levels[index + 1] = item;\n levels.length = index + 2;\n });\n\n return topLevelChildren;\n },\n};\n\n\nexport const container = {\n name: 'container',\n selector: 'div',\n vdom: (children, attr) =>
    \n {children && children.length && children || paragraph.vdom()}\n
    ,\n};\n\n\nexport const bold = {\n name: 'bold',\n selector: 'strong, b',\n vdom: children => {children},\n};\n\n\nexport const italics = {\n name: 'italics',\n selector: 'em, i',\n vdom: children => {children},\n};\n\n\nexport const link = {\n name: 'link',\n selector: 'a[href]',\n attr: node => node.href,\n vdom: (children, attr) => {children},\n};\n\n\nexport const image = {\n name: 'image',\n selector: 'img',\n attr: node => node.src,\n vdom: (children, attr) => \n};\n\n\nexport default {\n blocks: [ paragraph, header, list, container ],\n markups: [ bold, italics, link ],\n embeds: [ image ],\n};\n","const indexOf = [].indexOf;\n\n// Get the range (a tuple of indexes) for this view from the browser selection\nexport function getSelection(view) {\n const root = view.root;\n const selection = root.ownerDocument.defaultView.getSelection();\n\n if (!root.contains(selection.anchorNode)) {\n return null;\n } else {\n const anchorIndex = getNodeIndex(view, selection.anchorNode);\n const focusIndex = selection.anchorNode === selection.focusNode ?\n anchorIndex : getNodeIndex(view, selection.focusNode);\n\n return [\n anchorIndex + selection.anchorOffset,\n focusIndex + selection.focusOffset,\n ];\n }\n}\n\n// Set the browser selection to the range (a tuple of indexes) of this view\nexport function setSelection(view, range) {\n const root = view.root;\n const selection = root.ownerDocument.defaultView.getSelection();\n const hasFocus = root.contains(root.ownerDocument.activeElement);\n\n if (range == null) {\n if (hasFocus) {\n root.blur();\n selection.setBaseAndExtent(null, 0, null, 0);\n }\n } else {\n const [ anchorNode, anchorOffset, focusNode, focusOffset ] = getNodesForRange(view, range);\n selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);\n if (!hasFocus) root.focus();\n }\n}\n\n// Get a browser range object for the given editor range tuple\nexport function getBrowserRange(view, range) {\n if (range[0] > range[1]) range = [ range[1], range[0] ];\n const [ anchorNode, anchorOffset, focusNode, focusOffset ] = getNodesForRange(view, range);\n const browserRange = document.createRange();\n browserRange.setStart(anchorNode, anchorOffset);\n browserRange.setEnd(focusNode, focusOffset);\n return browserRange;\n}\n\n\n// Get the browser nodes and offsets for the range (a tuple of indexes) of this view\nexport function getNodesForRange(view, range) {\n if (range == null) {\n return [ null, 0, null, 0 ];\n } else {\n const [ anchorNode, anchorOffset ] = getNodeAndOffset(view, range[0]);\n const [ focusNode, focusOffset ] = range[0] === range[1] ?\n [ anchorNode, anchorOffset ] : getNodeAndOffset(view, range[1]);\n\n return [ anchorNode, anchorOffset, focusNode, focusOffset ];\n }\n}\n\nexport function getNodeAndOffset(view, index) {\n const root = view.root;\n const blocksSelector = view.dom.blocks.selector;\n const walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, {\n acceptNode: node => {\n return (node.nodeType === Node.TEXT_NODE || node.offsetParent) &&\n NodeFilter.FILTER_ACCEPT ||\n NodeFilter.FILTER_REJECT;\n }\n });\n\n let count = 0, node, firstBlockSeen = false;\n walker.currentNode = root;\n while ((node = walker.nextNode())) {\n if (node.nodeType === Node.TEXT_NODE) {\n const size = node.nodeValue.length\n if (index <= count + size) return [ node, index - count ];\n count += size;\n } else if (node.matches(blocksSelector)) {\n if (firstBlockSeen) count += 1;\n else firstBlockSeen = true;\n\n // If the selection lands at the beginning of a block, and the first node isn't a text node, place the selection\n if (count === index && (!node.firstChild || node.firstChild.nodeType !== Node.TEXT_NODE)) {\n return [ node, 0 ];\n }\n } else if (node.nodeName === 'BR' && node.parentNode.lastChild !== node) {\n count += 1;\n // If the selection lands after this br, and the next node isn't a text node, place the selection\n if (count === index && (!node.nextSibling || node.nextSibling.nodeType !== Node.TEXT_NODE)) {\n return [ node.parentNode, indexOf.call(node.parentNode.childNodes, node) + 1 ];\n }\n }\n }\n return [ null, 0 ];\n}\n\n// Get the index the node starts at in the content\nexport function getNodeIndex(view, node) {\n const root = view.root;\n const blocksSelector = view.dom.blocks.selector;\n const walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, {\n acceptNode: node => {\n return (node.nodeType === Node.TEXT_NODE || node.offsetParent) &&\n NodeFilter.FILTER_ACCEPT ||\n NodeFilter.FILTER_REJECT;\n }\n });\n\n walker.currentNode = node;\n let index = node.nodeType === Node.ELEMENT_NODE ? 0 : -1;\n while ((node = walker.previousNode())) {\n if (node.nodeType === Node.TEXT_NODE) index += node.nodeValue.length;\n else if (node.nodeName === 'BR' && node.parentNode.lastChild !== node) index++;\n else if (node !== root && node.matches(blocksSelector)) index++;\n }\n return index;\n}\n\n","/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n","import Delta from 'quill-delta';\nimport { deepEqual } from 'fast-equals';\nimport escape from 'escape-html';\nimport { h } from 'ultradom';\n\nconst br =
    ;\nconst voidElements = {\n area: true, base: true, br: true, col: true, embed: true, hr: true, img: true, input: true,\n link: true, meta: true, param: true, source: true, track: true, wbr: true\n};\n\nexport class DOM {\n constructor(types) {\n this.blocks = new DOMTypes();\n this.markups = new DOMTypes();\n if (types && types.blocks) types.blocks.forEach(block => this.blocks.add(block));\n if (types && types.markups) types.markups.forEach(markup => this.markups.add(markup));\n }\n}\n\n\nexport class DOMTypes {\n constructor() {\n this.selector = '';\n this.domTypes = {};\n this.array = [];\n this.priorities = {};\n }\n\n add(definition, index) {\n if (!definition.name || !definition.selector || !definition.vdom) {\n throw new Error('DOMType definitions must include a name, selector, and vdom function');\n }\n if (this.domTypes[definition.name]) this.remove(definition.name);\n this.selector += (this.selector ? ', ' : '') + definition.selector;\n this.domTypes[definition.name] = definition;\n if (typeof index !== 'number') {\n this.priorities[name] = this.array.length;\n this.array.push(definition);\n } else {\n this.array.splice(i, 0, definition);\n this.array.forEach(({ name }, i) => this.priorities[name] = i);\n }\n }\n\n remove(name) {\n if (!this.domTypes[name]) return;\n delete this.domTypes[name];\n this.array = this.array.filter(domType => domType.name === name);\n this.array.forEach(({ name }, i) => this.priorities[name] = i);\n this.selector = this.array.map(type => type.selector).join(', ');\n }\n\n get(name) {\n return this.domTypes[name];\n }\n\n priority(name) {\n return this.priorities[name];\n }\n\n getDefault() {\n return this.array[0];\n }\n\n matches(node) {\n return node.matches(this.selector);\n }\n\n find(nodeOrAttr) {\n if (nodeOrAttr instanceof Node) {\n return this.array.find(domType => nodeOrAttr.matches(domType.selector));\n } else if (nodeOrAttr && typeof nodeOrAttr === 'object') {\n let domType;\n Object.keys(nodeOrAttr).some(name => domType = this.get(name));\n return domType;\n }\n }\n}\n\n\n\nexport function deltaToVdom(view, delta) {\n const { blocks, markups } = view.dom;\n const blockData = [];\n\n delta.eachLine((line, attr) => {\n let inlineChildren = [];\n\n // Collect block children\n line.forEach(op => {\n let children = [];\n op.insert.split(/\\r/).forEach((child, i) => {\n if (i !== 0) children.push(br);\n child && children.push(child.replace(/ /g, '\\xA0 ').replace(/ +$/, '\\xA0'));\n });\n\n if (op.attributes) {\n // Sort them by the order found in markups and be efficient\n Object.keys(op.attributes).sort((a, b) => markups.priority(b) - markups.priority(a)).forEach(name => {\n const markup = markups.get(name);\n if (markup) {\n const node = markup.vdom.call(view.dom, children, op.attributes);\n node.markup = markup;\n children = [ node ];\n }\n });\n }\n inlineChildren = inlineChildren.concat(children);\n });\n\n // Merge markups to optimize\n inlineChildren = mergeChildren(inlineChildren);\n if (!inlineChildren.length || inlineChildren[inlineChildren.length - 1] === br) {\n inlineChildren.push(br);\n }\n\n let block = blocks.find(attr);\n if (!block) block = blocks.getDefault();\n\n blockData.push([ block, inlineChildren, attr ]);\n });\n\n // If a block has optimize=true on it, vdom will be called with all sibling nodes of the same block\n let blockChildren = [], prevBlock;\n let collect = [];\n blockData.forEach((data, i) => {\n const [ block, children, attr ] = data;\n if (block.optimize) {\n collect.push([ children, attr ]);\n const next = blockData[i + 1];\n if (!next || next[0] !== block) {\n blockChildren = blockChildren.concat(block.vdom.call(view.dom, collect));\n collect = [];\n }\n } else {\n blockChildren.push(block.vdom.call(view.dom, children, attr));\n }\n });\n\n return blocks.get('container').vdom.call(view.dom, blockChildren, view);\n}\n\n\nexport function deltaFromDom(view, root = view.root) {\n const { blocks, markups } = view.dom;\n\n const walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, {\n acceptNode: node => {\n return (node.nodeType === Node.TEXT_NODE || node.offsetParent) &&\n NodeFilter.FILTER_ACCEPT ||\n NodeFilter.FILTER_REJECT;\n }\n });\n const delta = new Delta();\n let currentBlock, firstBlockSeen = false, node;\n\n walker.currentNode = root;\n\n while ((node = walker.nextNode())) {\n const isBr = node.nodeName === 'BR' && node.parentNode.lastChild !== node;\n\n if (node.nodeType === Node.TEXT_NODE || isBr) {\n const text = isBr ? '\\r' : node.nodeValue.replace(/\\xA0/g, ' ');\n let parent = node.parentNode, attr = {};\n\n while (parent && !blocks.matches(parent) && parent !== root) {\n if (markups.matches(parent)) {\n const markup = markups.find(parent);\n attr[markup.name] = markup.attr ? markup.attr(parent) : true;\n }\n parent = parent.parentNode;\n }\n delta.insert(text, attr);\n } else if (blocks.matches(node)) {\n if (firstBlockSeen) delta.insert('\\n', currentBlock);\n else firstBlockSeen = true;\n const block = blocks.find(node);\n if (block !== blocks.getDefault()) {\n currentBlock = block.attr ? block.attr(node) : { [block.name]: true };\n } else {\n currentBlock = undefined;\n }\n }\n }\n delta.insert('\\n', currentBlock);\n return delta;\n}\n\n\nexport function deltaToHTML(view, delta) {\n return childrenToHTML(deltaToVdom(view, delta).children);\n}\n\n\nexport function deltaFromHTML(view, html) {\n const template = document.createElement('template');\n template.innerHTML = '
    ' + html + '
    ';\n const container = (template.content || template).firstChild;\n return deltaFromDom(view, container);\n}\n\n\n// Join adjacent blocks\nfunction mergeChildren(oldChildren) {\n const children = [];\n oldChildren.forEach((next, i) => {\n const prev = children[children.length - 1];\n\n if (prev && typeof prev !== 'string' && typeof next !== 'string' && prev.markup &&\n prev.markup === next.markup && deepEqual(prev.attributes, next.attributes))\n {\n prev.children = prev.children.concat(next.children);\n } else {\n children.push(next);\n }\n });\n return children;\n}\n\nfunction elementToHTML(node) {\n const attr = Object.keys(node.attributes)\n .reduce((attr, name) =>\n `${attr} ${escape(name)}=\"${escape(node.attributes[name])}\"`, '');\n const children = childrenToHTML(node.children);\n const closingTag = children || !voidElements[node.nodeName] ? `` : '';\n return `<${node.nodeName}${attr}>${children}${closingTag}`;\n}\n\nfunction childrenToHTML(children) {\n if (!children || !children.length) return '';\n return children.reduce((html, child) => html + (child.nodeName ? elementToHTML(child) : escape(child)), '');\n}\n","/* global define, KeyboardEvent, module */\n\n(function () {\n\n var keyboardeventKeyPolyfill = {\n polyfill: polyfill,\n keys: {\n 3: 'Cancel',\n 6: 'Help',\n 8: 'Backspace',\n 9: 'Tab',\n 12: 'Clear',\n 13: 'Enter',\n 16: 'Shift',\n 17: 'Control',\n 18: 'Alt',\n 19: 'Pause',\n 20: 'CapsLock',\n 27: 'Escape',\n 28: 'Convert',\n 29: 'NonConvert',\n 30: 'Accept',\n 31: 'ModeChange',\n 32: ' ',\n 33: 'PageUp',\n 34: 'PageDown',\n 35: 'End',\n 36: 'Home',\n 37: 'ArrowLeft',\n 38: 'ArrowUp',\n 39: 'ArrowRight',\n 40: 'ArrowDown',\n 41: 'Select',\n 42: 'Print',\n 43: 'Execute',\n 44: 'PrintScreen',\n 45: 'Insert',\n 46: 'Delete',\n 48: ['0', ')'],\n 49: ['1', '!'],\n 50: ['2', '@'],\n 51: ['3', '#'],\n 52: ['4', '$'],\n 53: ['5', '%'],\n 54: ['6', '^'],\n 55: ['7', '&'],\n 56: ['8', '*'],\n 57: ['9', '('],\n 91: 'OS',\n 93: 'ContextMenu',\n 144: 'NumLock',\n 145: 'ScrollLock',\n 181: 'VolumeMute',\n 182: 'VolumeDown',\n 183: 'VolumeUp',\n 186: [';', ':'],\n 187: ['=', '+'],\n 188: [',', '<'],\n 189: ['-', '_'],\n 190: ['.', '>'],\n 191: ['/', '?'],\n 192: ['`', '~'],\n 219: ['[', '{'],\n 220: ['\\\\', '|'],\n 221: [']', '}'],\n 222: [\"'\", '\"'],\n 224: 'Meta',\n 225: 'AltGraph',\n 246: 'Attn',\n 247: 'CrSel',\n 248: 'ExSel',\n 249: 'EraseEof',\n 250: 'Play',\n 251: 'ZoomOut'\n }\n };\n\n // Function keys (F1-24).\n var i;\n for (i = 1; i < 25; i++) {\n keyboardeventKeyPolyfill.keys[111 + i] = 'F' + i;\n }\n\n // Printable ASCII characters.\n var letter = '';\n for (i = 65; i < 91; i++) {\n letter = String.fromCharCode(i);\n keyboardeventKeyPolyfill.keys[i] = [letter.toLowerCase(), letter.toUpperCase()];\n }\n\n function polyfill () {\n if (!('KeyboardEvent' in window) ||\n 'key' in KeyboardEvent.prototype) {\n return false;\n }\n\n // Polyfill `key` on `KeyboardEvent`.\n var proto = {\n get: function (x) {\n var key = keyboardeventKeyPolyfill.keys[this.which || this.keyCode];\n\n if (Array.isArray(key)) {\n key = key[+this.shiftKey];\n }\n\n return key;\n }\n };\n Object.defineProperty(KeyboardEvent.prototype, 'key', proto);\n return proto;\n }\n\n if (typeof define === 'function' && define.amd) {\n define('keyboardevent-key-polyfill', keyboardeventKeyPolyfill);\n } else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {\n module.exports = keyboardeventKeyPolyfill;\n } else if (window) {\n window.keyboardeventKeyPolyfill = keyboardeventKeyPolyfill;\n }\n\n})();\n","require('keyboardevent-key-polyfill').polyfill();\n\nvar modifierKeys = {\n Control: true,\n Meta: true,\n Shift: true,\n Alt: true\n};\n\n\n/**\n * Returns the textual representation of a shortcut given a keyboard event. Examples of shortcuts:\n * Cmd+L\n * Cmd+Shift+M\n * Ctrl+O\n * Backspace\n * T\n * Right\n * Shift+Down\n * Shift+F1\n *\n */\nexports.fromEvent = function(event) {\n var shortcutArray = [];\n var key = event.key;\n\n if (event.metaKey) shortcutArray.push('Cmd');\n if (event.ctrlKey) shortcutArray.push('Ctrl');\n if (event.altKey) shortcutArray.push('Alt');\n if (event.shiftKey) shortcutArray.push('Shift');\n\n if (!modifierKeys[key]) {\n // a and A, b and B, should be the same shortcut\n if (key.length === 1) key = key.toUpperCase();\n shortcutArray.push(key);\n }\n\n return shortcutArray.join('+');\n}\n","import EventDispatcher from './eventdispatcher';\nimport Editor from './editor';\nimport { patch } from 'ultradom';\nimport defaultDom from './defaultDom';\nimport { getSelection, setSelection, getBrowserRange, getNodeAndOffset } from './selection';\nimport { DOM, deltaToVdom, deltaFromDom, deltaToHTML } from './dom';\nimport shortcuts from 'shortcut-string';\nimport { shallowEqual } from 'fast-equals';\n\nconst SOURCE_API = 'api';\nconst SOURCE_USER = 'user';\nconst SOURCE_SILENT = 'silent';\nconst isMac = navigator.userAgent.indexOf('Macintosh') !== -1;\n\n\nexport default class HTMLView extends EventDispatcher {\n\n constructor(editor, options = {}) {\n super();\n if (!editor) throw new Error('Editor view requires an editor');\n this.editor = editor;\n this.root = null;\n this.dom = new DOM(options.dom || defaultDom);\n this.enabled = true;\n this.isMac = isMac;\n this._settingEditorSelection = false;\n this._settingBrowserSelection = false;\n\n if (options.modules) options.modules.forEach(module => module(this));\n\n this.editor.on('text-change', () => this.update());\n this.editor.on('selection-change', () => !this._settingEditorSelection && this.updateBrowserSelection());\n }\n\n hasFocus() {\n return this.root.contains(this.root.ownerDocument.activeElement);\n }\n\n focus() {\n this.root.focus();\n }\n\n blur() {\n this.root.blur();\n }\n\n disable() {\n this.enable(false);\n }\n\n enable(enabled = true) {\n this.enabled = enabled;\n this.update();\n }\n\n getBounds(range) {\n range = this.editor.normalizeRange(range, this.editor.length - 1);\n const browserRange = getBrowserRange(this, range);\n if (browserRange.endContainer.nodeType === Node.ELEMENT_NODE) {\n browserRange.setEnd(browserRange.endContainer, browserRange.endOffset + 1);\n }\n return browserRange.getBoundingClientRect();\n }\n\n getHTML() {\n return deltaToHTML(this, this.editor.contents);\n }\n\n setHTML(html) {\n this.editor.setContents(deltaFromHTML(this, html));\n }\n\n update() {\n let contents = this.editor.contents;\n let viewEditor = new Editor({ contents });\n this.decorations = viewEditor.getChange(() => this.fire('decorate', viewEditor));\n if (this.root && this.decorations.ops.length) this.root.node = null;\n const vdom = deltaToVdom(this, contents.compose(this.decorations));\n this.root = patch(vdom, this.root);\n this.updateBrowserSelection();\n this.fire('update');\n }\n\n updateBrowserSelection() {\n if (this._settingEditorSelection) return;\n this._settingBrowserSelection = true;\n setSelection(this, this.editor.selection);\n setTimeout(() => this._settingBrowserSelection = false, 20);\n }\n\n updateEditorSelection() {\n if (this._settingBrowserSelection) return this._settingBrowserSelection = false;\n const range = getSelection(this);\n this._settingEditorSelection = true;\n this.editor.setSelection(range);\n this._settingEditorSelection = false;\n if (!shallowEqual(range, this.editor.selection)) this.updateBrowserSelection();\n }\n\n mount(container) {\n this.update();\n container.appendChild(this.root);\n this.root.ownerDocument.execCommand('defaultParagraphSeparator', false, 'p');\n\n const onKeyDown = event => {\n const shortcut = shortcuts.fromEvent(event);\n this.fire(`shortcut:${shortcut}`, event, shortcut);\n this.fire(`shortcut`, event, shortcut);\n };\n\n // TODO this was added to replace the mutation observer, however, it does not accurately capture changes that\n // occur with native changes such as spell-check replacements, cut or delete using the app menus, etc. Paste should\n // be handled elsewhere (probably?).\n const onInput = () => {\n if (!this.editor.selection) throw new Error('How did an input event occur without a selection?');\n const [ from, to ] = this.editor.getSelectedRange();\n const [ node, offset ] = getNodeAndOffset(this, from);\n if (!node || (node.nodeType !== Node.TEXT_NODE && node.nodeName !== 'BR')) {\n this.root.node = null;\n return this.update();\n //throw new Error('Text entry should always result in a text node');\n }\n if (from !== to || Object.keys(this.editor.activeFormats).length) {\n this.root.node = null; // The DOM may have (or will be) changing, refresh from scratch\n }\n const text = node.nodeValue.slice(offset, offset + 1).replace(/\\xA0/g, ' ');\n this.editor.insertText(this.editor.selection, text, this.editor.getTextFormat(from));\n };\n\n const onSelectionChange = () => {\n this.updateEditorSelection();\n };\n\n // Use mutation tracking during development to catch errors\n // TODO delete mutation observer\n let checking = 0;\n const onMutate = list => {\n if (checking) clearTimeout(checking);\n checking = setTimeout(() => {\n checking = 0;\n const diff = editor.contents.compose(this.decorations).diff(deltaFromDom(view));\n if (diff.length()) {\n console.error('Delta out of sync with DOM:', diff);\n }\n }, 20);\n };\n\n this.root.addEventListener('keydown', onKeyDown);\n this.root.addEventListener('input', onInput);\n container.ownerDocument.addEventListener('selectionchange', onSelectionChange);\n\n const observer = new MutationObserver(onMutate);\n observer.observe(this.root, { characterData: true, characterDataOldValue: true, childList: true, attributes: true, subtree: true });\n\n this.unmount = () => {\n observer.disconnect();\n this.root.removeEventListener('keydown', onKeyDown);\n this.root.removeEventListener('input', onInput);\n this.root.ownerDocument.removeEventListener('selectionchange', onSelectionChange);\n this.root.remove();\n this.unmount = () => {};\n }\n }\n\n unmount() {}\n\n}\n","const lastWord = /\\w+[^\\w]*$/;\nconst firstWord = /^[^\\w]*\\w+/;\nconst lastLine = /[^\\n]*$/;\nconst firstLine = /^[^\\n]*/;\n\n// Basic text input module. Prevent any actions other than typing characters and handle with the API.\nexport default function input(view) {\n const editor = view.editor;\n\n function onEnter(event, shortcut) {\n if (event.defaultPrevented) return;\n event.preventDefault();\n const [ from, to ] = editor.getSelectedRange();\n\n if (shortcut === 'Shift+Enter') {\n editor.insertText(from, to, '\\r');\n } else {\n const line = editor.contents.getLine(from);\n editor.insertText([from, to], '\\n', line.attributes);\n }\n }\n\n function onBackspace(event, shortcut) {\n if (event.defaultPrevented) return;\n event.preventDefault();\n\n let [ from, to ] = editor.selection;\n if (from + to === 0) {\n editor.formatLine(0, {});\n } else {\n // The \"from\" block needs to stay the same. The \"to\" block gets merged into it\n if (from === to) {\n if (shortcut === 'Alt+Backspace' && view.isMac) {\n const match = editor.getText().slice(0, from).match(lastWord);\n if (match) from -= match[0].length;\n } else if (shortcut === 'Cmd+Backspace' && view.isMac) {\n const match = editor.getText().slice(0, from).match(lastLine);\n if (match) from -= match[0].length;\n } else {\n from--;\n }\n }\n editor.deleteText([from, to]);\n }\n }\n\n function onDelete(event, shortcut) {\n if (event.defaultPrevented) return;\n event.preventDefault();\n\n let [ from, to ] = editor.selection;\n if (from === to && from === editor.length) return;\n\n if (from === to) {\n if (shortcut === 'Alt+Delete' && view.isMac) {\n const match = editor.getText().slice(from).match(firstWord);\n if (match) to += match[0].length;\n } else {\n to++;\n }\n }\n editor.deleteText([from, to]);\n }\n\n view.on('shortcut:Enter', onEnter);\n view.on('shortcut:Shift+Enter', onEnter);\n view.on('shortcut:Backspace', onBackspace);\n view.on('shortcut:Alt+Backspace', onBackspace);\n view.on('shortcut:Cmd+Backspace', onBackspace);\n view.on('shortcut:Delete', onDelete);\n view.on('shortcut:Alt+Delete', onDelete);\n}\n","const SOURCE_USER = 'user';\n\n\nexport const keymap = {\n 'Ctrl+B': editor => editor.toggleTextFormat(editor.selection, { bold: true }),\n 'Ctrl+I': editor => editor.toggleTextFormat(editor.selection, { italics: true }),\n 'Ctrl+1': editor => editor.toggleLineFormat(editor.selection, { header: 1 }),\n 'Ctrl+2': editor => editor.toggleLineFormat(editor.selection, { header: 2 }),\n 'Ctrl+3': editor => editor.toggleLineFormat(editor.selection, { header: 3 }),\n 'Ctrl+4': editor => editor.toggleLineFormat(editor.selection, { header: 4 }),\n 'Ctrl+5': editor => editor.toggleLineFormat(editor.selection, { header: 5 }),\n 'Ctrl+6': editor => editor.toggleLineFormat(editor.selection, { header: 6 }),\n 'Ctrl+0': editor => editor.formatLine(editor.selection, { }),\n};\n\nexport const macKeymap = {\n 'Cmd+B': keymap['Ctrl+B'],\n 'Cmd+I': keymap['Ctrl+I'],\n 'Cmd+1': keymap['Ctrl+1'],\n 'Cmd+2': keymap['Ctrl+2'],\n 'Cmd+3': keymap['Ctrl+3'],\n 'Cmd+4': keymap['Ctrl+4'],\n 'Cmd+5': keymap['Ctrl+5'],\n 'Cmd+6': keymap['Ctrl+6'],\n 'Cmd+0': keymap['Ctrl+0'],\n};\n\n// Basic text input module. Prevent any actions other than typing characters and handle with the API.\nexport default function keyShortcuts(view) {\n const editor = view.editor;\n\n view.on('shortcut', (event, shortcut) => {\n if (event.defaultPrevented) return;\n const map = view.isMac ? macKeymap : keymap;\n\n if (shortcut in map) {\n event.preventDefault();\n map[shortcut](editor);\n }\n });\n}\n","const SOURCE_USER = 'user';\nconst defaultSettings = {\n delay: 4000,\n maxStack: 500,\n};\n\nexport default function history(view, settings = defaultSettings) {\n const editor = view.editor;\n\n const stack = {\n undo: [],\n redo: [],\n };\n let lastRecorded = 0;\n let lastAction;\n let ignoreChange = false;\n\n function undo(event) {\n action(event, 'undo', 'redo');\n }\n\n function redo() {\n action(event, 'redo', 'undo');\n }\n\n function action(event, source, dest) {\n if (event.defaultPrevented) return;\n event.preventDefault();\n if (stack[source].length === 0) return;\n let entry = stack[source].pop();\n stack[dest].push(entry);\n lastRecorded = 0;\n ignoreChange = true;\n editor.updateContents(entry[source], SOURCE_USER, entry[source + 'Selection']);\n ignoreChange = false;\n }\n\n function record(change, contents, oldContents, selection, oldSelection) {\n const timestamp = Date.now();\n const action = getAction(change);\n stack.redo.length = 0;\n\n let undoChange = contents.diff(oldContents);\n // Break combining if actions are different (e.g. a delete then an insert should break it)\n if (!action || lastAction !== action) lastRecorded = 0;\n lastAction = action;\n\n if (lastRecorded + settings.delay > timestamp && stack.undo.length > 0) {\n // Combine with the last change\n let entry = stack.undo.pop();\n oldSelection = entry.undoSelection;\n undoChange = undoChange.compose(entry.undo);\n change = entry.redo.compose(change);\n } else {\n lastRecorded = timestamp;\n }\n\n stack.undo.push({\n redo: change,\n undo: undoChange,\n redoSelection: selection,\n undoSelection: oldSelection,\n });\n\n if (stack.undo.length > settings.maxStack) {\n stack.undo.shift();\n }\n }\n\n\n function transform(change) {\n stack.undo.forEach(function(entry) {\n entry.undo = change.transform(entry.undo, true);\n entry.redo = change.transform(entry.redo, true);\n });\n stack.redo.forEach(function(entry) {\n entry.undo = change.transform(entry.undo, true);\n entry.redo = change.transform(entry.redo, true);\n });\n }\n\n\n editor.on('editor-change', ({ change, contents, oldContents, selection, oldSelection, source }) => {\n if (ignoreChange) return;\n if (!change) {\n // Break the history merging when selection changes\n lastRecorded = 0;\n return;\n }\n if (source === SOURCE_USER) {\n record(change, contents, oldContents, selection, oldSelection);\n } else {\n transform(change);\n }\n });\n\n editor.on('selection')\n\n if (view.isMac) {\n view.on('shortcut:Cmd+Z', undo);\n view.on('shortcut:Cmd+Shift+Z', redo);\n } else {\n view.on('shortcut:Ctrl+Z', undo);\n view.on('shortcut:Cmd+Y', redo);\n }\n}\n\nfunction getAction(change) {\n if (change.ops.length === 1 || change.ops.length === 2 && change.ops[0].retain && !change.ops[0].attributes) {\n const changeOp = change.ops[change.ops.length - 1];\n if (changeOp.delete) return 'delete';\n if (changeOp.insert === '\\n') return 'newline';\n if (changeOp.insert) return 'insert';\n }\n return '';\n}\n","import Editor from './editor';\nimport HTMLView from './html-view';\n\nimport input from './modules/input';\nimport keyShortcuts from './modules/key-shortcuts';\nimport history from './modules/history';\n\nconst defaultViewModules = [ input, keyShortcuts, history ];\n\nexport { Editor, HTMLView, input, keyShortcuts, history, defaultViewModules };\n","import { Editor, HTMLView, defaultViewModules } from './index';\nimport placeholder from './modules/placeholder';\nimport { h } from 'ultradom';\n\nconst editor = new Editor();\nconst view = new HTMLView(editor, { modules: defaultViewModules });\n\nwindow.editor = editor;\nwindow.view = view;\n\n// ***** Search feature\nconst style = document.createElement('style');\nstyle.textContent = 'span.search { background: yellow }';\ndocument.head.appendChild(style);\n\nconst searchInput = document.createElement('input');\nlet searchString = '';\nsearchInput.type = 'search';\ndocument.body.appendChild(searchInput);\nsearchInput.addEventListener('input', () => {\n searchString = searchInput.value.toLowerCase().trim();\n view.update();\n});\n\nview.dom.markups.add({\n name: 'search',\n selector: 'span.search',\n vdom: children => {children},\n});\n\nview.on('decorate', editor => {\n if (!searchString) return;\n const text = editor.getText().toLowerCase();\n let lastIndex = 0, index;\n while ((index = text.indexOf(searchString, lastIndex)) !== -1) {\n lastIndex = index + searchString.length;\n editor.formatText(index, lastIndex, { search: true });\n }\n});\n// ***** Search feature\n\n\neditor.setText(`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur quis sagittis libero. Etiam egestas rhoncus risus, sed accumsan nisi laoreet a. Praesent pulvinar porttitor lorem, vel tempor est vulputate nec. Duis magna justo, ultrices at ullamcorper a, sagittis quis mi. Duis id libero non augue faucibus faucibus sed nec sapien. Vivamus pulvinar justo nec metus dapibus, quis tincidunt justo fermentum. Aliquam erat volutpat. Nam hendrerit libero ut nunc rutrum pellentesque. Nulla erat eros, molestie ac nibh non, consectetur luctus lorem. Mauris vel egestas nisi.\nMauris sed mi cursus urna pretium posuere sit amet id lorem. Maecenas tristique commodo diam at elementum. Maecenas dapibus risus at mauris consequat, ac semper justo commodo. Sed tempor mattis nisi, in accumsan felis gravida non. In dignissim pellentesque ornare. Mauris lorem sem, consectetur eu ornare at, laoreet sed dui. Nam gravida justo tempus ligula pharetra, sit amet vestibulum lorem sagittis. In mauris purus, cursus vitae tempus at, tincidunt et arcu. Etiam sed libero ac mi fermentum hendrerit. Cras vel cursus urna, sed pretium nisl. Mauris sodales tempor ex nec iaculis. Nulla ac erat ac nunc malesuada viverra. Pellentesque nec ipsum in arcu consectetur elementum a ut metus. Integer sit amet eleifend nulla. Morbi ac felis malesuada, dapibus libero eget, posuere neque. Cras porta ut metus sed vulputate.`);\n\nview.mount(document.body);\n"],"names":["dispatcherEvents","WeakMap","EventDispatcher","type","listener","getEventListeners","add","delete","once","off","args","apply","on","uncanceled","forEach","obj","events","get","set","Object","create","Set","isArguments","objectKeys","equal","op","diff","SOURCE_USER","SOURCE_SILENT","empty","Editor","options","selection","activeFormats","setContents","contents","delta","insert","modules","module","ops","Delta","from","to","length","_normalizeArguments","slice","getContents","filter","map","join","producer","change","updateContents","compose","singleChange","newContents","source","text","formats","retain","lineFormat","indexOf","getLineFormat","split","line","i","cleanDelete","embed","value","index","getLines","attributes","combineFormats","getOps","keys","name","getTextFormat","endIndex","startIndex","key","getText","format","existing","deepEqual","formatLine","isSame","every","formatText","oldContents","normalizeContents","oldSelection","transform","getSelectedRange","changeEvent","selectionEvent","shallowEqual","fire","event","range","max","Math","min","rest","Array","isArray","undefined","unshift","concat","editor","lineChange","getChange","push","combined","reduce","merged","prototype","predicate","lines","eachLine","number","getLine","at","some","deltaOp","getOp","paragraph","selector","vdom","children","header","attr","parseInt","node","nodeName","replace","H","list","optimize","indent","parent","parentNode","test","topLevelChildren","levels","lists","List","item","childrenArray","container","enabled","bold","italics","link","href","image","src","blocks","markups","embeds","getSelection","view","root","ownerDocument","defaultView","contains","anchorNode","anchorIndex","getNodeIndex","focusIndex","focusNode","anchorOffset","focusOffset","setSelection","hasFocus","activeElement","blur","setBaseAndExtent","getNodesForRange","focus","getBrowserRange","browserRange","document","createRange","setStart","setEnd","getNodeAndOffset","blocksSelector","dom","walker","createTreeWalker","NodeFilter","SHOW_ELEMENT","SHOW_TEXT","acceptNode","nodeType","Node","TEXT_NODE","offsetParent","FILTER_ACCEPT","FILTER_REJECT","count","firstBlockSeen","currentNode","nextNode","size","nodeValue","matches","firstChild","lastChild","nextSibling","call","childNodes","ELEMENT_NODE","previousNode","br","voidElements","area","base","col","hr","img","input","meta","param","track","wbr","DOM","types","DOMTypes","block","markup","domTypes","array","priorities","definition","Error","remove","splice","domType","nodeOrAttr","find","deltaToVdom","blockData","inlineChildren","child","sort","a","b","priority","mergeChildren","getDefault","blockChildren","collect","data","next","deltaFromDom","currentBlock","isBr","deltaToHTML","childrenToHTML","oldChildren","prev","elementToHTML","escape","closingTag","html","define","require$$0","isMac","navigator","userAgent","HTMLView","defaultDom","_settingEditorSelection","_settingBrowserSelection","update","updateBrowserSelection","enable","normalizeRange","endContainer","endOffset","getBoundingClientRect","deltaFromHTML","viewEditor","decorations","patch","setTimeout","appendChild","execCommand","onKeyDown","shortcut","shortcuts","fromEvent","onInput","offset","insertText","onSelectionChange","updateEditorSelection","checking","onMutate","clearTimeout","console","error","addEventListener","observer","MutationObserver","observe","characterData","characterDataOldValue","childList","subtree","unmount","disconnect","removeEventListener","lastWord","firstWord","lastLine","onEnter","defaultPrevented","preventDefault","onBackspace","match","deleteText","onDelete","keymap","toggleTextFormat","toggleLineFormat","macKeymap","keyShortcuts","defaultSettings","delay","maxStack","history","settings","stack","undo","redo","lastRecorded","lastAction","ignoreChange","action","dest","entry","pop","record","timestamp","Date","now","getAction","undoChange","undoSelection","redoSelection","shift","changeOp","defaultViewModules","window","style","createElement","textContent","head","searchInput","searchString","body","toLowerCase","trim","lastIndex","search","setText","mount"],"mappings":"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"} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 846c1bf..0000000 --- a/gulpfile.js +++ /dev/null @@ -1,164 +0,0 @@ -var gulp = require('gulp'); -var del = require('del'); -var newer = require('gulp-newer'); -var concat = require('gulp-concat'); -var browserify = require('browserify'); -var watchify = require('watchify'); -var source = require('vinyl-source-stream'); -var buffer = require('vinyl-buffer'); -var merge = require('merge-stream'); -var uglify = require('gulp-uglify'); -var sourcemaps = require('gulp-sourcemaps'); -var gutil = require('gulp-util'); -var notify = require('gulp-notify'); -var imagemin = require('gulp-imagemin'); -var less = require('gulp-less'); -var minifyCss = require('gulp-minify-css'); -var karma = require('karma'); -var liveServer = require("live-server"); -var LessPluginAutoPrefix = require('less-plugin-autoprefix'); -var autoprefix = new LessPluginAutoPrefix(); - -var paths = { - build: { - dir: 'build', - scripts: 'js', - script: 'app.js', - styles: 'css', - style: 'app.css', - images: 'images', - }, - scripts: 'index.js', - images: [ 'src/images/**/*', 'plugins/*/images/**/*' ], - less: [ 'src/**/*.less', 'plugins/**/*.less' ], - files: 'src/public/**/*' -}; - -var browserifyOptions = { - basedir: 'src', - entries: paths.scripts, - debug: true, - // Required properties for watchify - cache: {}, - packageCache: {} -}; - - -/* Register some tasks to expose to the cli */ -gulp.task('build', gulp.series( - clean, - gulp.parallel(scripts, images, files) -)); - -gulp.task(clean); -gulp.task(watch); -gulp.task(test); -gulp.task(tdd); - -// The default task (called when you run `gulp` from cli) -gulp.task('default', gulp.series('build')); - - -function clean() { - return del(paths.build.dir); -} - - -// Copy all static images -function images() { - return gulp.src(paths.images) - .pipe(newer(paths.build.dir + '/' + paths.build.images)) - .pipe(imagemin({optimizationLevel: 5})) - .pipe(gulp.dest(paths.build.dir + '/' + paths.build.images)); -} - - -function styles() { - return gulp.src(paths.less) - .pipe(sourcemaps.init()) - .pipe(less({ plugins: [ autoprefix ] })) - .pipe(concat(paths.build.style)) - .pipe(minifyCss({ keepSpecialComments: 0 })) - .pipe(sourcemaps.write('.', {sourceRoot: '/typewriter/src/'})) - .pipe(gulp.dest(paths.build.dir + '/' + paths.build.styles)); -} - - -// Minify and copy all JavaScript (except vendor scripts) with sourcemaps all the way down -function scripts() { - var bundler = browserify(browserifyOptions); - return bundler.bundle() - .pipe(source(paths.build.script)) - .pipe(buffer()) - .pipe(sourcemaps.init({loadMaps: true})) - // .pipe(uglify()) - .pipe(sourcemaps.write('.', {sourceRoot: '/typewriter/src/'})) - .pipe(gulp.dest(paths.build.dir + '/' + paths.build.scripts)); -} - - -function files() { - return gulp.src(paths.files) - .pipe(gulp.dest(paths.build.dir)); -} - - -function watchScripts() { - var bundler = watchify(browserify(browserifyOptions)); - bundler.on('log', gutil.log.bind(gutil)); - bundler.on('update', function() { - gutil.log('Rebundling'); - rebundle(); - }); - - function rebundle() { - return bundler.bundle() - .on('error', notify.onError({ - title: 'Compile Error', - message: function(error) { - return error.message; - } - })) - .pipe(source(paths.build.script)) - .pipe(buffer()) - .pipe(sourcemaps.init({loadMaps: true})) - // .pipe(uglify()) - .pipe(sourcemaps.write('.', {sourceRoot: '/typewriter/src/'})) - .pipe(gulp.dest(paths.build.dir + '/' + paths.build.scripts)); - } - - return rebundle(); -} - - -// Rerun the task when a file changes -function watch() { - watchScripts(); - images(); - styles(); - files(); - gulp.watch(paths.less, styles); - gulp.watch(paths.images, images); - gulp.watch(paths.files, files); - - liveServer.start({ - root: paths.build.dir, - file: 'index.html', - open: false - }); -} - - -function test(done) { - new karma.Server({ - configFile: __dirname + '/karma.conf.js', - singleRun: true - }, done).start(); -} - -function tdd(done) { - new karma.Server({ - configFile: __dirname + '/karma.conf.js' - }, done).start(); -} - diff --git a/index.html b/index.html new file mode 100644 index 0000000..aee2a95 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + Page Title + + + + + + \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index 72323b8..0000000 --- a/karma.conf.js +++ /dev/null @@ -1,79 +0,0 @@ -// Karma configuration -require('es6-shim'); -require('weakmap'); - -module.exports = function(config) { - config.set({ - - // Browserify configuration (passed directly to Browserify) - browserify: { - transform: ['stringify', 'require-globify'], - debug: true - }, - - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '', - - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['source-map-support', 'browserify', 'mocha', 'chai'], - - - // list of files / patterns to load in the browser - files: [ - './node_modules/es6-shim/es6-shim.js', - './node_modules/weakmap/weakmap.js', - './src/util/protofills.js', - './test/**/test-*.js' - ], - - - // list of files to exclude - exclude: [ - ], - - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - './test/**/test-*.js': ['browserify'] - }, - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress', 'nyan'], - - - // web server port - port: 9876, - - - // enable / disable colors in the output (reporters and logs) - colors: true, - - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, - - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['PhantomJS'], - - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: false, - - // Concurrency level - // how many browser should be started simultanous - concurrency: Infinity - }) -} diff --git a/package.json b/package.json index baf8955..6e9d576 100644 --- a/package.json +++ b/package.json @@ -2,54 +2,39 @@ "name": "typewriter-editor", "version": "0.0.3", "scripts": { - "start": "gulp watch", - "build": "gulp build", - "clean": "gulp clean", - "tdd": "gulp tdd", - "test": "gulp test" + "start": "rollup --config rollup.config.dev.js --watch", + "build": "rollup --config" }, "license": "MIT", "repository": { "type": "git", "url": "https://github.com/jacwright/typewriter.git" }, - "main": "src/editor/editor.js", + "main": "dist/index.js", + "module": "src/index.js", "engines": { "node": ">=4" }, "dependencies": { - "chip-utils": "^0.3.0", - "es6-object-assign": "^1.0.3", - "es6-shim": "^0.35.1", - "shortcut-string": "^1.0.0" + "escape-html": "^1.0.3", + "fast-equals": "^1.2.1", + "quill-delta": "^3.6.2", + "shortcut-string": "^1.1.0", + "ultradom": "^2.3.4" }, "devDependencies": { - "browserify": "^13.0.0", - "chai": "^3.5.0", - "del": "^2.2.0", - "gulp": "github:gulpjs/gulp#4.0", - "gulp-concat": "^2.6.0", - "gulp-imagemin": "^3.0.1", - "gulp-less": "^3.0.5", - "gulp-minify-css": "^1.2.3", - "gulp-newer": "^1.2.0", - "gulp-notify": "^2.2.0", - "gulp-sourcemaps": "^1.6.0", - "gulp-uglify": "^1.5.3", - "gulp-util": "^3.0.7", - "karma": "^0.13.21", - "karma-browserify": "^5.0.2", - "karma-chai": "^0.1.0", - "karma-mocha": "^1.0.1", - "karma-nyan-reporter": "^0.2.4", - "karma-phantomjs-launcher": "^1.0.0", - "karma-source-map-support": "^1.1.0", - "less-plugin-autoprefix": "^1.5.1", - "merge-stream": "^1.0.0", - "mocha": "^2.4.5", - "phantomjs-prebuilt": "^2.1.4", - "vinyl-buffer": "^1.0.0", - "vinyl-source-stream": "^1.1.0", - "watchify": "^3.7.0" + "babel-cli": "^6.26.0", + "babel-core": "^6.26.0", + "babel-plugin-external-helpers": "^6.22.0", + "babel-plugin-module-resolver": "^3.1.1", + "babel-plugin-transform-react-jsx": "^6.24.1", + "babel-preset-env": "^1.6.1", + "babel-preset-stage-2": "^6.24.1", + "rollup": "^0.57.1", + "rollup-plugin-babel": "^3.0.3", + "rollup-plugin-commonjs": "^9.1.0", + "rollup-plugin-livereload": "^0.6.0", + "rollup-plugin-node-resolve": "^3.3.0", + "rollup-plugin-serve": "^0.4.2" } } diff --git a/rollup.config.dev.js b/rollup.config.dev.js new file mode 100644 index 0000000..402ba14 --- /dev/null +++ b/rollup.config.dev.js @@ -0,0 +1,26 @@ +import resolve from 'rollup-plugin-node-resolve'; +import commonjs from 'rollup-plugin-commonjs'; +import babel from 'rollup-plugin-babel'; +import serve from 'rollup-plugin-serve' +import livereload from 'rollup-plugin-livereload' + +export default { + input: 'src/dev.js', + output: { + file: 'dist/index.js', + format: 'iife', + name: 'Typewriter', + sourcemap: true, + }, + plugins: [ + resolve(), + commonjs({ + include: 'node_modules/**' + }), + babel({ + exclude: 'node_modules/**' + }), + serve(), + livereload() + ], +}; \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..c3c0ead --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,20 @@ +import resolve from 'rollup-plugin-node-resolve'; +import commonjs from 'rollup-plugin-commonjs'; +import babel from 'rollup-plugin-babel'; + +export default { + input: 'src/index.js', + output: { + file: 'dist/index.js', + format: 'cjs' + }, + plugins: [ + resolve(), + commonjs({ + include: 'node_modules/**' + }), + babel({ + exclude: 'node_modules/**' + }) + ], +}; \ No newline at end of file diff --git a/src/defaultDom.js b/src/defaultDom.js new file mode 100644 index 0000000..a265db3 --- /dev/null +++ b/src/defaultDom.js @@ -0,0 +1,105 @@ +import { h } from 'ultradom'; + +export const paragraph = { + name: 'paragraph', + selector: 'p', + vdom: children =>

    {children}

    , +}; + + +export const header = { + name: 'header', + selector: 'h1, h2, h3, h4, h5, h6', + attr: node => ({ header: parseInt(node.nodeName.replace('H', '')) }), + vdom: (children, attr) => { + const H = `h${attr.header}`; + return {children}; + }, +}; + + +export const list = { + name: 'list', + selector: 'ul > li, ol > li', + optimize: true, + attr: node => { + let indent = -1, parent = node.parentNode; + const list = parent.nodeName === 'OL' ? 'ordered' : 'bullet'; + while (parent) { + if (/^UL|OL$/.test(parent.nodeName)) indent++; + else if (parent.nodeName !== 'LI') break; + parent = parent.parentNode; + } + return indent ? { list, indent } : { list }; + }, + vdom: lists => { + const topLevelChildren = []; + let levels = []; + // e.g. levels = [ul, li, ul, li] + + lists.forEach(([children, attr]) => { + const List = attr.list === 'ordered' ? 'ol' : 'ul'; + const index = (attr.indent || 0) * 2; + const item =
  • {children}
  • ; + let list = levels[index]; + if (list && list.nodeName === List) { + list.children.push(item); + } else { + list = {item}; + const childrenArray = index ? levels[index - 1].children : topLevelChildren; + childrenArray.push(list); + levels[index] = list; + } + levels[index + 1] = item; + levels.length = index + 2; + }); + + return topLevelChildren; + }, +}; + + +export const container = { + name: 'container', + selector: 'div', + vdom: (children, attr) =>
    + {children && children.length && children || paragraph.vdom()} +
    , +}; + + +export const bold = { + name: 'bold', + selector: 'strong, b', + vdom: children => {children}, +}; + + +export const italics = { + name: 'italics', + selector: 'em, i', + vdom: children => {children}, +}; + + +export const link = { + name: 'link', + selector: 'a[href]', + attr: node => node.href, + vdom: (children, attr) => {children}, +}; + + +export const image = { + name: 'image', + selector: 'img', + attr: node => node.src, + vdom: (children, attr) => +}; + + +export default { + blocks: [ paragraph, header, list, container ], + markups: [ bold, italics, link ], + embeds: [ image ], +}; diff --git a/src/dev.js b/src/dev.js new file mode 100644 index 0000000..465a52d --- /dev/null +++ b/src/dev.js @@ -0,0 +1,46 @@ +import { Editor, HTMLView, defaultViewModules } from './index'; +import placeholder from './modules/placeholder'; +import { h } from 'ultradom'; + +const editor = new Editor(); +const view = new HTMLView(editor, { modules: defaultViewModules }); + +window.editor = editor; +window.view = view; + +// ***** Search feature +const style = document.createElement('style'); +style.textContent = 'span.search { background: yellow }'; +document.head.appendChild(style); + +const searchInput = document.createElement('input'); +let searchString = ''; +searchInput.type = 'search'; +document.body.appendChild(searchInput); +searchInput.addEventListener('input', () => { + searchString = searchInput.value.toLowerCase().trim(); + view.update(); +}); + +view.dom.markups.add({ + name: 'search', + selector: 'span.search', + vdom: children => {children}, +}); + +view.on('decorate', editor => { + if (!searchString) return; + const text = editor.getText().toLowerCase(); + let lastIndex = 0, index; + while ((index = text.indexOf(searchString, lastIndex)) !== -1) { + lastIndex = index + searchString.length; + editor.formatText(index, lastIndex, { search: true }); + } +}); +// ***** Search feature + + +editor.setText(`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur quis sagittis libero. Etiam egestas rhoncus risus, sed accumsan nisi laoreet a. Praesent pulvinar porttitor lorem, vel tempor est vulputate nec. Duis magna justo, ultrices at ullamcorper a, sagittis quis mi. Duis id libero non augue faucibus faucibus sed nec sapien. Vivamus pulvinar justo nec metus dapibus, quis tincidunt justo fermentum. Aliquam erat volutpat. Nam hendrerit libero ut nunc rutrum pellentesque. Nulla erat eros, molestie ac nibh non, consectetur luctus lorem. Mauris vel egestas nisi. +Mauris sed mi cursus urna pretium posuere sit amet id lorem. Maecenas tristique commodo diam at elementum. Maecenas dapibus risus at mauris consequat, ac semper justo commodo. Sed tempor mattis nisi, in accumsan felis gravida non. In dignissim pellentesque ornare. Mauris lorem sem, consectetur eu ornare at, laoreet sed dui. Nam gravida justo tempus ligula pharetra, sit amet vestibulum lorem sagittis. In mauris purus, cursus vitae tempus at, tincidunt et arcu. Etiam sed libero ac mi fermentum hendrerit. Cras vel cursus urna, sed pretium nisl. Mauris sodales tempor ex nec iaculis. Nulla ac erat ac nunc malesuada viverra. Pellentesque nec ipsum in arcu consectetur elementum a ut metus. Integer sit amet eleifend nulla. Morbi ac felis malesuada, dapibus libero eget, posuere neque. Cras porta ut metus sed vulputate.`); + +view.mount(document.body); diff --git a/src/document.js b/src/document.js new file mode 100644 index 0000000..63216b1 --- /dev/null +++ b/src/document.js @@ -0,0 +1,232 @@ +// import Delta from 'quill-delta'; + + +// export default class Document { + +// constructor(contents) { +// setContents(this, contents || this.delta().insert('\n')); +// } + +// delta() { +// return new Delta(); +// } + +// getContents(from = 0, to = this.length) { +// const [ from, to ] = this._normalizeArguments(this, from, to); +// return this.contents.slice(from, to); +// } + +// getText(from, to) { +// return this.getContents(from, to) +// .filter(op => typeof op.insert === 'string') +// .map(op => op.insert) +// .join('') +// .slice(0, -1); // remove the head newline +// } + +// getChange(producer) { +// let change; +// this.updateContents = singleChange => change = change ? change.compose(singleChange) : singleChange; +// producer(); +// delete this.updateContents; +// return change; +// } + +// setContents(newContents) { +// const change = this.contents.diff(newContents); +// return this.updateContents(change); +// } + +// setText(text) { +// return this.setContents(this.delta().insert('\n' + text)); +// } + +// insertText(index, text, formats) { +// const change = this.delta().retain(index); +// const lineFormat = from === to && text.indexOf('\n') === -1 ? null : this.getLineFormat(from); +// text.split('\n').forEach((line, i) => { +// if (i) change.insert('\n', lineFormat); +// line.length && change.insert(line, formats); +// }); + +// return this.updateContents(change); +// } + +// insertEmbed(index, embed, value) { +// const change = this.delta().retain(index).insert({ [embed]: value }); +// return this.updateContents(change); +// } + +// deleteText(from, to) { +// const [ from, to ] = this._normalizeArguments(this, from, to); +// let change = this.delta().retain(from).delete(to - from); +// change = cleanDelete(this, from, to, change); +// return this.updateContents(change); +// } + +// getLineFormat(from, to) { +// const [ from, to ] = this._normalizeArguments(this, from, to); +// let formats; + +// this.contents.getLines(from, to).forEach(line => { +// if (!line.attributes) formats = {}; +// else if (!formats) formats = { ...line.attributes }; +// else formats = combineFormats(formats, line.attributes); +// }); + +// return formats; +// } + +// getTextFormat(from, to) { +// const [ from, to ] = this._normalizeArguments(this, from, to); +// let formats; + +// this.contents.eachOpAt(from, to, op => { +// if (!op.attributes) formats = {}; +// else if (!formats) formats = { ...op.attributes }; +// else formats = combineFormats(formats, op.attributes); +// }); + +// return formats; +// } + +// getFormat(from, to) { +// return { ...this.getTextFormat(from, to), ...this.getLineFormat(from, to) }; +// } + +// formatLine(from, to, formats) { +// const [ from, to, formats ] = this._normalizeArguments(this, from, to, formats); +// const change = this.delta(); + +// this.contents.getLines(from, to).forEach(line => { +// if (!change.ops.length) change.retain(line.endIndex - 1); +// else change.retain(line.endIndex - line.startIndex - 1); +// // Clear out old formats on the line +// Object.keys(line.attributes).forEach(name => !formats[name] && (formats[name] = null)); +// change.retain(1, formats); +// }); + +// return change.ops.length ? this.updateContents(change) : this.contents; +// } + +// formatText(from, to, formats) { +// const [ from, to, formats ] = this._normalizeArguments(this, from, to, formats); +// Object.keys(formats).forEach(name => formats[name] === false && (formats[name] = null)); +// const text = this.getText(); +// const change = this.delta().retain(from); +// text.slice(from, to).split('\n').forEach(line => { +// line.length && change.retain(line.length, formats).retain(1); +// }); + +// return this.updateContents(change); +// } + +// toggleLineFormat(from, to, formats) { +// const existing = this.getLineFormat(range); +// if (deepEqual(existing, formats)) { +// Object.keys(formats).forEach(key => formats[key] = null); +// } +// return this.formatLine(from, to, formats); +// } + +// toggleTextFormat(from, TypeError, formats) { +// const existing = this.getTextFormat(range); +// if (deepEqual(existing, formats)) { +// Object.keys(formats).forEach(key => formats[key] = null); +// } +// return this.formatText(range, formats); +// } + +// removeFormat(from, to) { +// const [ from, to ] = this._normalizeArguments(this, from, to); +// const formats = {}; + +// this.contents.eachOpAt(from, to, op => { +// op.attributes && Object.keys(op.attributes).forEach(key => formats[key] = null); +// }); + +// let change = this.delta().retain(from).retain(to - from, formats); + +// // If the last block was not captured be sure to clear that too +// this.contents.getLines(from, to).forEach(line => { +// const formats = {}; +// Object.keys(line.attributes).forEach(key => formats[key] = null); +// change = change.compose(this.delta().retain(line.endIndex - 1).retain(1, formats)); +// }); + +// return this.updateContents(change); +// } + +// updateContents(change) { +// if (!change.ops.length) return this.contents; +// const contents = normalizeContents(this.contents.compose(change)); +// setContents(this, contents); +// return this.contents; +// } + +// /** +// * Normalizes range values to a proper range if it is not already. A range is a `from` and a `to` index, e.g. 0, 4. +// * This will ensure the lower index is first. Example usage: +// * doc._this._normalizeArguments(5); // [5, 5] +// * doc._this._normalizeArguments(-4, 100); // for a doc with length 10, [0, 10] +// * doc._this._normalizeArguments(25, 18); // [18, 25] +// * doc._this._normalizeArguments([12, 13]); // [12, 13] +// * doc._this._normalizeArguments(5, { bold: true }); // [5, 5, { bold: true }] +// */ +// _normalizeArguments(from, to, ...rest) { +// if (Array.isArray(from)) { +// rest.unshift(to); +// [from, to] = from; +// if (to === undefined) to = from; +// } else if (typeof to !== 'number') { +// rest.unshift(to); +// to = from; +// } +// from = Math.max(0, Math.min(this.length, ~~from)); +// to = Math.max(0, Math.min(this.length, ~~to)); +// if (from > to) { +// [from, to] = [to, from]; +// } +// return [from, to].concat(rest); +// } +// } + + +// function cleanDelete(editor, from, to, change) { +// if (from !== to) { +// const lineFormat = editor.getLineFormat(from); +// if (!deepEqual(lineFormat, editor.getLineFormat(to))) { +// const lineChange = editor.getChange(() => editor.formatLine(to, lineFormat)) +// change = change.compose(change.transform(lineChange)); +// } +// } +// return change; +// } + +// function normalizeContents(contents) { +// if (!contents.ops.length || contents.ops[contents.ops.length - 1].insert.slice(-1) !== '\n') contents.insert('\n'); +// return contents; +// } + +// function setContents(editor, contents) { +// normalizeContents(contents); +// contents.push = function() { return this; } // freeze from modification +// editor.contents = contents; +// editor.length = contents.length(); +// } + +// function combineFormats(formats, combined) { +// return Object.keys(combined).reduce(function(merged, name) { +// if (formats[name] == null) return merged; +// if (combined[name] === formats[name]) { +// merged[name] = combined[name]; +// } else if (Array.isArray(combined[name])) { +// if (combined[name].indexOf(formats[name]) < 0) { +// merged[name] = combined[name].concat([formats[name]]); +// } +// } else { +// merged[name] = [combined[name], formats[name]]; +// } +// return merged; +// }, {}); +// } diff --git a/src/dom.js b/src/dom.js new file mode 100644 index 0000000..3fb242e --- /dev/null +++ b/src/dom.js @@ -0,0 +1,233 @@ +import Delta from 'quill-delta'; +import { deepEqual } from 'fast-equals'; +import escape from 'escape-html'; +import { h } from 'ultradom'; + +const br =
    ; +const voidElements = { + area: true, base: true, br: true, col: true, embed: true, hr: true, img: true, input: true, + link: true, meta: true, param: true, source: true, track: true, wbr: true +}; + +export class DOM { + constructor(types) { + this.blocks = new DOMTypes(); + this.markups = new DOMTypes(); + if (types && types.blocks) types.blocks.forEach(block => this.blocks.add(block)); + if (types && types.markups) types.markups.forEach(markup => this.markups.add(markup)); + } +} + + +export class DOMTypes { + constructor() { + this.selector = ''; + this.domTypes = {}; + this.array = []; + this.priorities = {}; + } + + add(definition, index) { + if (!definition.name || !definition.selector || !definition.vdom) { + throw new Error('DOMType definitions must include a name, selector, and vdom function'); + } + if (this.domTypes[definition.name]) this.remove(definition.name); + this.selector += (this.selector ? ', ' : '') + definition.selector; + this.domTypes[definition.name] = definition; + if (typeof index !== 'number') { + this.priorities[name] = this.array.length; + this.array.push(definition); + } else { + this.array.splice(i, 0, definition); + this.array.forEach(({ name }, i) => this.priorities[name] = i); + } + } + + remove(name) { + if (!this.domTypes[name]) return; + delete this.domTypes[name]; + this.array = this.array.filter(domType => domType.name === name); + this.array.forEach(({ name }, i) => this.priorities[name] = i); + this.selector = this.array.map(type => type.selector).join(', '); + } + + get(name) { + return this.domTypes[name]; + } + + priority(name) { + return this.priorities[name]; + } + + getDefault() { + return this.array[0]; + } + + matches(node) { + return node.matches(this.selector); + } + + find(nodeOrAttr) { + if (nodeOrAttr instanceof Node) { + return this.array.find(domType => nodeOrAttr.matches(domType.selector)); + } else if (nodeOrAttr && typeof nodeOrAttr === 'object') { + let domType; + Object.keys(nodeOrAttr).some(name => domType = this.get(name)); + return domType; + } + } +} + + + +export function deltaToVdom(view, delta) { + const { blocks, markups } = view.dom; + const blockData = []; + + delta.eachLine((line, attr) => { + let inlineChildren = []; + + // Collect block children + line.forEach(op => { + let children = []; + op.insert.split(/\r/).forEach((child, i) => { + if (i !== 0) children.push(br); + child && children.push(child.replace(/ /g, '\xA0 ').replace(/ +$/, '\xA0')); + }); + + if (op.attributes) { + // Sort them by the order found in markups and be efficient + Object.keys(op.attributes).sort((a, b) => markups.priority(b) - markups.priority(a)).forEach(name => { + const markup = markups.get(name); + if (markup) { + const node = markup.vdom.call(view.dom, children, op.attributes); + node.markup = markup; + children = [ node ]; + } + }); + } + inlineChildren = inlineChildren.concat(children); + }); + + // Merge markups to optimize + inlineChildren = mergeChildren(inlineChildren); + if (!inlineChildren.length || inlineChildren[inlineChildren.length - 1] === br) { + inlineChildren.push(br); + } + + let block = blocks.find(attr); + if (!block) block = blocks.getDefault(); + + blockData.push([ block, inlineChildren, attr ]); + }); + + // If a block has optimize=true on it, vdom will be called with all sibling nodes of the same block + let blockChildren = [], prevBlock; + let collect = []; + blockData.forEach((data, i) => { + const [ block, children, attr ] = data; + if (block.optimize) { + collect.push([ children, attr ]); + const next = blockData[i + 1]; + if (!next || next[0] !== block) { + blockChildren = blockChildren.concat(block.vdom.call(view.dom, collect)); + collect = []; + } + } else { + blockChildren.push(block.vdom.call(view.dom, children, attr)); + } + }); + + return blocks.get('container').vdom.call(view.dom, blockChildren, view); +} + + +export function deltaFromDom(view, root = view.root) { + const { blocks, markups } = view.dom; + + const walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, { + acceptNode: node => { + return (node.nodeType === Node.TEXT_NODE || node.offsetParent) && + NodeFilter.FILTER_ACCEPT || + NodeFilter.FILTER_REJECT; + } + }); + const delta = new Delta(); + let currentBlock, firstBlockSeen = false, node; + + walker.currentNode = root; + + while ((node = walker.nextNode())) { + const isBr = node.nodeName === 'BR' && node.parentNode.lastChild !== node; + + if (node.nodeType === Node.TEXT_NODE || isBr) { + const text = isBr ? '\r' : node.nodeValue.replace(/\xA0/g, ' '); + let parent = node.parentNode, attr = {}; + + while (parent && !blocks.matches(parent) && parent !== root) { + if (markups.matches(parent)) { + const markup = markups.find(parent); + attr[markup.name] = markup.attr ? markup.attr(parent) : true; + } + parent = parent.parentNode; + } + delta.insert(text, attr); + } else if (blocks.matches(node)) { + if (firstBlockSeen) delta.insert('\n', currentBlock); + else firstBlockSeen = true; + const block = blocks.find(node); + if (block !== blocks.getDefault()) { + currentBlock = block.attr ? block.attr(node) : { [block.name]: true }; + } else { + currentBlock = undefined; + } + } + } + delta.insert('\n', currentBlock); + return delta; +} + + +export function deltaToHTML(view, delta) { + return childrenToHTML(deltaToVdom(view, delta).children); +} + + +export function deltaFromHTML(view, html) { + const template = document.createElement('template'); + template.innerHTML = '
    ' + html + '
    '; + const container = (template.content || template).firstChild; + return deltaFromDom(view, container); +} + + +// Join adjacent blocks +function mergeChildren(oldChildren) { + const children = []; + oldChildren.forEach((next, i) => { + const prev = children[children.length - 1]; + + if (prev && typeof prev !== 'string' && typeof next !== 'string' && prev.markup && + prev.markup === next.markup && deepEqual(prev.attributes, next.attributes)) + { + prev.children = prev.children.concat(next.children); + } else { + children.push(next); + } + }); + return children; +} + +function elementToHTML(node) { + const attr = Object.keys(node.attributes) + .reduce((attr, name) => + `${attr} ${escape(name)}="${escape(node.attributes[name])}"`, ''); + const children = childrenToHTML(node.children); + const closingTag = children || !voidElements[node.nodeName] ? `` : ''; + return `<${node.nodeName}${attr}>${children}${closingTag}`; +} + +function childrenToHTML(children) { + if (!children || !children.length) return ''; + return children.reduce((html, child) => html + (child.nodeName ? elementToHTML(child) : escape(child)), ''); +} diff --git a/src/editor.js b/src/editor.js new file mode 100644 index 0000000..eada334 --- /dev/null +++ b/src/editor.js @@ -0,0 +1,349 @@ +import EventDispatcher from './eventdispatcher'; +import Delta from 'quill-delta'; +import deltaOp from 'quill-delta/lib/op'; +import { shallowEqual, deepEqual } from 'fast-equals'; + +const SOURCE_API = 'api'; +const SOURCE_USER = 'user'; +const SOURCE_SILENT = 'silent'; +const empty = {}; + +export default class Editor extends EventDispatcher { + + constructor(options = {}) { + super(); + this.selection = null; + this.activeFormats = empty; + setContents(this, options.contents || this.delta().insert('\n')); + if (options.modules) options.modules.forEach(module => module(this)); + } + + delta(ops) { + return new Delta(ops); + } + + getContents(from = 0, to = this.length) { + [ from, to ] = this._normalizeArguments(from, to); + return this.contents.slice(from, to); + } + + getText(from, to) { + return this.getContents(from, to) + .filter(op => typeof op.insert === 'string') + .map(op => op.insert) + .join('') + .slice(0, -1); // remove the trailing newline + } + + getChange(producer) { + let change = this.delta(); + this.updateContents = singleChange => change = change.compose(singleChange); + producer(); + delete this.updateContents; + return change; + } + + setContents(newContents, source, selection) { + const change = this.contents.diff(newContents); + return this.updateContents(change, source, selection); + } + + setText(text, source, selection) { + return this.setContents(this.delta().insert(text + '\n'), source, selection); + } + + insertText(from, to, text, formats, source, selection) { + [ from, to, text, formats, source, selection ] = + this._normalizeArguments(from, to, text, formats, source, selection); + if (selection == null) selection = from + text.length; + let change = this.delta().retain(from).delete(to - from); + const lineFormat = from === to && text.indexOf('\n') === -1 ? null : this.getLineFormat(from); + text.split('\n').forEach((line, i) => { + if (i) change.insert('\n', lineFormat); + line.length && change.insert(line, formats); + }); + + change = cleanDelete(this, from, to, change); + return this.updateContents(change, source, selection); + } + + insertEmbed(from, to, embed, value, source, selection) { + [ from, to, embed, value, source, selection ] = + this._normalizeArguments(from, to, embed, value, source, selection); + if (selection == null) selection = from + 1; + let change = this.delta().retain(index).delete(to - from).insert({ [embed]: value }); + change = cleanDelete(this, from, to, change); + return this.updateContents(change, source, selection); + } + + deleteText(from, to, source, selection) { + [ from, to, source, selection ] = this._normalizeArguments(from, to, source, selection); + if (selection == null) selection = from; + let change = this.delta().retain(from).delete(to - from); + change = cleanDelete(this, from, to, change); + return this.updateContents(change, source, from); + } + + getLineFormat(from, to) { + [ from, to ] = this._normalizeArguments(from, to); + let formats; + + this.contents.getLines(from, to).forEach(line => { + if (!line.attributes) formats = {}; + else if (!formats) formats = { ...line.attributes }; + else formats = combineFormats(formats, line.attributes); + }); + + return formats; + } + + getTextFormat(from, to) { + [ from, to ] = this._normalizeArguments(from, to); + let formats; + + this.contents.getOps(from, to).forEach(({ op }) => { + if (!op.attributes) formats = {}; + else if (!formats) formats = { ...op.attributes }; + else formats = combineFormats(formats, op.attributes); + }); + + if (!formats) formats = {}; + + if (this.activeFormats !== empty) { + Object.keys(this.activeFormats).forEach(name => { + const value = this.activeFormats[name]; + if (value === null) delete formats[name]; + else formats[name] = value; + }); + } + + return formats; + } + + getFormat(from, to) { + return { ...this.getTextFormat(from, to), ...this.getLineFormat(from, to) }; + } + + formatLine(from, to, formats, source) { + [ from, to, formats, source ] = this._normalizeArguments(from, to, formats, source); + const change = this.delta(); + + this.contents.getLines(from, to).forEach(line => { + if (!change.ops.length) change.retain(line.endIndex - 1); + else change.retain(line.endIndex - line.startIndex - 1); + // Clear out old formats on the line + Object.keys(line.attributes).forEach(name => !formats[name] && (formats[name] = null)); + change.retain(1, formats); + }); + + return change.ops.length ? this.updateContents(change, source) : this.contents; + } + + formatText(from, to, formats, source) { + [ from, to, formats, source ] = this._normalizeArguments(from, to, formats, source); + if (from === to) { + if (this.activeFormats === empty) this.activeFormats = {}; + Object.keys(formats).forEach(key => this.activeFormats[key] = formats[key]); + return; + } + Object.keys(formats).forEach(name => formats[name] === false && (formats[name] = null)); + const text = this.getText(); + const change = this.delta().retain(from); + text.slice(from, to).split('\n').forEach(line => { + line.length && change.retain(line.length, formats).retain(1); + }); + + return this.updateContents(change, source); + } + + toggleLineFormat(from, to, format, source) { + [ from, to, format, source ] = this._normalizeArguments(from, to, format, source); + const existing = this.getLineFormat(from, to); + if (deepEqual(existing, format)) { + Object.keys(format).forEach(key => format[key] = null); + } + return this.formatLine(from, to, format, source); + } + + toggleTextFormat(from, to, format, source) { + [ from, to, format, source ] = this._normalizeArguments(from, to, format, source); + const existing = this.getTextFormat(from, to); + const isSame = Object.keys(format).every(key => format[key] === existing[key]); + if (isSame) { + Object.keys(format).forEach(key => format[key] = null); + } + return this.formatText(from, to, format, source); + } + + removeFormat(from, to, source) { + [ from, to, source ] = this._normalizeArguments(from, to, source); + const formats = {}; + + this.contents.getOps(from, to).forEach(({ op }) => { + op.attributes && Object.keys(op.attributes).forEach(key => formats[key] = null); + }); + + let change = this.delta().retain(from).retain(to - from, formats); + + // If the last block was not captured be sure to clear that too + this.contents.getLines(from, to).forEach(line => { + const formats = {}; + Object.keys(line.attributes).forEach(key => formats[key] = null); + change = change.compose(this.delta().retain(line.endIndex - 1).retain(1, formats)); + }); + + return this.updateContents(change, source); + } + + updateContents(change, source = SOURCE_USER, selection) { + const oldContents = this.contents; + const contents = normalizeContents(oldContents.compose(change)); + const length = contents.length(); + const oldSelection = this.selection; + if (!selection) selection = this.selection ? this.selection.map(i => change.transform(i)) : oldSelection; + selection = selection && this.getSelectedRange(selection, length - 1); + + const changeEvent = { contents, oldContents, change, selection, oldSelection, source }; + const selectionEvent = shallowEqual(oldSelection, selection) ? null : { selection, oldSelection, source }; + + if (change.ops.length && this.fire('text-changing', changeEvent)) { + setContents(this, contents); + if (selection) this.selection = selection; + + if (source !== SOURCE_SILENT) { + this.fire('text-change', changeEvent); + if (selectionEvent) this.fire('selection-change', selectionEvent); + } + this.fire('editor-change', changeEvent); + } + + return this.contents; + } + + setSelection(selection, source = SOURCE_USER) { + const oldSelection = this.selection; + selection = this.getSelectedRange(selection); + this.activeFormats = empty; + + if (shallowEqual(oldSelection, selection)) return false; + + this.selection = selection; + const event = { selection, oldSelection, source }; + + if (source !== SOURCE_SILENT) this.fire('selection-change', event); + this.fire('editor-change', event); + return true; + } + + getSelectedRange(range = this.selection, max = this.length - 1) { + if (range == null) return range; + if (typeof range === 'number') range = [ range, range ]; + if (range[0] > range[1]) [range[0], range[1]] = [range[1], range[0]]; + return range.map(index => Math.max(0, Math.min(max, index))); + } + + /** + * Normalizes range values to a proper range if it is not already. A range is a `from` and a `to` index, e.g. 0, 4. + * This will ensure the lower index is first. Example usage: + * editor._normalizeArguments(5); // [5, 5] + * editor._normalizeArguments(-4, 100); // for a doc with length 10, [0, 10] + * editor._normalizeArguments(25, 18); // [18, 25] + * editor._normalizeArguments([12, 13]); // [12, 13] + * editor._normalizeArguments(5, { bold: true }); // [5, 5, { bold: true }] + */ + _normalizeArguments(from, to, ...rest) { + if (Array.isArray(from)) { + if (to !== undefined || rest.length) rest.unshift(to); + [from, to] = from; + if (to === undefined) to = from; + } else if (typeof from !== 'number') { + if (to !== undefined || rest.length) rest.unshift(to); + if (from !== undefined || rest.length) rest.unshift(from); + from = to = 0; + } else if (typeof to !== 'number') { + if (to !== undefined || rest.length) rest.unshift(to); + to = from; + } + from = Math.max(0, Math.min(this.length, ~~from)); + to = Math.max(0, Math.min(this.length, ~~to)); + if (from > to) { + [from, to] = [to, from]; + } + return [from, to].concat(rest); + } +} + +function cleanDelete(editor, from, to, change) { + if (from !== to) { + const lineFormat = editor.getLineFormat(from); + if (!deepEqual(lineFormat, editor.getLineFormat(to))) { + const lineChange = editor.getChange(() => editor.formatLine(to, lineFormat)) + change = change.compose(change.transform(lineChange)); + } + } + return change; +} + +function normalizeContents(contents) { + if (!contents.ops.length || contents.ops[contents.ops.length - 1].insert.slice(-1) !== '\n') contents.insert('\n'); + return contents; +} + +function setContents(editor, contents) { + normalizeContents(contents); + contents.push = function() { return this; } // freeze from modification + editor.contents = contents; + editor.length = contents.length(); +} + +function combineFormats(formats, combined) { + return Object.keys(combined).reduce(function(merged, name) { + if (formats[name] == null) return merged; + if (combined[name] === formats[name]) { + merged[name] = combined[name]; + } else if (Array.isArray(combined[name])) { + if (combined[name].indexOf(formats[name]) < 0) { + merged[name] = combined[name].concat([formats[name]]); + } + } else { + merged[name] = [combined[name], formats[name]]; + } + return merged; + }, {}); +} + +Delta.prototype.getLines = function(from, to, predicate) { + let startIndex = 0; + const lines = []; + this.eachLine((contents, attributes, number) => { + if (startIndex >= to) return false; + const endIndex = startIndex + contents.length() + 1; + if (endIndex > from || (from === to && endIndex === to)) { + lines.push({ contents, attributes, number, startIndex, endIndex }); + } + startIndex = endIndex; + }); + return lines; +} + +Delta.prototype.getLine = function(at) { + return this.getLines(at, at)[0]; +} + +Delta.prototype.getOps = function(from, to) { + let startIndex = 0; + const ops = []; + this.ops.some(op => { + if (startIndex >= to) return true; + const endIndex = startIndex + deltaOp.length(op); + if (endIndex > from || (from === to && endIndex === to)) { + ops.push({ op, startIndex, endIndex }); + } + startIndex = endIndex; + }); + return ops; +} + +Delta.prototype.getOp = function(from) { + return this.getOps(from, from)[0]; +} diff --git a/src/editor/README.md b/src/editor/README.md deleted file mode 100644 index ccd1776..0000000 --- a/src/editor/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Dabble Editor - -The editor controls text input and interaction with the content within a Dabble document. It runs the editing commands, -controls the undo/redo stack, contains the logic for how the user interactions should behave within the document, and -provides an API to work upon the document. - - -## History - -A History object can be used to execute actions against the current project. The history stores these actions -(optionally limited in length), allowing for undo and redo. These actions are plain JavaScript objects and can be stored -in a database, allowing for undo/redo across sessions of document editing. - -### History.fromJSON(obj) - -A static property that will return a new `History` instance from an old history object that was serialized by JSON. - -### history.exec(commandName, action) - -Executes the command added by `commandName` on `History.commands`. This passes the `action` object to the command. Each -command expects different properties on the action in order to successfully run. - -### history.canUndo - -A Boolean property indicating whether there are any actions that can be undone. - -### history.canRedo - -A Boolean property indicating whether there are any actions that can be redone. - -### history.undo() - -Will undo the last action in the history. - -### history.redo() - -Will redo the last undone action in the history. - diff --git a/src/editor/arrayfind-polyfill.js b/src/editor/arrayfind-polyfill.js deleted file mode 100644 index 92a76fc..0000000 --- a/src/editor/arrayfind-polyfill.js +++ /dev/null @@ -1,23 +0,0 @@ -if (!Array.prototype.find) { - Array.prototype.find = function(predicate) { - 'use strict'; - if (this == null) { - throw new TypeError('Array.prototype.find called on null or undefined'); - } - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - var list = Object(this); - var length = list.length >>> 0; - var thisArg = arguments[1]; - var value; - - for (var i = 0; i < length; i++) { - value = list[i]; - if (predicate.call(thisArg, value, i, list)) { - return value; - } - } - return undefined; - }; -} diff --git a/src/editor/blocks/block.js b/src/editor/blocks/block.js deleted file mode 100644 index f6fa589..0000000 --- a/src/editor/blocks/block.js +++ /dev/null @@ -1,77 +0,0 @@ -module.exports = Block; -var Class = require('chip-utils/class'); -var selectors = require('../selectors'); - -/** - * A block of content such as a paragraph or blockquote - * @param {String} selector The selector for this block - * @param {String} text The text within the block - * @param {String} markups An array of markups for this block - */ -function Block(selector, text, markups) { - if (!selector) throw new TypeError('A selector is required to create a block'); - this.id = getId(); - this.selector = selector; - this.text = text || ''; - this.markups = markups || []; -} - -Class.extend(Block, { - static: { - /** - * Determines what markups this block is limited to. If null, all markups are allowed. - */ - limitMarkupsTo: null, - - /** - * Determines how the enter key works within this block. - * enterModes: - * * regular - moves onto a P next, allows splits, allows BRs, e.g. headers, blockquotes, paragraphs - * * continuation - moves onto same block unless empty then to a P, allows BRs, e.g. lists - * * contained - creates BRs, always followed by a block, e.g. preformatted - * * leaveOnly - no splits, no BRs, moves to P when at the end only, e.g. figcaption - * * none - no splits, no BRs, no new blocks - */ - enterMode: 'regular' - }, - - getLimitMarkupsTo: function() { - return this.constructor.limitMarkupsTo; - }, - - getEnterMode: function() { - return this.constructor.enterMode; - }, - - createElement: function() { - var block = selectors.createElement(this.selector); - block.setAttribute('name', this.id); - return block; - }, - - same: function(block) { - return block && this.constructor === block.constructor && this.selector === block.selector; - }, - - equals: function(block) { - return this.same(block) && - this.text === block.text && - this.markups.length === block.markups.length && - this.markups.every(function(markup, index) { - return markup.equals(block.markups[index]); - }); - }, - - clone: function(newId) { - var block = new this.constructor(this.selector, this.text, this.markups.map(function(markup) { - return markup.clone(); - })); - if (!newId) block.id = this.id; - return block; - } -}); - - -function getId() { - return Math.random().toString(36).slice(2, 7); -} diff --git a/src/editor/blocks/header.js b/src/editor/blocks/header.js deleted file mode 100644 index 49e929b..0000000 --- a/src/editor/blocks/header.js +++ /dev/null @@ -1,22 +0,0 @@ -module.exports = HeaderBlock; -var Block = require('./block'); - - -function HeaderBlock(selector, text, markups) { - Block.call(this, selector, text, markups); -} - -Block.extend(HeaderBlock, { - static: { - limitMarkups: [ 'a[href]' ] - }, - - createElement: function() { - var element = Block.prototype.createElement.call(this); - var trimmed = this.text.trim().toLowerCase(); - if (trimmed) { - element.id = trimmed.replace("'", '').replace(/[^a-z]+/, '-').replace(/^-|-$/g, ''); - } - return element; - } -}); diff --git a/src/editor/blocks/image.js b/src/editor/blocks/image.js deleted file mode 100644 index 4a400c0..0000000 --- a/src/editor/blocks/image.js +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = ImageBlock; -var Block = require('./block'); -var selectors = require('../selectors'); - - -function ImageBlock(selector, text, markups, metadata) { - if (!metadata) metadata = { src: '' }; - Block.call(this, selector, text, markups, metadata); -} - -Block.extend(ImageBlock, { - static: { - enterMode: 'leaveOnly', - }, - - createElement: function() { - var figure = selectors.createElement('figure[contenteditable="false"]'); - var image = selectors.createElement('img[src="' + this.metadata.src + '"]'); - var caption = selectors.createElement('figcaption[contenteditable="true"]'); - figure.setAttribute('name', this.id); - figure.appendChild(image); - figure.appendChild(caption); - return figure; - }, - - equals: function(block) { - return Block.prototype.equals.call(this, block) && this.metadata.src === block.metadata.src; - }, - - clone: function(constructor) { - return new Block(this.selector, this.text, this.markups.map(function(markup) { - return markup.clone(); - }), { src: this.metadata.src }); - } -}); diff --git a/src/editor/blocks/list.js b/src/editor/blocks/list.js deleted file mode 100644 index 3679e18..0000000 --- a/src/editor/blocks/list.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = ListBlock; -var Block = require('./block'); - - -function ListBlock(selector, text, markups) { - Block.call(this, selector, text, markups); -} - -Block.extend(ListBlock, { - static: { - enterMode: 'continuation' - } -}); diff --git a/src/editor/blocks/preformatted.js b/src/editor/blocks/preformatted.js deleted file mode 100644 index e454255..0000000 --- a/src/editor/blocks/preformatted.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = PreformattedBlock; -var Block = require('./block'); - - -function PreformattedBlock(selector, text, markups) { - Block.call(this, selector, text, markups); -} - -Block.extend(PreformattedBlock, { - static: { - enterMode: 'contained' - } -}); diff --git a/src/editor/command.js b/src/editor/command.js deleted file mode 100644 index e933e0e..0000000 --- a/src/editor/command.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = Command; -var Class = require('chip-utils/class'); - -function Command(args) { - -} - -Class.extend(Command, { - // Set by the history that executes the command - editor: null, - selectionAfter: null, - selectionBefore: null, - - exec: function() { - throw new Error('Abstract method `exec` needs to be overridden'); - }, - - undo: function() { - throw new Error('Abstract method `undo` needs to be overridden'); - }, - - redo: function() { - // Default implementation, only override if there is a difference in implementation - this.exec(); - } -}); diff --git a/src/editor/commands/composite.js b/src/editor/commands/composite.js deleted file mode 100644 index 416ed91..0000000 --- a/src/editor/commands/composite.js +++ /dev/null @@ -1,40 +0,0 @@ -module.exports = CompositeCommand; -var Command = require('../command'); - -/** - * Runs multiple commands at once. These will all be undone and redone together in order. - * @param {Array} commands An array of commands which MUST be of type Command. - */ -function CompositeCommand(args) { - if (!args || !Array.isArray(args.commands)) { - throw new TypeError('Invalid commands for CompositeCommand. Expected Array and got:', args); - } - args.commands.forEach(function(command) { - if (!(command instanceof Command)) { - throw new TypeError('`command` is not an instance of Command in CompositeCommand'); - } - }); - this.commands = args.commands; -} - -Command.extend(CompositeCommand, { - - exec: function() { - this.commands.forEach(function(command) { - command.editor = this.editor; - command.exec(); - }, this); - }, - - undo: function() { - this.commands.slice().reverse().forEach(function(command) { - command.undo(); - }); - }, - - redo: function() { - this.commands.forEach(function(command) { - command.redo(); - }); - } -}); diff --git a/src/editor/commands/delete-block.js b/src/editor/commands/delete-block.js deleted file mode 100644 index 98d0960..0000000 --- a/src/editor/commands/delete-block.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = DeleteBlockCommand; -var Command = require('../command'); -var mapping = require('../mapping'); - -/** - * Deletes a block from an editor. - * @param {Editor} index The index as where this block is being deleted - * @param {Number} index The index as where this block is being deleted - */ -function DeleteBlockCommand(args) { - if (!args || typeof args.index !== 'number' || args.index < 0) { - throw new TypeError('Invalid arguments, DeleteBlockCommand requires a valid index'); - } - this.index = args.index; -} - -Command.extend(DeleteBlockCommand, { - - exec: function() { - this.block = this.editor.blocks.splice(this.index, 1)[0]; - mapping.removeElement(this.editor, this.block); - }, - - undo: function() { - this.editor.blocks.splice(this.index, 0, this.block); - mapping.generateElement(this.editor, this.block); - } -}); diff --git a/src/editor/commands/index.js b/src/editor/commands/index.js deleted file mode 100644 index cf96e25..0000000 --- a/src/editor/commands/index.js +++ /dev/null @@ -1,4 +0,0 @@ -exports.composite = require('./composite'); -exports.deleteBlock = require('./delete-block'); -exports.insertBlock = require('./insert-block'); -exports.updateBlock = require('./update-block'); diff --git a/src/editor/commands/insert-block.js b/src/editor/commands/insert-block.js deleted file mode 100644 index 719a627..0000000 --- a/src/editor/commands/insert-block.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = InsertBlockCommand; -var Command = require('../command'); -var Block = require('../blocks/block'); -var mapping = require('../mapping'); - -/** - * Creates and adds a block to an editor. - * @param {Number} index The index as which this block is being inserted - * @param {Object} block A block object which will be added to the editor - */ -function InsertBlockCommand(args) { - if (!args || typeof args.index !== 'number' || args.index < 0 || !(args.block instanceof Block)) { - throw new TypeError('Invalid arguments, InsertBlockCommand requires a valid index and block'); - } - this.index = args.index; - this.block = args.block; -} - -Command.extend(InsertBlockCommand, { - - exec: function() { - this.editor.blocks.splice(this.index, 0, this.block); - mapping.generateElement(this.editor, this.block); - }, - - undo: function() { - this.editor.blocks.splice(this.index, 1); - mapping.removeElement(this.editor, this.block); - } -}); diff --git a/src/editor/commands/update-block.js b/src/editor/commands/update-block.js deleted file mode 100644 index 718f768..0000000 --- a/src/editor/commands/update-block.js +++ /dev/null @@ -1,33 +0,0 @@ -module.exports = UpdateBlockCommand; -var Command = require('../command'); -var Block = require('../blocks/block'); -var mapping = require('../mapping'); -var stylesExp = / style="[^"]+"/g; -/** - * Udates a block in an editor. - * @param {Number} index The index as which this block is located - * @param {Object} block The new block object which will update the old - */ -function UpdateBlockCommand(args) { - if (!args || typeof args.index !== 'number' || args.index < 0 || !(args.block instanceof Block)) { - throw new TypeError('Invalid arguments, UpdateBlockCommand requires a valid index and block'); - } - this.index = args.index; - this.block = args.block; - this.oldBlock = null; -} - -Command.extend(UpdateBlockCommand, { - - exec: function() { - this.oldBlock = this.editor.blocks.splice(this.index, 1, this.block)[0]; - this.block.id = this.oldBlock.id; - mapping.generateElement(this.editor, this.block); - }, - - undo: function() { - var newBlock = this.editor.blocks.splice(this.index, 1, this.oldBlock)[0]; - this.oldBlock.id = newBlock.id; - mapping.generateElement(this.editor, this.oldBlock); - } -}); diff --git a/src/editor/editor.js b/src/editor/editor.js deleted file mode 100644 index 47b8525..0000000 --- a/src/editor/editor.js +++ /dev/null @@ -1,402 +0,0 @@ -module.exports = Editor; -require('es6-object-assign').polyfill(); -var Class = require('chip-utils/class'); -var shortcuts = require('shortcut-string'); -var History = require('./history'); -var EditorSelection = require('./selection'); -var EditorRange = require('./range'); -var defaultSchema = require('./schema/default'); -var mapping = require('./mapping'); -var menu = require('./menu'); -var Block = require('./blocks/block'); -var interactions = require('./interactions'); -var slice = Array.prototype.slice; -var modifiedEvents = [ 'input', 'focus', 'blur', 'focusin', 'focusout', 'paste' ]; -require('./selectionchange-polyfill'); -require('./arrayfind-polyfill'); - - -/** - * Creates a rich text editor within the element provided. - * @param {HTMLElement} element An HTML element which will be used as the container for the editable content - * @param {Object} options A hash of options for the editor - */ -function Editor(element, options) { - this.element = element; - this.options = options || {}; - this.schema = this.options.schema || defaultSchema.get(); - this.history = new History(); - this.selection = new EditorSelection(this); - this.blocks = this.schema.getInitial(); - this.onKeyDown = this.onKeyDown.bind(this); - if (!options.initiallyDisabled) { - this.enabled = true; - } - this.render(); -} - - -Class.extend(Editor, { - static: { - menu: menu, - - /** - * The currently active editor, or null if there is none. The active editor is the one which has focus. - * @type {Editor} - */ - get active() { - return EditorSelection.activeEditor; - } - }, - - menu: menu, - - - /** - * Whether an editor is enabled. By default it is enabled, but if disabled you cannot type or edit within it. - * @type {Boolean} - */ - get enabled() { - return !!this.element && this.element.contentEditable === 'true'; - }, - - set enabled(value) { - if (value === this.enabled) return; - if (value) { - this.element.setAttribute('spellcheck', 'false'); - this.element.classList.add('editable'); - this.on('keydown', this.onKeyDown); - this.on('input', this.onInput); - this.on('focus', this.onFocus); - this.on('blur', this.onBlur); - modifiedEvents.forEach(addEditorToEvent.bind(null, this)); - this.element.editor = this; - Object.keys(interactions).forEach(function(key) { - interactions[key].enable(this); - }, this); - } else { - this.element.removeAttribute('spellcheck'); - this.element.classList.remove('editable'); - this.off('keydown', this.onKeyDown); - this.off('input', this.onInput); - this.off('focus', this.onFocus); - this.off('blur', this.onBlur); - modifiedEvents.forEach(removeEditorToEvent.bind(null, this)); - this.element.editor = null; - Object.keys(interactions).forEach(function(key) { - interactions[key].disable(this); - }, this); - } - this.element.contentEditable = value; - }, - - - /** - * The HTML contents of the editor. Internally the editor contains blocks of content, but this gets or sets the - * current HTML representation of that content. - * @type {String} - */ - get html() { - var content = this.element.cloneNode(true); - slice.call(content.querySelectorAll(this.schema.blocksSelector)).forEach(function(blockElement) { - blockElement.removeAttribute('name'); - blockElement.removeAttribute('placeholder'); - blockElement.classList.remove('empty', 'selected'); - if (blockElement.getAttribute('class') === '') { - blockElement.removeAttribute('class'); - } - }); - return content.innerHTML; - }, - - set html(html) { - var div = document.createElement('div'); - div.innerHTML = html; - this.blocks = mapping.blocksFromDOM(this, div); - this.render(); - }, - - - /** - * The HTML contents of the editor. Internally the editor contains blocks of content, but this gets or sets the - * current HTML representation of that content. - * TODO allow blocks to parse/output text so lists can work with "1. List item" and "* item" correctly - * @type {String} - */ - get text() { - var count = 0; - var tagExp = /^[a-z1-6]+/; - var headerExp = /^h(\d)$/; - - return this.blocks.map(function(block, i, blocks) { - var tag = block.selector.match(tagExp)[0]; - var prev = blocks[i - 1]; - var text = block.text; - var match; - - if (tag === 'ol') { - text = ++count + '. ' + text; - } else { - count = 0; - } - - if (tag === 'ul') { - text = '* ' + text; - } else if (tag === 'blockquote') { - text = '> ' + text; - } else if ((match = tag.match(headerExp))) { - text = ' ' + text; - var i = parseInt(match[1]); - while (i--) text = '#' + text; - } - - if (prev) { - if ((tag === 'ul' || tag === 'ol') && prev.selector === block.selector) { - text = '\n' + text; - } else { - text = '\n\n' + text; - } - } - return text; - }).join(''); - }, - - set text(text) { - var prefixExp = /^(#+|\d+\.|\*|>) /; - - this.blocks = text.split(/\n/).map(function(text) { - if (!text) return; - var selector = this.schema.defaultBlock; - var match = text.match(prefixExp); - if (match) { - var prefix = match[1]; - if (prefix[0] === '#') { - selector = 'h' + prefix.length; - } else if (prefix === '*') { - selector = 'ul>li'; - } else if (prefix === '>') { - selector = 'blockquote'; - } else { - selector = 'ol>li'; - } - if (!this.schema.blocks[selector]) { - selector = this.schema.defaultBlock; - } else { - text = text.replace(prefixExp, ''); - } - } - - var BlockType = this.schema.blocks[selector]; - var block = new BlockType(selector); - block.text = text; - return block; - }, this).filter(Boolean); - this.render(); - }, - - /** - * The history object for this editor - * @return {History} This editor's history for undo/redo - */ - get history() { - return this._history; - }, - - set history(value) { - this._history = value; - }, - - /** - * Returns all the block elements - * @return {Array} An array of all the block elements - */ - get blockElements() { - return slice.call(this.element.querySelectorAll(this.schema.blocksSelector)); - }, - - isBlockType: function(selector) { - return this.selection.selectedBlocks.every(function(block) { - return block.selector === selector; - }); - }, - - isMarkupType: function(selector) { - var selection = this.selection; - return selection.selectedBlocks.every(function(block, index) { - // check if the selected range in this block is already marked up with the same type - var startOffset = index === 0 ? selection.startOffset : 0; - var endOffset = index === selection.endBlockIndex - selection.startBlockIndex ? - selection.endOffset : block.text.length; - return block.markups.some(function(markup) { - return markup.selector === selector && markup.startOffset <= startOffset && markup.endOffset >= endOffset; - }); - }); - }, - - - setBlockType: function(selector, toggle) { - var BlockType = this.schema.blocks[selector]; - if (!BlockType) return; - - var selectedBlocks = this.selection.selectedBlocks; - var blockStart = this.selection.startBlockIndex; - - if (toggle) { - if (this.isBlockType(selector)) { - selector = this.schema.defaultBlock; - BlockType = this.schema.blocks[selector]; - } - } - - this.startTransaction(); - selectedBlocks.forEach(function(block, index) { - if (block.selector === selector) return; - block = block.clone(BlockType); - block = new BlockType(selector, block.text, block.markups); - this.exec('updateBlock', { index: blockStart + index, block: block }); - }, this); - this.setTransactionSelection(this.selection.range); - this.commit(); - }, - - - toggleBlockType: function(selector) { - this.setBlockType(selector, true); - }, - - - toggleMarkup: function(selector) { - var MarkupType = this.schema.markups[selector]; - if (!MarkupType) return; - - var selectedBlocks = this.selection.selectedBlocks; - var blockStart = this.selection.startBlockIndex; - var textStart = this.selection.startOffset; - var blockEnd = this.selection.endBlockIndex; - var textEnd = this.selection.endOffset; - - var toggleOff = selectedBlocks.every(function(block, index) { - // check if the selected range in this block is already marked up with the same type - var startOffset = index === 0 ? textStart : 0; - var endOffset = index === selectedBlocks.length - 1 ? textEnd : block.text.length; - if (startOffset === endOffset) return true; - return block.markups.some(function(markup) { - return markup.selector === selector && markup.startOffset <= startOffset && markup.endOffset >= endOffset; - }); - }); - - this.startTransaction(); - - // TODO clean this up, moving methods to block/markup/schema where necessary - - selectedBlocks.forEach(function(block, index) { - var startOffset = index === 0 ? textStart : 0; - var endOffset = index === selectedBlocks.length - 1 ? textEnd : block.text.length; - if (startOffset === endOffset) return; - block = block.clone(); - - // Remove the markup (or reduce it) - // TODO determine if this could be part of the Add Markup code, but with a negative markup object, (flag for remove?) - if (toggleOff) { - block.markups.some(function(markup, i) { - if (markup.selector !== selector || markup.startOffset > startOffset || markup.endOffset < endOffset) { - return false; - } - - // If the whole markup needs to be removed - if (markup.startOffset === startOffset && markup.endOffset === endOffset) { - block.markups.splice(i, 1); - // If the markup needs to be split into two - } else if (markup.startOffset !== startOffset && markup.endOffset !== endOffset) { - var secondMarkup = markup.clone(); - markup.endOffset = startOffset; - secondMarkup.startOffset = endOffset; - block.markups.splice(i + 1, 0, secondMarkup); - // If the markup needs to be shortened at the front or back - } else if (markup.startOffset === startOffset) { - markup.startOffset = endOffset; - } else { - markup.endOffset = startOffset; - } - - return true; - }); - - // Add the markup - } else { - - var markup = new MarkupType(selector, startOffset, endOffset); - block.markups.push(markup); - - this.schema.normalizeMarkups(block); - } - - this.setTransactionSelection(this.selection.range); - this.exec('updateBlock', { index: blockStart + index, block: block }); - }, this); - this.commit(); - }, - - exec: function(commandName, action) { - return this.history.exec(this, commandName, action); - }, - - startTransaction: function() { - return this.history.start(this); - }, - - commit: function() { - return this.history.commit(); - }, - - setTransactionSelection: function(type, anchorIndex, anchorOffset, focusIndex, focusOffset) { - if (type instanceof EditorRange) { - this.history.setNextSelection(type); - } else { - this.history.setNextSelection(new EditorRange(this, type, anchorIndex, anchorOffset, - focusIndex !== undefined ? focusIndex : anchorIndex, - focusOffset !== undefined ? focusOffset : anchorOffset)); - } - }, - - on: function(event, listener) { - this.element.addEventListener(event, listener); - }, - - off: function(event, listener) { - this.element.removeEventListener(event, listener); - }, - - dispatch: function(eventName, data) { - if (!this.element) return; - var event = new Event(eventName, data); - if (data) Object.keys(data).forEach(function(key) { - event[key] = data[key]; - }); - event.editor = this; - return this.element.dispatchEvent(event); - }, - - render: function() { - mapping.generateElements(this); - }, - - onKeyDown: function(event) { - var shortcut = shortcuts.fromEvent(event); - if (!this.dispatch('shortcut', { cancelable: true, shortcut: shortcut, originalEvent: event })) { - event.preventDefault(); - } - } -}); - - -function addEditorToEvent(editor, eventName) { - editor._modifiedEvents = {}; - editor.on(eventName, editor._modifiedEvents[eventName] = function(event) { - event.editor = editor; - }); -} - -function removeEditorToEvent(editor, eventName) { - editor.off(eventName, editor._modifiedEvents[eventName]); -} diff --git a/src/editor/editor.less b/src/editor/editor.less deleted file mode 100644 index 9ffc0e0..0000000 --- a/src/editor/editor.less +++ /dev/null @@ -1,89 +0,0 @@ -#editor-menu { - position: absolute; - background-image: linear-gradient(to bottom, rgba(49, 49, 47, .99), #262625); - border-radius: 5px; - animation: pop-upwards 180ms forwards linear; -} -#editor-menu.active { - transition: top 75ms ease-out, left 75ms ease-out; -} -#editor-menu::after { - content: ''; - position: absolute; - bottom: -13px; - left: 50%; - margin-left: -7px; - border: 7px solid transparent; - border-top-color: #262625; -} -#editor-menu button { - height: 42px; - line-height: 42px; - vertical-align: middle; - border: none; - padding: 0 8px; - color: #fff; - background: none; - font-size: 14px; - outline: none; - cursor: pointer; -} -#editor-menu button:first-child { - padding-left: 14px; -} -#editor-menu button:last-child { - padding-right: 14px; -} -#editor-menu button.active { - color: limegreen; -} -#editor-menu .editor-menu-separator { - display: inline-block; - vertical-align: middle; - width: 1px; - margin: 0 6px; - height: 24px; - background: rgba(255, 255, 255, .2); -} -@keyframes pop-upwards { - 0% { - transform: matrix(.97, 0, 0, 1, 0, 12); - opacity: 0 - } - 20% { - transform: matrix(.99, 0, 0, 1, 0, 2); - opacity: .7 - } - 40% { - transform: matrix(1, 0, 0, 1, 0, -1); - opacity: 1 - } - 70% { - transform: matrix(1, 0, 0, 1, 0, 0); - opacity: 1 - } - 100% { - transform: matrix(1, 0, 0, 1, 0, 0); - opacity: 1 - } -} - -#editor-menu .editor-menu-input { - display: none; -} - -#editor-menu .icon { - font-size: 20px; -} -.icon.editor-bold::before { - content: '\E238'; -} -.icon.editor-italic::before { - content: '\E23F'; -} -.icon.editor-header::before { - content: '\E262'; -} -.icon.editor-quote::before { - content: '\E244'; -} diff --git a/src/editor/history.js b/src/editor/history.js deleted file mode 100644 index 756643a..0000000 --- a/src/editor/history.js +++ /dev/null @@ -1,184 +0,0 @@ -module.exports = History; -var Class = require('chip-utils/class'); -var Command = require('./command'); -var slice = Array.prototype.slice; -var commands = require('./commands'); - -/** - * Holds the history of a project for a user. This can be serialzied and stored in the database because each action is - * represented by a plain JavaScript object. The code to execute, undo, and redo these actions is contained in the - * commands folder. - */ -function History(options) { - this.undoStack = []; - this.redoStack = []; - this.composite = null; - this.nextSelection = null; -} - - -Class.extend(History, { - static: { - // The default commands, more can be added using History.commands[commandName] = { exec: function, undo: function } - commands: commands - }, - - // Make the commands available to "this", also allowing for substituting them on a single instance (for testing) - commands: commands, - - // The default options, can be changed using History.options.maxHistory = 123 - options: { - // limits the length of history stored in memory - maxHistory: 100 - }, - - /** - * Whether there are any actions that can be undone or not - * @return {Boolean} - */ - get canUndo() { - return this.undoStack.length > 0; - }, - - /** - * Whether there are any actions that can be redone or not - * @return {Boolean} - */ - get canRedo() { - return this.redoStack.length > 0; - }, - - /** - * Execute a command by name with the given arguments. Each command may take different arguments. - * @param {Editor} editor The editor to apply this command to - * @param {String} commandName The name of the command to be executed - * @param {Object} args Zero or more arguments which will be passed to the command - * @return {Boolean} `false` if the command failed, `true` otherwise - */ - exec: function(editor, commandName, args) { - var command; - if (commandName instanceof Command) { - command = commandName; - } else { - var CommandClass = this.commands[commandName]; - if (!CommandClass) { - throw new TypeError('No such command "' + commandName + '" exists'); - } - command = new CommandClass(args); - } - - command.editor = editor; - - // If we are in a command transaction started with `start` append this command to composite to be exec'ed later - if (this.composite) { - this.composite.commands.push(command); - return command; - } - - var rangeNow = editor.selection.getRange(); - - if (command.exec() === false) { - return false; - } - - if (this.nextSelection) { - command.selectionBefore = editor.selection.range; - command.selectionAfter = this.nextSelection; - this.nextSelection = null; - } else { - command.selectionBefore = editor.selection.range; - command.selectionAfter = rangeNow; - } - - editor.selection.range = command.selectionAfter; - editor.selection.update(); - - this.undoStack.push(command); - if (this.options.maxHistory && this.undoStack.length >= this.options.maxHistory) { - this.undoStack = this.undoStack.slice(-this.options.maxHistory); - } - this.redoStack.length = 0; - - editor.dispatch('change'); - editor.dispatch('editorchange', { bubbles: true }); - - return command; - }, - - /** - * Start a composite command. Every `exec` call after this will add to the composite until `commit` is called. - */ - start: function(editor) { - if (this.composite && this.composite.editor !== editor) { - throw new Error('Two editors trying to run a transaction at once with the same history. Can\'t compute.'); - } - if (this.composite) { - return this.composite; - } else { - this.composite = new commands.composite({ commands: [] }); - this.composite.editor = editor; - return this.composite; - } - }, - - /** - * Finishes a composite command started with `start` and executes it. - * @return {Boolean} The result of executing the composite command - */ - commit: function() { - if (this.composite) { - var command = this.composite; - var editor = command.editor; - this.composite = null; - if (command.commands.length) { - if (command.commands.length === 1) command = command.commands[0]; - return this.exec(editor, command); - } - } - this.nextSelection = null; - return false; - }, - - /** - * Set the selection used for the end of the next exec operation - * @param {SelectionRange} range The selection range that will be used - */ - setNextSelection: function(range) { - this.nextSelection = range; - }, - - - /** - * Undo the last action taken - * @return {Boolean} A `false` if there is no command to undo, `true` if the command was undone - */ - undo: function() { - var command = this.undoStack.pop(); - if (!command) return false; - - command.undo(); - command.editor.selection.range = command.selectionBefore; - this.redoStack.push(command); - command.editor.dispatch('change'); - command.editor.dispatch('editorchange', { bubbles: true }); - return true; - }, - - - /** - * Redo the last undone action - * @return {Boolean} A `false` if there is no command to redo, `true` if the command was redone - */ - redo: function() { - var command = this.redoStack.pop(); - if (!command) return false; - - command.redo(); - command.editor.selection.range = command.selectionAfter; - this.undoStack.push(command); - command.editor.dispatch('change'); - command.editor.dispatch('editorchange', { bubbles: true }); - return true; - } - -}); diff --git a/src/editor/interactions/backspace.js b/src/editor/interactions/backspace.js deleted file mode 100644 index 5366430..0000000 --- a/src/editor/interactions/backspace.js +++ /dev/null @@ -1,121 +0,0 @@ -var utils = require('./utils'); -var mergeDeletes = false; -var changing = false; -var isDeletion = /Backspace|Delete/; -var isOption = /Alt\+/; -var isBackspace = /Backspace/; - -/** - * Handle when a user preses the backspace key or delete key - */ - -exports.enable = function(editor) { - editor.on('shortcut', onShortcut); - editor.on('change', onChange); - editor.on('selectionchange', onSelectionChange); -}; - -exports.disable = function(editor) { - editor.off('shortcut', onShortcut); - editor.off('change', onChange); - editor.off('selectionchange', onSelectionChange); -}; - -function onShortcut(event) { - // Let text-entry handle Option+Deletes within the block - if (isDeletion.test(event.shortcut)) { - var sel = event.editor.selection; - var back = isBackspace.test(event.shortcut); - var del = !back; - var end = sel.startBlock.text.length; - - if (sel.isCollapsed && !(back && sel.startOffset === 0) && !(del && sel.startOffset === end)) { - return; - } - - event.preventDefault(); - onDelete(event.editor, back ? -1 : 1); - } -} - -// If the selection changed and it wasn't the result of an delete, don't merge next deletes -function onSelectionChange(event) { - if (!changing && mergeDeletes) { - mergeDeletes = false; - } -} - -// If something changed that wasn't an delete, don't merge next deletes -function onChange(event) { - if (!changing && mergeDeletes) { - mergeDeletes = false; - } -} - - -function onDelete(editor, direction) { - changing = true; - - editor.startTransaction(); - - var start = editor.selection.startBlock; - var startBlockIndex = editor.selection.startBlockIndex; - var startOffset = editor.selection.startOffset; - var endIndex = start.text.length; - - if (editor.selection.isCollapsed) { - var updated, merged; - - if (direction === -1 && startOffset === 0) { - var previous = editor.blocks[startBlockIndex - 1]; - var enterMode = previous && previous.getEnterMode(); - if (previous && enterMode !== 'none' || enterMode === 'leaveOnly') { - editor.setTransactionSelection('text', startBlockIndex - 1, previous.text.length); - // merge with the previous block - merged = utils.mergeBlocks(editor, previous, start, 0); - editor.exec('deleteBlock', { index: startBlockIndex }); - if (merged) { - editor.exec('updateBlock', { index: startBlockIndex - 1, block: merged }); - } - } - } else if (direction === 1 && startOffset === endIndex) { - var next = editor.blocks[startBlockIndex + 1]; - var enterMode = next && next.getEnterMode(); - editor.setTransactionSelection('text', startBlockIndex, startOffset); - if (next && enterMode !== 'none' || enterMode === 'leaveOnly') { - // merge with the following block - merged = utils.mergeBlocks(editor, start, next, 0); - editor.exec('deleteBlock', { index: startBlockIndex + 1 }); - editor.exec('updateBlock', { index: startBlockIndex, block: merged }); - } - } else { - // No special cases, just make text less - editor.setTransactionSelection('text', startBlockIndex, (direction < 0 ? startOffset + direction : startOffset)); - var endOffset = startOffset + direction; - var first = Math.min(startOffset, endOffset), second = Math.max(startOffset, endOffset); - updated = utils.deleteTextInBlock(editor, start, first, second); - editor.exec('updateBlock', { index: startBlockIndex, block: updated }); - editor.commit(); - - if (mergeDeletes) { - // Remove the newly added command and merge it with the previous - var undoStack = editor.history.undoStack; - var command = undoStack.pop(); - var lastCommand = undoStack[undoStack.length - 1]; - var updateCommand = lastCommand.commands ? lastCommand.commands[lastCommand.commands.length - 1] : lastCommand; - // Update the previous command - updateCommand.block = command.block; - lastCommand.selectionAfter = command.selectionAfter; - } - } - - } else { - editor.setTransactionSelection('text', startBlockIndex, startOffset); - utils.deleteSelection(editor, true); - } - - editor.commit(); - - mergeDeletes = true; - changing = false; -} diff --git a/src/editor/interactions/enter.js b/src/editor/interactions/enter.js deleted file mode 100644 index cf12110..0000000 --- a/src/editor/interactions/enter.js +++ /dev/null @@ -1,92 +0,0 @@ -var utils = require('./utils'); - -/** - * Handle when a user preses the enter key - * - * 1. When at the end of a block, create a new block of the correct type or a BR - * 2. When in the middle of a block, split it into two (same block type) if allowed - * 3. When pressing Shift+Enter create a BR in the text if allowed - * - * - * enterModes: - * * regular - moves onto a P next, allows splits, allows BRs, e.g. headers, blockquotes, paragraphs - * * continuation - moves onto same block unless empty then to a P, allows BRs, e.g. lists - * * contained - creates BRs, always followed by a block, e.g. preformatted - * * leaveOnly - no splits, no BRs, moves to P when at the end only, e.g. figcaption - * * none - no splits, no BRs, no new blocks - */ - -exports.enable = function(editor) { - editor.on('shortcut', onShortcut); -}; - -exports.disable = function(editor) { - editor.off('shortcut', onShortcut); -}; - -function onShortcut(event) { - if (event.shortcut.indexOf('Enter') !== -1) { - event.preventDefault(); - - if (event.shortcut === 'Enter') { - onEnter(event.editor); - } else if (event.shortcut === 'Shift+Enter') { - onShiftEnter(event.editor); - } - } -} - - -function onEnter(editor) { - var start = editor.selection.startBlock; - var startBlockIndex = editor.selection.startBlockIndex; - var startOffset = editor.selection.startOffset; - var enterMode = start.getEnterMode(); - - if (enterMode === 'contained') { - return onShiftEnter(editor); - } else if (enterMode === 'none') { - return; - } - - editor.startTransaction(); - editor.setTransactionSelection('text', startBlockIndex + 1, 0); - - if (editor.selection.isCollapsed) { - var atEnd = startOffset === start.text.length; - if (atEnd) { - utils.insertAfter(editor, startBlockIndex) - } else if (enterMode !== 'leaveOnly') { - utils.splitBlock(editor, startBlockIndex, startOffset); - } - } else { - utils.deleteSelection(editor); - utils.insertAfter(editor, startBlockIndex); - } - - editor.commit(); -} - - -function onShiftEnter(editor) { - var start = editor.selection.startBlock; - var startBlockIndex = editor.selection.startBlockIndex; - var startOffset = editor.selection.startOffset; - if (start.getEnterMode() === 'none') { - return; - } - - editor.setTransactionSelection('text', startBlockIndex, startOffset + 1); - - if (editor.selection.isCollapsed) { - var updated = start.clone(); - updated.text = updated.text.slice(0, startOffset) + '\n' + updated.text.slice(startOffset); - editor.exec('updateBlock', { index: startBlockIndex, block: updated }); - } else { - editor.startTransaction(); - var updated = utils.deleteSelection(editor, true); - updated.text += '\n'; - editor.commit(); - } -} - diff --git a/src/editor/interactions/index.js b/src/editor/interactions/index.js deleted file mode 100644 index c7ebe5a..0000000 --- a/src/editor/interactions/index.js +++ /dev/null @@ -1,5 +0,0 @@ -exports.undo = require('./undo'); -exports.enter = require('./enter'); -exports.backspace = require('./backspace'); -exports.textEntry = require('./text-entry'); -exports.paste = require('./paste'); diff --git a/src/editor/interactions/paste.js b/src/editor/interactions/paste.js deleted file mode 100644 index f34ace5..0000000 --- a/src/editor/interactions/paste.js +++ /dev/null @@ -1,252 +0,0 @@ -var utils = require('./utils'); -var mapping = require('../mapping'); -var newlineExp = /\r?\n/g; -var tagAliases = { - b: 'strong', - i: 'em' -}; -var blockElements = { - address: true, - article: true, - aside: true, - blockquote: true, - canvas: true, - dd: true, - div: true, - dl: true, - fieldset: true, - figcaption: true, - figure: true, - form: true, - h1: true, - header: true, - hgroup: true, - hr: true, - li: true, - main: true, - nav: true, - noscript: true, - ol: true, - output: true, - p: true, - pre: true, - section: true, - table: true, - tfoot: true, - ul: true, - video: true -}; - - -/* - -******* PASTED CONTENT NEEDS TO BE CLEANED ********** -13. the user pastes multiple blocks into the editor - * delete before.start + 1 to before.end - * update before.start - * insert before.start + 1 to after.end - -If the paste contains blocks, replace the current block if it is empty, or merge the first block of the paste into it if -it is not empty - -*/ - - -exports.enable = function(editor) { - editor.on('paste', onPaste); -}; - -exports.disable = function(editor) { - editor.off('paste', onPaste); -}; - - -/** - * Handle paste - */ -function onPaste(event) { - event.preventDefault(); - var editor = event.editor; - var data = event.clipboardData; - - if (data.types.indexOf('text/html') !== -1) { - pasteHTML(editor, data.getData('text/html')); - } else if (data.types.indexOf('text/plain') !== -1) { - pasteText(editor, data.getData('text/plain')); - } -} - - -/** - * Handles pasting text - */ -function pasteText(editor, text) { - if (!text) return; - var sel = editor.selection; - var startIndex = sel.startBlockIndex; - var start = sel.startBlock; - var offset = sel.startOffset; - - editor.startTransaction(); - var updated = utils.deleteSelection(editor, true); - var lines = text.split(newlineExp); - - // If it will not add blocks, just add text to the selected block - if (editor.schema.locked || lines.length === 1) { - if (updated.getEnterMode() === 'none') { - text = text.replace(newlineExp, ' '); - } - editor.setTransactionSelection('text', startIndex, offset + text.length); - updated.text = updated.text.slice(0, offset) + text + updated.text.slice(offset); - } else { - var trailing = updated.text.slice(offset); - updated.text = updated.text.slice(0, offset) + lines[0]; - - editor.setTransactionSelection('text', startIndex + lines.length - 1, lines[lines.length - 1].length); - lines.slice(1).forEach(function(line, i) { - var block = editor.schema.createDefaultBlock(line); - if (i == lines.length - 1) { - block.text += trailing; - } - editor.exec('insertBlock', { index: startIndex + i + 1, block: block }); - }); - } - - editor.commit(); -} - - -/** - * Handles pasting text - * - * - * TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO - * Merge the end block of the selected text - * - */ -function pasteHTML(editor, html) { - if (!html) return; - - editor.startTransaction(); - - var blocks = getBlocks(editor, html); - - // Set selection - var selectOffset = blocks[blocks.length - 1].text.length; - if (blocks.length === 1) selectOffset += editor.selection.startOffset; - editor.setTransactionSelection('text', editor.selection.startBlockIndex + blocks.length - 1, selectOffset); - - utils.mergeIntoSelection(editor, blocks); - - editor.commit(); -} - - -function getBlocks(editor, html) { - var schema = editor.schema; - var blocks = []; - var div = document.createElement('div'); - div.innerHTML = html; - - - var walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT); - var node; - var currentBlock; - - while ( (node = walker.nextNode()) ) { - var name = node.nodeName.toLowerCase(); - - if (name === 'style' || name === 'script') { - walker.previousNode(); - node.parentNode.removeChild(node); - continue; - } - - // Text - if (name === '#text') { - var value = node.nodeValue; - if (value.trim() === '') { - if (value && currentBlock && currentBlock.text) { - currentBlock.text += ' '; - } - } else { - if (!currentBlock) { - currentBlock = schema.createDefaultBlock(); - blocks.push(currentBlock); - } - var normalized = value.replace(/\s+/g, ' '); - if (normalized[0] === ' ' && currentBlock.text.slice(-1) === ' ') { - normalized = normalized.slice(1); - } - currentBlock.text += normalized; - } - continue; - } - - // Newline - if (name === 'br') { - if (!currentBlock) { - currentBlock = schema.createDefaultBlock(); - blocks.push(currentBlock); - } - currentBlock.text += '\n'; - continue; - } - - // Markup - var tag = tagAliases[name] || name; - var MarkupType = schema.markups[tag]; - if (MarkupType) { - if (!currentBlock) { - currentBlock = schema.createDefaultBlock(); - blocks.push(currentBlock); - } - var startOffset = currentBlock.text.length; - var endOffset = startOffset + getTextLength(node); - if (startOffset !== endOffset) { - var markup = new MarkupType(tag, startOffset, endOffset); - currentBlock.markups.push(markup); - } - continue; - } - - // Block - var blockSelector = schema.getBlockSelector(node); - var BlockType = blockSelector && schema.blocks[blockSelector]; - if (BlockType) { - if (currentBlock) { - currentBlock.text = currentBlock.text.replace(/\n$/, ''); - schema.normalizeMarkups(currentBlock); - } - currentBlock = new BlockType(blockSelector); - blocks.push(currentBlock); - continue; - } - - // Add a new default block for unsupported block types, but only if the current one has text - if (blockElements[tag] && (!currentBlock || currentBlock.text)) { - if (currentBlock) { - currentBlock.text = currentBlock.text.replace(/\n$/, ''); - schema.normalizeMarkups(currentBlock); - } - currentBlock = schema.createDefaultBlock(); - blocks.push(currentBlock); - } - } - - return blocks; -} - - -function getTextLength(markupElement) { - var walker = document.createTreeWalker(markupElement, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT); - var node, length = 0; - while ((node = walker.nextNode())) { - if (node.nodeType === Node.TEXT_NODE) { - length += node.nodeValue.length; - } else if (node.nodeName === 'BR') { - length += 1; - } - } - return length; -} diff --git a/src/editor/interactions/text-entry.js b/src/editor/interactions/text-entry.js deleted file mode 100644 index 77e9c76..0000000 --- a/src/editor/interactions/text-entry.js +++ /dev/null @@ -1,116 +0,0 @@ -var mapping = require('../mapping'); -var UpdateBlockCommand = require('../commands/update-block'); -var inputMode; -var isDelete = false; -var changing = false; -var isDeletion = /Backspace|Delete/; - -exports.enable = function(editor) { - editor.on('input', onInput); - editor.on('shortcut', onShortcut); - editor.on('change', onChange); - editor.on('selectionchange', onSelectionChange); -}; - -exports.disable = function(editor) { - editor.off('input', onInput); - editor.off('shortcut', onShortcut); - editor.off('change', onChange); - editor.off('selectionchange', onSelectionChange); -}; - -// If the selection changed and it wasn't the result of an input, don't merge next inputs -function onSelectionChange(event) { - if (!changing && inputMode) { - inputMode = null; - } -} - -// If something changed that wasn't an input, don't merge next inputs -function onChange(event) { - if (!changing && inputMode) { - inputMode = null; - } -} - -function onShortcut(event) { - isDelete = isDeletion.test(event.shortcut); - setTimeout(function() { - isDelete = false; - }); -} - - -/* -Will only be called when the user is typing text. We handle delete/enter/paste in other interations. -The following actions need to be captured correctly by this method: - -1. the user types one or more characters - * update the before.start block - -2. the user deletes or backspaces one or more characters - * update the before.start block - -3. the user selects some text in one block and types characters - * update the before.start block - -4. the user selects some text in one block and deletes it - * update the before.start block - -5. the user selects text across multiple blocks and types characters - * update the before.start block - * delete the before.start + 1 to before.end blocks (end becomes merged with start) - -6. the user selects text across multiple blocks and deletes it - * update the before.start block - * delete the before.start + 1 to before.end blocks (end becomes merged with start) -*/ -function onInput(event) { - changing = true; - - var changeMode = isDelete ? 'delete' : 'input'; - var editor = event.editor; - var element = editor.element; - var startBlockIndex = editor.selection.startBlockIndex; - var endBlockIndex = editor.selection.endBlockIndex; - var start = editor.selection.startBlock; - var startElement = editor.selection.startBlockElement; - - // Delete the blocks that were selected except for the first one, update the first one - editor.startTransaction(); - - // Delete the blocks except the first - for (var i = startBlockIndex + 1; i <= endBlockIndex; i++) { - editor.exec('deleteBlock', { index: i }); - } - - // Update the first block with the change - var updated = mapping.updateBlock(editor, start.clone(), startElement); - - // Something changed that didn't take, put the selection back and continue - if (updated.equals(start)) { - mapping.generateElement(this.editor, start); - editor.selection.range = editor.selection.range.collapse(true); - if (!editor.commit()) { - changing = false; - return; - } - } else { - editor.exec('updateBlock', { index: startBlockIndex, block: updated }); - editor.commit(); - } - - if (inputMode === changeMode) { - // Remove the newly added command and merge it with the previous - var undoStack = editor.history.undoStack; - var command = undoStack.pop(); - var lastCommand = undoStack[undoStack.length - 1]; - var updateCommand = lastCommand.commands ? lastCommand.commands[lastCommand.commands.length - 1] : lastCommand; - // Update the previous command - updateCommand.block = command.block; - lastCommand.selectionAfter = command.selectionAfter; - } - - inputMode = changeMode; - changing = false; -} diff --git a/src/editor/interactions/undo.js b/src/editor/interactions/undo.js deleted file mode 100644 index 73e94dd..0000000 --- a/src/editor/interactions/undo.js +++ /dev/null @@ -1,40 +0,0 @@ -var platform = require('../platform'); -var shortcuts = {}; - -// Undo/Redo -if (platform.isMac) { - shortcuts['Cmd+Z'] = undo; - shortcuts['Cmd+Shift+Z'] = redo; -} else { - shortcuts['Ctrl+Z'] = undo; - shortcuts['Ctrl+Y'] = redo; -} - -exports.enable = function(editor) { - editor.on('shortcut', onShortcut); -}; - -exports.disable = function(editor) { - editor.off('shortcut', onShortcut); -}; - -function onShortcut(event) { - var shortcut = event.shortcut; - if (shortcuts.hasOwnProperty(shortcut)) { - if (shortcuts[shortcut](event.editor) === false) { - event.originalEvent.preventDefault(); - } - } -} - - -function undo(editor) { - editor.history.undo(); - return false; -} - - -function redo(editor) { - editor.history.redo(); - return false; -} diff --git a/src/editor/interactions/utils.js b/src/editor/interactions/utils.js deleted file mode 100644 index 53eb302..0000000 --- a/src/editor/interactions/utils.js +++ /dev/null @@ -1,185 +0,0 @@ -exports.insertAfter = insertAfter; -exports.deleteSelection = deleteSelection; -exports.mergeIntoSelection = mergeIntoSelection; -exports.splitBlock = splitBlock; -exports.mergeBlocks = mergeBlocks; -exports.deleteTextInBlock = deleteTextInBlock; - - - -function insertAfter(editor, index) { - var block = editor.blocks[index]; - var enterMode = block.getEnterMode(); - var defaultSelector = editor.schema.defaultBlock; - var BlockClass = editor.schema.blocks[defaultSelector]; - var newBlock; - - if (enterMode === 'continuation') { - if (block.text) { - newBlock = new block.constructor(block.selector); - } else { - update = new BlockClass(defaultSelector); - editor.setTransactionSelection('text', index, 0); - editor.exec('updateBlock', { index: index, block: update }); - } - } else if (enterMode !== 'none') { - newBlock = new BlockClass(defaultSelector); - } - - if (newBlock) { - editor.exec('insertBlock', { index: index + 1, block: newBlock }); - } -} - - - -function deleteSelection(editor, includeLastBlock) { - var startBlockIndex = editor.selection.startBlockIndex; - var endBlockIndex = editor.selection.endBlockIndex; - var startBlock = editor.blocks[startBlockIndex]; - var endBlock = editor.blocks[endBlockIndex]; - var updatedStartBlock; - - if (startBlock === endBlock) { - updatedStartBlock = deleteTextInBlock(editor, startBlock, editor.selection.startOffset, editor.selection.endOffset); - } else { - updatedStartBlock = deleteTextInBlock(editor, startBlock, editor.selection.startOffset); - - if (includeLastBlock) { - editor.exec('deleteBlock', { index: endBlockIndex }); - var updatedStartBlock = mergeBlocks(editor, updatedStartBlock, endBlock, editor.selection.endOffset); - } else { - var updatedEndBlock = deleteTextInBlock(editor, endBlock, 0, editor.selection.endOffset); - // Since we've deleted the other blocks, the end block is now just 1 after the start block - editor.exec('updateBlock', { index: startBlockIndex + 1, block: updatedEndBlock }); - } - } - - // Delete all blocks but the start, and merge any text/markups after the selection in the end into the start - // Go backwards, otherwise the indexes will be trying to delete the wrong blocks - for (var i = endBlockIndex - 1; i > startBlockIndex; i--) { - editor.exec('deleteBlock', { index: i }); - } - - editor.exec('updateBlock', { index: startBlockIndex, block: updatedStartBlock }); - - return updatedStartBlock; -} - - -function mergeIntoSelection(editor, blocks) { - var selection = editor.selection; - var startIndex = selection.startBlockIndex; - var endIndex = selection.endBlockIndex; - var startOffset = selection.startOffset; - var endOffset = selection.endOffset; - var start = selection.startBlock; - var end = selection.endBlock; - var i, updatedStart, updatedEnd, merged; - var firstBlock = blocks[0]; - var lastBlock = blocks[blocks.length - 1]; - - if (start.text) { - updatedStart = deleteTextInBlock(editor, start, startOffset); - updatedStart = mergeBlocks(editor, updatedStart, firstBlock); - } else { - updatedStart = firstBlock.clone(); - } - - updatedEnd = deleteTextInBlock(editor, end, 0, endOffset); - - if (blocks.length === 1) { - updatedStart = mergeBlocks(editor, updatedStart, updatedEnd); - editor.exec('updateBlock', { index: startIndex, block: updatedStart }); - } else { - updatedEnd = mergeBlocks(editor, lastBlock, updatedEnd); - editor.exec('updateBlock', { index: startIndex, block: updatedStart }); - if (start === end) { - editor.exec('insertBlock', { index: startIndex + 1, block: updatedEnd }); - } else { - editor.exec('updateBlock', { index: endIndex, block: updatedEnd }); - } - } - - // Delete the fully-selected blocks - for (i = endIndex - 1; i > startIndex; i--) { - editor.exec('deleteBlock', { index: i }); - } - - // Insert the blocks other than the first/last - for (i = 1; i < blocks.length - 1; i++) { - editor.exec('insertBlock', { index: startIndex + i, block: blocks[i] }); - } -} - - -function splitBlock(editor, blockIndex, offset) { - var block = editor.blocks[blockIndex]; - editor.exec('updateBlock', { index: blockIndex, block: deleteTextInBlock(editor, block, offset) }); - editor.exec('insertBlock', { index: blockIndex + 1, block: deleteTextInBlock(editor, block.clone(true), 0, offset) }); -} - - -function mergeBlocks(editor, target, source, fromOffset) { - fromOffset = fromOffset || 0; - var text = source.text.slice(fromOffset); - if (!text) return target.clone(); - - var markups = []; - var newStart = target.text.length; - - source.markups.forEach(function(markup) { - if (markup.endOffset <= fromOffset) return; - markup = markup.clone(); - - // Move it to 0-based from the fromOffset - markup.startOffset -= fromOffset; - markup.endOffset -= fromOffset; - - // Shorten it if it went behind fromOffset - if (markup.startOffset < 0) markup.startOffset = 0; - - // Move it out to the new position - markup.startOffset += newStart; - markup.endOffset += newStart; - markups.push(markup); - }); - - var merged = target.clone(); - merged.text += text; - merged.markups = merged.markups.concat(markups); - editor.schema.normalizeMarkups(merged); - - return merged; -} - - -function deleteTextInBlock(editor, target, startOffset, endOffset) { - if (startOffset === undefined) startOffset = 0; - if (endOffset === undefined) endOffset = target.text.length; - var updated = target.clone(); - updated.text = updated.text.slice(0, startOffset) + updated.text.slice(endOffset); - var length = endOffset - startOffset; - - updated.markups = updated.markups.filter(function(markup) { - - if (markup.startOffset < startOffset) { - markup.endOffset = Math.min(markup.endOffset, startOffset); - return true; - } else { - - } - - if (markup.endOffset > endOffset) { - markup.endOffset -= length; - markup.startOffset = Math.max(markup.startOffset, endOffset) - length; - return true; - } else { - return false; - } - }); - - editor.schema.normalizeMarkups(updated); - return updated; -} - diff --git a/src/editor/mapping.js b/src/editor/mapping.js deleted file mode 100644 index 50db554..0000000 --- a/src/editor/mapping.js +++ /dev/null @@ -1,366 +0,0 @@ -var slice = Array.prototype.slice; -var forEach = Array.prototype.forEach; -var selectors = require('./selectors'); -var Editor = require('./editor'); -var Block = require('./blocks/block'); -var mapping = exports; - -/** - * Generate (and insert/replace) an element for the given block - * @param {Editor} editor The editor - * @param {Block} block The block to generate the element from - */ -mapping.generateElement = function(editor, block) { - if (!(editor instanceof Editor)) throw new TypeError('Must include editor for mapping'); - if (!(block instanceof Block)) throw new TypeError('Must include block for mapping'); - var index = editor.blocks.indexOf(block); - if (index === -1) throw new TypeError('Must be a block in the editor'); - var blockElements = editor.blockElements; - var isDeep = selectors.isDeep(block.selector); - var prev = editor.blocks[index - 1]; - var next = editor.blocks[index + 1]; - var prevElement = blockElements[index - 1]; - var nextElement = blockElements[index]; - var element = mapping.blockToDOM(editor, block); - - // If there is an existing element we can update it or remove it to be replaced - if (nextElement && nextElement.getAttribute('name') === block.id) { - // Update the element in-place and don't worry about the rest - if (nextElement.nodeName === element.nodeName) { - cloneElementTo(nextElement, element); - return; - } - - // Remove the element for replacing - removeElement(editor, block, nextElement); - nextElement = blockElements[index + 1]; - blockElements.splice(index, 1, element); - } else { - blockElements.splice(index, 0, element); - } - - // Add the new element to the DOM - if (isDeep && prev && prev.selector === block.selector && prevElement) { - prevElement.parentNode.insertBefore(element, prevElement.nextSibling); - - // If next is also the same but with a different parent, we need to join it to this list - if (next && next.selector === block.selector && nextElement) { - var prevSibling = outerElement(editor, prevElement); - var nextSibling = outerElement(editor, nextElement); - - if (prevSibling !== nextSibling) { - var i = index, sibling; - while ((sibling = editor.blocks[++i]) && sibling.selector === block.selector && blockElements[i]) { - prevElement.parentNode.appendChild(blockElements[i]); - } - nextSibling.parentNode.removeChild(nextSibling); - } - } - } else if (isDeep && next && next.selector === block.selector && nextElement) { - nextElement.parentNode.insertBefore(element, nextElement); - } else { - // If this is a list, be sure to get the outermost element (the ul/ol) - element = selectors.createElementDeep(block.selector, element); - var prevSibling = outerElement(editor, prevElement); - var nextSibling = outerElement(editor, nextElement); - - // If we need to split a list into two to insert our element - if (prev && next && prev.selector === next.selector && selectors.isDeep(prev.selector) - && prevElement && nextElement) { - nextSibling = selectors.createElementDeep(next.selector, nextElement); - var i = index + 1, sibling; - while ((sibling = editor.blocks[++i]) && sibling.selector === prev.selector && blockElements[i]) { - nextElement.parentNode.appendChild(blockElements[i]); - } - prevSibling.parentNode.insertBefore(nextSibling, prevSibling.nextSibling); - } - - if (prevSibling) { - editor.element.insertBefore(element, prevSibling.nextSibling); - } else { - editor.element.insertBefore(element, nextSibling); - } - } -}; - -/** - * Generate (and insert/replace) all elements in the editor - * @param {Editor} editor The editor - * @param {Block} block The block to generate the element from - */ -mapping.generateElements = function(editor) { - editor.element.innerHTML = ''; - editor.blocks.forEach(mapping.generateElement.bind(null, editor)); -}; - - - -mapping.removeElement = function(editor, block) { - var element = editor.blockElements.find(function(elem) { - return elem.getAttribute('name') === block.id; - }); - removeElement(editor, block, element); -}; - - - -mapping.blocksFromDOM = function(editor, container, options) { - if (!(editor instanceof Editor)) throw new TypeError('Must include editor for mapping'); - if (!container) container = editor.element; - var blockElements = slice.call(container.querySelectorAll(editor.schema.blocksSelector)); - var blocks = blockElements.map(function(blockElement) { - return mapping.blockFromDOM(editor, blockElement, container); - }).filter(Boolean); - - if (!blocks.length && (!options || !options.noDefault)) { - blocks = editor.schema.getInitial(); - if (blocks.length) { - blocks[0] - } - } - - return blocks; -}; - - -mapping.blockFromDOM = function(editor, element, container) { - if (!(editor instanceof Editor)) throw new TypeError('Must include editor for mapping'); - if (!(element instanceof HTMLElement)) throw new TypeError('Must include element for mapping'); - if (!container) container = editor.element; - var Type = editor.schema.getBlockType(element); - var block = Type && new Type(selectors.fromElement(element, container)); - if (block) { - return mapping.updateBlock(editor, block, element); - } -}; - - -mapping.updateBlock = function(editor, block, element) { - var result = mapping.textFromDOM(editor, element); - block.text = result.text; - block.markups = result.markups; - return block; -}; - - -mapping.blockToDOM = function(editor, block) { - if (!(editor instanceof Editor)) throw new TypeError('Must include editor for mapping'); - if (!block.markups) throw new TypeError('Must include block for mapping'); - var element = block.createElement(); - var fragment = mapping.textToDOM(editor, block); - var contentElement = element; - - if (contentElement.contentEditable === 'false') { - // If this block is e.g. a figure look for the editable section inside - contentElement = element.querySelector('[contenteditable="true"]'); - } - - if (contentElement) { - contentElement.appendChild(fragment); - - if (!block.text) { - contentElement.classList.add('empty'); - } - if (editor.blocks[0] === block && editor.element.getAttribute('placeholder')) { - contentElement.setAttribute('placeholder', editor.element.getAttribute('placeholder')); - } - } - - return element; -}; - - -mapping.textFromDOM = function(editor, element) { - if (!(editor instanceof Editor)) throw new TypeError('Must include editor for mapping'); - if (!(element instanceof HTMLElement)) throw new TypeError('Must include element for mapping'); - var result = { text: '', markups: [] }; - - if (element.contentEditable === 'false') { - // If this block is e.g. a figure look for the editable section inside - element = element.querySelector('[contenteditable="true"]'); - if (!element) { - return result; - } - } - - if (element.innerHTML === '
    ') { - return result; - } - - var walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT); - var markupMap = new Map(); - - while ((node = walker.nextNode())) { - var name = node.nodeName.toLowerCase(); - var lastNode = node; - var markup; - - if (name === '#text') { - result.text += node.nodeValue - .replace(/[“”]/g, '"') - .replace(/[’‘]/g, "'") - .replace(/[  ]{2,}/g, '  '); - } else if (name === 'br') { - result.text += '\n'; - } else if (node.textContent.trim() !== '') { - var Type = editor.schema.getMarkupType(node); - var markup = Type && new Type(selectors.fromElement(node)); - if (markup) { - markup.startOffset = result.text.length; - markupMap.set(node, markup); - result.markups.push(markup); - } - } - - if (name === '#text' || name === 'br') { - while (node !== element && node.parentNode.lastChild === node) { - if (markupMap.has(node.parentNode)) { - markup = markupMap.get(node.parentNode); - if (markup) { - markup.endOffset = result.text.length; - } - } - node = node.parentNode; - } - } - } - - // If the block.text ends in
    there will be 1 extra \n, we can always take off the last \n - result.text = result.text.replace(/\n$/, ''); - - editor.schema.normalizeMarkups(result); - - return result; -} - - -mapping.textToDOM = function(editor, block) { - if (!(editor instanceof Editor)) throw new TypeError('Must include editor for mapping'); - if (!(block instanceof Block)) throw new TypeError('Must include block for mapping'); - var fragment = document.createDocumentFragment(); - var text = block.text; - var markups = block.markups; - var elements = []; - - if (!text) { - fragment.appendChild(document.createElement('br')); - return fragment; - } - - // Add open/closing quotes - text = text.replace(/(^|\s)"/g, '$1“') - .replace(/"($|[\s,.!])/g, '”$1') - .replace(/\b'/g, '’') - .replace(/'\b/g, '‘'); - - // TODO update to follow algorithm, respecting schema correctly - fragment.appendChild(document.createTextNode(text)); - var walker = document.createTreeWalker(fragment, NodeFilter.SHOW_TEXT); - - markups.forEach(function(markup) { - // Start at the beginning and find the text node this markup starts before - var remainingLength = markup.endOffset - markup.startOffset; - walker.currentNode = fragment; - var textNode = walker.nextNode(); - - // Find the first textNode this markup starts in - var currentIndex = 0; - while (textNode) { - if (currentIndex + textNode.nodeValue.length > markup.startOffset) { - if (markup.startOffset !== currentIndex) { - breakTextNode(textNode, markup.startOffset - currentIndex); - textNode = walker.nextNode(); - } - break; - } - currentIndex += textNode.nodeValue.length; - textNode = walker.nextNode(); - } - - // Add the elements to each text node necessary - while (remainingLength > 0) { - if (textNode.nodeValue.length > remainingLength) { - breakTextNode(textNode, remainingLength); - } - - var element = markup.createElement(); - textNode.parentNode.replaceChild(element, textNode); - element.appendChild(textNode); - remainingLength -= textNode.nodeValue.length; - textNode = walker.nextNode(); - } - }); - - // Insert the BRs - walker.currentNode = fragment; - var textNode; - while ((textNode = walker.nextNode())) { - var index = textNode.nodeValue.indexOf('\n'); - if (index !== -1) { - breakTextNode(textNode, index + 1); - textNode.parentNode.insertBefore(document.createElement('br'), textNode.nextSibling); - textNode.nodeValue = textNode.nodeValue.slice(0, index); - if (textNode.nodeValue.length === 0) { - walker.previousNode(); - textNode.remove(); - } - } - // You can't tell, but that first replacement space is a non-breaking space ( ) - textNode.nodeValue = textNode.nodeValue.replace(/ $/g, ' '); - } - - if (fragment.lastChild.nodeType === Node.TEXT_NODE && fragment.lastChild.nodeValue.length === 0) { - fragment.lastChild.remove(); - } - - if (text.slice(-1) === '\n') { - var parent = fragment; - while (parent.lastChild.nodeType !== Node.TEXT_NODE && parent.lastChild.nodeName !== 'BR') { - parent = parent.lastChild; - } - parent.appendChild(document.createElement('br')); - } - - return fragment; -} - -function breakTextNode(node, index) { - node.parentNode.insertBefore(document.createTextNode(node.nodeValue.slice(index)), node.nextSibling); - node.nodeValue = node.nodeValue.slice(0, index); -} - -function outerElement(editor, element) { - while (element && element.parentNode !== editor.element) { - element = element.parentNode; - } - return element; -} - -function removeElement(editor, block, element) { - if (element) { - if (selectors.isDeep(block.selector) && !element.previousSibling && !element.nextSibling) { - var element = outerElement(editor, element); - } - element.parentNode.removeChild(element); - } -} - -function cloneElementTo(target, source) { - // remove attributes that are not on the source - forEach.call(target.attributes, function(attr) { - if (!source.attributes[attr.name]) { - target.removeAttribute(attr.name); - } - }); - - // set the attributes that are on the source - forEach.call(source.attributes, function(attr) { - if (target.getAttribute(attr.name) !== attr.value) { - target.setAttribute(attr.name, attr.value); - } - }); - - if (target.innerHTML !== source.innerHTML) { - target.innerHTML = source.innerHTML; - } -} diff --git a/src/editor/markups/markup.js b/src/editor/markups/markup.js deleted file mode 100644 index 6498925..0000000 --- a/src/editor/markups/markup.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = Markup; -var Class = require('chip-utils/class'); -var selectors = require('../selectors'); - - -function Markup(selector, startOffset, endOffset) { - if (!selector) throw new TypeError('A selector is required to create a markup'); - this.selector = selector; - this.startOffset = startOffset || 0; - this.endOffset = endOffset || 0; -} - -Class.extend(Markup, { - - - createElement: function() { - return selectors.createElement(this.selector); - }, - - same: function(markup) { - return markup && this.constructor === markup.constructor && this.selector === markup.selector; - }, - - equals: function(markup) { - return this.same(markup) && this.startOffset === markup.startOffset && this.endOffset === markup.endOffset; - }, - - clone: function(constructor) { - return new Markup(this.selector, this.startOffset, this.endOffset); - } -}); diff --git a/src/editor/menu.js b/src/editor/menu.js deleted file mode 100644 index 1df2c96..0000000 --- a/src/editor/menu.js +++ /dev/null @@ -1,129 +0,0 @@ -var menu = document.createElement('div'); -module.exports = menu; - -menu.id = 'editor-menu'; -var currentEditor; -var mouseDown = false; -var lastSelectionEvent; -var forEach = Array.prototype.forEach; -menu.innerHTML = '
    '; -menu.items = menu.firstChild; -menu.input = menu.lastChild; - - -addItem('editor-bold', function() { - currentEditor.toggleMarkup('strong'); -}, function() { - return currentEditor.isMarkupType('strong'); -}); -addItem('editor-italic', function() { - currentEditor.toggleMarkup('em'); -}, function() { - return currentEditor.isMarkupType('em'); -}); -// addItem('insert_link', function() { - -// }, function() { -// return currentEditor.isMarkupType('a[href]'); -// }); -addSeparator(); -addItem('editor-header', function() { - currentEditor.toggleBlockType('h2'); -}, function() { - return currentEditor.isBlockType('h2'); -}); -addItem('editor-quote', function() { - currentEditor.toggleBlockType('blockquote'); -}, function() { - return currentEditor.isBlockType('blockquote'); -}); - - - -menu.reposition = function() { - updateState(); - var container = menu.parentNode; - if (!container) return; - var rect = currentEditor.selection.rect; - var containerRect = container.getBoundingClientRect(); - menu.style.left = Math.floor(rect.left - containerRect.left + - container.scrollLeft - (menu.offsetWidth - rect.width)/2) + 'px'; - menu.style.top = (rect.top - containerRect.top + - container.scrollTop - menu.offsetHeight - 6) + 'px'; -}; - -menu.show = function() { - var scroller = currentEditor.element; - while (scroller) { - var overflow = getComputedStyle(scroller).overflow; - if (overflow === 'scroll' || overflow === 'auto' || scroller.parentNode.nodeType !== Node.ELEMENT_NODE) { - break; - } - scroller = scroller.parentNode; - } - scroller.appendChild(menu); - requestAnimationFrame(function() { - menu.classList.add('active'); - }); -}; - -menu.hide = function() { - menu.remove(); - menu.classList.remove('active'); -}; - - -document.addEventListener('editorselectionchange', updateSelection); -document.addEventListener('editorchange', menu.reposition); -document.addEventListener('mousedown', function() { - mouseDown = true; -}); -document.addEventListener('mouseup', function() { - mouseDown = false; - if (lastSelectionEvent) { - updateSelection(lastSelectionEvent); - lastSelectionEvent = null; - } -}); - -function updateSelection(event) { - currentEditor = event.editor; - - if (mouseDown) { - lastSelectionEvent = event; - } - - if (currentEditor.selection.type !== 'text' || currentEditor.selection.isCollapsed || mouseDown) { - menu.hide(); - } else if (!currentEditor.element.hasAttribute('no-menu')) { - menu.show(); - menu.reposition(); - } -} - -function addItem(icon, callback, stateCheck) { - var item = document.createElement('button'); - item.className = 'editor-menu-' + icon; - item.innerHTML = ''; - item.addEventListener('click', callback); - item.stateCheck = stateCheck; - menu.items.appendChild(item); -} - -function addSeparator() { - var separator = document.createElement('div'); - separator.className = 'editor-menu-separator'; - menu.items.appendChild(separator); -} - -function updateState() { - forEach.call(menu.items.children, function(item) { - if (item.stateCheck) { - if (item.stateCheck()) { - item.classList.add('active'); - } else { - item.classList.remove('active'); - } - } - }); -} diff --git a/src/editor/menu/index.js b/src/editor/menu/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/editor/platform.js b/src/editor/platform.js deleted file mode 100644 index 14d18be..0000000 --- a/src/editor/platform.js +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Used for determining which set of keyboard shortcuts to use. - */ -exports.isMac = navigator.userAgent.indexOf('Macintosh') !== -1; diff --git a/src/editor/range.js b/src/editor/range.js deleted file mode 100644 index c247d0b..0000000 --- a/src/editor/range.js +++ /dev/null @@ -1,102 +0,0 @@ -module.exports = EditorRange; -var Class = require('chip-utils/class'); -var types = { text: true, media: true, none: true }; - - -function EditorRange(editor, type, anchorBlockIndex, anchorOffset, focusBlockIndex, focusOffset) { - if (type && !types[type]) { - throw new TypeError('Invalid selection range type: ' + type); - } - // Keep editor from enumeration so it won't serialize into JSON - Object.defineProperty(this, 'editor', { value: editor, writable: true, configurable: true }); - this.type = type || 'none'; - this.anchorBlockIndex = anchorBlockIndex !== undefined ? anchorBlockIndex : -1; - this.anchorOffset = anchorOffset !== undefined ? anchorOffset : -1; - this.focusBlockIndex = focusBlockIndex !== undefined ? focusBlockIndex : -1; - this.focusOffset = focusOffset !== undefined ? focusOffset : -1; -} - -Class.extend(EditorRange, { - static: { - fromJSON: function(value) { - return new EditorRange(null, - value.type, - value.anchorBlockIndex, - value.anchorOffset, - value.focusBlockIndex, - value.focusOffset - ); - } - }, - - get valid() { - return this.editor && - this.type !== 'none' && - this.anchorBlockIndex !== -1 && - this.anchorOffset !== -1 && - this.focusBlockIndex !== -1 && - this.focusOffset !== -1; - }, - - get startBlockIndex() { - return Math.min(this.anchorBlockIndex, this.focusBlockIndex); - }, - - get startOffset() { - if (this.anchorBlockIndex === this.focusBlockIndex) { - return Math.min(this.anchorOffset, this.focusOffset); - } else if (this.anchorBlockIndex < this.focusBlockIndex) { - return this.anchorOffset; - } else { - return this.focusOffset; - } - }, - - get endBlockIndex() { - return Math.max(this.anchorBlockIndex, this.focusBlockIndex); - }, - - get endOffset() { - if (this.anchorBlockIndex === this.focusBlockIndex) { - return Math.max(this.anchorOffset, this.focusOffset); - } else if (this.anchorBlockIndex > this.focusBlockIndex) { - return this.anchorOffset; - } else { - return this.focusOffset; - } - }, - - get isCollapsed() { - return this.anchorBlockIndex === this.focusBlockIndex && this.anchorOffset === this.focusOffset; - }, - - collapse: function(toEnd) { - if (toEnd) { - this.anchorBlockIndex = this.focusBlockIndex; - this.anchorOffset = this.focusOffset; - } else { - this.focusBlockIndex = this.anchorBlockIndex; - this.focusOffset = this.anchorOffset; - } - return this; - }, - - equals: function(range) { - return range && - this.editor === range.editor && - this.type === range.type && - this.anchorBlockIndex === range.anchorBlockIndex && - this.anchorOffset === range.anchorOffset && - this.focusBlockIndex === range.focusBlockIndex && - this.focusOffset === range.focusOffset; - }, - - clone: function() { - return new EditorRange(this.editor, - this.type, - this.anchorBlockIndex, - this.anchorOffset, - this.focusBlockIndex, - this.focusOffset); - } -}); diff --git a/src/editor/rect.js b/src/editor/rect.js deleted file mode 100644 index 6684e98..0000000 --- a/src/editor/rect.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = Rect; - -function Rect(clientRect) { - this.left = clientRect.left; - this.top = clientRect.top; - this.width = clientRect.width; - this.height = clientRect.height; - this.right = clientRect.right; - this.bottom = clientRect.bottom; -} diff --git a/src/editor/schema/default.js b/src/editor/schema/default.js deleted file mode 100644 index 44c41ac..0000000 --- a/src/editor/schema/default.js +++ /dev/null @@ -1,34 +0,0 @@ -var Schema = require('./schema'); -var Block = require('../blocks/block'); -var HeaderBlock = require('../blocks/header'); -var ListBlock = require('../blocks/list'); -var PreformattedBlock = require('../blocks/preformatted'); -var ImageBlock = require('../blocks/image'); -var Markup = require('../markups/markup'); - -exports.get = function() { - var blocks = { - p: Block, - h1: HeaderBlock, - h2: HeaderBlock, - h3: HeaderBlock, - h4: HeaderBlock, - h5: HeaderBlock, - h6: HeaderBlock, - pre: PreformattedBlock, - 'blockquote.pullquote': Block, - blockquote: Block, - 'ul>li': ListBlock, - 'ol>li': ListBlock, - // figure: ImageBlock - }; - - - var markups = { - 'a[href]': Markup, - strong: Markup, - em: Markup - }; - - return new Schema(blocks, markups, 'p', [ new Block('p') ]); -} diff --git a/src/editor/schema/schema.js b/src/editor/schema/schema.js deleted file mode 100644 index 929a517..0000000 --- a/src/editor/schema/schema.js +++ /dev/null @@ -1,95 +0,0 @@ -module.exports = Schema; -var Class = require('chip-utils/class'); -var map = Array.prototype.map; -var aliases = { - strong: 'b', - em: 'i' -}; - - -function Schema(blocks, markups, defaultBlock, initial) { - this.locked = false; - this.blocks = blocks || []; - this.markups = markups || []; - this.defaultBlock = defaultBlock; - this.initial = initial; - this.blocksSelector = Object.keys(this.blocks).join(','); - this.markupsSelector = Object.keys(this.markups).join(','); -} - -Class.extend(Schema, { - - createDefaultBlock: function(text, markups) { - return new this.blocks[this.defaultBlock](this.defaultBlock, text, markups); - }, - - getBlockSelector: function(element) { - return findSelector(this.blocks, element); - }, - - getMarkupSelector: function(element) { - return findSelector(this.markups, element); - }, - - getBlockType: function(element) { - return findType(this.blocks, element); - }, - - getMarkupType: function(element) { - return findType(this.markups, element); - }, - - getInitial: function() { - return this.initial.map(function(block) { - return block.clone(true); - }); - }, - - // Sort the markups by type first, then by location - sortMarkups: function(block) { - var schema = this; - block.markups.sort(function(markupA, markupB) { - var indexA = schema.markupsSelector.indexOf(markupA.selector); - var indexB = schema.markupsSelector.indexOf(markupB.selector); - if (indexA === indexB) return markupA.startOffset - markupB.startOffset; - return indexA - indexB; - }); - }, - - normalizeMarkups: function(block) { - // Ensure the markups are in order first - this.sortMarkups(block); - - // Merge the markups together that need to be merged - for (var i = 1; i < block.markups.length; i++) { - var currentMarkup = block.markups[i]; - var prevMarkup = block.markups[i - 1]; - if (currentMarkup.endOffset > block.text.length) { - currentMarkup.endOffset = block.text.length; - } - - // If it is a different type of markup don't try to merge - // If they don't overlap in any way don't try to merge either - if (!prevMarkup.same(currentMarkup) || currentMarkup.startOffset > prevMarkup.endOffset) { - continue; - } - - // Merge the current into the previous and remove the current - prevMarkup.endOffset = Math.max(currentMarkup.endOffset, prevMarkup.endOffset); - block.markups.splice(i--, 1); - } - } - -}); - - -function findType(items, element) { - return items[findSelector(items, element)]; -} - -function findSelector(items, element) { - return Object.keys(items).find(function(selector) { - if (aliases[selector]) selector += ',' + aliases[selector]; - return element.matches(selector); - }); -} diff --git a/src/editor/selection-rect.js b/src/editor/selection-rect.js deleted file mode 100644 index 1b3e27c..0000000 --- a/src/editor/selection-rect.js +++ /dev/null @@ -1,29 +0,0 @@ -var Rect = require('./rect'); - -exports.get = function() { - var selection = window.getSelection(); - if (!selection.rangeCount) { - return; - } else if (selection.isCollapsed) { - var range = selection.getRangeAt(0); - var rects = range.getClientRects(); - var rect = rects[rects.length - 1]; - if (!rect && range.startContainer.nodeType === Node.ELEMENT_NODE) { - var child = range.startContainer.childNodes[range.startOffset]; - if (child && child.getBoundingClientRect) { - rect = child.getBoundingClientRect(); - } else { - return; - } - } - // if (!rect) { - // var shadowCaret = document.createTextNode('|'); - // range.insertNode(shadowCaret); - // rect = range.getBoundingClientRect(); - // shadowCaret.parentNode.removeChild(shadowCaret); - // } - return new Rect(rect); - } else { - return new Rect(selection.getRangeAt(0).getBoundingClientRect()); - } -}; diff --git a/src/editor/selection.js b/src/editor/selection.js deleted file mode 100644 index c5ba450..0000000 --- a/src/editor/selection.js +++ /dev/null @@ -1,508 +0,0 @@ -module.exports = EditorSelection; -var Class = require('chip-utils/class'); -var selectionRect = require('./selection-rect'); -var EditorRange = require('./range'); -var indexOf = Array.prototype.indexOf; - -var lastRange = new EditorRange(); -var currentRange = new EditorRange(); -var paused = false; - - -function EditorSelection(editor) { - this.editor = editor; -} - - -Class.extend(EditorSelection, { - static: { - get activeEditor() { - return currentRange.editor; - }, - - pause: function() { - paused = true; - }, - - resume: function() { - paused = false; - }, - - get range() { - return getEditorRange(); - }, - - set range(editorRange) { - selectEditorRange(editorRange); - } - }, - - /** - * Selects the range provided - * @param {String} type Selection type, text or media - * @param {Number} anchorBlockIndex Anchor block of the selection - * @param {Number} anchorOffset Anchor offset of the selection - * @param {Number} focusBlockIndex [Optional] Focus block of the selection - * @param {Number} focusOffset [Optional] Focus offset of the selection - */ - select: function(type, anchorBlockIndex, anchorOffset, focusBlockIndex, focusOffset) { - if (focusBlockIndex === undefined) { - focusBlockIndex = anchorBlockIndex; - focusOffset = anchorOffset; - } - this.range = new EditorRange(this.editor, type, anchorBlockIndex, anchorOffset, focusBlockIndex, focusOffset); - }, - - /** - * The type of this selection, either text, media, or none - * @return {String} One of 'text', 'media', or 'none' - */ - get type() { - return this.editor === currentRange.editor ? currentRange.type : 'none'; - }, - - /** - * The selection's anchor block's index, the block the selection starts in - * @return {Number} The index of the block the selection starts in - */ - get anchorBlockIndex() { - return this.editor === currentRange.editor ? currentRange.anchorBlockIndex : -1; - }, - - /** - * The selection's anchor block, the block the selection starts in - * @return {Block} The block the selection starts in - */ - get anchorBlock() { - return this.editor === currentRange.editor ? this.editor.blocks[currentRange.anchorBlockIndex] : null; - }, - - /** - * The selection's anchor block element, the element the selection starts in - * @return {HTMLElement} The element the selection starts in - */ - get anchorBlockElement() { - return this.editor === currentRange.editor ? this.editor.blockElements[currentRange.anchorBlockIndex] : null; - }, - - /** - * The selection's anchor offset, the index into the text the selection starts at - * @return {Number} The text offset the selection starts at - */ - get anchorOffset() { - return this.editor === currentRange.editor ? currentRange.anchorOffset : -1; - }, - - /** - * The selection's focus block's index, the block the selection ends in - * @return {Number} The index of the block the selection ends in - */ - get focusBlockIndex() { - return this.editor === currentRange.editor ? currentRange.focusBlockIndex : -1; - }, - - /** - * The selection's focus block, the block the selection ends in - * @return {Block} The block the selection ends in - */ - get focusBlock() { - return this.editor === currentRange.editor ? this.editor.blocks[currentRange.focusBlockIndex] : null; - }, - - /** - * The selection's anchor block element, the element the selection ends in - * @return {HTMLElement} The element the selection ends in - */ - get focusBlockElement() { - return this.editor === currentRange.editor ? this.editor.blockElements[currentRange.focusBlockIndex] : null; - }, - - /** - * The selection's focus offset, the index into the text the selection ends at - * @return {Number} The text offset the selection ends at - */ - get focusOffset() { - return this.editor === currentRange.editor ? currentRange.focusOffset : -1; - }, - - /** - * The selection's start block index. This is the first of anchor or focus as it appears in the document. - * @return {Number} The index of the first block in the selection - */ - get startBlockIndex() { - return this.editor === currentRange.editor ? currentRange.startBlockIndex : -1; - }, - - /** - * The selection's start block. This is the first of anchor or focus as it appears in the document. - * @return {Number} The first block in the selection - */ - get startBlock() { - return this.editor === currentRange.editor ? this.editor.blocks[currentRange.startBlockIndex] : null; - }, - - /** - * The selection's start block element, the element the selection starts in - * @return {HTMLElement} The element the selection starts in - */ - get startBlockElement() { - return this.editor === currentRange.editor ? this.editor.blockElements[currentRange.startBlockIndex] : null; - }, - - /** - * The selection's starting offset. This is the first of anchor or focus as it appears in the document. - * @return {Number} The offset of the first block in the selection - */ - get startOffset() { - return this.editor === currentRange.editor ? currentRange.startOffset : -1; - }, - - /** - * The selection's end block index. This is the last of anchor or focus as it appears in the document. - * @return {Number} The index of the last block in the selection - */ - get endBlockIndex() { - return this.editor === currentRange.editor ? currentRange.endBlockIndex : -1; - }, - - /** - * The selection's end block. This is the last of anchor or focus as it appears in the document. - * @return {Number} The last block in the selection - */ - get endBlock() { - return this.editor === currentRange.editor ? this.editor.blocks[currentRange.endBlockIndex] : null; - }, - - /** - * The selection's end block element, the element the selection ends in - * @return {HTMLElement} The element the selection ends in - */ - get endBlockElement() { - return this.editor === currentRange.editor ? this.editor.blockElements[currentRange.endBlockIndex] : null; - }, - - /** - * The selection's ending offset. This is the first of anchor or focus as it appears in the document. - * @return {Number} The offset of the first block in the selection - */ - get endOffset() { - return this.editor === currentRange.editor ? currentRange.endOffset : -1; - }, - - get selectedBlocks() { - return this.editor === currentRange.editor ? - this.editor.blocks.slice(currentRange.startBlockIndex, currentRange.endBlockIndex + 1) : - []; - }, - - /** - * The Rect of the current caret position - * @return {Rect} Contains top, left, right, bottom, width, and height - */ - get rect() { - return this.editor === currentRange.editor ? selectionRect.get() : null; - }, - - /** - * Indicates if the selection is a single point rather than a range of items - * @return {Boolean} Whether the selection is collapsed - */ - get isCollapsed() { - return this.editor !== currentRange.editor || currentRange.isCollapsed; - }, - - /** - * Whether or not the selection is at the very beginning of the editor (and collapsed) - * @return {Boolean} - */ - get atBeginning() { - if (this.editor !== currentRange.editor) return false; - return currentRange.isCollapsed && currentRange.anchorBlockIndex === 0 && currentRange.anchorOffset === 0; - }, - - /** - * Whether or not the selection is at the very end of the editor (and collapsed) - * @return {Boolean} - */ - get atEnd() { - if (this.editor !== currentRange.editor) return false; - var lastIndex = this.editor.blocks.length - 1; - var textLength = this.editor.blocks[lastIndex].text.length; - return currentRange.isCollapsed && - currentRange.anchorBlockIndex === lastIndex && - currentRange.anchorOffset === textLength; - }, - - /** - * Returns a range for this selection - * @return {EditorRange} The range of the selection in the given editor - */ - get range() { - return this.editor === currentRange.editor ? currentRange.clone() : null; - }, - - set range(editorRange) { - var actualRange = getEditorRange(); - if (!editorRange || editorRange.equals(actualRange)) { - if (!currentRange.equals(actualRange)) { - updateSelectionRange(); - } - return; - } - editorRange.editor = this.editor; - selectEditorRange(editorRange); - }, - - getRange: function() { - return getEditorRange(); - }, - - /** - * Returns the last range for this selection before the current selection - * @return {EditorRange} The last range of the selection in the given editor - */ - get lastRange() { - return this.editor === lastRange.editor ? lastRange.clone() : null; - }, - - /** - * Updates the selection immediately to match the browser selection - */ - update: function() { - updateSelectionRange(true); - }, - - /** - * Convert the selection into an object for storage - * @return {Object} A plain JavaScript object with all the information for the current selection - */ - toJSON: function() { - return this.toRange(); - } -}); - - -// Update the selections whenever selection changes -document.addEventListener('selectionchanged', updateSelectionRange); - -function updateSelectionRange(forceLastRangeUpdate) { - if (!paused) { - var previousRange = currentRange; - currentRange = getEditorRange(); - - if (previousRange && previousRange.anchorBlockIndex !== -1 && previousRange.anchorBlockIndex === previousRange.focusBlockIndex && previousRange.editor.element) { - var prevBlock = previousRange.editor.blockElements[previousRange.anchorBlockIndex]; - prevBlock && prevBlock.classList.remove('selected'); - } - - if (currentRange && currentRange.anchorBlockIndex !== -1 && currentRange.anchorBlockIndex === currentRange.focusBlockIndex && currentRange.editor.element) { - currentRange.editor.blockElements[currentRange.anchorBlockIndex].classList.add('selected'); - } - - if (previousRange.equals(currentRange)) { - if (forceLastRangeUpdate) lastRange = previousRange; - return; - } - - lastRange = previousRange; - // Dispatch one selection change event per editor that was affected - if (lastRange.editor) { - dispatchSelectionEvent(lastRange.editor, 'selectionchange'); - } - if (currentRange.editor && currentRange.editor !== lastRange.editor) { - dispatchSelectionEvent(currentRange.editor, 'selectionchange'); - } - - // Dispatch an editor selection change event once, with the target being either the current editor or the last - // editor if there are no current editor's selected. This one bubbles - dispatchSelectionEvent(currentRange.editor || lastRange.editor, 'editorselectionchange', { bubbles: true }); - } -} - -function getEditorRange() { - var selection = window.getSelection(); - var editorRange = new EditorRange(); - if (!selection.anchorNode || !selection.rangeCount) { - return editorRange; - } - - var anchorElement = getElement(selection.anchorNode); - var focusElement = getElement(selection.focusNode); - var editorElement = anchorElement.closest('.editable'); - var editor = editorElement && editorElement.editor; - - if (!editor || anchorElement === editorElement) { - return editorRange; - } - - var blockElements = editor.blockElements; - editorRange.editor = editor; - var anchor = getBlock(anchorElement, editor); - var focus = getBlock(focusElement, editor); - if (!anchor || !focus) { - return editorRange; - } - editorRange.anchorBlockIndex = indexOf.call(blockElements, anchor); - editorRange.focusBlockIndex = indexOf.call(blockElements, focus); - - editorRange.type = getType(selection); - if (editorRange.type === 'none') { - editorRange.anchorBlockIndex = editorRange.focusBlockIndex = -1; - } else if (editorRange.type === 'media') { - var element = getSelectedElement(selection); - editorRange.anchorOffset = focusOffset = getElementIndex(anchor, element); - } else { - editorRange.anchorOffset = getTextOffset(anchor, selection.anchorNode, selection.anchorOffset); - if (selection.isCollapsed) { - editorRange.focusOffset = editorRange.anchorOffset; - } else { - editorRange.focusOffset = getTextOffset(focus, selection.focusNode, selection.focusOffset); - } - } - - if (editorRange.type === 'text' && editorRange.anchorBlockIndex !== editorRange.focusBlockIndex && editorRange.endOffset === 0) { - var which = editorRange.anchorBlockIndex === editorRange.endBlockIndex ? 'anchor' : 'focus'; - var index = --editorRange[which + 'BlockIndex']; - editorRange[which + 'Offset'] = editor.blocks[index].text.length; - selectEditorRange(editorRange); - } - - return editorRange; -} - - -function selectEditorRange(editorRange) { - if (!editorRange.editor) { - return; - } - var blockElements = editorRange.editor.blockElements; - var anchorBlockElement = blockElements[editorRange.anchorBlockIndex]; - var focusBlockElement = blockElements[editorRange.focusBlockIndex]; - var selection = window.getSelection(); - var range = document.createRange(); - - if (editorRange.type === 'media') { - // TODO support media selection - } else { - var anchor = getDOMOffset(anchorBlockElement, editorRange.anchorOffset); - var focus = editorRange.isCollapsed ? anchor : getDOMOffset(focusBlockElement, editorRange.focusOffset); - - // Only change the selection if it is not correct - if (selection.anchorNode !== anchor.node || - selection.focusNode !== focus.node || - selection.anchorOffset !== anchor.offset || - selection.focusOffset !== focus.offset) - { - range.setStart(anchor.node, anchor.offset); - selection.removeAllRanges(); - selection.addRange(range); - - // Since native browser Ranges don't allow the "start" to be after the "end" we need to use the selection APIs to - // move the focus so that it may go in reverse - if (!editorRange.isCollapsed) { - selection.extend(focus.node, focus.offset); - } - } - } -} - -function getElement(node) { - return node.nodeType === Node.ELEMENT_NODE ? node : node.parentNode; -} - -function getSelectedElement(selection) { - return selection.anchorNode.childNodes[selection.anchorOffset] || selection.anchorNode.childNodes[selection.anchorOffset - 1]; -} - -function getBlock(node, editor) { - return node.closest(editor.schema.blocksSelector); - while (node && node.parentNode !== editorElement) { - node = node.parentNode; - } - - if (!node) throw new Error('Somehow the selection was inside an editor but not inside selection block'); - return node; -} - -function getType(selection) { - if (selection.isCollapsed && selection.anchorNode.nodeType === Node.ELEMENT_NODE) { - var selectedNode = getSelectedElement(selection); - if (!selectedNode) { - return 'none'; - } else if (selectedNode.nodeType === Node.ELEMENT_NODE && selectedNode.tagName !== 'BR') { - return 'media'; - } - } - return 'text'; -} - -// Given a text node and local offset, get the text offset within the block element -function getTextOffset(within, selectionNode, offset) { - if (within === selectionNode) { - if (offset === 0) return 0; - selectionNode = within.childNodes[offset]; - offset = 0; - // This happens when there is a single BR child and no text nodes - // return offset; - } - - var walker = getTextWalker(within); - var node, i = 0; - while ( (node = walker.nextNode())) { - if (node === selectionNode) { - i += offset; - break; - } else if (node.nodeName === 'BR') { - i++; - } else { - i += node.nodeValue.length; - } - } - return i; -} - -function getElementIndex(within, element) { - var likeElements = within.querySelectorAll(element.tagName); - return indexOf.call(likeElements, element); -} - - -// Given the block element and text offset within it, find the text node + local offset of that node for selecting -function getDOMOffset(within, index) { - if (index === 0 && within.firstChild.nodeName === 'BR') { - return { node: within, offset: 0 }; - } - - var walker = getTextWalker(within); - var node, i = 0; - while ( (node = walker.nextNode())) { - if (node.nodeName === 'BR') { - i++; // newline - if (i === index) { - return { node: node.parentNode, offset: indexOf.call(node.parentNode.childNodes, node) + 1 }; - } - } else { - if (i + node.nodeValue.length >= index) { - return { node: node, offset: index - i }; - } else { - i += node.nodeValue.length; - } - } - } -} - -function getTextWalker(root) { - return document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, function(node) { - if (node.nodeName === '#text' || node.nodeName === 'BR') { - return NodeFilter.FILTER_ACCEPT; - } else { - return NodeFilter.FILTER_SKIP; - } - }); -} - -function dispatchSelectionEvent(editor, name, options) { - var options = options || {}; - options.selection = editor.selection; - return editor.dispatch(name, options); -} diff --git a/src/editor/selectionchange-polyfill.js b/src/editor/selectionchange-polyfill.js deleted file mode 100644 index 358a417..0000000 --- a/src/editor/selectionchange-polyfill.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Dispatches selectionchanged (note the "d" at the end) events for browsers which do not support the "selectionchange" - * event, as well as for browsers who do that don't dispatch the event when DOM modifications change the selection. - * Since the order of events matters I am not naming this the same as "selectionchange" because it would then behave - * differently on different browsers. - */ -var anchorNode = null, anchorOffset = 0, focusNode = null, focusOffset = 0; - -function checkSelection() { - var selection = window.getSelection(); - if (anchorNode !== selection.anchorNode || - anchorOffset !== selection.anchorOffset || - focusNode !== selection.focusNode || - focusOffset !== selection.focusOffset) { - - // The selection has changed during the last frame, name it with an "ed" to differentiate from "selectionchange" in - // browsers that fire it - var selectionEvent = new Event('selectionchanged'); - selectionEvent.selection = selection; - document.dispatchEvent(selectionEvent); - anchorNode = selection.anchorNode; - anchorOffset = selection.anchorOffset; - focusNode = selection.focusNode; - focusOffset = selection.focusOffset; - } - - requestAnimationFrame(checkSelection); -} - -checkSelection(); diff --git a/src/editor/selectors.js b/src/editor/selectors.js deleted file mode 100644 index f2ae880..0000000 --- a/src/editor/selectors.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Utilities for comparing and creating elements using selectors - */ - -// Export functions -exports.isDeep = isDeep; -exports.normalize = normalize; -exports.fromElement = fromElement; -exports.createElementDeep = createElementDeep; -exports.createElement = createElement; - -// First match tag, then classes and attributes -var descExp = /\s*>\s*/; -var tagExp = new RegExp('(\\w+)'); -var classesExp = new RegExp('\\.([-a-z]+)', 'g'); -var attribsExp = new RegExp('\\[([-a-z]+)(?:="([^"]*)")?\\]', 'g'); -var membersExp = new RegExp(classesExp.source + '|' + attribsExp.source, 'gi'); -var selectorExp = new RegExp(tagExp.source + '((?:' + membersExp.source + ')*)', 'i'); -var aliases = { - b: 'strong', - i: 'em' -}; - - -/** - * Normalizes a selector for easy string comparison - * @param {String} selector A CSS selector that MUST have tag names for each element and MAY have class names and - * attribute selectors. It MAY have child selectors (e.g. ul > li). - * @return {String} A normalized version of this selector sorted correctly - */ -function isDeep(selector) { - return descExp.test(selector); -} - -/** - * Normalizes a selector for easy string comparison - * @param {String} selector A CSS selector that MUST have tag names for each element and MAY have class names and - * attribute selectors. It MAY have child selectors (e.g. ul > li). - * @return {String} A normalized version of this selector sorted correctly - */ -function normalize(selector) { - return selector.split(descExp).map(normalizeElementSelector).join('>'); -} - -/** - * Get the selector that represents this element - * @param {Element} element The element whose selector we are determining - * @param {Element} container [Optional] The container that holds our blocks. If element is a markup, leave out - * @param {Object} ignore [Optional] An optional object with optional properties `classes` and `attributes` with the - * class names and attribute names which should be ignored set to true. - * @return {String} A selector for the given element - */ -function fromElement(element, container, ignore) { - if (container && !container.nodeType) { - ignore = container; - container = null; - } - if (!ignore) ignore = {}; - if (!ignore.classes) ignore.classes = {}; - if (!ignore.attributes) ignore.attributes = {}; - ignore.classes.empty = true; - ignore.classes.selected = true; - ignore.attributes.id = true; - ignore.attributes.class = true; - ignore.attributes.name = true; - ignore.attributes.placeholder = true; - - var selector = element.tagName.toLowerCase(); - if (aliases[selector]) { - selector = aliases[selector]; - } - - var classList = element.className.split(/\s+/).filter(function(name) { - return name && !ignore.classes[name]; - }).sort(); - - if (classList.length) { - selector += '.' + classList.join('.'); - } - - var attrNames = []; - for (var i = 0; i < element.attributes.length; i++) { - var attr = element.attributes[i]; - if (!ignore.attributes[attr.name]) attrNames.push(attr.name); - } - - attrNames.forEach(function(name) { - var value = element.getAttribute('name'); - selector += '[' + name + (!value ? ']' : '="' + value + '"]'); - }); - - if (container && element.parentNode !== container) { - selector = fromElement(element.parentNode, container, ignore) + '>' + selector; - } - return selector; -} - -/** - * Creates a new element and all the children to match a given selector, e.g. ul>li will create a ul with an li in it - * @param {String} selector The selector to use for creating - * @return {Element} The element created from the selector, with possible children - */ -function createElementDeep(selector, blockElement) { - var parts = selector.split(descExp) - if (blockElement) parts.pop(); - return parts.reverse().reduce(function(child, selector) { - var element = createElement(selector); - if (child) element.appendChild(child); - return element; - }, blockElement); -} - -/** - * Create a new element matching the selector (if there are children it will create the deepest) - * @param {String} selector The selector to use for creating - * @return {Element} The element created from the selector - */ -function createElement(selector) { - selector = selector.split(descExp).pop(); - var match = selectorExp.exec(selector); - if (!match) throw new TypeError('Invalid selector ' + selector); - var tag = match[1], members = match[2]; - var element = document.createElement(tag); - var classes = []; - while (match = classesExp.exec(members)) { - classes.push(match[1]); - } - if (classes.length) element.className = classes.join(' '); - while (match = attribsExp.exec(members)) { - element.setAttribute(match[1], match[2]); - } - return element; -} - - -// Normalizes selectors to match a certain order for easy string comparison. -// Put the classes first, then the attributes, both in alphabetical order. -function normalizeElementSelector(selector) { - var match = selectorExp.exec(selector); - if (!match) throw new TypeError('Invalid selector ' + selector); - var tag = match[1], members = match[2]; - var normalized = tag.toLowerCase(); - if (!match[2]) return normalized; - var classes = []; - var attributes = {}; - while (match = membersExp.exec(members)) { - if (match[1]) classes.push(match[1]); - else attributes[match[2].toLowerCase()] = match[3]; - } - if (classes.length) normalized += '.' + classes.sort().join('.'); - var attrNames = Object.keys(attributes).sort(); - attrNames.forEach(function(name) { - var value = attributes[name]; - normalized += '[' + name.toLowerCase(); - normalized += (value === undefined) ? ']' : '="' + value + '"]'; - }); - return normalized; -} diff --git a/src/eventdispatcher.js b/src/eventdispatcher.js new file mode 100644 index 0000000..6805baf --- /dev/null +++ b/src/eventdispatcher.js @@ -0,0 +1,36 @@ +const dispatcherEvents = new WeakMap(); + + +export default class EventDispatcher { + + on(type, listener) { + getEventListeners(this, type).add(listener); + } + + off(type, listener) { + getEventListeners(this, type).delete(listener); + } + + once(type, listener) { + function once(...args) { + this.off(type, once); + listener.apply(this, args); + } + this.on(type, once); + } + + fire(type, ...args) { + let uncanceled = true; + getEventListeners(this, type).forEach(listener => { + uncanceled && listener.apply(this, args) !== false || (uncanceled = false); + }); + return uncanceled; + } +} + + +function getEventListeners(obj, type) { + let events = dispatcherEvents.get(obj); + if (!events) dispatcherEvents.set(obj, events = Object.create(null)); + return events[type] || (events[type] = new Set()); +} diff --git a/src/html-view.js b/src/html-view.js new file mode 100644 index 0000000..ef7a41e --- /dev/null +++ b/src/html-view.js @@ -0,0 +1,167 @@ +import EventDispatcher from './eventdispatcher'; +import Editor from './editor'; +import { patch } from 'ultradom'; +import defaultDom from './defaultDom'; +import { getSelection, setSelection, getBrowserRange, getNodeAndOffset } from './selection'; +import { DOM, deltaToVdom, deltaFromDom, deltaToHTML } from './dom'; +import shortcuts from 'shortcut-string'; +import { shallowEqual } from 'fast-equals'; + +const SOURCE_API = 'api'; +const SOURCE_USER = 'user'; +const SOURCE_SILENT = 'silent'; +const isMac = navigator.userAgent.indexOf('Macintosh') !== -1; + + +export default class HTMLView extends EventDispatcher { + + constructor(editor, options = {}) { + super(); + if (!editor) throw new Error('Editor view requires an editor'); + this.editor = editor; + this.root = null; + this.dom = new DOM(options.dom || defaultDom); + this.enabled = true; + this.isMac = isMac; + this._settingEditorSelection = false; + this._settingBrowserSelection = false; + + if (options.modules) options.modules.forEach(module => module(this)); + + this.editor.on('text-change', () => this.update()); + this.editor.on('selection-change', () => !this._settingEditorSelection && this.updateBrowserSelection()); + } + + hasFocus() { + return this.root.contains(this.root.ownerDocument.activeElement); + } + + focus() { + this.root.focus(); + } + + blur() { + this.root.blur(); + } + + disable() { + this.enable(false); + } + + enable(enabled = true) { + this.enabled = enabled; + this.update(); + } + + getBounds(range) { + range = this.editor.normalizeRange(range, this.editor.length - 1); + const browserRange = getBrowserRange(this, range); + if (browserRange.endContainer.nodeType === Node.ELEMENT_NODE) { + browserRange.setEnd(browserRange.endContainer, browserRange.endOffset + 1); + } + return browserRange.getBoundingClientRect(); + } + + getHTML() { + return deltaToHTML(this, this.editor.contents); + } + + setHTML(html) { + this.editor.setContents(deltaFromHTML(this, html)); + } + + update() { + let contents = this.editor.contents; + let viewEditor = new Editor({ contents }); + this.decorations = viewEditor.getChange(() => this.fire('decorate', viewEditor)); + if (this.root && this.decorations.ops.length) this.root.node = null; + const vdom = deltaToVdom(this, contents.compose(this.decorations)); + this.root = patch(vdom, this.root); + this.updateBrowserSelection(); + this.fire('update'); + } + + updateBrowserSelection() { + if (this._settingEditorSelection) return; + this._settingBrowserSelection = true; + setSelection(this, this.editor.selection); + setTimeout(() => this._settingBrowserSelection = false, 20); + } + + updateEditorSelection() { + if (this._settingBrowserSelection) return this._settingBrowserSelection = false; + const range = getSelection(this); + this._settingEditorSelection = true; + this.editor.setSelection(range); + this._settingEditorSelection = false; + if (!shallowEqual(range, this.editor.selection)) this.updateBrowserSelection(); + } + + mount(container) { + this.update(); + container.appendChild(this.root); + this.root.ownerDocument.execCommand('defaultParagraphSeparator', false, 'p'); + + const onKeyDown = event => { + const shortcut = shortcuts.fromEvent(event); + this.fire(`shortcut:${shortcut}`, event, shortcut); + this.fire(`shortcut`, event, shortcut); + }; + + // TODO this was added to replace the mutation observer, however, it does not accurately capture changes that + // occur with native changes such as spell-check replacements, cut or delete using the app menus, etc. Paste should + // be handled elsewhere (probably?). + const onInput = () => { + if (!this.editor.selection) throw new Error('How did an input event occur without a selection?'); + const [ from, to ] = this.editor.getSelectedRange(); + const [ node, offset ] = getNodeAndOffset(this, from); + if (!node || (node.nodeType !== Node.TEXT_NODE && node.nodeName !== 'BR')) { + this.root.node = null; + return this.update(); + //throw new Error('Text entry should always result in a text node'); + } + if (from !== to || Object.keys(this.editor.activeFormats).length) { + this.root.node = null; // The DOM may have (or will be) changing, refresh from scratch + } + const text = node.nodeValue.slice(offset, offset + 1).replace(/\xA0/g, ' '); + this.editor.insertText(this.editor.selection, text, this.editor.getTextFormat(from)); + }; + + const onSelectionChange = () => { + this.updateEditorSelection(); + }; + + // Use mutation tracking during development to catch errors + // TODO delete mutation observer + let checking = 0; + const onMutate = list => { + if (checking) clearTimeout(checking); + checking = setTimeout(() => { + checking = 0; + const diff = editor.contents.compose(this.decorations).diff(deltaFromDom(view)); + if (diff.length()) { + console.error('Delta out of sync with DOM:', diff); + } + }, 20); + }; + + this.root.addEventListener('keydown', onKeyDown); + this.root.addEventListener('input', onInput); + container.ownerDocument.addEventListener('selectionchange', onSelectionChange); + + const observer = new MutationObserver(onMutate); + observer.observe(this.root, { characterData: true, characterDataOldValue: true, childList: true, attributes: true, subtree: true }); + + this.unmount = () => { + observer.disconnect(); + this.root.removeEventListener('keydown', onKeyDown); + this.root.removeEventListener('input', onInput); + this.root.ownerDocument.removeEventListener('selectionchange', onSelectionChange); + this.root.remove(); + this.unmount = () => {}; + } + } + + unmount() {} + +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..09de630 --- /dev/null +++ b/src/index.js @@ -0,0 +1,10 @@ +import Editor from './editor'; +import HTMLView from './html-view'; + +import input from './modules/input'; +import keyShortcuts from './modules/key-shortcuts'; +import history from './modules/history'; + +const defaultViewModules = [ input, keyShortcuts, history ]; + +export { Editor, HTMLView, input, keyShortcuts, history, defaultViewModules }; diff --git a/src/modules/history.js b/src/modules/history.js new file mode 100644 index 0000000..8a3a041 --- /dev/null +++ b/src/modules/history.js @@ -0,0 +1,116 @@ +const SOURCE_USER = 'user'; +const defaultSettings = { + delay: 4000, + maxStack: 500, +}; + +export default function history(view, settings = defaultSettings) { + const editor = view.editor; + + const stack = { + undo: [], + redo: [], + }; + let lastRecorded = 0; + let lastAction; + let ignoreChange = false; + + function undo(event) { + action(event, 'undo', 'redo'); + } + + function redo() { + action(event, 'redo', 'undo'); + } + + function action(event, source, dest) { + if (event.defaultPrevented) return; + event.preventDefault(); + if (stack[source].length === 0) return; + let entry = stack[source].pop(); + stack[dest].push(entry); + lastRecorded = 0; + ignoreChange = true; + editor.updateContents(entry[source], SOURCE_USER, entry[source + 'Selection']); + ignoreChange = false; + } + + function record(change, contents, oldContents, selection, oldSelection) { + const timestamp = Date.now(); + const action = getAction(change); + stack.redo.length = 0; + + let undoChange = contents.diff(oldContents); + // Break combining if actions are different (e.g. a delete then an insert should break it) + if (!action || lastAction !== action) lastRecorded = 0; + lastAction = action; + + if (lastRecorded + settings.delay > timestamp && stack.undo.length > 0) { + // Combine with the last change + let entry = stack.undo.pop(); + oldSelection = entry.undoSelection; + undoChange = undoChange.compose(entry.undo); + change = entry.redo.compose(change); + } else { + lastRecorded = timestamp; + } + + stack.undo.push({ + redo: change, + undo: undoChange, + redoSelection: selection, + undoSelection: oldSelection, + }); + + if (stack.undo.length > settings.maxStack) { + stack.undo.shift(); + } + } + + + function transform(change) { + stack.undo.forEach(function(entry) { + entry.undo = change.transform(entry.undo, true); + entry.redo = change.transform(entry.redo, true); + }); + stack.redo.forEach(function(entry) { + entry.undo = change.transform(entry.undo, true); + entry.redo = change.transform(entry.redo, true); + }); + } + + + editor.on('editor-change', ({ change, contents, oldContents, selection, oldSelection, source }) => { + if (ignoreChange) return; + if (!change) { + // Break the history merging when selection changes + lastRecorded = 0; + return; + } + if (source === SOURCE_USER) { + record(change, contents, oldContents, selection, oldSelection); + } else { + transform(change); + } + }); + + editor.on('selection') + + if (view.isMac) { + view.on('shortcut:Cmd+Z', undo); + view.on('shortcut:Cmd+Shift+Z', redo); + } else { + view.on('shortcut:Ctrl+Z', undo); + view.on('shortcut:Cmd+Y', redo); + } +} + +function getAction(change) { + if (change.ops.length === 1 || change.ops.length === 2 && change.ops[0].retain && !change.ops[0].attributes) { + const changeOp = change.ops[change.ops.length - 1]; + if (changeOp.delete) return 'delete'; + if (changeOp.insert === '\n') return 'newline'; + if (changeOp.insert) return 'insert'; + } + return ''; +} diff --git a/src/modules/input.js b/src/modules/input.js new file mode 100644 index 0000000..acf90bd --- /dev/null +++ b/src/modules/input.js @@ -0,0 +1,72 @@ +const lastWord = /\w+[^\w]*$/; +const firstWord = /^[^\w]*\w+/; +const lastLine = /[^\n]*$/; +const firstLine = /^[^\n]*/; + +// Basic text input module. Prevent any actions other than typing characters and handle with the API. +export default function input(view) { + const editor = view.editor; + + function onEnter(event, shortcut) { + if (event.defaultPrevented) return; + event.preventDefault(); + const [ from, to ] = editor.getSelectedRange(); + + if (shortcut === 'Shift+Enter') { + editor.insertText(from, to, '\r'); + } else { + const line = editor.contents.getLine(from); + editor.insertText([from, to], '\n', line.attributes); + } + } + + function onBackspace(event, shortcut) { + if (event.defaultPrevented) return; + event.preventDefault(); + + let [ from, to ] = editor.selection; + if (from + to === 0) { + editor.formatLine(0, {}); + } else { + // The "from" block needs to stay the same. The "to" block gets merged into it + if (from === to) { + if (shortcut === 'Alt+Backspace' && view.isMac) { + const match = editor.getText().slice(0, from).match(lastWord); + if (match) from -= match[0].length; + } else if (shortcut === 'Cmd+Backspace' && view.isMac) { + const match = editor.getText().slice(0, from).match(lastLine); + if (match) from -= match[0].length; + } else { + from--; + } + } + editor.deleteText([from, to]); + } + } + + function onDelete(event, shortcut) { + if (event.defaultPrevented) return; + event.preventDefault(); + + let [ from, to ] = editor.selection; + if (from === to && from === editor.length) return; + + if (from === to) { + if (shortcut === 'Alt+Delete' && view.isMac) { + const match = editor.getText().slice(from).match(firstWord); + if (match) to += match[0].length; + } else { + to++; + } + } + editor.deleteText([from, to]); + } + + view.on('shortcut:Enter', onEnter); + view.on('shortcut:Shift+Enter', onEnter); + view.on('shortcut:Backspace', onBackspace); + view.on('shortcut:Alt+Backspace', onBackspace); + view.on('shortcut:Cmd+Backspace', onBackspace); + view.on('shortcut:Delete', onDelete); + view.on('shortcut:Alt+Delete', onDelete); +} diff --git a/src/modules/key-shortcuts.js b/src/modules/key-shortcuts.js new file mode 100644 index 0000000..035247e --- /dev/null +++ b/src/modules/key-shortcuts.js @@ -0,0 +1,41 @@ +const SOURCE_USER = 'user'; + + +export const keymap = { + 'Ctrl+B': editor => editor.toggleTextFormat(editor.selection, { bold: true }), + 'Ctrl+I': editor => editor.toggleTextFormat(editor.selection, { italics: true }), + 'Ctrl+1': editor => editor.toggleLineFormat(editor.selection, { header: 1 }), + 'Ctrl+2': editor => editor.toggleLineFormat(editor.selection, { header: 2 }), + 'Ctrl+3': editor => editor.toggleLineFormat(editor.selection, { header: 3 }), + 'Ctrl+4': editor => editor.toggleLineFormat(editor.selection, { header: 4 }), + 'Ctrl+5': editor => editor.toggleLineFormat(editor.selection, { header: 5 }), + 'Ctrl+6': editor => editor.toggleLineFormat(editor.selection, { header: 6 }), + 'Ctrl+0': editor => editor.formatLine(editor.selection, { }), +}; + +export const macKeymap = { + 'Cmd+B': keymap['Ctrl+B'], + 'Cmd+I': keymap['Ctrl+I'], + 'Cmd+1': keymap['Ctrl+1'], + 'Cmd+2': keymap['Ctrl+2'], + 'Cmd+3': keymap['Ctrl+3'], + 'Cmd+4': keymap['Ctrl+4'], + 'Cmd+5': keymap['Ctrl+5'], + 'Cmd+6': keymap['Ctrl+6'], + 'Cmd+0': keymap['Ctrl+0'], +}; + +// Basic text input module. Prevent any actions other than typing characters and handle with the API. +export default function keyShortcuts(view) { + const editor = view.editor; + + view.on('shortcut', (event, shortcut) => { + if (event.defaultPrevented) return; + const map = view.isMac ? macKeymap : keymap; + + if (shortcut in map) { + event.preventDefault(); + map[shortcut](editor); + } + }); +} diff --git a/src/modules/placeholder.js b/src/modules/placeholder.js new file mode 100644 index 0000000..9eeb18d --- /dev/null +++ b/src/modules/placeholder.js @@ -0,0 +1,18 @@ +import { h } from 'ultradom'; + +export default function placeholder(placeholder) { + return view => { + + view.dom.markups.add({ + name: 'placeholder', + selector: 'span.placholder', + vdom: children => {children}, + }); + + view.on('decorate', editor => { + if (editor.length === 1) { + editor.insertText(placeholder, { placeholder: true }); + } + }); + } +} diff --git a/src/selection.js b/src/selection.js new file mode 100644 index 0000000..ee7ecd0 --- /dev/null +++ b/src/selection.js @@ -0,0 +1,122 @@ +const indexOf = [].indexOf; + +// Get the range (a tuple of indexes) for this view from the browser selection +export function getSelection(view) { + const root = view.root; + const selection = root.ownerDocument.defaultView.getSelection(); + + if (!root.contains(selection.anchorNode)) { + return null; + } else { + const anchorIndex = getNodeIndex(view, selection.anchorNode); + const focusIndex = selection.anchorNode === selection.focusNode ? + anchorIndex : getNodeIndex(view, selection.focusNode); + + return [ + anchorIndex + selection.anchorOffset, + focusIndex + selection.focusOffset, + ]; + } +} + +// Set the browser selection to the range (a tuple of indexes) of this view +export function setSelection(view, range) { + const root = view.root; + const selection = root.ownerDocument.defaultView.getSelection(); + const hasFocus = root.contains(root.ownerDocument.activeElement); + + if (range == null) { + if (hasFocus) { + root.blur(); + selection.setBaseAndExtent(null, 0, null, 0); + } + } else { + const [ anchorNode, anchorOffset, focusNode, focusOffset ] = getNodesForRange(view, range); + selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset); + if (!hasFocus) root.focus(); + } +} + +// Get a browser range object for the given editor range tuple +export function getBrowserRange(view, range) { + if (range[0] > range[1]) range = [ range[1], range[0] ]; + const [ anchorNode, anchorOffset, focusNode, focusOffset ] = getNodesForRange(view, range); + const browserRange = document.createRange(); + browserRange.setStart(anchorNode, anchorOffset); + browserRange.setEnd(focusNode, focusOffset); + return browserRange; +} + + +// Get the browser nodes and offsets for the range (a tuple of indexes) of this view +export function getNodesForRange(view, range) { + if (range == null) { + return [ null, 0, null, 0 ]; + } else { + const [ anchorNode, anchorOffset ] = getNodeAndOffset(view, range[0]); + const [ focusNode, focusOffset ] = range[0] === range[1] ? + [ anchorNode, anchorOffset ] : getNodeAndOffset(view, range[1]); + + return [ anchorNode, anchorOffset, focusNode, focusOffset ]; + } +} + +export function getNodeAndOffset(view, index) { + const root = view.root; + const blocksSelector = view.dom.blocks.selector; + const walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, { + acceptNode: node => { + return (node.nodeType === Node.TEXT_NODE || node.offsetParent) && + NodeFilter.FILTER_ACCEPT || + NodeFilter.FILTER_REJECT; + } + }); + + let count = 0, node, firstBlockSeen = false; + walker.currentNode = root; + while ((node = walker.nextNode())) { + if (node.nodeType === Node.TEXT_NODE) { + const size = node.nodeValue.length + if (index <= count + size) return [ node, index - count ]; + count += size; + } else if (node.matches(blocksSelector)) { + if (firstBlockSeen) count += 1; + else firstBlockSeen = true; + + // If the selection lands at the beginning of a block, and the first node isn't a text node, place the selection + if (count === index && (!node.firstChild || node.firstChild.nodeType !== Node.TEXT_NODE)) { + return [ node, 0 ]; + } + } else if (node.nodeName === 'BR' && node.parentNode.lastChild !== node) { + count += 1; + // If the selection lands after this br, and the next node isn't a text node, place the selection + if (count === index && (!node.nextSibling || node.nextSibling.nodeType !== Node.TEXT_NODE)) { + return [ node.parentNode, indexOf.call(node.parentNode.childNodes, node) + 1 ]; + } + } + } + return [ null, 0 ]; +} + +// Get the index the node starts at in the content +export function getNodeIndex(view, node) { + const root = view.root; + const blocksSelector = view.dom.blocks.selector; + const walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, { + acceptNode: node => { + return (node.nodeType === Node.TEXT_NODE || node.offsetParent) && + NodeFilter.FILTER_ACCEPT || + NodeFilter.FILTER_REJECT; + } + }); + + walker.currentNode = node; + let index = node.nodeType === Node.ELEMENT_NODE ? 0 : -1; + while ((node = walker.previousNode())) { + if (node.nodeType === Node.TEXT_NODE) index += node.nodeValue.length; + else if (node.nodeName === 'BR' && node.parentNode.lastChild !== node) index++; + else if (node !== root && node.matches(blocksSelector)) index++; + } + return index; +} + diff --git a/test/editor/test-history.js b/test/editor/test-history.js deleted file mode 100644 index c2538e9..0000000 --- a/test/editor/test-history.js +++ /dev/null @@ -1,125 +0,0 @@ -var History = require('../../src/editor/history'); -var Command = require('../../src/editor/command'); - -describe('History', function() { - var history, CommandClass; - - beforeEach(function() { - history = new History(); - history.editor = { - selection: { - lastRange: {}, - range: {} - } - }; - history.commands = {}; - history.options = Object.assign({}, history.options); - CommandClass = function(args) { - Object.assign(this, args); - }; - Command.extend(CommandClass); - }); - - it('should execute an action', function() { - var executed = false; - CommandClass.prototype.exec = function() { - executed = true; - return true; - }; - - history.commands.test = CommandClass; - - var command = history.exec('test', { foo: 'bar' }); - expect(command).to.have.property('foo', 'bar'); - expect(executed).to.be.true; - }); - - it('should undo an action', function() { - var undone = false; - CommandClass.prototype.exec = function() { - executed = true; - return true; - }; - CommandClass.prototype.undo = function() { - undone = true; - return true; - }; - - history.commands.test = CommandClass; - - history.exec('test'); - var result = history.undo(); - expect(result).to.be.true; - expect(undone).to.be.true; - }); - - it('should redo an action', function() { - var executed = 0; - CommandClass.prototype.exec = function() { - executed++; - return true; - }; - CommandClass.prototype.undo = function() { - return true; - }; - - history.commands.test = CommandClass; - - history.exec('test'); - history.undo(); - var result = history.redo(); - expect(result).to.be.true; - expect(executed).to.equal(2); - }); - - it('should indicate if undo and redo can run', function() { - CommandClass.prototype.exec = function() { - return true; - }; - CommandClass.prototype.undo = function() { - return true; - }; - history.commands.test = CommandClass; - - expect(history.canUndo).to.be.false; - expect(history.canRedo).to.be.false; - - history.exec('test'); - - expect(history.canUndo).to.be.true; - expect(history.canRedo).to.be.false; - - history.undo(); - - expect(history.canUndo).to.be.false; - expect(history.canRedo).to.be.true; - - history.redo(); - - expect(history.canUndo).to.be.true; - expect(history.canRedo).to.be.false; - }); - - it('should clear redo after actions', function() { - CommandClass.prototype.exec = function() { - return true; - }; - CommandClass.prototype.undo = function() { - return true; - }; - history.commands.test = CommandClass; - - history.exec('test', {}); - history.undo(); - - expect(history.canRedo).to.be.true; - history.exec('test', {}); - - expect(history.canRedo).to.be.false; - - var result = history.redo(); - - expect(result).to.be.false; - }); - -}); diff --git a/test/editor/test-mapping.js b/test/editor/test-mapping.js deleted file mode 100644 index 6d30046..0000000 --- a/test/editor/test-mapping.js +++ /dev/null @@ -1,136 +0,0 @@ -var mapping = require('../../src/editor/mapping'); -var defaultSchema = require('../../src/editor/schema/default'); -var Paragraph = require('../../src/editor/blocks/paragraph'); -var Bold = require('../../src/editor/markups/bold'); -var Italic = require('../../src/editor/markups/italic'); -var Link = require('../../src/editor/markups/link'); - -describe('Mapping', function() { - var p; - var text = 'This is a test, it is only\na test'; - var html = 'This is a test, it is only
    a
    ' + - ' test'; - var markups = [ - new Link(22, 28, 'blah'), - new Bold(10, 14), - new Bold(22, 33), - new Italic(22, 33) - ]; - - beforeEach(function() { - p = document.createElement('p'); - }); - - - describe('textFromDOM', function() { - - it('should parse the text from a paragraph correctly', function() { - p.innerHTML = html; - var result = mapping.textFromDOM(defaultSchema, p); - - expect(result.text).to.equal(text); - expect(result.markups).to.have.length(4); - expect(result.markups).to.deep.equal(markups); - }); - - it('should parse an empty paragraph correctly', function() { - p.innerHTML = '
    '; - var result = mapping.textFromDOM(defaultSchema, p); - - expect(result.text).to.equal(''); - expect(result.markups).to.have.length(0); - }); - - }); - - - describe('textToDOM', function() { - - it('should create the text for a paragraph correctly', function() { - var fragment = mapping.textToDOM(defaultSchema, { text: text, markups: markups }); - p.appendChild(fragment); - - expect(p.innerHTML).to.equal(html); - }); - - }); - - - describe('blockFromDOM', function() { - - it('should create a block from a paragraph', function() { - p.innerHTML = html; - var block = mapping.blockFromDOM(defaultSchema, p); - - expect(block.text).to.equal(text); - expect(block.markups).to.deep.equal(markups); - expect(block).to.be.instanceof(Paragraph); - }); - - }); - - - describe('blockToDOM', function() { - - it('should create a paragraph element from a block', function() { - var block = new Paragraph(); - block.text = text; - block.markups = markups; - - var element = mapping.blockToDOM(defaultSchema, block); - - expect(element.nodeName).to.equal('P'); - expect(element.innerHTML).to.equal(html); - }); - - it('should create a paragraph with an "empty" class if there is no text', function() { - var block = new Paragraph(); - - var element = mapping.blockToDOM(defaultSchema, block); - - expect(element.nodeName).to.equal('P'); - expect(element.innerHTML).to.equal('
    '); - expect(element.className).to.equal('empty'); - }); - - }); - - - describe('blocksFromDOM', function() { - - it('should convert an element into an array of blocks', function() { - var element = document.createElement('div'); - var pHTML = '

    ' + html + '

    '; - element.innerHTML = pHTML + pHTML + pHTML; - var blocks = mapping.blocksFromDOM(defaultSchema, element); - var block = new Paragraph(); - block.text = text; - block.markups = markups; - - expect(blocks).to.have.length(3); - expect(blocks).to.deep.equal([ block, block, block ]); - }); - - }); - - - describe('blocksToDOM', function() { - - it('should convert an array of blocks into a document fragment', function() { - var element = document.createElement('div'); - var pHTML = '

    ' + html + '

    '; - pHTML = pHTML + pHTML + pHTML; - var block = new Paragraph(); - block.text = text; - block.markups = markups; - var fragment = mapping.blocksToDOM(defaultSchema, [ block, block, block ]); - - expect(fragment.childNodes).to.have.length(3); - - element.appendChild(fragment); - expect(element.innerHTML).to.equal(pHTML); - }); - - }); - -});