From 2a3472a0fa016d9d395195ab2fb9c021272745f3 Mon Sep 17 00:00:00 2001 From: Zahra Jabini Date: Wed, 27 May 2020 09:28:14 -0400 Subject: [PATCH] =?UTF-8?q?Adds=20prettier=20+=20applies=20formatting=20?= =?UTF-8?q?=F0=9F=A7=9F=E2=80=8D=E2=99=80=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 10 +- src/css/mobiledoc-kit.css | 28 +- src/js/cards/image.js | 14 +- src/js/editor/edit-history.js | 110 ++- src/js/editor/edit-state.js | 111 +-- src/js/editor/editor.js | 705 +++++++++--------- src/js/editor/event-manager.js | 273 +++---- src/js/editor/key-commands.js | 312 ++++---- src/js/editor/mutation-handler.js | 123 ++- src/js/editor/post.js | 828 ++++++++++----------- src/js/editor/post/post-inserter.js | 230 +++--- src/js/editor/selection-change-observer.js | 74 +- src/js/editor/selection-manager.js | 24 +- src/js/editor/text-input-handler.js | 77 +- src/js/editor/text-input-handlers.js | 62 +- src/js/editor/ui.js | 56 +- src/js/index.js | 18 +- src/js/models/_attributable.js | 28 +- src/js/models/_markerable.js | 251 +++---- src/js/models/_section.js | 88 ++- src/js/models/atom-node.js | 69 +- src/js/models/atom.js | 93 ++- src/js/models/card-node.js | 88 +-- src/js/models/card.js | 44 +- src/js/models/image.js | 14 +- src/js/models/lifecycle-callbacks.js | 46 +- src/js/models/list-item.js | 40 +- src/js/models/list-section.js | 73 +- src/js/models/marker.js | 108 ++- src/js/models/markup-section.js | 66 +- src/js/models/markup.js | 47 +- src/js/models/post-node-builder.js | 143 ++-- src/js/models/post.js | 184 +++-- src/js/models/render-node.js | 93 +-- src/js/models/render-tree.js | 40 +- src/js/models/types.js | 18 +- src/js/parsers/dom.js | 323 ++++---- src/js/parsers/html.js | 20 +- src/js/parsers/mobiledoc/0-2.js | 102 +-- src/js/parsers/mobiledoc/0-3-1.js | 130 ++-- src/js/parsers/mobiledoc/0-3-2.js | 140 ++-- src/js/parsers/mobiledoc/0-3.js | 130 ++-- src/js/parsers/mobiledoc/index.js | 37 +- src/js/parsers/section.js | 394 +++++----- src/js/parsers/text.js | 93 ++- src/js/renderers/editor-dom.js | 579 +++++++------- src/js/renderers/mobiledoc/0-2.js | 124 +-- src/js/renderers/mobiledoc/0-3-1.js | 162 ++-- src/js/renderers/mobiledoc/0-3-2.js | 166 ++--- src/js/renderers/mobiledoc/0-3.js | 162 ++-- src/js/renderers/mobiledoc/index.js | 26 +- src/js/utils/array-utils.js | 125 ++-- src/js/utils/assert.js | 6 +- src/js/utils/browser.js | 8 +- src/js/utils/characters.js | 6 +- src/js/utils/compiler.js | 30 +- src/js/utils/copy.js | 12 +- src/js/utils/cursor.js | 170 ++--- src/js/utils/cursor/position.js | 340 ++++----- src/js/utils/cursor/range.js | 126 ++-- src/js/utils/deprecate.js | 4 +- src/js/utils/dom-utils.js | 71 +- src/js/utils/element-map.js | 23 +- src/js/utils/element-utils.js | 86 +-- src/js/utils/environment.js | 6 +- src/js/utils/fixed-queue.js | 18 +- src/js/utils/key.js | 217 +++--- src/js/utils/keycodes.js | 72 +- src/js/utils/keys.js | 42 +- src/js/utils/linked-item.js | 4 +- src/js/utils/linked-list.js | 202 ++--- src/js/utils/log-manager.js | 28 +- src/js/utils/markuperable.js | 50 +- src/js/utils/merge.js | 14 +- src/js/utils/mixin.js | 14 +- src/js/utils/mobiledoc-error.js | 20 +- src/js/utils/object-utils.js | 10 +- src/js/utils/parse-utils.js | 95 +-- src/js/utils/placeholder-image-src.js | 5 +- src/js/utils/selection-utils.js | 186 ++--- src/js/utils/set.js | 14 +- src/js/utils/string-utils.js | 14 +- src/js/utils/to-range.js | 14 +- src/js/version.js | 2 +- src/js/views/tooltip.js | 102 ++- src/js/views/view.js | 50 +- yarn.lock | 5 + 87 files changed, 4664 insertions(+), 4703 deletions(-) diff --git a/package.json b/package.json index 171225041..ee7b40a37 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "scripts": { "start": "rollup -c --watch", "test:ci": "yarn build:docs && yarn build && testem ci -f testem-ci.js", - "test": "yarn build && testem ci -f testem.js", + "test": "yarn format && yarn build && testem ci -f testem.js", + "format": "prettier src --write", "build": "rollup -c", "build:docs": "jsdoc -c ./.jsdoc", "build:website": "yarn build:docs && yarn build && ./bin/build-website.sh", @@ -52,6 +53,7 @@ "conventional-changelog-cli": "^2.0.34", "jquery": "^3.5.1", "jsdoc": "^3.6.4", + "prettier": "^2.0.5", "qunit": "^2.10.0", "rollup": "^2.10.3", "rollup-plugin-copy": "^3.3.0", @@ -60,6 +62,12 @@ "saucie": "^3.3.3", "testem": "^3.1.0" }, + "prettier": { + "arrowParens": "avoid", + "printWidth": 120, + "semi": false, + "singleQuote": true + }, "volta": { "node": "12.14.1", "yarn": "1.22.1" diff --git a/src/css/mobiledoc-kit.css b/src/css/mobiledoc-kit.css index ae0cb66d3..1fcdf89be 100644 --- a/src/css/mobiledoc-kit.css +++ b/src/css/mobiledoc-kit.css @@ -96,26 +96,34 @@ */ @-webkit-keyframes fade-in { - 0% { opacity: 0; } - 100% { opacity: 1; } + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } } @keyframes fade-in { - 0% { opacity: 0; } - 100% { opacity: 1; } + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } } .__mobiledoc-tooltip { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 0.7em; white-space: nowrap; position: absolute; - background-color: rgba(43,43,43,0.9); + background-color: rgba(43, 43, 43, 0.9); border-radius: 3px; line-height: 1em; padding: 0.7em 0.9em; - color: #FFF; + color: #fff; -webkit-animation: fade-in 0.2s; - animation: fade-in 0.2s; + animation: fade-in 0.2s; } .__mobiledoc-tooltip:before { @@ -126,7 +134,7 @@ height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; - border-bottom: 5px solid rgba(43,43,43,0.9); + border-bottom: 5px solid rgba(43, 43, 43, 0.9); top: -5px; margin-left: -5px; } @@ -142,7 +150,7 @@ } .__mobiledoc-tooltip a { - color: #FFF; + color: #fff; text-decoration: none; } diff --git a/src/js/cards/image.js b/src/js/cards/image.js index 5b30ea2fc..0d10202db 100644 --- a/src/js/cards/image.js +++ b/src/js/cards/image.js @@ -1,12 +1,12 @@ -import placeholderImageSrc from 'mobiledoc-kit/utils/placeholder-image-src'; +import placeholderImageSrc from 'mobiledoc-kit/utils/placeholder-image-src' export default { name: 'image', type: 'dom', - render({payload}) { - let img = document.createElement('img'); - img.src = payload.src || placeholderImageSrc; - return img; - } -}; + render({ payload }) { + let img = document.createElement('img') + img.src = payload.src || placeholderImageSrc + return img + }, +} diff --git a/src/js/editor/edit-history.js b/src/js/editor/edit-history.js index 3d24cf9ec..07a076c5a 100644 --- a/src/js/editor/edit-history.js +++ b/src/js/editor/edit-history.js @@ -1,126 +1,122 @@ -import mobiledocParsers from 'mobiledoc-kit/parsers/mobiledoc'; -import FixedQueue from 'mobiledoc-kit/utils/fixed-queue'; +import mobiledocParsers from 'mobiledoc-kit/parsers/mobiledoc' +import FixedQueue from 'mobiledoc-kit/utils/fixed-queue' function findLeafSectionAtIndex(post, index) { - let section; + let section post.walkAllLeafSections((_section, _index) => { if (index === _index) { - section = _section; + section = _section } - }); - return section; + }) + return section } export class Snapshot { - constructor(takenAt, editor, editAction=null) { - this.mobiledoc = editor.serialize(); - this.editor = editor; - this.editAction = editAction; - this.takenAt = takenAt; + constructor(takenAt, editor, editAction = null) { + this.mobiledoc = editor.serialize() + this.editor = editor + this.editAction = editAction + this.takenAt = takenAt - this.snapshotRange(); + this.snapshotRange() } snapshotRange() { - let { range, cursor } = this.editor; + let { range, cursor } = this.editor if (cursor.hasCursor() && !range.isBlank) { - let { head, tail } = range; + let { head, tail } = range this.range = { head: [head.leafSectionIndex, head.offset], - tail: [tail.leafSectionIndex, tail.offset] - }; + tail: [tail.leafSectionIndex, tail.offset], + } } } getRange(post) { if (this.range) { - let { head, tail } = this.range; - let [headLeafSectionIndex, headOffset] = head; - let [tailLeafSectionIndex, tailOffset] = tail; - let headSection = findLeafSectionAtIndex(post, headLeafSectionIndex); - let tailSection = findLeafSectionAtIndex(post, tailLeafSectionIndex); + let { head, tail } = this.range + let [headLeafSectionIndex, headOffset] = head + let [tailLeafSectionIndex, tailOffset] = tail + let headSection = findLeafSectionAtIndex(post, headLeafSectionIndex) + let tailSection = findLeafSectionAtIndex(post, tailLeafSectionIndex) - head = headSection.toPosition(headOffset); - tail = tailSection.toPosition(tailOffset); + head = headSection.toPosition(headOffset) + tail = tailSection.toPosition(tailOffset) - return head.toRange(tail); + return head.toRange(tail) } } groupsWith(groupingTimeout, editAction, takenAt) { - return ( - editAction !== null && - this.editAction === editAction && - this.takenAt + groupingTimeout > takenAt - ); + return editAction !== null && this.editAction === editAction && this.takenAt + groupingTimeout > takenAt } } export default class EditHistory { constructor(editor, queueLength, groupingTimeout) { - this.editor = editor; - this._undoStack = new FixedQueue(queueLength); - this._redoStack = new FixedQueue(queueLength); + this.editor = editor + this._undoStack = new FixedQueue(queueLength) + this._redoStack = new FixedQueue(queueLength) - this._pendingSnapshot = null; - this._groupingTimeout = groupingTimeout; + this._pendingSnapshot = null + this._groupingTimeout = groupingTimeout } snapshot() { // update the current snapshot with the range read from DOM if (this._pendingSnapshot) { - this._pendingSnapshot.snapshotRange(); + this._pendingSnapshot.snapshotRange() } } - storeSnapshot(editAction=null) { - let now = Date.now(); + storeSnapshot(editAction = null) { + let now = Date.now() // store pending snapshot - let pendingSnapshot = this._pendingSnapshot; + let pendingSnapshot = this._pendingSnapshot if (pendingSnapshot) { if (!pendingSnapshot.groupsWith(this._groupingTimeout, editAction, now)) { - this._undoStack.push(pendingSnapshot); + this._undoStack.push(pendingSnapshot) } - this._redoStack.clear(); + this._redoStack.clear() } // take new pending snapshot to store next time `storeSnapshot` is called - this._pendingSnapshot = new Snapshot(now, this.editor, editAction); + this._pendingSnapshot = new Snapshot(now, this.editor, editAction) } stepBackward(postEditor) { // Throw away the pending snapshot - this._pendingSnapshot = null; + this._pendingSnapshot = null - let snapshot = this._undoStack.pop(); + let snapshot = this._undoStack.pop() if (snapshot) { - this._redoStack.push(new Snapshot(Date.now(), this.editor)); - this._restoreFromSnapshot(snapshot, postEditor); + this._redoStack.push(new Snapshot(Date.now(), this.editor)) + this._restoreFromSnapshot(snapshot, postEditor) } } stepForward(postEditor) { - let snapshot = this._redoStack.pop(); + let snapshot = this._redoStack.pop() if (snapshot) { - this._undoStack.push(new Snapshot(Date.now(), this.editor)); - this._restoreFromSnapshot(snapshot, postEditor); + this._undoStack.push(new Snapshot(Date.now(), this.editor)) + this._restoreFromSnapshot(snapshot, postEditor) } - postEditor.cancelSnapshot(); + postEditor.cancelSnapshot() } _restoreFromSnapshot(snapshot, postEditor) { - let { mobiledoc } = snapshot; - let { editor } = this; - let { builder, post } = editor; - let restoredPost = mobiledocParsers.parse(builder, mobiledoc); + let { mobiledoc } = snapshot + let { editor } = this + let { builder, post } = editor + let restoredPost = mobiledocParsers.parse(builder, mobiledoc) - postEditor.removeAllSections(); - postEditor.migrateSectionsFromPost(restoredPost); + postEditor.removeAllSections() + postEditor.migrateSectionsFromPost(restoredPost) // resurrect snapshotted range if it exists - let newRange = snapshot.getRange(post); + let newRange = snapshot.getRange(post) if (newRange) { - postEditor.setRange(newRange); + postEditor.setRange(newRange) } } } diff --git a/src/js/editor/edit-state.js b/src/js/editor/edit-state.js index be4d0f9c6..0d68d0d5a 100644 --- a/src/js/editor/edit-state.js +++ b/src/js/editor/edit-state.js @@ -1,9 +1,5 @@ -import { - contains, - isArrayEqual, - objectToSortedKVArray -} from 'mobiledoc-kit/utils/array-utils'; -import Range from 'mobiledoc-kit/utils/cursor/range'; +import { contains, isArrayEqual, objectToSortedKVArray } from 'mobiledoc-kit/utils/array-utils' +import Range from 'mobiledoc-kit/utils/cursor/range' /** * Used by {@link Editor} to manage its current state (cursor, active markups @@ -12,36 +8,39 @@ import Range from 'mobiledoc-kit/utils/cursor/range'; */ class EditState { constructor(editor) { - this.editor = editor; + this.editor = editor let defaultState = { range: Range.blankRange(), activeMarkups: [], activeSections: [], activeSectionTagNames: [], - activeSectionAttributes: {} - }; + activeSectionAttributes: {}, + } - this.prevState = this.state = defaultState; + this.prevState = this.state = defaultState } updateRange(newRange) { - this.prevState = this.state; - this.state = this._readState(newRange); + this.prevState = this.state + this.state = this._readState(newRange) } destroy() { - this.editor = null; - this.prevState = this.state = null; + this.editor = null + this.prevState = this.state = null } /** * @return {Boolean} */ rangeDidChange() { - let { state: { range } , prevState: {range: prevRange} } = this; + let { + state: { range }, + prevState: { range: prevRange }, + } = this - return !prevRange.isEqual(range); + return !prevRange.isEqual(range) } /** @@ -49,39 +48,43 @@ class EditState { * has changed. */ inputModeDidChange() { - let { state, prevState } = this; - return (!isArrayEqual(state.activeMarkups, prevState.activeMarkups) || - !isArrayEqual(state.activeSectionTagNames, prevState.activeSectionTagNames) || - !isArrayEqual(objectToSortedKVArray(state.activeSectionAttributes), objectToSortedKVArray(prevState.activeSectionAttributes))); + let { state, prevState } = this + return ( + !isArrayEqual(state.activeMarkups, prevState.activeMarkups) || + !isArrayEqual(state.activeSectionTagNames, prevState.activeSectionTagNames) || + !isArrayEqual( + objectToSortedKVArray(state.activeSectionAttributes), + objectToSortedKVArray(prevState.activeSectionAttributes) + ) + ) } /** * @return {Range} */ get range() { - return this.state.range; + return this.state.range } /** * @return {Section[]} */ get activeSections() { - return this.state.activeSections; + return this.state.activeSections } - /** * @return {Object} */ get activeSectionAttributes() { - return this.state.activeSectionAttributes; + return this.state.activeSectionAttributes } /** * @return {Markup[]} */ get activeMarkups() { - return this.state.activeMarkups; + return this.state.activeMarkups } /** @@ -92,67 +95,71 @@ class EditState { */ toggleMarkupState(markup) { if (contains(this.activeMarkups, markup)) { - this._removeActiveMarkup(markup); + this._removeActiveMarkup(markup) } else { - this._addActiveMarkup(markup); + this._addActiveMarkup(markup) } } _readState(range) { let state = { range, - activeMarkups: this._readActiveMarkups(range), - activeSections: this._readActiveSections(range) - }; + activeMarkups: this._readActiveMarkups(range), + activeSections: this._readActiveSections(range), + } // Section objects are 'live', so to check that they changed, we // need to map their tagNames now (and compare to mapped tagNames later). // In addition, to catch changes from ul -> ol, we keep track of the // un-nested tag names (otherwise we'd only see li -> li change) state.activeSectionTagNames = state.activeSections.map(s => { - return s.isNested ? s.parent.tagName : s.tagName; - }); - state.activeSectionAttributes = this._readSectionAttributes(state.activeSections); - return state; + return s.isNested ? s.parent.tagName : s.tagName + }) + state.activeSectionAttributes = this._readSectionAttributes(state.activeSections) + return state } _readActiveSections(range) { - let { head, tail } = range; - let { editor: { post } } = this; + let { head, tail } = range + let { + editor: { post }, + } = this if (range.isBlank) { - return []; + return [] } else { - return post.sections.readRange(head.section, tail.section); + return post.sections.readRange(head.section, tail.section) } } _readActiveMarkups(range) { - let { editor: { post } } = this; - return post.markupsInRange(range); + let { + editor: { post }, + } = this + return post.markupsInRange(range) } _readSectionAttributes(sections) { return sections.reduce((sectionAttributes, s) => { - let attributes = s.isNested ? s.parent.attributes : s.attributes; + let attributes = s.isNested ? s.parent.attributes : s.attributes Object.keys(attributes || {}).forEach(attrName => { - let camelizedAttrName = attrName.replace(/^data-md-/, ''); - let attrValue = attributes[attrName]; - sectionAttributes[camelizedAttrName] = sectionAttributes[camelizedAttrName] || []; + let camelizedAttrName = attrName.replace(/^data-md-/, '') + let attrValue = attributes[attrName] + sectionAttributes[camelizedAttrName] = sectionAttributes[camelizedAttrName] || [] if (!contains(sectionAttributes[camelizedAttrName], attrValue)) { - sectionAttributes[camelizedAttrName].push(attrValue); + sectionAttributes[camelizedAttrName].push(attrValue) } - }); - return sectionAttributes; - }, {}); + }) + return sectionAttributes + }, {}) } _removeActiveMarkup(markup) { - let index = this.state.activeMarkups.indexOf(markup); - this.state.activeMarkups.splice(index, 1); + let index = this.state.activeMarkups.indexOf(markup) + this.state.activeMarkups.splice(index, 1) } _addActiveMarkup(markup) { - this.state.activeMarkups.push(markup); + this.state.activeMarkups.push(markup) } } -export default EditState; +export default EditState diff --git a/src/js/editor/editor.js b/src/js/editor/editor.js index 54a699924..34150a20e 100644 --- a/src/js/editor/editor.js +++ b/src/js/editor/editor.js @@ -1,43 +1,41 @@ -import Tooltip, { DEFAULT_TOOLTIP_PLUGIN } from '../views/tooltip'; -import PostEditor from './post'; -import ImageCard from '../cards/image'; -import { DIRECTION } from '../utils/key'; -import mobiledocParsers from '../parsers/mobiledoc'; -import HTMLParser from '../parsers/html'; -import DOMParser from '../parsers/dom'; -import Renderer from 'mobiledoc-kit/renderers/editor-dom'; -import RenderTree from 'mobiledoc-kit/models/render-tree'; -import mobiledocRenderers from '../renderers/mobiledoc'; -import { MOBILEDOC_VERSION } from 'mobiledoc-kit/renderers/mobiledoc'; -import { mergeWithOptions } from '../utils/merge'; -import { normalizeTagName, clearChildNodes, serializeHTML } from '../utils/dom-utils'; -import { forEach, filter, contains, values, detect } from '../utils/array-utils'; -import { setData } from '../utils/element-utils'; -import Cursor from '../utils/cursor'; -import Range from '../utils/cursor/range'; -import Position from '../utils/cursor/position'; -import Environment from '../utils/environment'; -import PostNodeBuilder from '../models/post-node-builder'; -import { DEFAULT_TEXT_INPUT_HANDLERS } from './text-input-handlers'; -import { - DEFAULT_KEY_COMMANDS, buildKeyCommand, findKeyCommands, validateKeyCommand -} from './key-commands'; -import { CARD_MODES } from '../models/card'; -import assert from '../utils/assert'; -import MutationHandler from 'mobiledoc-kit/editor/mutation-handler'; -import EditHistory from 'mobiledoc-kit/editor/edit-history'; -import EventManager from 'mobiledoc-kit/editor/event-manager'; -import EditState from 'mobiledoc-kit/editor/edit-state'; -import DOMRenderer from 'mobiledoc-dom-renderer'; -import TextRenderer from 'mobiledoc-text-renderer'; -import LifecycleCallbacks from 'mobiledoc-kit/models/lifecycle-callbacks'; -import LogManager from 'mobiledoc-kit/utils/log-manager'; -import toRange from 'mobiledoc-kit/utils/to-range'; -import MobiledocError from 'mobiledoc-kit/utils/mobiledoc-error'; +import Tooltip, { DEFAULT_TOOLTIP_PLUGIN } from '../views/tooltip' +import PostEditor from './post' +import ImageCard from '../cards/image' +import { DIRECTION } from '../utils/key' +import mobiledocParsers from '../parsers/mobiledoc' +import HTMLParser from '../parsers/html' +import DOMParser from '../parsers/dom' +import Renderer from 'mobiledoc-kit/renderers/editor-dom' +import RenderTree from 'mobiledoc-kit/models/render-tree' +import mobiledocRenderers from '../renderers/mobiledoc' +import { MOBILEDOC_VERSION } from 'mobiledoc-kit/renderers/mobiledoc' +import { mergeWithOptions } from '../utils/merge' +import { normalizeTagName, clearChildNodes, serializeHTML } from '../utils/dom-utils' +import { forEach, filter, contains, values, detect } from '../utils/array-utils' +import { setData } from '../utils/element-utils' +import Cursor from '../utils/cursor' +import Range from '../utils/cursor/range' +import Position from '../utils/cursor/position' +import Environment from '../utils/environment' +import PostNodeBuilder from '../models/post-node-builder' +import { DEFAULT_TEXT_INPUT_HANDLERS } from './text-input-handlers' +import { DEFAULT_KEY_COMMANDS, buildKeyCommand, findKeyCommands, validateKeyCommand } from './key-commands' +import { CARD_MODES } from '../models/card' +import assert from '../utils/assert' +import MutationHandler from 'mobiledoc-kit/editor/mutation-handler' +import EditHistory from 'mobiledoc-kit/editor/edit-history' +import EventManager from 'mobiledoc-kit/editor/event-manager' +import EditState from 'mobiledoc-kit/editor/edit-state' +import DOMRenderer from 'mobiledoc-dom-renderer' +import TextRenderer from 'mobiledoc-text-renderer' +import LifecycleCallbacks from 'mobiledoc-kit/models/lifecycle-callbacks' +import LogManager from 'mobiledoc-kit/utils/log-manager' +import toRange from 'mobiledoc-kit/utils/to-range' +import MobiledocError from 'mobiledoc-kit/utils/mobiledoc-error' // This export may later be deprecated, but re-export it from the renderer here // for consumers that may depend on it. -export { EDITOR_ELEMENT_CLASS_NAME } from 'mobiledoc-kit/renderers/editor-dom'; +export { EDITOR_ELEMENT_CLASS_NAME } from 'mobiledoc-kit/renderers/editor-dom' const defaults = { placeholder: 'Write here...', @@ -49,16 +47,16 @@ const defaults = { cards: [], atoms: [], cardOptions: {}, - unknownCardHandler: ({env}) => { - throw new MobiledocError(`Unknown card encountered: ${env.name}`); + unknownCardHandler: ({ env }) => { + throw new MobiledocError(`Unknown card encountered: ${env.name}`) }, - unknownAtomHandler: ({env}) => { - throw new MobiledocError(`Unknown atom encountered: ${env.name}`); + unknownAtomHandler: ({ env }) => { + throw new MobiledocError(`Unknown atom encountered: ${env.name}`) }, mobiledoc: null, html: null, - tooltipPlugin: DEFAULT_TOOLTIP_PLUGIN -}; + tooltipPlugin: DEFAULT_TOOLTIP_PLUGIN, +} const CALLBACK_QUEUES = { DID_UPDATE: 'didUpdate', @@ -71,8 +69,8 @@ const CALLBACK_QUEUES = { DID_REPARSE: 'didReparse', POST_DID_CHANGE: 'postDidChange', INPUT_MODE_DID_CHANGE: 'inputModeDidChange', - WILL_COPY: 'willCopy' -}; + WILL_COPY: 'willCopy', +} /** * The Editor is a core component of mobiledoc-kit. After instantiating @@ -122,37 +120,39 @@ class Editor { * @return {Editor} * @public */ - constructor(options={}) { - assert('editor create accepts an options object. For legacy usage passing an element for the first argument, consider the `html` option for loading DOM or HTML posts. For other cases call `editor.render(domNode)` after editor creation', - (options && !options.nodeType)); - this._views = []; - this.isEditable = true; - this._parserPlugins = options.parserPlugins || []; + constructor(options = {}) { + assert( + 'editor create accepts an options object. For legacy usage passing an element for the first argument, consider the `html` option for loading DOM or HTML posts. For other cases call `editor.render(domNode)` after editor creation', + options && !options.nodeType + ) + this._views = [] + this.isEditable = true + this._parserPlugins = options.parserPlugins || [] // FIXME: This should merge onto this.options - mergeWithOptions(this, defaults, options); - this.cards.push(ImageCard); + mergeWithOptions(this, defaults, options) + this.cards.push(ImageCard) - DEFAULT_KEY_COMMANDS.forEach(kc => this.registerKeyCommand(kc)); + DEFAULT_KEY_COMMANDS.forEach(kc => this.registerKeyCommand(kc)) - this._logManager = new LogManager(); - this._parser = new DOMParser(this.builder); - let {cards, atoms, unknownCardHandler, unknownAtomHandler, cardOptions} = this; - this._renderer = new Renderer(this, cards, atoms, unknownCardHandler, unknownAtomHandler, cardOptions); + this._logManager = new LogManager() + this._parser = new DOMParser(this.builder) + let { cards, atoms, unknownCardHandler, unknownAtomHandler, cardOptions } = this + this._renderer = new Renderer(this, cards, atoms, unknownCardHandler, unknownAtomHandler, cardOptions) - this.post = this.loadPost(); - this._renderTree = new RenderTree(this.post); + this.post = this.loadPost() + this._renderTree = new RenderTree(this.post) - this._editHistory = new EditHistory(this, this.undoDepth, this.undoBlockTimeout); - this._eventManager = new EventManager(this); - this._mutationHandler = new MutationHandler(this); - this._editState = new EditState(this); - this._callbacks = new LifecycleCallbacks(values(CALLBACK_QUEUES)); - this._beforeHooks = { toggleMarkup: [] }; + this._editHistory = new EditHistory(this, this.undoDepth, this.undoBlockTimeout) + this._eventManager = new EventManager(this) + this._mutationHandler = new MutationHandler(this) + this._editState = new EditState(this) + this._callbacks = new LifecycleCallbacks(values(CALLBACK_QUEUES)) + this._beforeHooks = { toggleMarkup: [] } - DEFAULT_TEXT_INPUT_HANDLERS.forEach(handler => this.onTextInput(handler)); + DEFAULT_TEXT_INPUT_HANDLERS.forEach(handler => this.onTextInput(handler)) - this.hasRendered = false; + this.hasRendered = false } /** @@ -160,11 +160,11 @@ class Editor { * @param {Array} [logTypes=[]] If present, only the given log types will be logged. * @public */ - enableLogging(logTypes=[]) { + enableLogging(logTypes = []) { if (logTypes.length === 0) { - this._logManager.enableAll(); + this._logManager.enableAll() } else { - this._logManager.enableTypes(logTypes); + this._logManager.enableTypes(logTypes) } } @@ -173,14 +173,14 @@ class Editor { * @public */ disableLogging() { - this._logManager.disable(); + this._logManager.disable() } /** * @private */ loggerFor(type) { - return this._logManager.for(type); + return this._logManager.for(type) } /** @@ -188,43 +188,44 @@ class Editor { * @type {PostNodeBuilder} */ get builder() { - if (!this._builder) { this._builder = new PostNodeBuilder(); } - return this._builder; + if (!this._builder) { + this._builder = new PostNodeBuilder() + } + return this._builder } loadPost() { - let {mobiledoc, html} = this; + let { mobiledoc, html } = this if (mobiledoc) { - return mobiledocParsers.parse(this.builder, mobiledoc); + return mobiledocParsers.parse(this.builder, mobiledoc) } else if (html) { if (typeof html === 'string') { - let options = {plugins: this._parserPlugins}; - return new HTMLParser(this.builder, options).parse(this.html); + let options = { plugins: this._parserPlugins } + return new HTMLParser(this.builder, options).parse(this.html) } else { - let dom = html; - return this._parser.parse(dom); + let dom = html + return this._parser.parse(dom) } } else { - return this.builder.createPost([this.builder.createMarkupSection()]); + return this.builder.createPost([this.builder.createMarkupSection()]) } } rerender() { - let postRenderNode = this.post.renderNode; + let postRenderNode = this.post.renderNode // if we haven't rendered this post's renderNode before, mark it dirty if (!postRenderNode.element) { - assert('Must call `render` before `rerender` can be called', - this.hasRendered); - postRenderNode.element = this.element; - postRenderNode.markDirty(); + assert('Must call `render` before `rerender` can be called', this.hasRendered) + postRenderNode.element = this.element + postRenderNode.markDirty() } - this.runCallbacks(CALLBACK_QUEUES.WILL_RENDER); + this.runCallbacks(CALLBACK_QUEUES.WILL_RENDER) this._mutationHandler.suspendObservation(() => { - this._renderer.render(this._renderTree); - }); - this.runCallbacks(CALLBACK_QUEUES.DID_RENDER); + this._renderer.render(this._renderTree) + }) + this.runCallbacks(CALLBACK_QUEUES.DID_RENDER) } /** @@ -233,55 +234,60 @@ class Editor { * @public */ render(element) { - assert('Cannot render an editor twice. Use `rerender` to update the ' + - 'rendering of an existing editor instance.', - !this.hasRendered); + assert( + 'Cannot render an editor twice. Use `rerender` to update the ' + 'rendering of an existing editor instance.', + !this.hasRendered + ) - element.spellcheck = this.spellcheck; + element.spellcheck = this.spellcheck - clearChildNodes(element); + clearChildNodes(element) - this.element = element; + this.element = element if (this.showLinkTooltips) { - this._addTooltip(); + this._addTooltip() } // A call to `run` will trigger the didUpdatePostCallbacks hooks with a // postEditor. - this.run(() => {}); + this.run(() => {}) // Only set `hasRendered` to true after calling `run` to ensure that // no cursorDidChange or other callbacks get fired before the editor is // done rendering - this.hasRendered = true; - this.rerender(); + this.hasRendered = true + this.rerender() - this._mutationHandler.init(); - this._eventManager.init(); + this._mutationHandler.init() + this._eventManager.init() if (this.isEditable === false) { - this.disableEditing(); + this.disableEditing() } else { - this.enableEditing(); + this.enableEditing() } if (this.autofocus) { - this.selectRange(this.post.headPosition()); + this.selectRange(this.post.headPosition()) } } _addTooltip() { - this.addView(new Tooltip({ - rootElement: this.element, - showForTag: 'a', - editor: this - })); + this.addView( + new Tooltip({ + rootElement: this.element, + showForTag: 'a', + editor: this, + }) + ) } get keyCommands() { - if (!this._keyCommands) { this._keyCommands = []; } - return this._keyCommands; + if (!this._keyCommands) { + this._keyCommands = [] + } + return this._keyCommands } /** @@ -292,9 +298,9 @@ class Editor { * @public */ registerKeyCommand(rawKeyCommand) { - const keyCommand = buildKeyCommand(rawKeyCommand); - assert('Key Command is not valid', validateKeyCommand(keyCommand)); - this.keyCommands.unshift(keyCommand); + const keyCommand = buildKeyCommand(rawKeyCommand) + assert('Key Command is not valid', validateKeyCommand(keyCommand)) + this.keyCommands.unshift(keyCommand) } /** @@ -302,11 +308,11 @@ class Editor { * @public */ unregisterKeyCommands(name) { - for(let i = this.keyCommands.length-1; i > -1; i--) { - let keyCommand = this.keyCommands[i]; + for (let i = this.keyCommands.length - 1; i > -1; i--) { + let keyCommand = this.keyCommands[i] - if(keyCommand.name === name) { - this.keyCommands.splice(i,1); + if (keyCommand.name === name) { + this.keyCommands.splice(i, 1) } } } @@ -316,11 +322,11 @@ class Editor { * cursor in the new position. * @public */ - deleteAtPosition(position, direction, {unit}) { + deleteAtPosition(position, direction, { unit }) { this.run(postEditor => { - let nextPosition = postEditor.deleteAtPosition(position, direction, {unit}); - postEditor.setRange(nextPosition); - }); + let nextPosition = postEditor.deleteAtPosition(position, direction, { unit }) + postEditor.setRange(nextPosition) + }) } /** @@ -331,52 +337,60 @@ class Editor { */ deleteRange(range) { this.run(postEditor => { - let nextPosition = postEditor.deleteRange(range); - postEditor.setRange(nextPosition); - }); + let nextPosition = postEditor.deleteRange(range) + postEditor.setRange(nextPosition) + }) } /** * @private */ - performDelete({direction, unit}={direction: DIRECTION.BACKWARD, unit: 'char'}) { - let { range } = this; + performDelete({ direction, unit } = { direction: DIRECTION.BACKWARD, unit: 'char' }) { + let { range } = this - this.runCallbacks(CALLBACK_QUEUES.WILL_DELETE, [range, direction, unit]); + this.runCallbacks(CALLBACK_QUEUES.WILL_DELETE, [range, direction, unit]) if (range.isCollapsed) { - this.deleteAtPosition(range.head, direction, {unit}); + this.deleteAtPosition(range.head, direction, { unit }) } else { - this.deleteRange(range); + this.deleteRange(range) } - this.runCallbacks(CALLBACK_QUEUES.DID_DELETE, [range, direction, unit]); + this.runCallbacks(CALLBACK_QUEUES.DID_DELETE, [range, direction, unit]) } handleNewline(event) { - if (!this.hasCursor()) { return; } + if (!this.hasCursor()) { + return + } - event.preventDefault(); + event.preventDefault() - let { range } = this; + let { range } = this this.run(postEditor => { - let cursorSection; + let cursorSection if (!range.isCollapsed) { - let nextPosition = postEditor.deleteRange(range); - cursorSection = nextPosition.section; + let nextPosition = postEditor.deleteRange(range) + cursorSection = nextPosition.section if (cursorSection && cursorSection.isBlank) { - postEditor.setRange(cursorSection.headPosition()); - return; + postEditor.setRange(cursorSection.headPosition()) + return } } // Above logic might delete redundant range, so callback must run after it. - let defaultPrevented = false; - const event = { preventDefault() { defaultPrevented = true; } }; - this.runCallbacks(CALLBACK_QUEUES.WILL_HANDLE_NEWLINE, [event]); - if (defaultPrevented) { return; } + let defaultPrevented = false + const event = { + preventDefault() { + defaultPrevented = true + }, + } + this.runCallbacks(CALLBACK_QUEUES.WILL_HANDLE_NEWLINE, [event]) + if (defaultPrevented) { + return + } - cursorSection = postEditor.splitSection(range.head)[1]; - postEditor.setRange(cursorSection.headPosition()); - }); + cursorSection = postEditor.splitSection(range.head)[1] + postEditor.setRange(cursorSection.headPosition()) + }) } /** @@ -385,7 +399,7 @@ class Editor { * @private */ _postDidChange() { - this.runCallbacks(CALLBACK_QUEUES.POST_DID_CHANGE); + this.runCallbacks(CALLBACK_QUEUES.POST_DID_CHANGE) } /** @@ -395,14 +409,14 @@ class Editor { * @param {Range|Position} range */ selectRange(range) { - range = toRange(range); + range = toRange(range) - this.cursor.selectRange(range); - this.range = range; + this.cursor.selectRange(range) + this.range = range } get cursor() { - return new Cursor(this); + return new Cursor(this) } /** @@ -410,66 +424,66 @@ class Editor { * @return {Range} */ get range() { - return this._editState.range; + return this._editState.range } set range(newRange) { - this._editState.updateRange(newRange); + this._editState.updateRange(newRange) if (this._editState.rangeDidChange()) { - this._rangeDidChange(); + this._rangeDidChange() } if (this._editState.inputModeDidChange()) { - this._inputModeDidChange(); + this._inputModeDidChange() } } _readRangeFromDOM() { - this.range = this.cursor.offsets; + this.range = this.cursor.offsets } setPlaceholder(placeholder) { - setData(this.element, 'placeholder', placeholder); + setData(this.element, 'placeholder', placeholder) } _reparsePost() { - let post = this._parser.parse(this.element); + let post = this._parser.parse(this.element) this.run(postEditor => { - postEditor.removeAllSections(); - postEditor.migrateSectionsFromPost(post); - postEditor.setRange(Range.blankRange()); - }); + postEditor.removeAllSections() + postEditor.migrateSectionsFromPost(post) + postEditor.setRange(Range.blankRange()) + }) - this.runCallbacks(CALLBACK_QUEUES.DID_REPARSE); - this._postDidChange(); + this.runCallbacks(CALLBACK_QUEUES.DID_REPARSE) + this._postDidChange() } - _reparseSections(sections=[]) { - let currentRange; + _reparseSections(sections = []) { + let currentRange sections.forEach(section => { - this._parser.reparseSection(section, this._renderTree); - }); - this._removeDetachedSections(); + this._parser.reparseSection(section, this._renderTree) + }) + this._removeDetachedSections() if (this._renderTree.isDirty) { - currentRange = this.range; + currentRange = this.range } // force the current snapshot's range to remain the same rather than // rereading it from DOM after the new character is applied and the browser // updates the cursor position - let range = this._editHistory._pendingSnapshot.range; + let range = this._editHistory._pendingSnapshot.range this.run(() => { - this._editHistory._pendingSnapshot.range = range; - }); - this.rerender(); + this._editHistory._pendingSnapshot.range = range + }) + this.rerender() if (currentRange) { - this.selectRange(currentRange); + this.selectRange(currentRange) } - this.runCallbacks(CALLBACK_QUEUES.DID_REPARSE); - this._postDidChange(); + this.runCallbacks(CALLBACK_QUEUES.DID_REPARSE) + this._postDidChange() } // FIXME this should be able to be removed now -- if any sections are detached, @@ -478,7 +492,7 @@ class Editor { forEach( filter(this.post.sections, s => !s.renderNode.isAttached()), s => s.renderNode.scheduleForRemoval() - ); + ) } /** @@ -486,23 +500,23 @@ class Editor { * @type {Section[]} */ get activeSections() { - return this._editState.activeSections; + return this._editState.activeSections } get activeSection() { - const { activeSections } = this; - return activeSections[activeSections.length - 1]; + const { activeSections } = this + return activeSections[activeSections.length - 1] } get activeSectionAttributes() { - return this._editState.activeSectionAttributes; + return this._editState.activeSectionAttributes } detectMarkupInRange(range, markupTagName) { - let markups = this.post.markupsInRange(range); + let markups = this.post.markupsInRange(range) return detect(markups, markup => { - return markup.hasTag(markupTagName); - }); + return markup.hasTag(markupTagName) + }) } /** @@ -510,7 +524,7 @@ class Editor { * @public */ get activeMarkups() { - return this._editState.activeMarkups; + return this._editState.activeMarkups } /** @@ -518,15 +532,15 @@ class Editor { * @return {boolean} */ hasActiveMarkup(markup) { - let matchesFn; + let matchesFn if (typeof markup === 'string') { - let tagName = normalizeTagName(markup); - matchesFn = (m) => m.tagName === tagName; + let tagName = normalizeTagName(markup) + matchesFn = m => m.tagName === tagName } else { - matchesFn = (m) => m === markup; + matchesFn = m => m === markup } - return !!detect(this.activeMarkups, matchesFn); + return !!detect(this.activeMarkups, matchesFn) } /** @@ -534,8 +548,8 @@ class Editor { * @return {Mobiledoc} Serialized mobiledoc * @public */ - serialize(version=MOBILEDOC_VERSION) { - return this.serializePost(this.post, 'mobiledoc', {version}); + serialize(version = MOBILEDOC_VERSION) { + return this.serializePost(this.post, 'mobiledoc', { version }) } /** @@ -549,8 +563,8 @@ class Editor { * @public */ serializeTo(format) { - let post = this.post; - return this.serializePost(post, format); + let post = this.post + return this.serializePost(post, format) } /** @@ -561,47 +575,46 @@ class Editor { * @return {Object|String} * @private */ - serializePost(post, format, options={}) { - const validFormats = ['mobiledoc', 'html', 'text']; - assert(`Unrecognized serialization format ${format}`, - contains(validFormats, format)); + serializePost(post, format, options = {}) { + const validFormats = ['mobiledoc', 'html', 'text'] + assert(`Unrecognized serialization format ${format}`, contains(validFormats, format)) if (format === 'mobiledoc') { - let version = options.version || MOBILEDOC_VERSION; - return mobiledocRenderers.render(post, version); + let version = options.version || MOBILEDOC_VERSION + return mobiledocRenderers.render(post, version) } else { - let rendered; - let mobiledoc = this.serializePost(post, 'mobiledoc'); - let unknownCardHandler = () => {}; - let unknownAtomHandler = () => {}; - let rendererOptions = { unknownCardHandler, unknownAtomHandler }; + let rendered + let mobiledoc = this.serializePost(post, 'mobiledoc') + let unknownCardHandler = () => {} + let unknownAtomHandler = () => {} + let rendererOptions = { unknownCardHandler, unknownAtomHandler } switch (format) { case 'html': { - let result; + let result if (Environment.hasDOM()) { - rendered = new DOMRenderer(rendererOptions).render(mobiledoc); - result = `
${serializeHTML(rendered.result)}
`; + rendered = new DOMRenderer(rendererOptions).render(mobiledoc) + result = `
${serializeHTML(rendered.result)}
` } else { // Fallback to text serialization - result = this.serializePost(post, 'text', options); + result = this.serializePost(post, 'text', options) } - return result; + return result } case 'text': - rendered = new TextRenderer(rendererOptions).render(mobiledoc); - return rendered.result; + rendered = new TextRenderer(rendererOptions).render(mobiledoc) + return rendered.result } } } addView(view) { - this._views.push(view); + this._views.push(view) } removeAllViews() { - this._views.forEach((v) => v.destroy()); - this._views = []; + this._views.forEach(v => v.destroy()) + this._views = [] } /** @@ -613,7 +626,7 @@ class Editor { * @public */ hasCursor() { - return this.cursor.hasCursor(); + return this.cursor.hasCursor() } /** @@ -621,18 +634,18 @@ class Editor { * @public */ destroy() { - this.isDestroyed = true; + this.isDestroyed = true if (this._hasSelection()) { - this.cursor.clearSelection(); + this.cursor.clearSelection() } if (this._hasFocus()) { - this.element.blur(); // FIXME This doesn't blur the element on IE11 + this.element.blur() // FIXME This doesn't blur the element on IE11 } - this._mutationHandler.destroy(); - this._eventManager.destroy(); - this.removeAllViews(); - this._renderer.destroy(); - this._editState.destroy(); + this._mutationHandler.destroy() + this._eventManager.destroy() + this.removeAllViews() + this._renderer.destroy() + this._editState.destroy() } /** @@ -642,12 +655,12 @@ class Editor { * @public */ disableEditing() { - this.isEditable = false; + this.isEditable = false if (this.hasRendered) { - this._eventManager.stop(); - this.element.setAttribute('contentEditable', false); - this.setPlaceholder(''); - this.selectRange(Range.blankRange()); + this._eventManager.stop() + this.element.setAttribute('contentEditable', false) + this.setPlaceholder('') + this.selectRange(Range.blankRange()) } } @@ -659,11 +672,11 @@ class Editor { * @public */ enableEditing() { - this.isEditable = true; + this.isEditable = true if (this.hasRendered) { - this._eventManager.start(); - this.element.setAttribute('contentEditable', true); - this.setPlaceholder(this.placeholder); + this._eventManager.start() + this.element.setAttribute('contentEditable', true) + this.setPlaceholder(this.placeholder) } } @@ -675,7 +688,7 @@ class Editor { * @public */ editCard(cardSection) { - this._setCardMode(cardSection, CARD_MODES.EDIT); + this._setCardMode(cardSection, CARD_MODES.EDIT) } /** @@ -687,7 +700,7 @@ class Editor { * @public */ displayCard(cardSection) { - this._setCardMode(cardSection, CARD_MODES.DISPLAY); + this._setCardMode(cardSection, CARD_MODES.DISPLAY) } /** @@ -716,20 +729,20 @@ class Editor { * @public */ run(callback) { - const postEditor = new PostEditor(this); - postEditor.begin(); - this._editHistory.snapshot(); - const result = callback(postEditor); - this.runCallbacks(CALLBACK_QUEUES.DID_UPDATE, [postEditor]); - postEditor.complete(); - this._readRangeFromDOM(); + const postEditor = new PostEditor(this) + postEditor.begin() + this._editHistory.snapshot() + const result = callback(postEditor) + this.runCallbacks(CALLBACK_QUEUES.DID_UPDATE, [postEditor]) + postEditor.complete() + this._readRangeFromDOM() if (postEditor._shouldCancelSnapshot) { - this._editHistory._pendingSnapshot = null; + this._editHistory._pendingSnapshot = null } - this._editHistory.storeSnapshot(postEditor.editActionTaken); + this._editHistory.storeSnapshot(postEditor.editActionTaken) - return result; + return result } /** @@ -737,7 +750,7 @@ class Editor { * @public */ didUpdatePost(callback) { - this.addCallback(CALLBACK_QUEUES.DID_UPDATE, callback); + this.addCallback(CALLBACK_QUEUES.DID_UPDATE, callback) } /** @@ -746,7 +759,7 @@ class Editor { * retrieve the post in portable mobiledoc format. */ postDidChange(callback) { - this.addCallback(CALLBACK_QUEUES.POST_DID_CHANGE, callback); + this.addCallback(CALLBACK_QUEUES.POST_DID_CHANGE, callback) } /** @@ -765,7 +778,7 @@ class Editor { * @public */ onTextInput(inputHandler) { - this._eventManager.registerInputHandler(inputHandler); + this._eventManager.registerInputHandler(inputHandler) } /** @@ -774,7 +787,7 @@ class Editor { * @public */ unregisterAllTextInputHandlers() { - this._eventManager.unregisterAllTextInputHandlers(); + this._eventManager.unregisterAllTextInputHandlers() } /** @@ -784,7 +797,7 @@ class Editor { * @public */ unregisterTextInputHandler(name) { - this._eventManager.unregisterInputHandler(name); + this._eventManager.unregisterInputHandler(name) } /** @@ -792,7 +805,7 @@ class Editor { * active sections) has changed, either via user input or programmatically */ inputModeDidChange(callback) { - this.addCallback(CALLBACK_QUEUES.INPUT_MODE_DID_CHANGE, callback); + this.addCallback(CALLBACK_QUEUES.INPUT_MODE_DID_CHANGE, callback) } /** @@ -801,7 +814,7 @@ class Editor { * @public */ willRender(callback) { - this.addCallback(CALLBACK_QUEUES.WILL_RENDER, callback); + this.addCallback(CALLBACK_QUEUES.WILL_RENDER, callback) } /** @@ -810,11 +823,11 @@ class Editor { * @public */ didRender(callback) { - this.addCallback(CALLBACK_QUEUES.DID_RENDER, callback); + this.addCallback(CALLBACK_QUEUES.DID_RENDER, callback) } willCopy(callback) { - this.addCallback(CALLBACK_QUEUES.WILL_COPY, callback); + this.addCallback(CALLBACK_QUEUES.WILL_COPY, callback) } /** @@ -822,7 +835,7 @@ class Editor { * @public */ willDelete(callback) { - this.addCallback(CALLBACK_QUEUES.WILL_DELETE, callback); + this.addCallback(CALLBACK_QUEUES.WILL_DELETE, callback) } /** @@ -830,7 +843,7 @@ class Editor { * @public */ didDelete(callback) { - this.addCallback(CALLBACK_QUEUES.DID_DELETE, callback); + this.addCallback(CALLBACK_QUEUES.DID_DELETE, callback) } /** @@ -838,7 +851,7 @@ class Editor { * @public */ willHandleNewline(callback) { - this.addCallback(CALLBACK_QUEUES.WILL_HANDLE_NEWLINE, callback); + this.addCallback(CALLBACK_QUEUES.WILL_HANDLE_NEWLINE, callback) } /** @@ -847,25 +860,25 @@ class Editor { * @public */ cursorDidChange(callback) { - this.addCallback(CALLBACK_QUEUES.CURSOR_DID_CHANGE, callback); + this.addCallback(CALLBACK_QUEUES.CURSOR_DID_CHANGE, callback) } _rangeDidChange() { if (this.hasRendered) { - this.runCallbacks(CALLBACK_QUEUES.CURSOR_DID_CHANGE); + this.runCallbacks(CALLBACK_QUEUES.CURSOR_DID_CHANGE) } } _inputModeDidChange() { - this.runCallbacks(CALLBACK_QUEUES.INPUT_MODE_DID_CHANGE); + this.runCallbacks(CALLBACK_QUEUES.INPUT_MODE_DID_CHANGE) } _insertEmptyMarkupSectionAtCursor() { this.run(postEditor => { - const section = postEditor.builder.createMarkupSection('p'); - postEditor.insertSectionBefore(this.post.sections, section); - postEditor.setRange(section.toRange()); - }); + const section = postEditor.builder.createMarkupSection('p') + postEditor.insertSectionBefore(this.post.sections, section) + postEditor.setRange(section.toRange()) + }) } /** @@ -884,7 +897,7 @@ class Editor { * @param {editorBeforeCallback} */ beforeToggleMarkup(callback) { - this._beforeHooks.toggleMarkup.push(callback); + this._beforeHooks.toggleMarkup.push(callback) } /** @@ -901,34 +914,36 @@ class Editor { * @public * @see PostEditor#toggleMarkup */ - toggleMarkup(markup, attributes={}) { - markup = this.builder.createMarkup(markup, attributes); - let { range } = this; - let willAdd = !this.detectMarkupInRange(range, markup.tagName); - let shouldCancel = this._runBeforeHooks('toggleMarkup', {markup, range, willAdd}); - if (shouldCancel) { return; } + toggleMarkup(markup, attributes = {}) { + markup = this.builder.createMarkup(markup, attributes) + let { range } = this + let willAdd = !this.detectMarkupInRange(range, markup.tagName) + let shouldCancel = this._runBeforeHooks('toggleMarkup', { markup, range, willAdd }) + if (shouldCancel) { + return + } if (range.isCollapsed) { - this._editState.toggleMarkupState(markup); - this._inputModeDidChange(); + this._editState.toggleMarkupState(markup) + this._inputModeDidChange() // when clicking a button to toggle markup, the button can end up being focused, // so ensure the editor is focused - this._ensureFocus(); + this._ensureFocus() } else { - this.run(postEditor => postEditor.toggleMarkup(markup, range)); + this.run(postEditor => postEditor.toggleMarkup(markup, range)) } } // If the editor has a selection but is not focused, focus it _ensureFocus() { if (this._hasSelection() && !this._hasFocus()) { - this.focus(); + this.focus() } } focus() { - this.element.focus(); + this.element.focus() } /** @@ -938,8 +953,8 @@ class Editor { * @return {Boolean} */ _hasSelection() { - let { cursor } = this; - return this.hasRendered && (cursor._hasCollapsedSelection() || cursor._hasSelection()); + let { cursor } = this + return this.hasRendered && (cursor._hasCollapsedSelection() || cursor._hasSelection()) } /** @@ -949,7 +964,7 @@ class Editor { * @return {Boolean} */ _hasFocus() { - return document.activeElement === this.element; + return document.activeElement === this.element } /** @@ -961,7 +976,7 @@ class Editor { * @see PostEditor#toggleSection */ toggleSection(tagName) { - this.run(postEditor => postEditor.toggleSection(tagName, this.range)); + this.run(postEditor => postEditor.toggleSection(tagName, this.range)) } /** @@ -973,7 +988,7 @@ class Editor { * @see PostEditor#setAttribute */ setAttribute(key, value) { - this.run(postEditor => postEditor.setAttribute(key, value, this.range)); + this.run(postEditor => postEditor.setAttribute(key, value, this.range)) } /** @@ -984,7 +999,7 @@ class Editor { * @see PostEditor#removeAttribute */ removeAttribute(key) { - this.run(postEditor => postEditor.removeAttribute(key, this.range)); + this.run(postEditor => postEditor.removeAttribute(key, this.range)) } /** @@ -1001,15 +1016,15 @@ class Editor { * @private */ handleKeyCommand(event) { - const keyCommands = findKeyCommands(this.keyCommands, event); - for (let i=0; i { if (!range.isCollapsed) { - position = postEditor.deleteRange(range); + position = postEditor.deleteRange(range) } - postEditor.insertTextWithMarkup(position, text, activeMarkups); - }); + postEditor.insertTextWithMarkup(position, text, activeMarkups) + }) } /** @@ -1046,25 +1067,27 @@ class Editor { * @return {Atom} The inserted atom. * @public */ - insertAtom(atomName, atomText='', atomPayload={}) { - if (!this.hasCursor()) { return; } + insertAtom(atomName, atomText = '', atomPayload = {}) { + if (!this.hasCursor()) { + return + } if (this.post.isBlank) { - this._insertEmptyMarkupSectionAtCursor(); + this._insertEmptyMarkupSectionAtCursor() } - let atom; - let { range } = this; + let atom + let { range } = this this.run(postEditor => { - let position = range.head; + let position = range.head - atom = postEditor.builder.createAtom(atomName, atomText, atomPayload); + atom = postEditor.builder.createAtom(atomName, atomText, atomPayload) if (!range.isCollapsed) { - position = postEditor.deleteRange(range); + position = postEditor.deleteRange(range) } - postEditor.insertMarkers(position, [atom]); - }); - return atom; + postEditor.insertMarkers(position, [atom]) + }) + return atom } /** @@ -1079,33 +1102,37 @@ class Editor { * @return {Card} The inserted Card section. * @public */ - insertCard(cardName, cardPayload={}, inEditMode=false) { - if (!this.hasCursor()) { return; } + insertCard(cardName, cardPayload = {}, inEditMode = false) { + if (!this.hasCursor()) { + return + } if (this.post.isBlank) { - this._insertEmptyMarkupSectionAtCursor(); + this._insertEmptyMarkupSectionAtCursor() } - let card; - let { range } = this; + let card + let { range } = this this.run(postEditor => { - let position = range.tail; - card = postEditor.builder.createCardSection(cardName, cardPayload); + let position = range.tail + card = postEditor.builder.createCardSection(cardName, cardPayload) if (inEditMode) { - this.editCard(card); + this.editCard(card) } if (!range.isCollapsed) { - position = postEditor.deleteRange(range); + position = postEditor.deleteRange(range) } - let section = position.section; - if (section.isNested) { section = section.parent; } + let section = position.section + if (section.isNested) { + section = section.parent + } if (section.isBlank) { - postEditor.replaceSection(section, card); + postEditor.replaceSection(section, card) } else { - let collection = this.post.sections; - postEditor.insertSectionBefore(collection, card, section.next); + let collection = this.post.sections + postEditor.insertSectionBefore(collection, card, section.next) } // It is important to explicitly set the range to the end of the card. @@ -1116,9 +1143,9 @@ class Editor { // will cause an unexpected DOM mutation (which can wipe out the // card). // See: https://github.com/bustle/mobiledoc-kit/issues/286 - postEditor.setRange(card.tailPosition()); - }); - return card; + postEditor.setRange(card.tailPosition()) + }) + return card } /** @@ -1127,40 +1154,40 @@ class Editor { * @return {Position|null} */ positionAtPoint(x, y) { - return Position.atPoint(x, y, this); + return Position.atPoint(x, y, this) } /** * @private */ _setCardMode(cardSection, mode) { - const renderNode = cardSection.renderNode; + const renderNode = cardSection.renderNode if (renderNode && renderNode.isRendered) { - const cardNode = renderNode.cardNode; - cardNode[mode](); + const cardNode = renderNode.cardNode + cardNode[mode]() } else { - cardSection.setInitialMode(mode); + cardSection.setInitialMode(mode) } } triggerEvent(context, eventName, event) { - this._eventManager._trigger(context, eventName, event); + this._eventManager._trigger(context, eventName, event) } addCallback(...args) { - this._callbacks.addCallback(...args); + this._callbacks.addCallback(...args) } addCallbackOnce(...args) { - this._callbacks.addCallbackOnce(...args); + this._callbacks.addCallbackOnce(...args) } runCallbacks(...args) { if (this.isDestroyed) { // TODO warn that callback attempted after editor was destroyed - return; + return } - this._callbacks.runCallbacks(...args); + this._callbacks.runCallbacks(...args) } /** @@ -1170,13 +1197,13 @@ class Editor { * @private */ _runBeforeHooks(hookName, ...args) { - let hooks = this._beforeHooks[hookName] || []; + let hooks = this._beforeHooks[hookName] || [] for (let i = 0; i < hooks.length; i++) { if (hooks[i](...args) === false) { - return true; + return true } } } } -export default Editor; +export default Editor diff --git a/src/js/editor/event-manager.js b/src/js/editor/event-manager.js index 6671f7805..ea6aa7408 100644 --- a/src/js/editor/event-manager.js +++ b/src/js/editor/event-manager.js @@ -1,79 +1,74 @@ -import assert from 'mobiledoc-kit/utils/assert'; -import { - parsePostFromPaste, - setClipboardData, - parsePostFromDrop -} from 'mobiledoc-kit/utils/parse-utils'; -import { filter, forEach } from 'mobiledoc-kit/utils/array-utils'; -import Key from 'mobiledoc-kit/utils/key'; -import TextInputHandler from 'mobiledoc-kit/editor/text-input-handler'; -import SelectionManager from 'mobiledoc-kit/editor/selection-manager'; -import Browser from 'mobiledoc-kit/utils/browser'; - -const ELEMENT_EVENT_TYPES = [ - 'keydown', 'keyup', 'cut', 'copy', 'paste', 'keypress', 'drop' -]; +import assert from 'mobiledoc-kit/utils/assert' +import { parsePostFromPaste, setClipboardData, parsePostFromDrop } from 'mobiledoc-kit/utils/parse-utils' +import { filter, forEach } from 'mobiledoc-kit/utils/array-utils' +import Key from 'mobiledoc-kit/utils/key' +import TextInputHandler from 'mobiledoc-kit/editor/text-input-handler' +import SelectionManager from 'mobiledoc-kit/editor/selection-manager' +import Browser from 'mobiledoc-kit/utils/browser' + +const ELEMENT_EVENT_TYPES = ['keydown', 'keyup', 'cut', 'copy', 'paste', 'keypress', 'drop'] export default class EventManager { constructor(editor) { - this.editor = editor; - this.logger = editor.loggerFor('event-manager'); - this._textInputHandler = new TextInputHandler(editor); - this._listeners = []; + this.editor = editor + this.logger = editor.loggerFor('event-manager') + this._textInputHandler = new TextInputHandler(editor) + this._listeners = [] this.modifierKeys = { - shift: false - }; + shift: false, + } - this._selectionManager = new SelectionManager( - this.editor, this.selectionDidChange.bind(this)); - this.started = true; + this._selectionManager = new SelectionManager(this.editor, this.selectionDidChange.bind(this)) + this.started = true } init() { - let { editor: { element } } = this; - assert(`Cannot init EventManager without element`, !!element); + let { + editor: { element }, + } = this + assert(`Cannot init EventManager without element`, !!element) ELEMENT_EVENT_TYPES.forEach(type => { - this._addListener(element, type); - }); + this._addListener(element, type) + }) - this._selectionManager.start(); + this._selectionManager.start() } start() { - this.started = true; + this.started = true } stop() { - this.started = false; + this.started = false } registerInputHandler(inputHandler) { - this._textInputHandler.register(inputHandler); + this._textInputHandler.register(inputHandler) } unregisterInputHandler(name) { - this._textInputHandler.unregister(name); + this._textInputHandler.unregister(name) } unregisterAllTextInputHandlers() { - this._textInputHandler.destroy(); - this._textInputHandler = new TextInputHandler(this.editor); + this._textInputHandler.destroy() + this._textInputHandler = new TextInputHandler(this.editor) } _addListener(context, type) { - assert(`Missing listener for ${type}`, !!this[type]); + assert(`Missing listener for ${type}`, !!this[type]) - let listener = (event) => this._handleEvent(type, event); - context.addEventListener(type, listener); - this._listeners.push([context, type, listener]); + let listener = event => this._handleEvent(type, event) + context.addEventListener(type, listener) + this._listeners.push([context, type, listener]) } _removeListeners() { this._listeners.forEach(([context, type, listener]) => { - context.removeEventListener(type, listener); - }); - this._listeners = []; + context.removeEventListener(type, listener) + }) + this._listeners = [] } // This is primarily useful for programmatically simulating events on the @@ -81,211 +76,223 @@ export default class EventManager { _trigger(context, type, event) { forEach( filter(this._listeners, ([_context, _type]) => { - return _context === context && _type === type; + return _context === context && _type === type }), - ([context,, listener]) => { - listener.call(context, event); + ([context, , listener]) => { + listener.call(context, event) } - ); + ) } destroy() { - this._textInputHandler.destroy(); - this._selectionManager.destroy(); - this._removeListeners(); + this._textInputHandler.destroy() + this._selectionManager.destroy() + this._removeListeners() } _handleEvent(type, event) { - let {target: element} = event; + let { target: element } = event if (!this.started) { // abort handling this event - return true; + return true } if (!this.isElementAddressable(element)) { // abort handling this event - return true; + return true } - this[type](event); + this[type](event) } isElementAddressable(element) { - return this.editor.cursor.isAddressable(element); + return this.editor.cursor.isAddressable(element) } selectionDidChange(selection /*, prevSelection */) { - let shouldNotify = true; - let { anchorNode } = selection; + let shouldNotify = true + let { anchorNode } = selection if (!this.isElementAddressable(anchorNode)) { if (!this.editor.range.isBlank) { // Selection changed from something addressable to something // not-addressable -- e.g., blur event, user clicked outside editor, // etc - shouldNotify = true; + shouldNotify = true } else { // selection changes wholly outside the editor should not trigger // change notifications - shouldNotify = false; + shouldNotify = false } } if (shouldNotify) { - this.editor._readRangeFromDOM(); + this.editor._readRangeFromDOM() } } keypress(event) { - let { editor, _textInputHandler } = this; - if (!editor.hasCursor()) { return; } + let { editor, _textInputHandler } = this + if (!editor.hasCursor()) { + return + } - let key = Key.fromEvent(event); + let key = Key.fromEvent(event) if (!key.isPrintable()) { - return; + return } else { - event.preventDefault(); + event.preventDefault() } - _textInputHandler.handle(key.toString()); + _textInputHandler.handle(key.toString()) } keydown(event) { - let { editor } = this; - if (!editor.hasCursor()) { return; } - if (!editor.isEditable) { return; } + let { editor } = this + if (!editor.hasCursor()) { + return + } + if (!editor.isEditable) { + return + } - let key = Key.fromEvent(event); - this._updateModifiersFromKey(key, {isDown:true}); + let key = Key.fromEvent(event) + this._updateModifiersFromKey(key, { isDown: true }) - if (editor.handleKeyCommand(event)) { return; } + if (editor.handleKeyCommand(event)) { + return + } if (editor.post.isBlank) { - editor._insertEmptyMarkupSectionAtCursor(); + editor._insertEmptyMarkupSectionAtCursor() } - let range = editor.range; + let range = editor.range - switch(true) { + switch (true) { // FIXME This should be restricted to only card/atom boundaries case key.isHorizontalArrowWithoutModifiersOtherThanShift(): { - let newRange; + let newRange if (key.isShift()) { - newRange = range.extend(key.direction * 1); + newRange = range.extend(key.direction * 1) } else { - newRange = range.move(key.direction); + newRange = range.move(key.direction) } - editor.selectRange(newRange); - event.preventDefault(); - break; + editor.selectRange(newRange) + event.preventDefault() + break } case key.isDelete(): { - let { direction } = key; - let unit = 'char'; + let { direction } = key + let unit = 'char' if (key.altKey && Browser.isMac()) { - unit = 'word'; + unit = 'word' } else if (key.ctrlKey && !Browser.isMac()) { - unit = 'word'; + unit = 'word' } - editor.performDelete({direction, unit}); - event.preventDefault(); - break; + editor.performDelete({ direction, unit }) + event.preventDefault() + break } case key.isEnter(): - this._textInputHandler.handleNewLine(); - editor.handleNewline(event); - break; + this._textInputHandler.handleNewLine() + editor.handleNewline(event) + break case key.isTab(): // Handle tab here because it does not fire a `keypress` event - event.preventDefault(); - this._textInputHandler.handle(key.toString()); - break; + event.preventDefault() + this._textInputHandler.handle(key.toString()) + break } } keyup(event) { - let { editor } = this; - if (!editor.hasCursor()) { return; } - let key = Key.fromEvent(event); - this._updateModifiersFromKey(key, {isDown:false}); + let { editor } = this + if (!editor.hasCursor()) { + return + } + let key = Key.fromEvent(event) + this._updateModifiersFromKey(key, { isDown: false }) } cut(event) { - event.preventDefault(); + event.preventDefault() - this.copy(event); - this.editor.performDelete(); + this.copy(event) + this.editor.performDelete() } copy(event) { - event.preventDefault(); + event.preventDefault() - let { editor, editor: { range, post } } = this; - post = post.trimTo(range); + let { + editor, + editor: { range, post }, + } = this + post = post.trimTo(range) let data = { html: editor.serializePost(post, 'html'), text: editor.serializePost(post, 'text'), - mobiledoc: editor.serializePost(post, 'mobiledoc') - }; + mobiledoc: editor.serializePost(post, 'mobiledoc'), + } - editor.runCallbacks('willCopy', [data]); + editor.runCallbacks('willCopy', [data]) - setClipboardData(event, data, window); + setClipboardData(event, data, window) } paste(event) { - event.preventDefault(); + event.preventDefault() - let { editor } = this; - let range = editor.range; + let { editor } = this + let range = editor.range if (!range.isCollapsed) { - editor.performDelete(); + editor.performDelete() } if (editor.post.isBlank) { - editor._insertEmptyMarkupSectionAtCursor(); + editor._insertEmptyMarkupSectionAtCursor() } - let position = editor.range.head; - let targetFormat = this.modifierKeys.shift ? 'text' : 'html'; - let pastedPost = parsePostFromPaste(event, editor, {targetFormat}); + let position = editor.range.head + let targetFormat = this.modifierKeys.shift ? 'text' : 'html' + let pastedPost = parsePostFromPaste(event, editor, { targetFormat }) editor.run(postEditor => { - let nextPosition = postEditor.insertPost(position, pastedPost); - postEditor.setRange(nextPosition); - }); + let nextPosition = postEditor.insertPost(position, pastedPost) + postEditor.setRange(nextPosition) + }) } drop(event) { - event.preventDefault(); + event.preventDefault() - let { clientX: x, clientY: y } = event; - let { editor } = this; + let { clientX: x, clientY: y } = event + let { editor } = this - let position = editor.positionAtPoint(x, y); + let position = editor.positionAtPoint(x, y) if (!position) { - this.logger.log('Could not find drop position'); - return; + this.logger.log('Could not find drop position') + return } - let post = parsePostFromDrop(event, editor, {logger: this.logger}); + let post = parsePostFromDrop(event, editor, { logger: this.logger }) if (!post) { - this.logger.log('Could not determine post from drop event'); - return; + this.logger.log('Could not determine post from drop event') + return } editor.run(postEditor => { - let nextPosition = postEditor.insertPost(position, post); - postEditor.setRange(nextPosition); - }); + let nextPosition = postEditor.insertPost(position, post) + postEditor.setRange(nextPosition) + }) } - _updateModifiersFromKey(key, {isDown}) { + _updateModifiersFromKey(key, { isDown }) { if (key.isShiftKey()) { - this.modifierKeys.shift = isDown; + this.modifierKeys.shift = isDown } } - } diff --git a/src/js/editor/key-commands.js b/src/js/editor/key-commands.js index f8bf610ef..8ea7c3c49 100644 --- a/src/js/editor/key-commands.js +++ b/src/js/editor/key-commands.js @@ -1,187 +1,213 @@ -import Key from '../utils/key'; -import { MODIFIERS, specialCharacterToCode } from '../utils/key'; -import { filter, reduce } from '../utils/array-utils'; -import assert from '../utils/assert'; -import Browser from '../utils/browser'; -import { toggleLink } from './ui'; +import Key from '../utils/key' +import { MODIFIERS, specialCharacterToCode } from '../utils/key' +import { filter, reduce } from '../utils/array-utils' +import assert from '../utils/assert' +import Browser from '../utils/browser' +import { toggleLink } from './ui' function selectAll(editor) { - let { post } = editor; - editor.selectRange(post.toRange()); + let { post } = editor + editor.selectRange(post.toRange()) } function gotoStartOfLine(editor) { - let {range} = editor; - let {tail: {section}} = range; + let { range } = editor + let { + tail: { section }, + } = range editor.run(postEditor => { - postEditor.setRange(section.headPosition()); - }); + postEditor.setRange(section.headPosition()) + }) } function gotoEndOfLine(editor) { - let {range} = editor; - let {tail: {section}} = range; + let { range } = editor + let { + tail: { section }, + } = range editor.run(postEditor => { - postEditor.setRange(section.tailPosition()); - }); + postEditor.setRange(section.tailPosition()) + }) } function deleteToEndOfSection(editor) { - let { range } = editor; + let { range } = editor if (range.isCollapsed) { - let { head, head: { section } } = range; - range = head.toRange(section.tailPosition()); + let { + head, + head: { section }, + } = range + range = head.toRange(section.tailPosition()) } editor.run(postEditor => { - let nextPosition = postEditor.deleteRange(range); - postEditor.setRange(nextPosition); - }); + let nextPosition = postEditor.deleteRange(range) + postEditor.setRange(nextPosition) + }) } -export const DEFAULT_KEY_COMMANDS = [{ - str: 'META+B', - run(editor) { - editor.toggleMarkup('strong'); - } -}, { - str: 'CTRL+B', - run(editor) { - editor.toggleMarkup('strong'); - } -}, { - str: 'META+I', - run(editor) { - editor.toggleMarkup('em'); - } -}, { - str: 'CTRL+I', - run(editor) { - editor.toggleMarkup('em'); - } -}, { - str: 'META+U', - run(editor) { - editor.toggleMarkup('u'); - } -}, { - str: 'CTRL+U', - run(editor) { - editor.toggleMarkup('u'); - } -}, { - str: 'CTRL+K', - run(editor) { - if (Browser.isMac()) { - return deleteToEndOfSection(editor); - } else if (Browser.isWin()) { - return toggleLink(editor); - } - } -}, { - str: 'CTRL+A', - run(editor) { - if (Browser.isMac()) { - gotoStartOfLine(editor); - } else { - selectAll(editor); - } - } -}, { - str: 'META+A', - run(editor) { - if (Browser.isMac()) { - selectAll(editor); - } - } -}, { - str: 'CTRL+E', - run(editor) { - if (Browser.isMac()) { - gotoEndOfLine(editor); - } - } -}, { - str: 'META+K', - run(editor) { - return toggleLink(editor); +export const DEFAULT_KEY_COMMANDS = [ + { + str: 'META+B', + run(editor) { + editor.toggleMarkup('strong') + }, }, - -}, { - str: 'META+Z', - run(editor) { - editor.run(postEditor => { - postEditor.undoLastChange(); - }); - } -}, { - str: 'META+SHIFT+Z', - run(editor) { - editor.run(postEditor => { - postEditor.redoLastChange(); - }); - } -}, { - str: 'CTRL+Z', - run(editor) { - if (Browser.isMac()) { return false; } - editor.run(postEditor => postEditor.undoLastChange()); - } -}, { - str: 'CTRL+SHIFT+Z', - run(editor) { - if (Browser.isMac()) { return false; } - editor.run(postEditor => postEditor.redoLastChange()); - } -}]; + { + str: 'CTRL+B', + run(editor) { + editor.toggleMarkup('strong') + }, + }, + { + str: 'META+I', + run(editor) { + editor.toggleMarkup('em') + }, + }, + { + str: 'CTRL+I', + run(editor) { + editor.toggleMarkup('em') + }, + }, + { + str: 'META+U', + run(editor) { + editor.toggleMarkup('u') + }, + }, + { + str: 'CTRL+U', + run(editor) { + editor.toggleMarkup('u') + }, + }, + { + str: 'CTRL+K', + run(editor) { + if (Browser.isMac()) { + return deleteToEndOfSection(editor) + } else if (Browser.isWin()) { + return toggleLink(editor) + } + }, + }, + { + str: 'CTRL+A', + run(editor) { + if (Browser.isMac()) { + gotoStartOfLine(editor) + } else { + selectAll(editor) + } + }, + }, + { + str: 'META+A', + run(editor) { + if (Browser.isMac()) { + selectAll(editor) + } + }, + }, + { + str: 'CTRL+E', + run(editor) { + if (Browser.isMac()) { + gotoEndOfLine(editor) + } + }, + }, + { + str: 'META+K', + run(editor) { + return toggleLink(editor) + }, + }, + { + str: 'META+Z', + run(editor) { + editor.run(postEditor => { + postEditor.undoLastChange() + }) + }, + }, + { + str: 'META+SHIFT+Z', + run(editor) { + editor.run(postEditor => { + postEditor.redoLastChange() + }) + }, + }, + { + str: 'CTRL+Z', + run(editor) { + if (Browser.isMac()) { + return false + } + editor.run(postEditor => postEditor.undoLastChange()) + }, + }, + { + str: 'CTRL+SHIFT+Z', + run(editor) { + if (Browser.isMac()) { + return false + } + editor.run(postEditor => postEditor.redoLastChange()) + }, + }, +] function modifierNamesToMask(modiferNames) { - let defaultVal = 0; - return reduce(modiferNames, - (sum, name) => { - let modifier = MODIFIERS[name.toUpperCase()]; - assert(`No modifier named "${name}" found`, !!modifier); - return sum + modifier; - }, - defaultVal); + let defaultVal = 0 + return reduce( + modiferNames, + (sum, name) => { + let modifier = MODIFIERS[name.toUpperCase()] + assert(`No modifier named "${name}" found`, !!modifier) + return sum + modifier + }, + defaultVal + ) } function characterToCode(character) { - const upperCharacter = character.toUpperCase(); - const special = specialCharacterToCode(upperCharacter); + const upperCharacter = character.toUpperCase() + const special = specialCharacterToCode(upperCharacter) if (special) { - return special; + return special } else { - assert(`Only 1 character can be used in a key command str (got "${character}")`, - character.length === 1); - return upperCharacter.charCodeAt(0); + assert(`Only 1 character can be used in a key command str (got "${character}")`, character.length === 1) + return upperCharacter.charCodeAt(0) } } export function buildKeyCommand(keyCommand) { - let { str } = keyCommand; + let { str } = keyCommand if (!str) { - return keyCommand; + return keyCommand } - assert('[deprecation] Key commands no longer use the `modifier` property', - !keyCommand.modifier); + assert('[deprecation] Key commands no longer use the `modifier` property', !keyCommand.modifier) - let [character, ...modifierNames] = str.split('+').reverse(); + let [character, ...modifierNames] = str.split('+').reverse() - keyCommand.modifierMask = modifierNamesToMask(modifierNames); - keyCommand.code = characterToCode(character); + keyCommand.modifierMask = modifierNamesToMask(modifierNames) + keyCommand.code = characterToCode(character) - return keyCommand; + return keyCommand } export function validateKeyCommand(keyCommand) { - return !!keyCommand.code && !!keyCommand.run; + return !!keyCommand.code && !!keyCommand.run } export function findKeyCommands(keyCommands, keyEvent) { - const key = Key.fromEvent(keyEvent); + const key = Key.fromEvent(keyEvent) - return filter(keyCommands, ({modifierMask, code}) => { - return key.keyCode === code && key.modifierMask === modifierMask; - }); + return filter(keyCommands, ({ modifierMask, code }) => { + return key.keyCode === code && key.modifierMask === modifierMask + }) } diff --git a/src/js/editor/mutation-handler.js b/src/js/editor/mutation-handler.js index d93476a66..a72dc5fb1 100644 --- a/src/js/editor/mutation-handler.js +++ b/src/js/editor/mutation-handler.js @@ -1,69 +1,69 @@ -import Set from 'mobiledoc-kit/utils/set'; -import { forEach, filter } from 'mobiledoc-kit/utils/array-utils'; -import assert from 'mobiledoc-kit/utils/assert'; -import { containsNode } from 'mobiledoc-kit/utils/dom-utils'; +import Set from 'mobiledoc-kit/utils/set' +import { forEach, filter } from 'mobiledoc-kit/utils/array-utils' +import assert from 'mobiledoc-kit/utils/assert' +import { containsNode } from 'mobiledoc-kit/utils/dom-utils' const MUTATION = { NODES_CHANGED: 'childList', - CHARACTER_DATA: 'characterData' -}; + CHARACTER_DATA: 'characterData', +} export default class MutationHandler { constructor(editor) { - this.editor = editor; - this.logger = editor.loggerFor('mutation-handler'); - this.renderTree = null; - this._isObserving = false; - - this._observer = new MutationObserver((mutations) => { - this._handleMutations(mutations); - }); + this.editor = editor + this.logger = editor.loggerFor('mutation-handler') + this.renderTree = null + this._isObserving = false + + this._observer = new MutationObserver(mutations => { + this._handleMutations(mutations) + }) } init() { - this.startObserving(); + this.startObserving() } destroy() { - this.stopObserving(); - this._observer = null; + this.stopObserving() + this._observer = null } suspendObservation(callback) { - this.stopObserving(); - callback(); - this.startObserving(); + this.stopObserving() + callback() + this.startObserving() } stopObserving() { if (this._isObserving) { - this._isObserving = false; - this._observer.disconnect(); + this._isObserving = false + this._observer.disconnect() } } startObserving() { if (!this._isObserving) { - let { editor } = this; - assert('Cannot observe un-rendered editor', editor.hasRendered); + let { editor } = this + assert('Cannot observe un-rendered editor', editor.hasRendered) - this._isObserving = true; - this.renderTree = editor._renderTree; + this._isObserving = true + this.renderTree = editor._renderTree this._observer.observe(editor.element, { characterData: true, childList: true, - subtree: true - }); + subtree: true, + }) } } reparsePost() { - this.editor._reparsePost(); + this.editor._reparsePost() } reparseSections(sections) { - this.editor._reparseSections(sections); + this.editor._reparseSections(sections) } /** @@ -80,77 +80,76 @@ export default class MutationHandler { * * if no section, reparse all (and break) */ _handleMutations(mutations) { - let reparsePost = false; - let sections = new Set(); + let reparsePost = false + let sections = new Set() for (let i = 0; i < mutations.length; i++) { if (reparsePost) { - break; + break } - let nodes = this._findTargetNodes(mutations[i]); + let nodes = this._findTargetNodes(mutations[i]) - for (let j=0; j < nodes.length; j++) { - let node = nodes[j]; - let renderNode = this._findRenderNodeFromNode(node); + for (let j = 0; j < nodes.length; j++) { + let node = nodes[j] + let renderNode = this._findRenderNodeFromNode(node) if (renderNode) { if (renderNode.reparsesMutationOfChildNode(node)) { - let section = this._findSectionFromRenderNode(renderNode); + let section = this._findSectionFromRenderNode(renderNode) if (section) { - sections.add(section); + sections.add(section) } else { - reparsePost = true; + reparsePost = true } } } else { - reparsePost = true; - break; + reparsePost = true + break } } } if (reparsePost) { - this.logger.log(`reparsePost (${mutations.length} mutations)`); - this.reparsePost(); + this.logger.log(`reparsePost (${mutations.length} mutations)`) + this.reparsePost() } else if (sections.length) { - this.logger.log(`reparse ${sections.length} sections (${mutations.length} mutations)`); - this.reparseSections(sections.toArray()); + this.logger.log(`reparse ${sections.length} sections (${mutations.length} mutations)`) + this.reparseSections(sections.toArray()) } } _findTargetNodes(mutation) { - let nodes = []; + let nodes = [] switch (mutation.type) { case MUTATION.CHARACTER_DATA: - nodes.push(mutation.target); - break; + nodes.push(mutation.target) + break case MUTATION.NODES_CHANGED: - forEach(mutation.addedNodes, n => nodes.push(n)); + forEach(mutation.addedNodes, n => nodes.push(n)) if (mutation.removedNodes.length) { - nodes.push(mutation.target); + nodes.push(mutation.target) } - break; + break } - let element = this.editor.element; - let attachedNodes = filter(nodes, node => containsNode(element, node)); - return attachedNodes; + let element = this.editor.element + let attachedNodes = filter(nodes, node => containsNode(element, node)) + return attachedNodes } _findSectionRenderNodeFromNode(node) { - return this.renderTree.findRenderNodeFromElement(node, (rn) => { - return rn.postNode.isSection; - }); + return this.renderTree.findRenderNodeFromElement(node, rn => { + return rn.postNode.isSection + }) } _findRenderNodeFromNode(node) { - return this.renderTree.findRenderNodeFromElement(node); + return this.renderTree.findRenderNodeFromElement(node) } _findSectionFromRenderNode(renderNode) { - let sectionRenderNode = this._findSectionRenderNodeFromNode(renderNode.element); - return sectionRenderNode && sectionRenderNode.postNode; + let sectionRenderNode = this._findSectionRenderNodeFromNode(renderNode.element) + return sectionRenderNode && sectionRenderNode.postNode } - } diff --git a/src/js/editor/post.js b/src/js/editor/post.js index 4245afbe6..181e5a1e0 100644 --- a/src/js/editor/post.js +++ b/src/js/editor/post.js @@ -1,43 +1,43 @@ -import Position from '../utils/cursor/position'; -import Range from 'mobiledoc-kit/utils/cursor/range'; -import { detect, forEach, reduce, filter, values, commonItems } from '../utils/array-utils'; -import { DIRECTION } from '../utils/key'; -import LifecycleCallbacks from '../models/lifecycle-callbacks'; -import assert from '../utils/assert'; -import { normalizeTagName } from '../utils/dom-utils'; -import PostInserter from './post/post-inserter'; -import deprecate from 'mobiledoc-kit/utils/deprecate'; -import toRange from 'mobiledoc-kit/utils/to-range'; - -const { FORWARD, BACKWARD } = DIRECTION; +import Position from '../utils/cursor/position' +import Range from 'mobiledoc-kit/utils/cursor/range' +import { detect, forEach, reduce, filter, values, commonItems } from '../utils/array-utils' +import { DIRECTION } from '../utils/key' +import LifecycleCallbacks from '../models/lifecycle-callbacks' +import assert from '../utils/assert' +import { normalizeTagName } from '../utils/dom-utils' +import PostInserter from './post/post-inserter' +import deprecate from 'mobiledoc-kit/utils/deprecate' +import toRange from 'mobiledoc-kit/utils/to-range' + +const { FORWARD, BACKWARD } = DIRECTION function isListSectionTagName(tagName) { - return tagName === 'ul' || tagName === 'ol'; + return tagName === 'ul' || tagName === 'ol' } function shrinkRange(range) { - const { head, tail } = range; + const { head, tail } = range if (tail.offset === 0 && head.section !== tail.section) { - range.tail = new Position(tail.section.prev, tail.section.prev.length); + range.tail = new Position(tail.section.prev, tail.section.prev.length) } - return range; + return range } const CALLBACK_QUEUES = { BEFORE_COMPLETE: 'beforeComplete', COMPLETE: 'complete', - AFTER_COMPLETE: 'afterComplete' -}; + AFTER_COMPLETE: 'afterComplete', +} // There are only two events that we're concerned about for Undo, that is inserting text and deleting content. // These are the only two states that go on a "run" and create a combined undo, everything else has it's own // deadicated undo. const EDIT_ACTIONS = { INSERT_TEXT: 1, - DELETE: 2 -}; + DELETE: 2, +} /** * The PostEditor is used to modify a post. It should not be instantiated directly. @@ -57,33 +57,33 @@ class PostEditor { * @private */ constructor(editor) { - this.editor = editor; - this.builder = this.editor.builder; - this._callbacks = new LifecycleCallbacks(values(CALLBACK_QUEUES)); + this.editor = editor + this.builder = this.editor.builder + this._callbacks = new LifecycleCallbacks(values(CALLBACK_QUEUES)) - this._didComplete = false; - this.editActionTaken = null; + this._didComplete = false + this.editActionTaken = null - this._renderRange = () => this.editor.selectRange(this._range); - this._postDidChange = () => this.editor._postDidChange(); - this._rerender = () => this.editor.rerender(); + this._renderRange = () => this.editor.selectRange(this._range) + this._postDidChange = () => this.editor._postDidChange() + this._rerender = () => this.editor.rerender() } addCallback(...args) { - this._callbacks.addCallback(...args); + this._callbacks.addCallback(...args) } addCallbackOnce(...args) { - this._callbacks.addCallbackOnce(...args); + this._callbacks.addCallbackOnce(...args) } runCallbacks(...args) { - this._callbacks.runCallbacks(...args); + this._callbacks.runCallbacks(...args) } begin() { // cache the editor's range - this._range = this.editor.range; + this._range = this.editor.range } /** @@ -105,12 +105,12 @@ class PostEditor { * @public */ setRange(range) { - range = toRange(range); + range = toRange(range) // TODO validate that the range is valid // (does not contain marked-for-removal head or tail sections?) - this._range = range; - this.scheduleAfterRender(this._renderRange, true); + this._range = range + this.scheduleAfterRender(this._renderRange, true) } /** @@ -129,61 +129,65 @@ class PostEditor { * @public */ deleteRange(range) { - assert("Must pass MobiledocKit Range to `deleteRange`", range instanceof Range); + assert('Must pass MobiledocKit Range to `deleteRange`', range instanceof Range) - this.editActionTaken = EDIT_ACTIONS.DELETE; + this.editActionTaken = EDIT_ACTIONS.DELETE let { - head, head: {section: headSection}, - tail, tail: {section: tailSection} - } = range; + head, + head: { section: headSection }, + tail, + tail: { section: tailSection }, + } = range - let { editor: { post } } = this; + let { + editor: { post }, + } = this if (headSection === tailSection) { - return this.cutSection(headSection, head, tail); + return this.cutSection(headSection, head, tail) } - let nextSection = headSection.nextLeafSection(); + let nextSection = headSection.nextLeafSection() - let nextPos = this.cutSection(headSection, head, headSection.tailPosition()); + let nextPos = this.cutSection(headSection, head, headSection.tailPosition()) // cutSection can replace the section, so re-read headSection here - headSection = nextPos.section; + headSection = nextPos.section // Remove sections in the middle of the range while (nextSection !== tailSection) { - let tmp = nextSection; - nextSection = nextSection.nextLeafSection(); - this.removeSection(tmp); + let tmp = nextSection + nextSection = nextSection.nextLeafSection() + this.removeSection(tmp) } - let tailPos = this.cutSection(tailSection, tailSection.headPosition(), tail); + let tailPos = this.cutSection(tailSection, tailSection.headPosition(), tail) // cutSection can replace the section, so re-read tailSection here - tailSection = tailPos.section; + tailSection = tailPos.section if (tailSection.isBlank) { - this.removeSection(tailSection); + this.removeSection(tailSection) } else { // If head and tail sections are markerable, join them // Note: They may not be the same section type. E.g. this may join // a tail section that was a list item onto a markup section, or vice versa. // (This is the desired behavior.) if (headSection.isMarkerable && tailSection.isMarkerable) { - headSection.join(tailSection); - this._markDirty(headSection); - this.removeSection(tailSection); + headSection.join(tailSection) + this._markDirty(headSection) + this.removeSection(tailSection) } else if (headSection.isBlank) { - this.removeSection(headSection); - nextPos = tailPos; + this.removeSection(headSection) + nextPos = tailPos } } if (post.isBlank) { - post.sections.append(this.builder.createMarkupSection('p')); - nextPos = post.headPosition(); + post.sections.append(this.builder.createMarkupSection('p')) + nextPos = post.headPosition() } - return nextPos; + return nextPos } /** @@ -202,34 +206,35 @@ class PostEditor { * @private */ cutSection(section, head, tail) { - assert('Must pass head position and tail position to `cutSection`', - head instanceof Position && tail instanceof Position); - assert('Must pass positions within same section to `cutSection`', - head.section === tail.section); + assert( + 'Must pass head position and tail position to `cutSection`', + head instanceof Position && tail instanceof Position + ) + assert('Must pass positions within same section to `cutSection`', head.section === tail.section) if (section.isBlank || head.isEqual(tail)) { - return head; + return head } if (section.isCardSection) { if (head.isHead() && tail.isTail()) { - let newSection = this.builder.createMarkupSection(); - this.replaceSection(section, newSection); - return newSection.headPosition(); + let newSection = this.builder.createMarkupSection() + this.replaceSection(section, newSection) + return newSection.headPosition() } else { - return tail; + return tail } } - let range = head.toRange(tail); - this.splitMarkers(range).forEach(m => this.removeMarker(m)); + let range = head.toRange(tail) + this.splitMarkers(range).forEach(m => this.removeMarker(m)) - return head; + return head } _coalesceMarkers(section) { if (section.isMarkerable) { - this._removeBlankMarkers(section); - this._joinSimilarMarkers(section); + this._removeBlankMarkers(section) + this._joinSimilarMarkers(section) } } @@ -237,129 +242,120 @@ class PostEditor { forEach( filter(section.markers, m => m.isBlank), m => this.removeMarker(m) - ); + ) } // joins markers that have identical markups _joinSimilarMarkers(section) { - let marker = section.markers.head; - let nextMarker; + let marker = section.markers.head + let nextMarker while (marker && marker.next) { - nextMarker = marker.next; + nextMarker = marker.next if (marker.canJoin(nextMarker)) { - nextMarker.value = marker.value + nextMarker.value; - this._markDirty(nextMarker); - this.removeMarker(marker); + nextMarker.value = marker.value + nextMarker.value + this._markDirty(nextMarker) + this.removeMarker(marker) } - marker = nextMarker; + marker = nextMarker } } removeMarker(marker) { - this._scheduleForRemoval(marker); + this._scheduleForRemoval(marker) if (marker.section) { - this._markDirty(marker.section); - marker.section.markers.remove(marker); + this._markDirty(marker.section) + marker.section.markers.remove(marker) } } _scheduleForRemoval(postNode) { if (postNode.renderNode) { - postNode.renderNode.scheduleForRemoval(); + postNode.renderNode.scheduleForRemoval() - this.scheduleRerender(); - this.scheduleDidUpdate(); + this.scheduleRerender() + this.scheduleDidUpdate() } - let removedAdjacentToList = (postNode.prev && postNode.prev.isListSection) || - (postNode.next && postNode.next.isListSection); + let removedAdjacentToList = + (postNode.prev && postNode.prev.isListSection) || (postNode.next && postNode.next.isListSection) if (removedAdjacentToList) { - this.addCallback( - CALLBACK_QUEUES.BEFORE_COMPLETE, - () => this._joinContiguousListSections() - ); + this.addCallback(CALLBACK_QUEUES.BEFORE_COMPLETE, () => this._joinContiguousListSections()) } } _joinContiguousListSections() { - let { post } = this.editor; - let range = this._range; - let prev; - let groups = []; - let currentGroup; + let { post } = this.editor + let range = this._range + let prev + let groups = [] + let currentGroup // FIXME do we need to force a re-render of the range if changed sections // are contained within the range? - let updatedHead = null; + let updatedHead = null forEach(post.sections, section => { - if (prev && - prev.isListSection && - section.isListSection && - prev.tagName === section.tagName) { - - currentGroup = currentGroup || [prev]; - currentGroup.push(section); + if (prev && prev.isListSection && section.isListSection && prev.tagName === section.tagName) { + currentGroup = currentGroup || [prev] + currentGroup.push(section) } else { if (currentGroup) { - groups.push(currentGroup); + groups.push(currentGroup) } - currentGroup = null; + currentGroup = null } - prev = section; - }); + prev = section + }) if (currentGroup) { - groups.push(currentGroup); + groups.push(currentGroup) } forEach(groups, group => { - let list = group[0]; + let list = group[0] forEach(group, listSection => { if (listSection === list) { - return; + return } - let currentHead = range.head; - let prevPosition; + let currentHead = range.head + let prevPosition // FIXME is there a currentHead if there is no range? // is the current head a list item in the section - if (!range.isBlank && currentHead.section.isListItem && - currentHead.section.parent === listSection) { - prevPosition = list.tailPosition(); + if (!range.isBlank && currentHead.section.isListItem && currentHead.section.parent === listSection) { + prevPosition = list.tailPosition() } - this._joinListSections(list, listSection); + this._joinListSections(list, listSection) if (prevPosition) { - updatedHead = prevPosition.move(FORWARD); + updatedHead = prevPosition.move(FORWARD) } - }); - }); + }) + }) if (updatedHead) { - this.setRange(updatedHead); + this.setRange(updatedHead) } } _joinListSections(baseList, nextList) { - baseList.join(nextList); - this._markDirty(baseList); - this.removeSection(nextList); + baseList.join(nextList) + this._markDirty(baseList) + this.removeSection(nextList) } _markDirty(postNode) { if (postNode.renderNode) { - postNode.renderNode.markDirty(); + postNode.renderNode.markDirty() - this.scheduleRerender(); - this.scheduleDidUpdate(); + this.scheduleRerender() + this.scheduleDidUpdate() } if (postNode.section) { - this._markDirty(postNode.section); + this._markDirty(postNode.section) } if (postNode.isMarkerable) { - this.addCallback( - CALLBACK_QUEUES.BEFORE_COMPLETE, () => this._coalesceMarkers(postNode)); + this.addCallback(CALLBACK_QUEUES.BEFORE_COMPLETE, () => this._coalesceMarkers(postNode)) } } @@ -370,9 +366,11 @@ class PostEditor { * @public * @deprecated after v0.10.3 */ - deleteFrom(position, direction=DIRECTION.BACKWARD) { - deprecate("`postEditor#deleteFrom is deprecated. Use `deleteAtPosition(position, direction=BACKWARD, {unit}={unit: 'char'})` instead"); - return this.deleteAtPosition(position, direction, {unit: 'char'}); + deleteFrom(position, direction = DIRECTION.BACKWARD) { + deprecate( + "`postEditor#deleteFrom is deprecated. Use `deleteAtPosition(position, direction=BACKWARD, {unit}={unit: 'char'})` instead" + ) + return this.deleteAtPosition(position, direction, { unit: 'char' }) } /** @@ -397,29 +395,29 @@ class PostEditor { * @param {String} [options.unit="char"] The unit of deletion ("word" or "char") * @return {Position} */ - deleteAtPosition(position, direction=DIRECTION.BACKWARD, {unit}={unit: 'char'}) { + deleteAtPosition(position, direction = DIRECTION.BACKWARD, { unit } = { unit: 'char' }) { if (direction === DIRECTION.BACKWARD) { - return this._deleteAtPositionBackward(position, unit); + return this._deleteAtPositionBackward(position, unit) } else { - return this._deleteAtPositionForward(position, unit); + return this._deleteAtPositionForward(position, unit) } } _deleteAtPositionBackward(position, unit) { if (position.isHead() && position.section.isListItem) { - this.toggleSection('p', position); - return this._range.head; + this.toggleSection('p', position) + return this._range.head } else { - let prevPosition = unit === 'word' ? position.moveWord(BACKWARD) : position.move(BACKWARD); - let range = prevPosition.toRange(position); - return this.deleteRange(range); + let prevPosition = unit === 'word' ? position.moveWord(BACKWARD) : position.move(BACKWARD) + let range = prevPosition.toRange(position) + return this.deleteRange(range) } } _deleteAtPositionForward(position, unit) { - let nextPosition = unit === 'word' ? position.moveWord(FORWARD) : position.move(FORWARD); - let range = position.toRange(nextPosition); - return this.deleteRange(range); + let nextPosition = unit === 'word' ? position.moveWord(FORWARD) : position.move(FORWARD) + let range = position.toRange(nextPosition) + return this.deleteRange(range) } /** @@ -441,18 +439,18 @@ class PostEditor { * @private */ splitMarkers(range) { - const { post } = this.editor; - const { head, tail } = range; + const { post } = this.editor + const { head, tail } = range - this.splitSectionMarkerAtOffset(head.section, head.offset); - this.splitSectionMarkerAtOffset(tail.section, tail.offset); + this.splitSectionMarkerAtOffset(head.section, head.offset) + this.splitSectionMarkerAtOffset(tail.section, tail.offset) - return post.markersContainedByRange(range); + return post.markersContainedByRange(range) } splitSectionMarkerAtOffset(section, offset) { - const edit = section.splitMarkerAtOffset(offset); - edit.removed.forEach(m => this.removeMarker(m)); + const edit = section.splitMarkerAtOffset(offset) + edit.removed.forEach(m => this.removeMarker(m)) } /** @@ -476,31 +474,31 @@ class PostEditor { * @public */ splitSection(position) { - const { section } = position; + const { section } = position if (section.isCardSection) { - return this._splitCardSection(section, position); + return this._splitCardSection(section, position) } else if (section.isListItem) { - let isLastAndBlank = section.isBlank && !section.next; + let isLastAndBlank = section.isBlank && !section.next if (isLastAndBlank) { // if is last, replace the item with a blank markup section - let parent = section.parent; - let collection = this.editor.post.sections; - let blank = this.builder.createMarkupSection(); - this.removeSection(section); - this.insertSectionBefore(collection, blank, parent.next); + let parent = section.parent + let collection = this.editor.post.sections + let blank = this.builder.createMarkupSection() + this.removeSection(section) + this.insertSectionBefore(collection, blank, parent.next) - return [null, blank]; + return [null, blank] } else { - let [pre, post] = this._splitListItem(section, position); - return [pre, post]; + let [pre, post] = this._splitListItem(section, position) + return [pre, post] } } else { - let splitSections = section.splitAtPosition(position); - splitSections.forEach(s => this._coalesceMarkers(s)); - this._replaceSection(section, splitSections); + let splitSections = section.splitAtPosition(position) + splitSections.forEach(s => this._coalesceMarkers(s)) + this._replaceSection(section, splitSections) - return splitSections; + return splitSections } } @@ -511,26 +509,25 @@ class PostEditor { * @private */ _splitCardSection(cardSection, position) { - let { offset } = position; - assert('Cards section must be split at offset 0 or 1', - offset === 0 || offset === 1); + let { offset } = position + assert('Cards section must be split at offset 0 or 1', offset === 0 || offset === 1) - let newSection = this.builder.createMarkupSection(); - let nextSection; - let surroundingSections; + let newSection = this.builder.createMarkupSection() + let nextSection + let surroundingSections if (offset === 0) { - nextSection = cardSection; - surroundingSections = [newSection, cardSection]; + nextSection = cardSection + surroundingSections = [newSection, cardSection] } else { - nextSection = cardSection.next; - surroundingSections = [cardSection, newSection]; + nextSection = cardSection.next + surroundingSections = [cardSection, newSection] } - let collection = this.editor.post.sections; - this.insertSectionBefore(collection, newSection, nextSection); + let collection = this.editor.post.sections + this.insertSectionBefore(collection, newSection, nextSection) - return surroundingSections; + return surroundingSections } /** @@ -542,17 +539,17 @@ class PostEditor { replaceSection(section, newSection) { if (!section) { // FIXME should a falsy section be a valid argument? - this.insertSectionBefore(this.editor.post.sections, newSection, null); + this.insertSectionBefore(this.editor.post.sections, newSection, null) } else { - this._replaceSection(section, [newSection]); + this._replaceSection(section, [newSection]) } } moveSectionBefore(collection, renderedSection, beforeSection) { - const newSection = renderedSection.clone(); - this.removeSection(renderedSection); - this.insertSectionBefore(collection, newSection, beforeSection); - return newSection; + const newSection = renderedSection.clone() + this.removeSection(renderedSection) + this.insertSectionBefore(collection, newSection, beforeSection) + return newSection } /** @@ -560,14 +557,14 @@ class PostEditor { * @public */ moveSectionUp(renderedSection) { - const isFirst = !renderedSection.prev; + const isFirst = !renderedSection.prev if (isFirst) { - return renderedSection; + return renderedSection } - const collection = renderedSection.parent.sections; - const beforeSection = renderedSection.prev; - return this.moveSectionBefore(collection, renderedSection, beforeSection); + const collection = renderedSection.parent.sections + const beforeSection = renderedSection.prev + return this.moveSectionBefore(collection, renderedSection, beforeSection) } /** @@ -575,14 +572,14 @@ class PostEditor { * @public */ moveSectionDown(renderedSection) { - const isLast = !renderedSection.next; + const isLast = !renderedSection.next if (isLast) { - return renderedSection; + return renderedSection } - const beforeSection = renderedSection.next.next; - const collection = renderedSection.parent.sections; - return this.moveSectionBefore(collection, renderedSection, beforeSection); + const beforeSection = renderedSection.next.next + const collection = renderedSection.parent.sections + return this.moveSectionBefore(collection, renderedSection, beforeSection) } /** @@ -595,28 +592,27 @@ class PostEditor { * @public */ insertMarkers(position, markers) { - let { section, offset } = position; - assert('Cannot insert markers at non-markerable position', - section.isMarkerable); + let { section, offset } = position + assert('Cannot insert markers at non-markerable position', section.isMarkerable) - this.editActionTaken = EDIT_ACTIONS.INSERT_TEXT; + this.editActionTaken = EDIT_ACTIONS.INSERT_TEXT - let edit = section.splitMarkerAtOffset(offset); - edit.removed.forEach(marker => this._scheduleForRemoval(marker)); + let edit = section.splitMarkerAtOffset(offset) + edit.removed.forEach(marker => this._scheduleForRemoval(marker)) - let prevMarker = section.markerBeforeOffset(offset); + let prevMarker = section.markerBeforeOffset(offset) markers.forEach(marker => { - section.markers.insertAfter(marker, prevMarker); - offset += marker.length; - prevMarker = marker; - }); + section.markers.insertAfter(marker, prevMarker) + offset += marker.length + prevMarker = marker + }) - this._coalesceMarkers(section); - this._markDirty(section); + this._coalesceMarkers(section) + this._markDirty(section) - let nextPosition = section.toPosition(offset); - this.setRange(nextPosition); - return nextPosition; + let nextPosition = section.toPosition(offset) + this.setRange(nextPosition) + return nextPosition } /** @@ -628,11 +624,13 @@ class PostEditor { * @param {Markup[]} markups * @return {Position} position at the end of the inserted text */ - insertTextWithMarkup(position, text, markups=[]) { - let { section } = position; - if (!section.isMarkerable) { return; } - let marker = this.builder.createMarker(text, markups); - return this.insertMarkers(position, [marker]); + insertTextWithMarkup(position, text, markups = []) { + let { section } = position + if (!section.isMarkerable) { + return + } + let marker = this.builder.createMarker(text, markups) + return this.insertMarkers(position, [marker]) } /** @@ -644,27 +642,29 @@ class PostEditor { * @return {Position} position at the end of the inserted text. */ insertText(position, text) { - let { section } = position; - if (!section.isMarkerable) { return; } - let markups = position.marker && position.marker.markups; - markups = markups || []; - return this.insertTextWithMarkup(position, text, markups); + let { section } = position + if (!section.isMarkerable) { + return + } + let markups = position.marker && position.marker.markups + markups = markups || [] + return this.insertTextWithMarkup(position, text, markups) } _replaceSection(section, newSections) { - let nextSection = section.next; - let collection = section.parent.sections; + let nextSection = section.next + let collection = section.parent.sections - let nextNewSection = newSections[0]; + let nextNewSection = newSections[0] if (nextNewSection.isMarkupSection && section.isListItem) { // put the new section after the ListSection (section.parent) // instead of after the ListItem - collection = section.parent.parent.sections; - nextSection = section.parent.next; + collection = section.parent.parent.sections + nextSection = section.parent.next } - newSections.forEach(s => this.insertSectionBefore(collection, s, nextSection)); - this.removeSection(section); + newSections.forEach(s => this.insertSectionBefore(collection, s, nextSection)) + this.removeSection(section) } /** @@ -687,9 +687,11 @@ class PostEditor { * @public */ addMarkupToRange(range, markup) { - if (range.isCollapsed) { return; } + if (range.isCollapsed) { + return + } - let markers = this.splitMarkers(range); + let markers = this.splitMarkers(range) if (markers.length) { // We insert the new markup at a consistent index across the range. // If we just push on the end of the list, it can end up in different positions @@ -701,15 +703,19 @@ class PostEditor { // at the end of those. // Prompted by https://github.com/bustle/mobiledoc-kit/issues/360 - let markupsOpenAcrossRange = reduce(markers, function (soFar, marker) { - return commonItems(soFar, marker.markups); - }, markers[0].markups); - let indexToInsert = markupsOpenAcrossRange.length; + let markupsOpenAcrossRange = reduce( + markers, + function (soFar, marker) { + return commonItems(soFar, marker.markups) + }, + markers[0].markups + ) + let indexToInsert = markupsOpenAcrossRange.length markers.forEach(marker => { - marker.addMarkupAtIndex(markup, indexToInsert); - this._markDirty(marker); - }); + marker.addMarkupAtIndex(markup, indexToInsert) + this._markDirty(marker) + }) } } @@ -733,12 +739,14 @@ class PostEditor { * @private */ removeMarkupFromRange(range, markupOrMarkupCallback) { - if (range.isCollapsed) { return; } + if (range.isCollapsed) { + return + } this.splitMarkers(range).forEach(marker => { - marker.removeMarkup(markupOrMarkupCallback); - this._markDirty(marker); - }); + marker.removeMarkup(markupOrMarkupCallback) + this._markDirty(marker) + }) } /** @@ -764,23 +772,22 @@ class PostEditor { * @param {Range|Position} range in which to toggle. Defaults to current editor range. * @public */ - toggleMarkup(markupOrMarkupString, range=this._range) { - range = toRange(range); - const markup = typeof markupOrMarkupString === 'string' ? - this.builder.createMarkup(markupOrMarkupString) : - markupOrMarkupString; + toggleMarkup(markupOrMarkupString, range = this._range) { + range = toRange(range) + const markup = + typeof markupOrMarkupString === 'string' ? this.builder.createMarkup(markupOrMarkupString) : markupOrMarkupString - const hasMarkup = this.editor.detectMarkupInRange(range, markup.tagName); + const hasMarkup = this.editor.detectMarkupInRange(range, markup.tagName) // FIXME: This implies only a single markup in a range. This may not be // true for links (which are not the same object instance like multiple // strong tags would be). if (hasMarkup) { - this.removeMarkupFromRange(range, hasMarkup); + this.removeMarkupFromRange(range, hasMarkup) } else { - this.addMarkupToRange(range, markup); + this.addMarkupToRange(range, markup) } - this.setRange(range); + this.setRange(range) } /** @@ -794,48 +801,48 @@ class PostEditor { * Defaults to the current editor range. * @public */ - toggleSection(sectionTagName, range=this._range) { - range = shrinkRange(toRange(range)); + toggleSection(sectionTagName, range = this._range) { + range = shrinkRange(toRange(range)) - sectionTagName = normalizeTagName(sectionTagName); - let { post } = this.editor; + sectionTagName = normalizeTagName(sectionTagName) + let { post } = this.editor - let everySectionHasTagName = true; + let everySectionHasTagName = true post.walkMarkerableSections(range, section => { if (!this._isSameSectionType(section, sectionTagName)) { - everySectionHasTagName = false; + everySectionHasTagName = false } - }); + }) - let tagName = everySectionHasTagName ? 'p' : sectionTagName; - let sectionTransformations = []; + let tagName = everySectionHasTagName ? 'p' : sectionTagName + let sectionTransformations = [] post.walkMarkerableSections(range, section => { - let changedSection = this.changeSectionTagName(section, tagName); + let changedSection = this.changeSectionTagName(section, tagName) sectionTransformations.push({ from: section, - to: changedSection - }); - }); + to: changedSection, + }) + }) - let nextRange = this._determineNextRangeAfterToggleSection(range, sectionTransformations); - this.setRange(nextRange); + let nextRange = this._determineNextRangeAfterToggleSection(range, sectionTransformations) + this.setRange(nextRange) } _determineNextRangeAfterToggleSection(range, sectionTransformations) { if (sectionTransformations.length) { let changedHeadSection = detect(sectionTransformations, ({ from }) => { - return from === range.headSection; - }).to; + return from === range.headSection + }).to let changedTailSection = detect(sectionTransformations, ({ from }) => { - return from === range.tailSection; - }).to; + return from === range.tailSection + }).to if (changedHeadSection.isListSection || changedTailSection.isListSection) { // We don't know to which ListItem's the original sections point at, so // we don't have enough information to reconstruct the range when // dealing with lists. - return sectionTransformations[0].to.headPosition().toRange(); + return sectionTransformations[0].to.headPosition().toRange() } else { return Range.create( changedHeadSection, @@ -843,53 +850,51 @@ class PostEditor { changedTailSection, range.tailSectionOffset, range.direction - ); + ) } } else { - return range; + return range } } - setAttribute(key, value, range=this._range) { + setAttribute(key, value, range = this._range) { this._mutateAttribute(key, range, (section, attribute) => { if (section.getAttribute(attribute) !== value) { - section.setAttribute(attribute, value); - return true; + section.setAttribute(attribute, value) + return true } - }); + }) } - removeAttribute(key, range=this._range) { + removeAttribute(key, range = this._range) { this._mutateAttribute(key, range, (section, attribute) => { if (section.hasAttribute(attribute)) { - section.removeAttribute(attribute); - return true; + section.removeAttribute(attribute) + return true } - }); + }) } _mutateAttribute(key, range, cb) { - range = toRange(range); - let { post } = this.editor; - let attribute = `data-md-${key}`; + range = toRange(range) + let { post } = this.editor + let attribute = `data-md-${key}` post.walkMarkerableSections(range, section => { if (section.isListItem) { - section = section.parent; + section = section.parent } if (cb(section, attribute) === true) { - this._markDirty(section); + this._markDirty(section) } - }); + }) - this.setRange(range); + this.setRange(range) } _isSameSectionType(section, sectionTagName) { - return section.isListItem ? - section.parent.tagName === sectionTagName : - section.tagName === sectionTagName; + return section.isListItem ? section.parent.tagName === sectionTagName : section.tagName === sectionTagName } /** @@ -897,17 +902,16 @@ class PostEditor { * @private */ changeSectionTagName(section, newTagName) { - assert('Cannot pass non-markerable section to `changeSectionTagName`', - section.isMarkerable); + assert('Cannot pass non-markerable section to `changeSectionTagName`', section.isMarkerable) if (isListSectionTagName(newTagName)) { - return this._changeSectionToListItem(section, newTagName); + return this._changeSectionToListItem(section, newTagName) } else if (section.isListItem) { - return this._changeSectionFromListItem(section, newTagName); + return this._changeSectionFromListItem(section, newTagName) } else { - section.tagName = newTagName; - this._markDirty(section); - return section; + section.tagName = newTagName + this._markDirty(section) + return section } } @@ -921,24 +925,23 @@ class PostEditor { * @private */ _splitListItem(item, position) { - let { section, offset } = position; - assert('Cannot split list item at position that does not include item', - item === section); + let { section, offset } = position + assert('Cannot split list item at position that does not include item', item === section) - item.splitMarkerAtOffset(offset); - let prevMarker = item.markerBeforeOffset(offset); - let preItem = this.builder.createListItem(), - postItem = this.builder.createListItem(); + item.splitMarkerAtOffset(offset) + let prevMarker = item.markerBeforeOffset(offset) + let preItem = this.builder.createListItem(), + postItem = this.builder.createListItem() - let currentItem = preItem; + let currentItem = preItem item.markers.forEach(marker => { - currentItem.markers.append(marker.clone()); + currentItem.markers.append(marker.clone()) if (marker === prevMarker) { - currentItem = postItem; + currentItem = postItem } - }); - this._replaceSection(item, [preItem, postItem]); - return [preItem, postItem]; + }) + this._replaceSection(item, [preItem, postItem]) + return [preItem, postItem] } /** @@ -952,37 +955,35 @@ class PostEditor { * @private */ _splitListAtPosition(list, position) { - assert('Cannot split list at position not in list', - position.section.parent === list); + assert('Cannot split list at position not in list', position.section.parent === list) - let positionIsMiddle = !position.isHead() && !position.isTail(); + let positionIsMiddle = !position.isHead() && !position.isTail() if (positionIsMiddle) { - let item = position.section; - let [pre,] = - this._splitListItem(item, position); - position = pre.tailPosition(); + let item = position.section + let [pre] = this._splitListItem(item, position) + position = pre.tailPosition() } - let preList = this.builder.createListSection(list.tagName); - let postList = this.builder.createListSection(list.tagName); + let preList = this.builder.createListSection(list.tagName) + let postList = this.builder.createListSection(list.tagName) - let preItem = position.section; - let currentList = preList; + let preItem = position.section + let currentList = preList list.items.forEach(item => { // If this item matches the start item and the position is at its start, // it should be appended to the postList instead of the preList if (item === preItem && position.isEqual(item.headPosition())) { - currentList = postList; + currentList = postList } - currentList.items.append(item.clone()); + currentList.items.append(item.clone()) // If we just appended the preItem, append the remaining items to the postList if (item === preItem) { - currentList = postList; + currentList = postList } - }); + }) - this._replaceSection(list, [preList, postList]); - return [preList, postList]; + this._replaceSection(list, [preList, postList]) + return [preList, postList] } /** @@ -995,80 +996,78 @@ class PostEditor { * @private */ _splitListAtItem(list, item) { - let next = list; - let prev = this.builder.createListSection(next.tagName, [], next.attributes); - let mid = this.builder.createListSection(next.tagName); + let next = list + let prev = this.builder.createListSection(next.tagName, [], next.attributes) + let mid = this.builder.createListSection(next.tagName) - let addToPrev = true; + let addToPrev = true // must turn the LinkedList into an array so that we can remove items // as we iterate through it - let items = next.items.toArray(); + let items = next.items.toArray() items.forEach(i => { - let listToAppend; + let listToAppend if (i === item) { - addToPrev = false; - listToAppend = mid; + addToPrev = false + listToAppend = mid } else if (addToPrev) { - listToAppend = prev; + listToAppend = prev } else { - return; // break after iterating prev and mid parts of the list + return // break after iterating prev and mid parts of the list } - listToAppend.join(i); - this.removeSection(i); - }); - let found = !addToPrev; - assert('Cannot split list at item that is not present in the list', found); + listToAppend.join(i) + this.removeSection(i) + }) + let found = !addToPrev + assert('Cannot split list at item that is not present in the list', found) - let collection = this.editor.post.sections; - this.insertSectionBefore(collection, mid, next); - this.insertSectionBefore(collection, prev, mid); + let collection = this.editor.post.sections + this.insertSectionBefore(collection, mid, next) + this.insertSectionBefore(collection, prev, mid) // Remove possibly blank prev/next lists this.addCallback(CALLBACK_QUEUES.BEFORE_COMPLETE, () => { - [prev, next].forEach(_list => { - let isAttached = !!_list.parent; + ;[prev, next].forEach(_list => { + let isAttached = !!_list.parent if (_list.isBlank && isAttached) { - this.removeSection(_list); + this.removeSection(_list) } - }); - }); + }) + }) - return [prev, mid, next]; + return [prev, mid, next] } _changeSectionFromListItem(section, newTagName) { - assert('Must pass list item to `_changeSectionFromListItem`', - section.isListItem); + assert('Must pass list item to `_changeSectionFromListItem`', section.isListItem) - let listSection = section.parent; - let markupSection = this.builder.createMarkupSection(newTagName); - markupSection.join(section); + let listSection = section.parent + let markupSection = this.builder.createMarkupSection(newTagName) + markupSection.join(section) - let [, mid,] = this._splitListAtItem(listSection, section); - this.replaceSection(mid, markupSection); - return markupSection; + let [, mid] = this._splitListAtItem(listSection, section) + this.replaceSection(mid, markupSection) + return markupSection } _changeSectionToListItem(section, newTagName) { - let isAlreadyCorrectListItem = section.isListItem && - section.parent.tagName === newTagName; + let isAlreadyCorrectListItem = section.isListItem && section.parent.tagName === newTagName if (isAlreadyCorrectListItem) { - return section; + return section } - let listSection = this.builder.createListSection(newTagName); - listSection.join(section); + let listSection = this.builder.createListSection(newTagName) + listSection.join(section) - let sectionToReplace; + let sectionToReplace if (section.isListItem) { - let [, mid,] = this._splitListAtItem(section.parent, section); - sectionToReplace = mid; + let [, mid] = this._splitListAtItem(section.parent, section) + sectionToReplace = mid } else { - sectionToReplace = section; + sectionToReplace = section } - this.replaceSection(sectionToReplace, listSection); - return listSection; + this.replaceSection(sectionToReplace, listSection) + return listSection } /** @@ -1092,8 +1091,8 @@ class PostEditor { * @public */ insertSectionBefore(collection, section, beforeSection) { - collection.insertBefore(section, beforeSection); - this._markDirty(section.parent); + collection.insertBefore(section, beforeSection) + this._markDirty(section.parent) } /** @@ -1103,11 +1102,11 @@ class PostEditor { * @public */ insertSection(section) { - const activeSection = this.editor.activeSection; - const nextSection = activeSection && activeSection.next; + const activeSection = this.editor.activeSection + const nextSection = activeSection && activeSection.next - const collection = this.editor.post.sections; - this.insertSectionBefore(collection, section, nextSection); + const collection = this.editor.post.sections + this.insertSectionBefore(collection, section, nextSection) } /** @@ -1116,7 +1115,7 @@ class PostEditor { * @public */ insertSectionAtEnd(section) { - this.insertSectionBefore(this.editor.post.sections, section, null); + this.insertSectionBefore(this.editor.post.sections, section, null) } /** @@ -1126,10 +1125,10 @@ class PostEditor { * @private */ insertPost(position, newPost) { - let post = this.editor.post; - let inserter = new PostInserter(this, post); - let nextPosition = inserter.insert(position, newPost); - return nextPosition; + let post = this.editor.post + let inserter = new PostInserter(this, post) + let nextPosition = inserter.insert(position, newPost) + return nextPosition } /** @@ -1147,37 +1146,37 @@ class PostEditor { * @public */ removeSection(section) { - let parent = section.parent; - this._scheduleForRemoval(section); - parent.sections.remove(section); + let parent = section.parent + this._scheduleForRemoval(section) + parent.sections.remove(section) if (parent.isListSection) { - this._scheduleListRemovalIfEmpty(parent); + this._scheduleListRemovalIfEmpty(parent) } } removeAllSections() { this.editor.post.sections.toArray().forEach(section => { - this.removeSection(section); - }); + this.removeSection(section) + }) } migrateSectionsFromPost(post) { post.sections.toArray().forEach(section => { - post.sections.remove(section); - this.insertSectionBefore(this.editor.post.sections, section, null); - }); + post.sections.remove(section) + this.insertSectionBefore(this.editor.post.sections, section, null) + }) } _scheduleListRemovalIfEmpty(listSection) { this.addCallback(CALLBACK_QUEUES.BEFORE_COMPLETE, () => { // if the list is attached and blank after we do other rendering stuff, // remove it - let isAttached = !!listSection.parent; + let isAttached = !!listSection.parent if (isAttached && listSection.isBlank) { - this.removeSection(listSection); + this.removeSection(listSection) } - }); + }) } /** @@ -1187,13 +1186,12 @@ class PostEditor { * @param {Boolean} [once=false] Whether to only schedule the callback once. * @public */ - schedule(callback, once=false) { - assert('Work can only be scheduled before a post edit has completed', - !this._didComplete); + schedule(callback, once = false) { + assert('Work can only be scheduled before a post edit has completed', !this._didComplete) if (once) { - this.addCallbackOnce(CALLBACK_QUEUES.COMPLETE, callback); + this.addCallbackOnce(CALLBACK_QUEUES.COMPLETE, callback) } else { - this.addCallback(CALLBACK_QUEUES.COMPLETE, callback); + this.addCallback(CALLBACK_QUEUES.COMPLETE, callback) } } @@ -1206,7 +1204,7 @@ class PostEditor { * @public */ scheduleOnce(callback) { - this.schedule(callback, true); + this.schedule(callback, true) } /** @@ -1215,7 +1213,7 @@ class PostEditor { * @public */ scheduleRerender() { - this.scheduleOnce(this._rerender); + this.scheduleOnce(this._rerender) } /** @@ -1226,14 +1224,14 @@ class PostEditor { * @public */ scheduleDidUpdate() { - this.scheduleOnce(this._postDidChange); + this.scheduleOnce(this._postDidChange) } - scheduleAfterRender(callback, once=false) { + scheduleAfterRender(callback, once = false) { if (once) { - this.addCallbackOnce(CALLBACK_QUEUES.AFTER_COMPLETE, callback); + this.addCallbackOnce(CALLBACK_QUEUES.AFTER_COMPLETE, callback) } else { - this.addCallback(CALLBACK_QUEUES.AFTER_COMPLETE, callback); + this.addCallback(CALLBACK_QUEUES.AFTER_COMPLETE, callback) } } @@ -1244,25 +1242,25 @@ class PostEditor { * @private */ complete() { - assert('Post editing can only be completed once', !this._didComplete); + assert('Post editing can only be completed once', !this._didComplete) - this.runCallbacks(CALLBACK_QUEUES.BEFORE_COMPLETE); - this._didComplete = true; - this.runCallbacks(CALLBACK_QUEUES.COMPLETE); - this.runCallbacks(CALLBACK_QUEUES.AFTER_COMPLETE); + this.runCallbacks(CALLBACK_QUEUES.BEFORE_COMPLETE) + this._didComplete = true + this.runCallbacks(CALLBACK_QUEUES.COMPLETE) + this.runCallbacks(CALLBACK_QUEUES.AFTER_COMPLETE) } undoLastChange() { - this.editor._editHistory.stepBackward(this); + this.editor._editHistory.stepBackward(this) } redoLastChange() { - this.editor._editHistory.stepForward(this); + this.editor._editHistory.stepForward(this) } cancelSnapshot() { - this._shouldCancelSnapshot = true; + this._shouldCancelSnapshot = true } } -export default PostEditor; +export default PostEditor diff --git a/src/js/editor/post/post-inserter.js b/src/js/editor/post/post-inserter.js index c3b55583b..46bce5489 100644 --- a/src/js/editor/post/post-inserter.js +++ b/src/js/editor/post/post-inserter.js @@ -1,4 +1,4 @@ -import assert from 'mobiledoc-kit/utils/assert'; +import assert from 'mobiledoc-kit/utils/assert' import { MARKUP_SECTION_TYPE, LIST_SECTION_TYPE, @@ -6,196 +6,194 @@ import { CARD_TYPE, IMAGE_SECTION_TYPE, LIST_ITEM_TYPE, -} from 'mobiledoc-kit/models/types'; +} from 'mobiledoc-kit/models/types' const MARKERABLE = 'markerable', - NESTED_MARKERABLE = 'nested_markerable', - NON_MARKERABLE = 'non_markerable'; + NESTED_MARKERABLE = 'nested_markerable', + NON_MARKERABLE = 'non_markerable' class Visitor { constructor(inserter, cursorPosition) { - let { postEditor, post } = inserter; - this.postEditor = postEditor; - this._post = post; - this.cursorPosition = cursorPosition; - this.builder = this.postEditor.builder; + let { postEditor, post } = inserter + this.postEditor = postEditor + this._post = post + this.cursorPosition = cursorPosition + this.builder = this.postEditor.builder - this._hasInsertedFirstLeafSection = false; + this._hasInsertedFirstLeafSection = false } get cursorPosition() { - return this._cursorPosition; + return this._cursorPosition } set cursorPosition(position) { - this._cursorPosition = position; - this.postEditor.setRange(position); + this._cursorPosition = position + this.postEditor.setRange(position) } visit(node) { - let method = node.type; - assert(`Cannot visit node of type ${node.type}`, !!this[method]); - this[method](node); + let method = node.type + assert(`Cannot visit node of type ${node.type}`, !!this[method]) + this[method](node) } _canMergeSection(section) { if (this._hasInsertedFirstLeafSection) { - return false; + return false } else { - return this._isMarkerable && section.isMarkerable; + return this._isMarkerable && section.isMarkerable } } get _isMarkerable() { - return this.cursorSection.isMarkerable; + return this.cursorSection.isMarkerable } get cursorSection() { - return this.cursorPosition.section; + return this.cursorPosition.section } get cursorOffset() { - return this.cursorPosition.offset; + return this.cursorPosition.offset } get _isNested() { - return this.cursorSection.isNested; + return this.cursorSection.isNested } [POST_TYPE](node) { if (this.cursorSection.isBlank && !this._isNested) { // replace blank section with entire post - let newSections = node.sections.map(s => s.clone()); - this._replaceSection(this.cursorSection, newSections); + let newSections = node.sections.map(s => s.clone()) + this._replaceSection(this.cursorSection, newSections) } else { - node.sections.forEach(section => this.visit(section)); + node.sections.forEach(section => this.visit(section)) } } [MARKUP_SECTION_TYPE](node) { - this[MARKERABLE](node); + this[MARKERABLE](node) } [LIST_SECTION_TYPE](node) { - let hasNext = !!node.next; - node.items.forEach(item => this.visit(item)); + let hasNext = !!node.next + node.items.forEach(item => this.visit(item)) if (this._isNested && hasNext) { - this._breakNestedAtCursor(); + this._breakNestedAtCursor() } } [LIST_ITEM_TYPE](node) { - this[NESTED_MARKERABLE](node); + this[NESTED_MARKERABLE](node) } [CARD_TYPE](node) { - this[NON_MARKERABLE](node); + this[NON_MARKERABLE](node) } [IMAGE_SECTION_TYPE](node) { - this[NON_MARKERABLE](node); + this[NON_MARKERABLE](node) } [NON_MARKERABLE](section) { if (this._isNested) { - this._breakNestedAtCursor(); + this._breakNestedAtCursor() } else if (!this.cursorSection.isBlank) { - this._breakAtCursor(); + this._breakAtCursor() } - this._insertLeafSection(section); + this._insertLeafSection(section) } [MARKERABLE](section) { if (this._canMergeSection(section)) { - this._mergeSection(section); + this._mergeSection(section) } else if (this._isNested && this._isMarkerable) { // If we are attaching a markerable section to a list item, // insert a linebreak then merge the section onto the resulting blank list item - this._breakAtCursor(); + this._breakAtCursor() // Advance the cursor to the head of the blank list item - let nextPosition = this.cursorSection.next.headPosition(); - this.cursorPosition = nextPosition; + let nextPosition = this.cursorSection.next.headPosition() + this.cursorPosition = nextPosition // Merge this section onto the list item - this._mergeSection(section); + this._mergeSection(section) } else { - this._breakAtCursor(); - this._insertLeafSection(section); + this._breakAtCursor() + this._insertLeafSection(section) } } [NESTED_MARKERABLE](section) { if (this._canMergeSection(section)) { - this._mergeSection(section); - return; + this._mergeSection(section) + return } - section = this._isNested ? section : this._wrapNestedSection(section); - this._breakAtCursor(); - this._insertLeafSection(section); + section = this._isNested ? section : this._wrapNestedSection(section) + this._breakAtCursor() + this._insertLeafSection(section) } // break out of a nested cursor position _breakNestedAtCursor() { - assert('Cannot call _breakNestedAtCursor if not nested', this._isNested); + assert('Cannot call _breakNestedAtCursor if not nested', this._isNested) - let parent = this.cursorSection.parent; - let cursorAtEndOfList = this.cursorPosition.isEqual(parent.tailPosition()); + let parent = this.cursorSection.parent + let cursorAtEndOfList = this.cursorPosition.isEqual(parent.tailPosition()) if (cursorAtEndOfList) { - let blank = this.builder.createMarkupSection(); - this._insertSectionAfter(blank, parent); + let blank = this.builder.createMarkupSection() + this._insertSectionAfter(blank, parent) } else { - let [, blank,] = this._breakListAtCursor(); - this.cursorPosition = blank.tailPosition(); + let [, blank] = this._breakListAtCursor() + this.cursorPosition = blank.tailPosition() } } _breakListAtCursor() { - assert('Cannot _splitParentSection if cursor position is not nested', - this._isNested); + assert('Cannot _splitParentSection if cursor position is not nested', this._isNested) - let list = this.cursorSection.parent, - position = this.cursorPosition, - blank = this.builder.createMarkupSection(); - let [pre, post] = this.postEditor._splitListAtPosition(list, position); + let list = this.cursorSection.parent, + position = this.cursorPosition, + blank = this.builder.createMarkupSection() + let [pre, post] = this.postEditor._splitListAtPosition(list, position) let collection = this._post.sections, - reference = post; - this.postEditor.insertSectionBefore(collection, blank, reference); - return [pre, blank, post]; + reference = post + this.postEditor.insertSectionBefore(collection, blank, reference) + return [pre, blank, post] } _wrapNestedSection(section) { - let tagName = section.parent.tagName; - let parent = this.builder.createListSection(tagName); - parent.items.append(section.clone()); - return parent; + let tagName = section.parent.tagName + let parent = this.builder.createListSection(tagName) + parent.items.append(section.clone()) + return parent } _mergeSection(section) { - assert('Can only merge markerable sections', - this._isMarkerable && section.isMarkerable); - this._hasInsertedFirstLeafSection = true; + assert('Can only merge markerable sections', this._isMarkerable && section.isMarkerable) + this._hasInsertedFirstLeafSection = true - let markers = section.markers.map(m => m.clone()); - let position = this.postEditor.insertMarkers(this.cursorPosition, markers); + let markers = section.markers.map(m => m.clone()) + let position = this.postEditor.insertMarkers(this.cursorPosition, markers) - this.cursorPosition = position; + this.cursorPosition = position } // Can be called to add a line break when in a nested section or a parent // section. _breakAtCursor() { if (this.cursorSection.isBlank) { - return; + return } else if (this._isMarkerable) { - this._breakMarkerableAtCursor(); + this._breakMarkerableAtCursor() } else { - this._breakNonMarkerableAtCursor(); + this._breakNonMarkerableAtCursor() } } @@ -203,84 +201,82 @@ class Visitor { // depending on cursor position. _breakNonMarkerableAtCursor() { let collection = this._post.sections, - blank = this.builder.createMarkupSection(), - reference = this.cursorPosition.isHead() ? this.cursorSection : - this.cursorSection.next; - this.postEditor.insertSectionBefore(collection, blank, reference); - this.cursorPosition = blank.tailPosition(); + blank = this.builder.createMarkupSection(), + reference = this.cursorPosition.isHead() ? this.cursorSection : this.cursorSection.next + this.postEditor.insertSectionBefore(collection, blank, reference) + this.cursorPosition = blank.tailPosition() } _breakMarkerableAtCursor() { - let [pre,] = - this.postEditor.splitSection(this.cursorPosition); + let [pre] = this.postEditor.splitSection(this.cursorPosition) - this.cursorPosition = pre.tailPosition(); + this.cursorPosition = pre.tailPosition() } _replaceSection(section, newSections) { - assert('Cannot replace section that does not have parent.sections', - section.parent && section.parent.sections); - assert('Must pass enumerable to _replaceSection', !!newSections.forEach); + assert('Cannot replace section that does not have parent.sections', section.parent && section.parent.sections) + assert('Must pass enumerable to _replaceSection', !!newSections.forEach) - let collection = section.parent.sections; - let reference = section.next; - this.postEditor.removeSection(section); + let collection = section.parent.sections + let reference = section.next + this.postEditor.removeSection(section) newSections.forEach(section => { - this.postEditor.insertSectionBefore(collection, section, reference); - }); - let lastSection = newSections[newSections.length - 1]; + this.postEditor.insertSectionBefore(collection, section, reference) + }) + let lastSection = newSections[newSections.length - 1] - this.cursorPosition = lastSection.tailPosition(); + this.cursorPosition = lastSection.tailPosition() } _insertSectionBefore(section, reference) { - let collection = this.cursorSection.parent.sections; - this.postEditor.insertSectionBefore(collection, section, reference); + let collection = this.cursorSection.parent.sections + this.postEditor.insertSectionBefore(collection, section, reference) - this.cursorPosition = section.tailPosition(); + this.cursorPosition = section.tailPosition() } // Insert a section after the parent section. // E.g., add a markup section after a list section _insertSectionAfter(section, parent) { - assert('Cannot _insertSectionAfter nested section', !parent.isNested); - let reference = parent.next; - let collection = this._post.sections; - this.postEditor.insertSectionBefore(collection, section, reference); - this.cursorPosition = section.tailPosition(); + assert('Cannot _insertSectionAfter nested section', !parent.isNested) + let reference = parent.next + let collection = this._post.sections + this.postEditor.insertSectionBefore(collection, section, reference) + this.cursorPosition = section.tailPosition() } _insertLeafSection(section) { - assert('Can only _insertLeafSection when cursor is at end of section', - this.cursorPosition.isTail()); + assert('Can only _insertLeafSection when cursor is at end of section', this.cursorPosition.isTail()) - this._hasInsertedFirstLeafSection = true; - section = section.clone(); + this._hasInsertedFirstLeafSection = true + section = section.clone() if (this.cursorSection.isBlank) { - assert('Cannot insert leaf non-markerable section when cursor is nested', - !(section.isMarkerable && this._isNested)); - this._replaceSection(this.cursorSection, [section]); + assert( + 'Cannot insert leaf non-markerable section when cursor is nested', + !(section.isMarkerable && this._isNested) + ) + this._replaceSection(this.cursorSection, [section]) } else if (this.cursorSection.next && this.cursorSection.next.isBlank) { - this._replaceSection(this.cursorSection.next, [section]); + this._replaceSection(this.cursorSection.next, [section]) } else { - let reference = this.cursorSection.next; - this._insertSectionBefore(section, reference); + let reference = this.cursorSection.next + this._insertSectionBefore(section, reference) } } } export default class Inserter { constructor(postEditor, post) { - this.postEditor = postEditor; - this.post = post; + this.postEditor = postEditor + this.post = post } insert(cursorPosition, newPost) { - let visitor = new Visitor(this, cursorPosition); + let visitor = new Visitor(this, cursorPosition) if (!newPost.isBlank) { - visitor.visit(newPost); + visitor.visit(newPost) } - return visitor.cursorPosition; + return visitor.cursorPosition } } diff --git a/src/js/editor/selection-change-observer.js b/src/js/editor/selection-change-observer.js index 8c37411f4..c48a74538 100644 --- a/src/js/editor/selection-change-observer.js +++ b/src/js/editor/selection-change-observer.js @@ -1,99 +1,103 @@ -let instance; +let instance class SelectionChangeObserver { constructor() { - this.started = false; - this.listeners = []; - this.selection = {}; + this.started = false + this.listeners = [] + this.selection = {} } static getInstance() { if (!instance) { - instance = new SelectionChangeObserver(); + instance = new SelectionChangeObserver() } - return instance; + return instance } static addListener(listener) { - SelectionChangeObserver.getInstance().addListener(listener); + SelectionChangeObserver.getInstance().addListener(listener) } addListener(listener) { if (this.listeners.indexOf(listener) === -1) { - this.listeners.push(listener); - this.start(); + this.listeners.push(listener) + this.start() } } static removeListener(listener) { - SelectionChangeObserver.getInstance().removeListener(listener); + SelectionChangeObserver.getInstance().removeListener(listener) } removeListener(listener) { - let index = this.listeners.indexOf(listener); + let index = this.listeners.indexOf(listener) if (index !== -1) { - this.listeners.splice(index, 1); + this.listeners.splice(index, 1) if (this.listeners.length === 0) { - this.stop(); + this.stop() } } } start() { - if (this.started) { return; } - this.started = true; + if (this.started) { + return + } + this.started = true - this.poll(); + this.poll() } stop() { - this.started = false; - this.selection = {}; + this.started = false + this.selection = {} } notifyListeners(/* newSelection, prevSelection */) { this.listeners.forEach(listener => { - listener.selectionDidChange(...arguments); - }); + listener.selectionDidChange(...arguments) + }) } destroy() { - this.stop(); - this.listeners = []; + this.stop() + this.listeners = [] } getSelection() { - let selection = window.getSelection(); - let { anchorNode, focusNode, anchorOffset, focusOffset } = selection; - return { anchorNode, focusNode, anchorOffset, focusOffset }; + let selection = window.getSelection() + let { anchorNode, focusNode, anchorOffset, focusOffset } = selection + return { anchorNode, focusNode, anchorOffset, focusOffset } } poll() { if (this.started) { - this.update(); - this.runNext(() => this.poll()); + this.update() + this.runNext(() => this.poll()) } } runNext(fn) { - window.requestAnimationFrame(fn); + window.requestAnimationFrame(fn) } update() { - let prevSelection = this.selection; - let curSelection = this.getSelection(); + let prevSelection = this.selection + let curSelection = this.getSelection() if (!this.selectionIsEqual(prevSelection, curSelection)) { - this.selection = curSelection; - this.notifyListeners(curSelection, prevSelection); + this.selection = curSelection + this.notifyListeners(curSelection, prevSelection) } } selectionIsEqual(s1, s2) { - return s1.anchorNode === s2.anchorNode && + return ( + s1.anchorNode === s2.anchorNode && s1.anchorOffset === s2.anchorOffset && s1.focusNode === s2.focusNode && - s1.focusOffset === s2.focusOffset; + s1.focusOffset === s2.focusOffset + ) } } -export default SelectionChangeObserver; +export default SelectionChangeObserver diff --git a/src/js/editor/selection-manager.js b/src/js/editor/selection-manager.js index 72a8beb2d..6bd80febe 100644 --- a/src/js/editor/selection-manager.js +++ b/src/js/editor/selection-manager.js @@ -1,31 +1,33 @@ -import SelectionChangeObserver from 'mobiledoc-kit/editor/selection-change-observer'; +import SelectionChangeObserver from 'mobiledoc-kit/editor/selection-change-observer' export default class SelectionManager { constructor(editor, callback) { - this.editor = editor; - this.callback = callback; - this.started = false; + this.editor = editor + this.callback = callback + this.started = false } start() { - if (this.started) { return; } + if (this.started) { + return + } - SelectionChangeObserver.addListener(this); - this.started = true; + SelectionChangeObserver.addListener(this) + this.started = true } stop() { - this.started = false; - SelectionChangeObserver.removeListener(this); + this.started = false + SelectionChangeObserver.removeListener(this) } destroy() { - this.stop(); + this.stop() } selectionDidChange() { if (this.started) { - this.callback(...arguments); + this.callback(...arguments) } } } diff --git a/src/js/editor/text-input-handler.js b/src/js/editor/text-input-handler.js index 535155157..ed2888e9b 100644 --- a/src/js/editor/text-input-handler.js +++ b/src/js/editor/text-input-handler.js @@ -1,76 +1,85 @@ -import { endsWith } from 'mobiledoc-kit/utils/string-utils'; -import assert from 'mobiledoc-kit/utils/assert'; -import deprecate from 'mobiledoc-kit/utils/deprecate'; -import { ENTER } from 'mobiledoc-kit/utils/characters'; +import { endsWith } from 'mobiledoc-kit/utils/string-utils' +import assert from 'mobiledoc-kit/utils/assert' +import deprecate from 'mobiledoc-kit/utils/deprecate' +import { ENTER } from 'mobiledoc-kit/utils/characters' class TextInputHandler { constructor(editor) { - this.editor = editor; - this._handlers = []; + this.editor = editor + this._handlers = [] } register(handler) { - assert(`Input Handler is not valid`, this._validateHandler(handler)); - this._handlers.push(handler); + assert(`Input Handler is not valid`, this._validateHandler(handler)) + this._handlers.push(handler) } unregister(name) { - let handlers = this._handlers; - for (let i=0; i { - let { builder } = postEditor; - let item = builder.createListItem(); - let listSection = builder.createListSection(listTagName, [item]); + let { builder } = postEditor + let item = builder.createListItem() + let listSection = builder.createListSection(listTagName, [item]) - postEditor.replaceSection(section, listSection); - postEditor.setRange(listSection.headPosition()); - }); + postEditor.replaceSection(section, listSection) + postEditor.setRange(listSection.headPosition()) + }) } /** @@ -37,18 +42,23 @@ export function replaceWithListSection(editor, listTagName) { * @public */ export function replaceWithHeaderSection(editor, headingTagName) { - let { range: { head, head: { section } } } = editor; + let { + range: { + head, + head: { section }, + }, + } = editor // Skip if cursor is not at end of section if (!head.isTail()) { - return; + return } editor.run(postEditor => { - let { builder } = postEditor; - let newSection = builder.createMarkupSection(headingTagName); - postEditor.replaceSection(section, newSection); - postEditor.setRange(newSection.headPosition()); - }); + let { builder } = postEditor + let newSection = builder.createMarkupSection(headingTagName) + postEditor.replaceSection(section, newSection) + postEditor.setRange(newSection.headPosition()) + }) } export const DEFAULT_TEXT_INPUT_HANDLERS = [ @@ -57,16 +67,16 @@ export const DEFAULT_TEXT_INPUT_HANDLERS = [ // "* " -> ul match: /^\* $/, run(editor) { - replaceWithListSection(editor, 'ul'); - } + replaceWithListSection(editor, 'ul') + }, }, { name: 'ol', // "1" -> ol, "1." -> ol match: /^1\.? $/, run(editor) { - replaceWithListSection(editor, 'ol'); - } + replaceWithListSection(editor, 'ol') + }, }, { name: 'heading', @@ -80,9 +90,9 @@ export const DEFAULT_TEXT_INPUT_HANDLERS = [ */ match: /^(#{1,6}) $/, run(editor, matches) { - let capture = matches[1]; - let headingTag = 'h' + capture.length; - replaceWithHeaderSection(editor, headingTag); - } - } -]; + let capture = matches[1] + let headingTag = 'h' + capture.length + replaceWithHeaderSection(editor, headingTag) + }, + }, +] diff --git a/src/js/editor/ui.js b/src/js/editor/ui.js index 06aa69d88..97099031b 100644 --- a/src/js/editor/ui.js +++ b/src/js/editor/ui.js @@ -2,10 +2,10 @@ * @module UI */ -import Position from '../utils/cursor/position'; -import Range from '../utils/cursor/range'; +import Position from '../utils/cursor/position' +import Range from '../utils/cursor/range' -let defaultShowPrompt = (message, defaultValue, callback) => callback(window.prompt(message, defaultValue)); +let defaultShowPrompt = (message, defaultValue, callback) => callback(window.prompt(message, defaultValue)) /** * @callback promptCallback @@ -44,26 +44,30 @@ let defaultShowPrompt = (message, defaultValue, callback) => callback(window.pro * }); * @public */ -export function toggleLink(editor, showPrompt=defaultShowPrompt) { +export function toggleLink(editor, showPrompt = defaultShowPrompt) { if (editor.range.isCollapsed) { - return; + return } - let selectedText = editor.cursor.selectedText(); - let defaultUrl = ''; - if (selectedText.indexOf('http') !== -1) { defaultUrl = selectedText; } + let selectedText = editor.cursor.selectedText() + let defaultUrl = '' + if (selectedText.indexOf('http') !== -1) { + defaultUrl = selectedText + } - let {range} = editor; - let hasLink = editor.detectMarkupInRange(range, 'a'); + let { range } = editor + let hasLink = editor.detectMarkupInRange(range, 'a') if (hasLink) { - editor.toggleMarkup('a'); + editor.toggleMarkup('a') } else { showPrompt('Enter a URL', defaultUrl, url => { - if (!url) { return; } + if (!url) { + return + } - editor.toggleMarkup('a', {href: url}); - }); + editor.toggleMarkup('a', { href: url }) + }) } } @@ -79,25 +83,27 @@ export function toggleLink(editor, showPrompt=defaultShowPrompt) { * * @public */ -export function editLink(target, editor, showPrompt=defaultShowPrompt) { +export function editLink(target, editor, showPrompt = defaultShowPrompt) { showPrompt('Enter a URL', target.href, url => { - if (!url) { return; } + if (!url) { + return + } - const position = Position.fromNode(editor._renderTree, target.firstChild); - const range = new Range(position, new Position(position.section, position.offset + target.textContent.length)); + const position = Position.fromNode(editor._renderTree, target.firstChild) + const range = new Range(position, new Position(position.section, position.offset + target.textContent.length)) editor.run(post => { - let markup = editor.builder.createMarkup('a', {href: url}); + let markup = editor.builder.createMarkup('a', { href: url }) // This is the only way to "update" a markup with new attributes in the // current API. - post.toggleMarkup(markup, range); - post.toggleMarkup(markup, range); - }); - }); + post.toggleMarkup(markup, range) + post.toggleMarkup(markup, range) + }) + }) } export default { toggleLink, - editLink -}; + editLink, +} diff --git a/src/js/index.js b/src/js/index.js index 404201f10..05f4d0d60 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -1,10 +1,10 @@ -import Editor from './editor/editor'; -import ImageCard from './cards/image'; -import UI from './editor/ui'; -import Range from './utils/cursor/range'; -import Position from './utils/cursor/position'; -import Error from './utils/mobiledoc-error'; -import VERSION from './version'; -import { MOBILEDOC_VERSION } from './renderers/mobiledoc'; +import Editor from './editor/editor' +import ImageCard from './cards/image' +import UI from './editor/ui' +import Range from './utils/cursor/range' +import Position from './utils/cursor/position' +import Error from './utils/mobiledoc-error' +import VERSION from './version' +import { MOBILEDOC_VERSION } from './renderers/mobiledoc' -export { Editor, UI, ImageCard, Range, Position, Error, VERSION, MOBILEDOC_VERSION }; +export { Editor, UI, ImageCard, Range, Position, Error, VERSION, MOBILEDOC_VERSION } diff --git a/src/js/models/_attributable.js b/src/js/models/_attributable.js index 459fd18ca..b1adc8c13 100644 --- a/src/js/models/_attributable.js +++ b/src/js/models/_attributable.js @@ -1,30 +1,28 @@ -import { entries } from '../utils/object-utils'; -import { contains } from '../utils/array-utils'; +import { entries } from '../utils/object-utils' +import { contains } from '../utils/array-utils' -export const VALID_ATTRIBUTES = [ - 'data-md-text-align' -]; +export const VALID_ATTRIBUTES = ['data-md-text-align'] /* * A "mixin" to add section attribute support * to markup and list sections. */ export function attributable(ctx) { - ctx.attributes = {}; + ctx.attributes = {} - ctx.hasAttribute = key => key in ctx.attributes; + ctx.hasAttribute = key => key in ctx.attributes ctx.setAttribute = (key, value) => { if (!contains(VALID_ATTRIBUTES, key)) { - throw new Error(`Invalid attribute "${key}" was passed. Constrain attributes to the spec-compliant whitelist.`); + throw new Error(`Invalid attribute "${key}" was passed. Constrain attributes to the spec-compliant whitelist.`) } - ctx.attributes[key] = value; - }; + ctx.attributes[key] = value + } ctx.removeAttribute = key => { - delete ctx.attributes[key]; - }; - ctx.getAttribute = key => ctx.attributes[key]; + delete ctx.attributes[key] + } + ctx.getAttribute = key => ctx.attributes[key] ctx.eachAttribute = cb => { - entries(ctx.attributes).forEach(([k,v]) => cb(k,v)); - }; + entries(ctx.attributes).forEach(([k, v]) => cb(k, v)) + } } diff --git a/src/js/models/_markerable.js b/src/js/models/_markerable.js index 49104a774..c89cab809 100644 --- a/src/js/models/_markerable.js +++ b/src/js/models/_markerable.js @@ -1,61 +1,57 @@ -import { forEach, reduce } from '../utils/array-utils'; -import Set from '../utils/set'; +import { forEach, reduce } from '../utils/array-utils' +import Set from '../utils/set' -import LinkedList from '../utils/linked-list'; -import Section from './_section'; -import assert from '../utils/assert'; +import LinkedList from '../utils/linked-list' +import Section from './_section' +import assert from '../utils/assert' export default class Markerable extends Section { - constructor(type, tagName, markers=[]) { - super(type); - this.isMarkerable = true; - this.tagName = tagName; + constructor(type, tagName, markers = []) { + super(type) + this.isMarkerable = true + this.tagName = tagName this.markers = new LinkedList({ adoptItem: m => { - assert(`Can only insert markers and atoms into markerable (was: ${m.type})`, - m.isMarker || m.isAtom); - m.section = m.parent = this; + assert(`Can only insert markers and atoms into markerable (was: ${m.type})`, m.isMarker || m.isAtom) + m.section = m.parent = this }, - freeItem: m => m.section = m.parent = null - }); + freeItem: m => (m.section = m.parent = null), + }) - markers.forEach(m => this.markers.append(m)); + markers.forEach(m => this.markers.append(m)) } canJoin(other) { - return other.isMarkerable && - other.type === this.type && - other.tagName === this.tagName; + return other.isMarkerable && other.type === this.type && other.tagName === this.tagName } clone() { - const newMarkers = this.markers.map(m => m.clone()); - return this.builder.createMarkerableSection( - this.type, this.tagName, newMarkers); + const newMarkers = this.markers.map(m => m.clone()) + return this.builder.createMarkerableSection(this.type, this.tagName, newMarkers) } get isBlank() { if (!this.markers.length) { - return true; + return true } - return this.markers.every(m => m.isBlank); + return this.markers.every(m => m.isBlank) } textUntil(position) { - assert(`Cannot get textUntil for a position not in this section`, position.section === this); - let {marker, offsetInMarker} = position; - let text = ''; - let currentMarker = this.markers.head; + assert(`Cannot get textUntil for a position not in this section`, position.section === this) + let { marker, offsetInMarker } = position + let text = '' + let currentMarker = this.markers.head while (currentMarker) { if (currentMarker === marker) { - text += currentMarker.textUntil(offsetInMarker); - break; + text += currentMarker.textUntil(offsetInMarker) + break } else { - text += currentMarker.text; - currentMarker = currentMarker.next; + text += currentMarker.text + currentMarker = currentMarker.next } } - return text; + return text } /** @@ -64,47 +60,45 @@ export default class Markerable extends Section { * * @return {Number} The offset relative to the start of this section */ - offsetOfMarker(marker, markerOffset=0) { - assert(`Cannot get offsetOfMarker for marker that is not child of this`, - marker.section === this); + offsetOfMarker(marker, markerOffset = 0) { + assert(`Cannot get offsetOfMarker for marker that is not child of this`, marker.section === this) // FIXME it is possible, when we get a cursor position before having finished reparsing, // for markerOffset to be > marker.length. We shouldn't rely on this functionality. - let offset = 0; - let currentMarker = this.markers.head; + let offset = 0 + let currentMarker = this.markers.head while (currentMarker && currentMarker !== marker.next) { - let length = currentMarker === marker ? markerOffset : - currentMarker.length; - offset += length; - currentMarker = currentMarker.next; + let length = currentMarker === marker ? markerOffset : currentMarker.length + offset += length + currentMarker = currentMarker.next } - return offset; + return offset } // puts clones of this.markers into beforeSection and afterSection, // all markers before the marker/offset split go in beforeSection, and all // after the marker/offset split go in afterSection // @return {Array} [beforeSection, afterSection], two new sections - _redistributeMarkers(beforeSection, afterSection, marker, offset=0) { - let currentSection = beforeSection; + _redistributeMarkers(beforeSection, afterSection, marker, offset = 0) { + let currentSection = beforeSection forEach(this.markers, m => { if (m === marker) { - const [beforeMarker, ...afterMarkers] = marker.split(offset); - beforeSection.markers.append(beforeMarker); - forEach(afterMarkers, _m => afterSection.markers.append(_m)); - currentSection = afterSection; + const [beforeMarker, ...afterMarkers] = marker.split(offset) + beforeSection.markers.append(beforeMarker) + forEach(afterMarkers, _m => afterSection.markers.append(_m)) + currentSection = afterSection } else { - currentSection.markers.append(m.clone()); + currentSection.markers.append(m.clone()) } - }); + }) - return [beforeSection, afterSection]; + return [beforeSection, afterSection] } splitAtMarker(/*marker, offset=0*/) { - assert('splitAtMarker must be implemented by sub-class', false); + assert('splitAtMarker must be implemented by sub-class', false) } /** @@ -117,85 +111,83 @@ export default class Markerable extends Section { * result returned from `markerBeforeOffset(offset)`. */ splitMarkerAtOffset(sectionOffset) { - assert('Cannot splitMarkerAtOffset when offset is > length', - sectionOffset <= this.length); - let markerOffset; - let len = 0; - let currentMarker = this.markers.head; - let edit = {added: [], removed: []}; + assert('Cannot splitMarkerAtOffset when offset is > length', sectionOffset <= this.length) + let markerOffset + let len = 0 + let currentMarker = this.markers.head + let edit = { added: [], removed: [] } if (!currentMarker) { - let blankMarker = this.builder.createMarker(); - this.markers.prepend(blankMarker); - edit.added.push(blankMarker); + let blankMarker = this.builder.createMarker() + this.markers.prepend(blankMarker) + edit.added.push(blankMarker) } else { while (currentMarker) { - len += currentMarker.length; + len += currentMarker.length if (len === sectionOffset) { // nothing to do, there is a gap at the requested offset - break; + break } else if (len > sectionOffset) { - markerOffset = currentMarker.length - (len - sectionOffset); - let newMarkers = currentMarker.splitAtOffset(markerOffset); - edit.added.push(...newMarkers); - edit.removed.push(currentMarker); - this.markers.splice(currentMarker, 1, newMarkers); - break; + markerOffset = currentMarker.length - (len - sectionOffset) + let newMarkers = currentMarker.splitAtOffset(markerOffset) + edit.added.push(...newMarkers) + edit.removed.push(currentMarker) + this.markers.splice(currentMarker, 1, newMarkers) + break } else { - currentMarker = currentMarker.next; + currentMarker = currentMarker.next } } } - return edit; + return edit } splitAtPosition(position) { - const {marker, offsetInMarker} = position; - return this.splitAtMarker(marker, offsetInMarker); + const { marker, offsetInMarker } = position + return this.splitAtMarker(marker, offsetInMarker) } // returns the marker just before this offset. // It is an error to call this method with an offset that is in the middle // of a marker. markerBeforeOffset(sectionOffset) { - let len = 0; - let currentMarker = this.markers.head; + let len = 0 + let currentMarker = this.markers.head while (currentMarker) { - len += currentMarker.length; + len += currentMarker.length if (len === sectionOffset) { - return currentMarker; + return currentMarker } else { - assert('markerBeforeOffset called with sectionOffset not between markers', - len < sectionOffset); - currentMarker = currentMarker.next; + assert('markerBeforeOffset called with sectionOffset not between markers', len < sectionOffset) + currentMarker = currentMarker.next } } } markerPositionAtOffset(offset) { - let currentOffset = 0; - let currentMarker; - let remaining = offset; - this.markers.detect((marker) => { - currentOffset = Math.min(remaining, marker.length); - remaining -= currentOffset; + let currentOffset = 0 + let currentMarker + let remaining = offset + this.markers.detect(marker => { + currentOffset = Math.min(remaining, marker.length) + remaining -= currentOffset if (remaining === 0) { - currentMarker = marker; - return true; // break out of detect + currentMarker = marker + return true // break out of detect } - }); + }) - return {marker:currentMarker, offset:currentOffset}; + return { marker: currentMarker, offset: currentOffset } } get text() { - return reduce(this.markers, (prev, m) => prev + m.value, ''); + return reduce(this.markers, (prev, m) => prev + m.value, '') } get length() { - return reduce(this.markers, (prev, m) => prev + m.length, 0); + return reduce(this.markers, (prev, m) => prev + m.length, 0) } /** @@ -203,75 +195,80 @@ export default class Markerable extends Section { * range. Does not change the existing markers in this section. */ markersFor(headOffset, tailOffset) { - const range = {head: {section:this, offset:headOffset}, - tail: {section:this, offset:tailOffset}}; + const range = { head: { section: this, offset: headOffset }, tail: { section: this, offset: tailOffset } } - let markers = []; - this._markersInRange(range, (marker, {markerHead, markerTail, isContained}) => { - const cloned = marker.clone(); + let markers = [] + this._markersInRange(range, (marker, { markerHead, markerTail, isContained }) => { + const cloned = marker.clone() if (!isContained) { // cannot do marker.value.slice if the marker is an atom -- this breaks the atom's "atomic" value // If a marker is an atom `isContained` should always be true so // we shouldn't hit this code path. FIXME add tests - cloned.value = marker.value.slice(markerHead, markerTail); + cloned.value = marker.value.slice(markerHead, markerTail) } - markers.push(cloned); - }); - return markers; + markers.push(cloned) + }) + return markers } markupsInRange(range) { - const markups = new Set(); + const markups = new Set() this._markersInRange(range, marker => { - marker.markups.forEach(m => markups.add(m)); - }); - return markups.toArray(); + marker.markups.forEach(m => markups.add(m)) + }) + return markups.toArray() } // calls the callback with (marker, {markerHead, markerTail, isContained}) // for each marker that is wholly or partially contained in the range. _markersInRange(range, callback) { - const { head, tail } = range; - assert('Cannot call #_markersInRange if range expands beyond this section', - head.section === this && tail.section === this); - const {offset:headOffset} = head, {offset:tailOffset} = tail; - - let currentHead = 0, currentTail = 0, currentMarker = this.markers.head; + const { head, tail } = range + assert( + 'Cannot call #_markersInRange if range expands beyond this section', + head.section === this && tail.section === this + ) + const { offset: headOffset } = head, + { offset: tailOffset } = tail + + let currentHead = 0, + currentTail = 0, + currentMarker = this.markers.head while (currentMarker) { - currentTail += currentMarker.length; + currentTail += currentMarker.length if (currentTail > headOffset && currentHead < tailOffset) { - let markerHead = Math.max(headOffset - currentHead, 0); - let markerTail = currentMarker.length - - Math.max(currentTail - tailOffset, 0); - let isContained = markerHead === 0 && markerTail === currentMarker.length; + let markerHead = Math.max(headOffset - currentHead, 0) + let markerTail = currentMarker.length - Math.max(currentTail - tailOffset, 0) + let isContained = markerHead === 0 && markerTail === currentMarker.length - callback(currentMarker, {markerHead, markerTail, isContained}); + callback(currentMarker, { markerHead, markerTail, isContained }) } - currentHead += currentMarker.length; - currentMarker = currentMarker.next; + currentHead += currentMarker.length + currentMarker = currentMarker.next - if (currentHead > tailOffset) { break; } + if (currentHead > tailOffset) { + break + } } } // mutates this by appending the other section's (cloned) markers to it join(otherSection) { - let beforeMarker = this.markers.tail; - let afterMarker = null; + let beforeMarker = this.markers.tail + let afterMarker = null otherSection.markers.forEach(m => { if (!m.isBlank) { - m = m.clone(); - this.markers.append(m); + m = m.clone() + this.markers.append(m) if (!afterMarker) { - afterMarker = m; + afterMarker = m } } - }); + }) - return { beforeMarker, afterMarker }; + return { beforeMarker, afterMarker } } } diff --git a/src/js/models/_section.js b/src/js/models/_section.js index 90e3a551c..439be88f8 100644 --- a/src/js/models/_section.js +++ b/src/js/models/_section.js @@ -1,54 +1,52 @@ -import { normalizeTagName } from '../utils/dom-utils'; -import LinkedItem from '../utils/linked-item'; -import assert from '../utils/assert'; -import Position from '../utils/cursor/position'; +import { normalizeTagName } from '../utils/dom-utils' +import LinkedItem from '../utils/linked-item' +import assert from '../utils/assert' +import Position from '../utils/cursor/position' function unimplementedMethod(methodName, me) { - assert(`\`${methodName}()\` must be implemented by ${me.constructor.name}`, - false); + assert(`\`${methodName}()\` must be implemented by ${me.constructor.name}`, false) } export default class Section extends LinkedItem { constructor(type) { - super(); - assert('Cannot create section without type', !!type); - this.type = type; - this.isSection = true; - this.isMarkerable = false; - this.isNested = false; - this.isSection = true; - this.isLeafSection = true; + super() + assert('Cannot create section without type', !!type) + this.type = type + this.isSection = true + this.isMarkerable = false + this.isNested = false + this.isSection = true + this.isLeafSection = true } set tagName(val) { - let normalizedTagName = normalizeTagName(val); - assert(`Cannot set section tagName to ${val}`, - this.isValidTagName(normalizedTagName)); - this._tagName = normalizedTagName; + let normalizedTagName = normalizeTagName(val) + assert(`Cannot set section tagName to ${val}`, this.isValidTagName(normalizedTagName)) + this._tagName = normalizedTagName } get tagName() { - return this._tagName; + return this._tagName } isValidTagName(/* normalizedTagName */) { - unimplementedMethod('isValidTagName', this); + unimplementedMethod('isValidTagName', this) } get length() { - return 0; + return 0 } get isBlank() { - unimplementedMethod('isBlank', this); + unimplementedMethod('isBlank', this) } clone() { - unimplementedMethod('clone', this); + unimplementedMethod('clone', this) } canJoin(/* otherSection */) { - unimplementedMethod('canJoin', this); + unimplementedMethod('canJoin', this) } /** @@ -56,7 +54,7 @@ export default class Section extends LinkedItem { * @public */ headPosition() { - return this.toPosition(0); + return this.toPosition(0) } /** @@ -64,7 +62,7 @@ export default class Section extends LinkedItem { * @public */ tailPosition() { - return this.toPosition(this.length); + return this.toPosition(this.length) } /** @@ -73,10 +71,10 @@ export default class Section extends LinkedItem { * @public */ toPosition(offset) { - assert("Must pass number to `toPosition`", typeof offset === 'number'); - assert("Cannot call `toPosition` with offset > length", offset <= this.length); + assert('Must pass number to `toPosition`', typeof offset === 'number') + assert('Cannot call `toPosition` with offset > length', offset <= this.length) - return new Position(this, offset); + return new Position(this, offset) } /** @@ -84,60 +82,60 @@ export default class Section extends LinkedItem { * @public */ toRange() { - return this.headPosition().toRange(this.tailPosition()); + return this.headPosition().toRange(this.tailPosition()) } join() { - unimplementedMethod('join', this); + unimplementedMethod('join', this) } textUntil(/* position */) { - return ''; + return '' } /** * Markerable sections should override this method */ splitMarkerAtOffset() { - let blankEdit = { added: [], removed: [] }; - return blankEdit; + let blankEdit = { added: [], removed: [] } + return blankEdit } nextLeafSection() { - const next = this.next; + const next = this.next if (next) { if (next.items) { - return next.items.head; + return next.items.head } else { - return next; + return next } } else { if (this.isNested) { - return this.parent.nextLeafSection(); + return this.parent.nextLeafSection() } } } immediatelyNextMarkerableSection() { - let next = this.nextLeafSection(); + let next = this.nextLeafSection() while (next && !next.isMarkerable) { - next = next.nextLeafSection(); + next = next.nextLeafSection() } - return next; + return next } previousLeafSection() { - const prev = this.prev; + const prev = this.prev if (prev) { if (prev.items) { - return prev.items.tail; + return prev.items.tail } else { - return prev; + return prev } } else { if (this.isNested) { - return this.parent.previousLeafSection(); + return this.parent.previousLeafSection() } } } diff --git a/src/js/models/atom-node.js b/src/js/models/atom-node.js index 58e7f409c..ff6a8ce3d 100644 --- a/src/js/models/atom-node.js +++ b/src/js/models/atom-node.js @@ -1,63 +1,66 @@ -import assert from '../utils/assert'; +import assert from '../utils/assert' export default class AtomNode { constructor(editor, atom, model, element, atomOptions) { - this.editor = editor; - this.atom = atom; - this.model = model; - this.atomOptions = atomOptions; - this.element = element; - - this._teardownCallback = null; - this._rendered = null; + this.editor = editor + this.atom = atom + this.model = model + this.atomOptions = atomOptions + this.element = element + + this._teardownCallback = null + this._rendered = null } render() { if (!this._rendered) { - let {atomOptions: options, env, model: { value, payload } } = this; + let { + atomOptions: options, + env, + model: { value, payload }, + } = this // cache initial render - this._rendered = this.atom.render({options, env, value, payload}); + this._rendered = this.atom.render({ options, env, value, payload }) } - this._validateAndAppendRenderResult(this._rendered); + this._validateAndAppendRenderResult(this._rendered) } get env() { return { name: this.atom.name, - onTeardown: (callback) => this._teardownCallback = callback, - save: (value, payload={}) => { - this.model.value = value; - this.model.payload = payload; - - this.editor._postDidChange(); - this.teardown(); - this.render(); - } - }; + onTeardown: callback => (this._teardownCallback = callback), + save: (value, payload = {}) => { + this.model.value = value + this.model.payload = payload + + this.editor._postDidChange() + this.teardown() + this.render() + }, + } } teardown() { if (this._teardownCallback) { - this._teardownCallback(); - this._teardownCallback = null; + this._teardownCallback() + this._teardownCallback = null } if (this._rendered) { - this.element.removeChild(this._rendered); - this._rendered = null; + this.element.removeChild(this._rendered) + this._rendered = null } } _validateAndAppendRenderResult(rendered) { if (!rendered) { - return; + return } - let { atom: { name } } = this; - assert( - `Atom "${name}" must return a DOM node (returned value was: "${rendered}")`, - !!rendered.nodeType - ); - this.element.appendChild(rendered); + let { + atom: { name }, + } = this + assert(`Atom "${name}" must return a DOM node (returned value was: "${rendered}")`, !!rendered.nodeType) + this.element.appendChild(rendered) } } diff --git a/src/js/models/atom.js b/src/js/models/atom.js index 3d94f4137..972012012 100644 --- a/src/js/models/atom.js +++ b/src/js/models/atom.js @@ -1,91 +1,88 @@ -import { ATOM_TYPE } from './types'; -import mixin from '../utils/mixin'; -import MarkuperableMixin from '../utils/markuperable'; -import LinkedItem from '../utils/linked-item'; -import assert from '../utils/assert'; +import { ATOM_TYPE } from './types' +import mixin from '../utils/mixin' +import MarkuperableMixin from '../utils/markuperable' +import LinkedItem from '../utils/linked-item' +import assert from '../utils/assert' -const ATOM_LENGTH = 1; +const ATOM_LENGTH = 1 class Atom extends LinkedItem { - constructor(name, value, payload, markups=[]) { - super(); - this.name = name; - this.value = value; - this.text = ''; // An atom never has text, but it does have a value - assert('Atom must have value', value !== undefined && value !== null); - this.payload = payload; - this.type = ATOM_TYPE; - this.isMarker = false; - this.isAtom = true; - - this.markups = []; - markups.forEach(m => this.addMarkup(m)); + constructor(name, value, payload, markups = []) { + super() + this.name = name + this.value = value + this.text = '' // An atom never has text, but it does have a value + assert('Atom must have value', value !== undefined && value !== null) + this.payload = payload + this.type = ATOM_TYPE + this.isMarker = false + this.isAtom = true + + this.markups = [] + markups.forEach(m => this.addMarkup(m)) } clone() { - let clonedMarkups = this.markups.slice(); - return this.builder.createAtom( - this.name, this.value, this.payload, clonedMarkups - ); + let clonedMarkups = this.markups.slice() + return this.builder.createAtom(this.name, this.value, this.payload, clonedMarkups) } get isBlank() { - return false; + return false } get length() { - return ATOM_LENGTH; + return ATOM_LENGTH } canJoin(/* other */) { - return false; + return false } textUntil(/* offset */) { - return ''; + return '' } - split(offset=0, endOffset=offset) { - let markers = []; + split(offset = 0, endOffset = offset) { + let markers = [] if (endOffset === 0) { - markers.push(this.builder.createMarker('', this.markups.slice())); + markers.push(this.builder.createMarker('', this.markups.slice())) } - markers.push(this.clone()); + markers.push(this.clone()) if (offset === ATOM_LENGTH) { - markers.push(this.builder.createMarker('', this.markups.slice())); + markers.push(this.builder.createMarker('', this.markups.slice())) } - return markers; + return markers } splitAtOffset(offset) { - assert('Cannot split a marker at an offset > its length', - offset <= this.length); + assert('Cannot split a marker at an offset > its length', offset <= this.length) - let { builder } = this; - let clone = this.clone(); - let blankMarker = builder.createMarker(''); - let pre, post; + let { builder } = this + let clone = this.clone() + let blankMarker = builder.createMarker('') + let pre, post if (offset === 0) { - ([pre, post] = [blankMarker, clone]); + ;[pre, post] = [blankMarker, clone] } else if (offset === ATOM_LENGTH) { - ([pre, post] = [clone, blankMarker]); + ;[pre, post] = [clone, blankMarker] } else { - assert(`Invalid offset given to Atom#splitAtOffset: "${offset}"`, false); + assert(`Invalid offset given to Atom#splitAtOffset: "${offset}"`, false) } this.markups.forEach(markup => { - pre.addMarkup(markup); - post.addMarkup(markup); - }); - return [pre, post]; + pre.addMarkup(markup) + post.addMarkup(markup) + }) + return [pre, post] } } -mixin(Atom, MarkuperableMixin); +mixin(Atom, MarkuperableMixin) -export default Atom; +export default Atom diff --git a/src/js/models/card-node.js b/src/js/models/card-node.js index abfd04b72..d3470c112 100644 --- a/src/js/models/card-node.js +++ b/src/js/models/card-node.js @@ -1,54 +1,55 @@ -import assert from '../utils/assert'; +import assert from '../utils/assert' export default class CardNode { constructor(editor, card, section, element, options) { - this.editor = editor; - this.card = card; - this.section = section; - this.element = element; - this.options = options; + this.editor = editor + this.card = card + this.section = section + this.element = element + this.options = options - this.mode = null; + this.mode = null - this._teardownCallback = null; - this._rendered = null; + this._teardownCallback = null + this._rendered = null } render(mode) { - if (this.mode === mode) { return; } + if (this.mode === mode) { + return + } - this.teardown(); + this.teardown() - this.mode = mode; + this.mode = mode - let method = mode === 'display' ? 'render' : 'edit'; - method = this.card[method]; + let method = mode === 'display' ? 'render' : 'edit' + method = this.card[method] - assert(`Card is missing "${method}" (tried to render mode: "${mode}")`, - !!method); + assert(`Card is missing "${method}" (tried to render mode: "${mode}")`, !!method) let rendered = method({ env: this.env, options: this.options, - payload: this.section.payload - }); + payload: this.section.payload, + }) - this._validateAndAppendRenderResult(rendered); + this._validateAndAppendRenderResult(rendered) } teardown() { if (this._teardownCallback) { - this._teardownCallback(); - this._teardownCallback = null; + this._teardownCallback() + this._teardownCallback = null } if (this._rendered) { - this.element.removeChild(this._rendered); - this._rendered = null; + this.element.removeChild(this._rendered) + this._rendered = null } } didRender() { if (this._didRenderCallback) { - this._didRenderCallback(); + this._didRenderCallback() } } @@ -56,47 +57,46 @@ export default class CardNode { return { name: this.card.name, isInEditor: true, - onTeardown: (callback) => this._teardownCallback = callback, - didRender: (callback) => this._didRenderCallback = callback, + onTeardown: callback => (this._teardownCallback = callback), + didRender: callback => (this._didRenderCallback = callback), edit: () => this.edit(), - save: (payload, transition=true) => { - this.section.payload = payload; + save: (payload, transition = true) => { + this.section.payload = payload - this.editor._postDidChange(); + this.editor._postDidChange() if (transition) { - this.display(); + this.display() } }, cancel: () => this.display(), remove: () => this.remove(), - postModel: this.section - }; + postModel: this.section, + } } display() { - this.render('display'); + this.render('display') } edit() { - this.render('edit'); + this.render('edit') } remove() { - this.editor.run(postEditor => postEditor.removeSection(this.section)); + this.editor.run(postEditor => postEditor.removeSection(this.section)) } _validateAndAppendRenderResult(rendered) { if (!rendered) { - return; + return } - let { card: { name } } = this; - assert( - `Card "${name}" must render dom (render value was: "${rendered}")`, - !!rendered.nodeType - ); - this.element.appendChild(rendered); - this._rendered = rendered; - this.didRender(); + let { + card: { name }, + } = this + assert(`Card "${name}" must render dom (render value was: "${rendered}")`, !!rendered.nodeType) + this.element.appendChild(rendered) + this._rendered = rendered + this.didRender() } } diff --git a/src/js/models/card.js b/src/js/models/card.js index 2bcb758fb..127230cb6 100644 --- a/src/js/models/card.js +++ b/src/js/models/card.js @@ -1,48 +1,48 @@ -import Section from './_section'; -import { CARD_TYPE } from './types'; -import { shallowCopyObject } from '../utils/copy'; +import Section from './_section' +import { CARD_TYPE } from './types' +import { shallowCopyObject } from '../utils/copy' export const CARD_MODES = { DISPLAY: 'display', - EDIT: 'edit' -}; + EDIT: 'edit', +} -const CARD_LENGTH = 1; +const CARD_LENGTH = 1 -const DEFAULT_INITIAL_MODE = CARD_MODES.DISPLAY; +const DEFAULT_INITIAL_MODE = CARD_MODES.DISPLAY export default class Card extends Section { constructor(name, payload) { - super(CARD_TYPE); - this.name = name; - this.payload = payload; - this.setInitialMode(DEFAULT_INITIAL_MODE); - this.isCardSection = true; + super(CARD_TYPE) + this.name = name + this.payload = payload + this.setInitialMode(DEFAULT_INITIAL_MODE) + this.isCardSection = true } get isBlank() { - return false; + return false } canJoin() { - return false; + return false } get length() { - return CARD_LENGTH; + return CARD_LENGTH } clone() { - let payload = shallowCopyObject(this.payload); - let card = this.builder.createCardSection(this.name, payload); + let payload = shallowCopyObject(this.payload) + let card = this.builder.createCardSection(this.name, payload) // If this card is currently rendered, clone the mode it is // currently in as the default mode of the new card. - let mode = this._initialMode; + let mode = this._initialMode if (this.renderNode && this.renderNode.cardNode) { - mode = this.renderNode.cardNode.mode; + mode = this.renderNode.cardNode.mode } - card.setInitialMode(mode); - return card; + card.setInitialMode(mode) + return card } /** @@ -51,6 +51,6 @@ export default class Card extends Section { */ setInitialMode(initialMode) { // TODO validate initialMode - this._initialMode = initialMode; + this._initialMode = initialMode } } diff --git a/src/js/models/image.js b/src/js/models/image.js index a29dcdffb..a18fdfd7d 100644 --- a/src/js/models/image.js +++ b/src/js/models/image.js @@ -1,21 +1,21 @@ -import { IMAGE_SECTION_TYPE } from './types'; -import Section from './_section'; +import { IMAGE_SECTION_TYPE } from './types' +import Section from './_section' export default class Image extends Section { constructor() { - super(IMAGE_SECTION_TYPE); - this.src = null; + super(IMAGE_SECTION_TYPE) + this.src = null } canJoin() { - return false; + return false } get isBlank() { - return false; + return false } get length() { - return 1; + return 1 } } diff --git a/src/js/models/lifecycle-callbacks.js b/src/js/models/lifecycle-callbacks.js index 11c80bcbc..4cd972349 100644 --- a/src/js/models/lifecycle-callbacks.js +++ b/src/js/models/lifecycle-callbacks.js @@ -1,50 +1,50 @@ -import assert from 'mobiledoc-kit/utils/assert'; +import assert from 'mobiledoc-kit/utils/assert' export default class LifecycleCallbacks { - constructor(queueNames=[]) { - this.callbackQueues = {}; - this.removalQueues = {}; + constructor(queueNames = []) { + this.callbackQueues = {} + this.removalQueues = {} queueNames.forEach(name => { - this.callbackQueues[name] = []; - this.removalQueues[name] = []; - }); + this.callbackQueues[name] = [] + this.removalQueues[name] = [] + }) } - runCallbacks(queueName, args=[]) { - let queue = this._getQueue(queueName); - queue.forEach(cb => cb(...args)); + runCallbacks(queueName, args = []) { + let queue = this._getQueue(queueName) + queue.forEach(cb => cb(...args)) - let toRemove = this.removalQueues[queueName]; + let toRemove = this.removalQueues[queueName] toRemove.forEach(cb => { - let index = queue.indexOf(cb); + let index = queue.indexOf(cb) if (index !== -1) { - queue.splice(index, 1); + queue.splice(index, 1) } - }); + }) - this.removalQueues[queueName] = []; + this.removalQueues[queueName] = [] } addCallback(queueName, callback) { - this._getQueue(queueName).push(callback); + this._getQueue(queueName).push(callback) } _scheduleCallbackForRemoval(queueName, callback) { - this.removalQueues[queueName].push(callback); + this.removalQueues[queueName].push(callback) } addCallbackOnce(queueName, callback) { - let queue = this._getQueue(queueName); + let queue = this._getQueue(queueName) if (queue.indexOf(callback) === -1) { - queue.push(callback); - this._scheduleCallbackForRemoval(queueName, callback); + queue.push(callback) + this._scheduleCallbackForRemoval(queueName, callback) } } _getQueue(queueName) { - let queue = this.callbackQueues[queueName]; - assert(`No queue found for "${queueName}"`, !!queue); - return queue; + let queue = this.callbackQueues[queueName] + assert(`No queue found for "${queueName}"`, !!queue) + return queue } } diff --git a/src/js/models/list-item.js b/src/js/models/list-item.js index 80173c1bc..98369e59c 100644 --- a/src/js/models/list-item.js +++ b/src/js/models/list-item.js @@ -1,42 +1,36 @@ -import Markerable from './_markerable'; -import { LIST_ITEM_TYPE } from './types'; -import { - normalizeTagName -} from 'mobiledoc-kit/utils/dom-utils'; -import { contains } from 'mobiledoc-kit/utils/array-utils'; +import Markerable from './_markerable' +import { LIST_ITEM_TYPE } from './types' +import { normalizeTagName } from 'mobiledoc-kit/utils/dom-utils' +import { contains } from 'mobiledoc-kit/utils/array-utils' -export const VALID_LIST_ITEM_TAGNAMES = [ - 'li' -].map(normalizeTagName); +export const VALID_LIST_ITEM_TAGNAMES = ['li'].map(normalizeTagName) export default class ListItem extends Markerable { - constructor(tagName, markers=[]) { - super(LIST_ITEM_TYPE, tagName, markers); - this.isListItem = true; - this.isNested = true; + constructor(tagName, markers = []) { + super(LIST_ITEM_TYPE, tagName, markers) + this.isListItem = true + this.isNested = true } isValidTagName(normalizedTagName) { - return contains(VALID_LIST_ITEM_TAGNAMES, normalizedTagName); + return contains(VALID_LIST_ITEM_TAGNAMES, normalizedTagName) } - splitAtMarker(marker, offset=0) { + splitAtMarker(marker, offset = 0) { // FIXME need to check if we are going to split into two list items // or a list item and a new markup section: - const isLastItem = !this.next; - const createNewSection = (!marker && offset === 0 && isLastItem); + const isLastItem = !this.next + const createNewSection = !marker && offset === 0 && isLastItem let [beforeSection, afterSection] = [ this.builder.createListItem(), - createNewSection ? this.builder.createMarkupSection() : - this.builder.createListItem() - ]; + createNewSection ? this.builder.createMarkupSection() : this.builder.createListItem(), + ] - return this._redistributeMarkers( - beforeSection, afterSection, marker, offset); + return this._redistributeMarkers(beforeSection, afterSection, marker, offset) } get post() { - return this.section.post; + return this.section.post } } diff --git a/src/js/models/list-section.js b/src/js/models/list-section.js index 33d1f4313..2e160e33f 100644 --- a/src/js/models/list-section.js +++ b/src/js/models/list-section.js @@ -1,66 +1,63 @@ -import { LIST_SECTION_TYPE } from './types'; -import Section from './_section'; -import { attributable } from './_attributable'; +import { LIST_SECTION_TYPE } from './types' +import Section from './_section' +import { attributable } from './_attributable' -import LinkedList from '../utils/linked-list'; -import { forEach, contains } from '../utils/array-utils'; -import { normalizeTagName } from '../utils/dom-utils'; -import assert from '../utils/assert'; -import { entries } from '../utils/object-utils'; +import LinkedList from '../utils/linked-list' +import { forEach, contains } from '../utils/array-utils' +import { normalizeTagName } from '../utils/dom-utils' +import assert from '../utils/assert' +import { entries } from '../utils/object-utils' -export const VALID_LIST_SECTION_TAGNAMES = [ - 'ul', 'ol' -].map(normalizeTagName); +export const VALID_LIST_SECTION_TAGNAMES = ['ul', 'ol'].map(normalizeTagName) -export const DEFAULT_TAG_NAME = VALID_LIST_SECTION_TAGNAMES[0]; +export const DEFAULT_TAG_NAME = VALID_LIST_SECTION_TAGNAMES[0] export default class ListSection extends Section { - constructor(tagName=DEFAULT_TAG_NAME, items=[], attributes={}) { - super(LIST_SECTION_TYPE); - this.tagName = tagName; - this.isListSection = true; - this.isLeafSection = false; + constructor(tagName = DEFAULT_TAG_NAME, items = [], attributes = {}) { + super(LIST_SECTION_TYPE) + this.tagName = tagName + this.isListSection = true + this.isLeafSection = false - attributable(this); - entries(attributes).forEach(([k,v]) => this.setAttribute(k, v)); + attributable(this) + entries(attributes).forEach(([k, v]) => this.setAttribute(k, v)) this.items = new LinkedList({ adoptItem: i => { - assert(`Cannot insert non-list-item to list (is: ${i.type})`, - i.isListItem); - i.section = i.parent = this; + assert(`Cannot insert non-list-item to list (is: ${i.type})`, i.isListItem) + i.section = i.parent = this }, - freeItem: i => i.section = i.parent = null - }); - this.sections = this.items; + freeItem: i => (i.section = i.parent = null), + }) + this.sections = this.items - items.forEach(i => this.items.append(i)); + items.forEach(i => this.items.append(i)) } canJoin() { - return false; + return false } isValidTagName(normalizedTagName) { - return contains(VALID_LIST_SECTION_TAGNAMES, normalizedTagName); + return contains(VALID_LIST_SECTION_TAGNAMES, normalizedTagName) } headPosition() { - return this.items.head.headPosition(); + return this.items.head.headPosition() } tailPosition() { - return this.items.tail.tailPosition(); + return this.items.tail.tailPosition() } get isBlank() { - return this.items.isEmpty; + return this.items.isEmpty } clone() { - let newSection = this.builder.createListSection(this.tagName); - forEach(this.items, i => newSection.items.append(i.clone())); - return newSection; + let newSection = this.builder.createListSection(this.tagName) + forEach(this.items, i => newSection.items.append(i.clone())) + return newSection } /** @@ -70,11 +67,11 @@ export default class ListSection extends Section { */ join(other) { if (other.isListSection) { - other.items.forEach(i => this.join(i)); + other.items.forEach(i => this.join(i)) } else if (other.isMarkerable) { - let item = this.builder.createListItem(); - item.join(other); - this.items.append(item); + let item = this.builder.createListItem() + item.join(other) + this.items.append(item) } } } diff --git a/src/js/models/marker.js b/src/js/models/marker.js index f0389cab2..b7ad72ff2 100644 --- a/src/js/models/marker.js +++ b/src/js/models/marker.js @@ -1,9 +1,9 @@ -import { MARKER_TYPE } from './types'; -import mixin from '../utils/mixin'; -import MarkuperableMixin from '../utils/markuperable'; -import LinkedItem from '../utils/linked-item'; -import assert from '../utils/assert'; -import { isArrayEqual } from '../utils/array-utils'; +import { MARKER_TYPE } from './types' +import mixin from '../utils/mixin' +import MarkuperableMixin from '../utils/markuperable' +import LinkedItem from '../utils/linked-item' +import assert from '../utils/assert' +import { isArrayEqual } from '../utils/array-utils' // Unicode uses a pair of "surrogate" characters" (a high- and low-surrogate) // to encode characters outside the basic multilingual plane (like emoji and @@ -12,36 +12,36 @@ import { isArrayEqual } from '../utils/array-utils'; // high- and low-surrogate characters. // See "high surrogate" and "low surrogate" on // https://en.wikipedia.org/wiki/Unicode_block -export const HIGH_SURROGATE_RANGE = [0xD800, 0xDBFF]; -export const LOW_SURROGATE_RANGE = [0xDC00, 0xDFFF]; +export const HIGH_SURROGATE_RANGE = [0xd800, 0xdbff] +export const LOW_SURROGATE_RANGE = [0xdc00, 0xdfff] const Marker = class Marker extends LinkedItem { - constructor(value='', markups=[]) { - super(); - this.value = value; - assert('Marker must have value', value !== undefined && value !== null); - this.markups = []; - this.type = MARKER_TYPE; - this.isMarker = true; - this.isAtom = false; - markups.forEach(m => this.addMarkup(m)); + constructor(value = '', markups = []) { + super() + this.value = value + assert('Marker must have value', value !== undefined && value !== null) + this.markups = [] + this.type = MARKER_TYPE + this.isMarker = true + this.isAtom = false + markups.forEach(m => this.addMarkup(m)) } clone() { - const clonedMarkups = this.markups.slice(); - return this.builder.createMarker(this.value, clonedMarkups); + const clonedMarkups = this.markups.slice() + return this.builder.createMarker(this.value, clonedMarkups) } get isEmpty() { - return this.isBlank; + return this.isBlank } get isBlank() { - return this.length === 0; + return this.length === 0 } charAt(offset) { - return this.value.slice(offset, offset+1); + return this.value.slice(offset, offset + 1) } /** @@ -49,77 +49,71 @@ const Marker = class Marker extends LinkedItem { * Compare with an Atom which distinguishes between text and value */ get text() { - return this.value; + return this.value } get length() { - return this.value.length; + return this.value.length } // delete the character at this offset, // update the value with the new value deleteValueAtOffset(offset) { - assert('Cannot delete value at offset outside bounds', - offset >= 0 && offset <= this.length); + assert('Cannot delete value at offset outside bounds', offset >= 0 && offset <= this.length) - let width = 1; - let code = this.value.charCodeAt(offset); + let width = 1 + let code = this.value.charCodeAt(offset) if (code >= HIGH_SURROGATE_RANGE[0] && code <= HIGH_SURROGATE_RANGE[1]) { - width = 2; + width = 2 } else if (code >= LOW_SURROGATE_RANGE[0] && code <= LOW_SURROGATE_RANGE[1]) { - width = 2; - offset = offset - 1; + width = 2 + offset = offset - 1 } - const [ left, right ] = [ - this.value.slice(0, offset), - this.value.slice(offset+width) - ]; - this.value = left + right; + const [left, right] = [this.value.slice(0, offset), this.value.slice(offset + width)] + this.value = left + right - return width; + return width } canJoin(other) { - return other && other.isMarker && isArrayEqual(this.markups, other.markups); + return other && other.isMarker && isArrayEqual(this.markups, other.markups) } textUntil(offset) { - return this.value.slice(0, offset); + return this.value.slice(0, offset) } - split(offset=0, endOffset=this.length) { + split(offset = 0, endOffset = this.length) { let markers = [ this.builder.createMarker(this.value.substring(0, offset)), this.builder.createMarker(this.value.substring(offset, endOffset)), - this.builder.createMarker(this.value.substring(endOffset)) - ]; + this.builder.createMarker(this.value.substring(endOffset)), + ] - this.markups.forEach(mu => markers.forEach(m => m.addMarkup(mu))); - return markers; + this.markups.forEach(mu => markers.forEach(m => m.addMarkup(mu))) + return markers } /** * @return {Array} 2 markers either or both of which could be blank */ splitAtOffset(offset) { - assert('Cannot split a marker at an offset > its length', - offset <= this.length); - let { value, builder } = this; + assert('Cannot split a marker at an offset > its length', offset <= this.length) + let { value, builder } = this - let pre = builder.createMarker(value.substring(0, offset)); - let post = builder.createMarker(value.substring(offset)); + let pre = builder.createMarker(value.substring(0, offset)) + let post = builder.createMarker(value.substring(offset)) this.markups.forEach(markup => { - pre.addMarkup(markup); - post.addMarkup(markup); - }); + pre.addMarkup(markup) + post.addMarkup(markup) + }) - return [pre, post]; + return [pre, post] } +} -}; +mixin(Marker, MarkuperableMixin) -mixin(Marker, MarkuperableMixin); - -export default Marker; +export default Marker diff --git a/src/js/models/markup-section.js b/src/js/models/markup-section.js index d9edd844b..700815d3b 100644 --- a/src/js/models/markup-section.js +++ b/src/js/models/markup-section.js @@ -1,62 +1,46 @@ -import Markerable from './_markerable'; -import { attributable } from './_attributable'; -import { MARKUP_SECTION_TYPE } from './types'; +import Markerable from './_markerable' +import { attributable } from './_attributable' +import { MARKUP_SECTION_TYPE } from './types' -import { normalizeTagName } from '../utils/dom-utils'; -import { contains } from '../utils/array-utils'; -import { entries } from '../utils/object-utils'; +import { normalizeTagName } from '../utils/dom-utils' +import { contains } from '../utils/array-utils' +import { entries } from '../utils/object-utils' // valid values of `tagName` for a MarkupSection -export const VALID_MARKUP_SECTION_TAGNAMES = [ - 'aside', - 'blockquote', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'p' -].map(normalizeTagName); +export const VALID_MARKUP_SECTION_TAGNAMES = ['aside', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'].map( + normalizeTagName +) // valid element names for a MarkupSection. A MarkupSection with a tagName // not in this will be rendered as a div with a className matching the // tagName -export const MARKUP_SECTION_ELEMENT_NAMES = [ - 'aside', - 'blockquote', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'p' -].map(normalizeTagName); -export const DEFAULT_TAG_NAME = VALID_MARKUP_SECTION_TAGNAMES[8]; +export const MARKUP_SECTION_ELEMENT_NAMES = ['aside', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'].map( + normalizeTagName +) +export const DEFAULT_TAG_NAME = VALID_MARKUP_SECTION_TAGNAMES[8] const MarkupSection = class MarkupSection extends Markerable { - constructor(tagName=DEFAULT_TAG_NAME, markers=[], attributes={}) { - super(MARKUP_SECTION_TYPE, tagName, markers); + constructor(tagName = DEFAULT_TAG_NAME, markers = [], attributes = {}) { + super(MARKUP_SECTION_TYPE, tagName, markers) - attributable(this); - entries(attributes).forEach(([k,v]) => this.setAttribute(k, v)); + attributable(this) + entries(attributes).forEach(([k, v]) => this.setAttribute(k, v)) - this.isMarkupSection = true; + this.isMarkupSection = true } isValidTagName(normalizedTagName) { - return contains(VALID_MARKUP_SECTION_TAGNAMES, normalizedTagName); + return contains(VALID_MARKUP_SECTION_TAGNAMES, normalizedTagName) } - splitAtMarker(marker, offset=0) { + splitAtMarker(marker, offset = 0) { let [beforeSection, afterSection] = [ this.builder.createMarkupSection(this.tagName, [], false, this.attributes), - this.builder.createMarkupSection() - ]; + this.builder.createMarkupSection(), + ] - return this._redistributeMarkers(beforeSection, afterSection, marker, offset); + return this._redistributeMarkers(beforeSection, afterSection, marker, offset) } -}; +} -export default MarkupSection; +export default MarkupSection diff --git a/src/js/models/markup.js b/src/js/models/markup.js index 1389473b2..ecece259e 100644 --- a/src/js/models/markup.js +++ b/src/js/models/markup.js @@ -1,7 +1,7 @@ -import { normalizeTagName } from '../utils/dom-utils'; -import { filterObject } from '../utils/array-utils'; -import { MARKUP_TYPE } from './types'; -import assert from '../utils/assert'; +import { normalizeTagName } from '../utils/dom-utils' +import { filterObject } from '../utils/array-utils' +import { MARKUP_TYPE } from './types' +import assert from '../utils/assert' export const VALID_MARKUP_TAGNAMES = [ 'a', @@ -9,18 +9,15 @@ export const VALID_MARKUP_TAGNAMES = [ 'code', 'em', 'i', - 's', // strikethrough + 's', // strikethrough 'del', // deleted text (also strikethrough) 'strong', 'sub', // subscript 'sup', // superscript - 'u' -].map(normalizeTagName); + 'u', +].map(normalizeTagName) -export const VALID_ATTRIBUTES = [ - 'href', - 'rel' -]; +export const VALID_ATTRIBUTES = ['href', 'rel'] /** * A Markup is similar with an inline HTML tag that might be added to @@ -32,17 +29,15 @@ class Markup { /* * @param {Object} attributes key-values */ - constructor(tagName, attributes={}) { - this.tagName = normalizeTagName(tagName); + constructor(tagName, attributes = {}) { + this.tagName = normalizeTagName(tagName) - assert('Must use attributes object param (not array) for Markup', - !Array.isArray(attributes)); + assert('Must use attributes object param (not array) for Markup', !Array.isArray(attributes)) - this.attributes = filterObject(attributes, VALID_ATTRIBUTES); - this.type = MARKUP_TYPE; + this.attributes = filterObject(attributes, VALID_ATTRIBUTES) + this.type = MARKUP_TYPE - assert(`Cannot create markup of tagName ${tagName}`, - VALID_MARKUP_TAGNAMES.indexOf(this.tagName) !== -1); + assert(`Cannot create markup of tagName ${tagName}`, VALID_MARKUP_TAGNAMES.indexOf(this.tagName) !== -1) } /** @@ -51,15 +46,15 @@ class Markup { * @private */ isForwardInclusive() { - return this.tagName === normalizeTagName("a") ? false : true; + return this.tagName === normalizeTagName('a') ? false : true } isBackwardInclusive() { - return false; + return false } hasTag(tagName) { - return this.tagName === normalizeTagName(tagName); + return this.tagName === normalizeTagName(tagName) } /** @@ -67,13 +62,13 @@ class Markup { * @param {String} name, e.g. "href" */ getAttribute(name) { - return this.attributes[name]; + return this.attributes[name] } static isValidElement(element) { - const tagName = normalizeTagName(element.tagName); - return VALID_MARKUP_TAGNAMES.indexOf(tagName) !== -1; + const tagName = normalizeTagName(element.tagName) + return VALID_MARKUP_TAGNAMES.indexOf(tagName) !== -1 } } -export default Markup; +export default Markup diff --git a/src/js/models/post-node-builder.js b/src/js/models/post-node-builder.js index 16755d6a8..0690ecc0c 100644 --- a/src/js/models/post-node-builder.js +++ b/src/js/models/post-node-builder.js @@ -1,37 +1,30 @@ -import Atom from '../models/atom'; -import Post from '../models/post'; -import MarkupSection from '../models/markup-section'; -import ListSection from '../models/list-section'; -import ListItem from '../models/list-item'; -import ImageSection from '../models/image'; -import Marker from '../models/marker'; -import Markup from '../models/markup'; -import Card from '../models/card'; -import { normalizeTagName } from '../utils/dom-utils'; -import { objectToSortedKVArray } from '../utils/array-utils'; -import { - LIST_ITEM_TYPE, - MARKUP_SECTION_TYPE -} from '../models/types'; -import { - DEFAULT_TAG_NAME as DEFAULT_MARKUP_SECTION_TAG_NAME -} from '../models/markup-section'; -import { - DEFAULT_TAG_NAME as DEFAULT_LIST_SECTION_TAG_NAME -} from '../models/list-section'; -import assert from '../utils/assert'; +import Atom from '../models/atom' +import Post from '../models/post' +import MarkupSection from '../models/markup-section' +import ListSection from '../models/list-section' +import ListItem from '../models/list-item' +import ImageSection from '../models/image' +import Marker from '../models/marker' +import Markup from '../models/markup' +import Card from '../models/card' +import { normalizeTagName } from '../utils/dom-utils' +import { objectToSortedKVArray } from '../utils/array-utils' +import { LIST_ITEM_TYPE, MARKUP_SECTION_TYPE } from '../models/types' +import { DEFAULT_TAG_NAME as DEFAULT_MARKUP_SECTION_TAG_NAME } from '../models/markup-section' +import { DEFAULT_TAG_NAME as DEFAULT_LIST_SECTION_TAG_NAME } from '../models/list-section' +import assert from '../utils/assert' function cacheKey(tagName, attributes) { - return `${normalizeTagName(tagName)}-${objectToSortedKVArray(attributes).join('-')}`; + return `${normalizeTagName(tagName)}-${objectToSortedKVArray(attributes).join('-')}` } function addMarkupToCache(cache, markup) { - cache[cacheKey(markup.tagName, markup.attributes)] = markup; + cache[cacheKey(markup.tagName, markup.attributes)] = markup } function findMarkupInCache(cache, tagName, attributes) { - const key = cacheKey(tagName, attributes); - return cache[key]; + const key = cacheKey(tagName, attributes) + return cache[key] } /** @@ -47,29 +40,29 @@ class PostNodeBuilder { * @private */ constructor() { - this.markupCache = {}; + this.markupCache = {} } /** * @return {Post} A new, blank post */ - createPost(sections=[]) { - const post = new Post(); - post.builder = this; + createPost(sections = []) { + const post = new Post() + post.builder = this - sections.forEach(s => post.sections.append(s)); + sections.forEach(s => post.sections.append(s)) - return post; + return post } - createMarkerableSection(type, tagName, markers=[]) { + createMarkerableSection(type, tagName, markers = []) { switch (type) { case LIST_ITEM_TYPE: - return this.createListItem(markers); + return this.createListItem(markers) case MARKUP_SECTION_TYPE: - return this.createMarkupSection(tagName, markers); + return this.createMarkupSection(tagName, markers) default: - assert(`Cannot create markerable section of type ${type}`, false); + assert(`Cannot create markerable section of type ${type}`, false) } } @@ -78,36 +71,36 @@ class PostNodeBuilder { * @param {Marker[]} [markers=[]] * @return {MarkupSection} */ - createMarkupSection(tagName=DEFAULT_MARKUP_SECTION_TAG_NAME, markers=[], isGenerated=false, attributes={}) { - tagName = normalizeTagName(tagName); - const section = new MarkupSection(tagName, markers, attributes); + createMarkupSection(tagName = DEFAULT_MARKUP_SECTION_TAG_NAME, markers = [], isGenerated = false, attributes = {}) { + tagName = normalizeTagName(tagName) + const section = new MarkupSection(tagName, markers, attributes) if (isGenerated) { - section.isGenerated = true; + section.isGenerated = true } - section.builder = this; - return section; + section.builder = this + return section } - createListSection(tagName=DEFAULT_LIST_SECTION_TAG_NAME, items=[], attributes={}) { - tagName = normalizeTagName(tagName); - const section = new ListSection(tagName, items, attributes); - section.builder = this; - return section; + createListSection(tagName = DEFAULT_LIST_SECTION_TAG_NAME, items = [], attributes = {}) { + tagName = normalizeTagName(tagName) + const section = new ListSection(tagName, items, attributes) + section.builder = this + return section } - createListItem(markers=[]) { - const tagName = normalizeTagName('li'); - const item = new ListItem(tagName, markers); - item.builder = this; - return item; + createListItem(markers = []) { + const tagName = normalizeTagName('li') + const item = new ListItem(tagName, markers) + item.builder = this + return item } createImageSection(url) { - let section = new ImageSection(); + let section = new ImageSection() if (url) { - section.src = url; + section.src = url } - return section; + return section } /** @@ -115,10 +108,10 @@ class PostNodeBuilder { * @param {Object} [payload={}] * @return {CardSection} */ - createCardSection(name, payload={}) { - const card = new Card(name, payload); - card.builder = this; - return card; + createCardSection(name, payload = {}) { + const card = new Card(name, payload) + card.builder = this + return card } /** @@ -126,10 +119,10 @@ class PostNodeBuilder { * @param {Markup[]} [markups=[]] * @return {Marker} */ - createMarker(value, markups=[]) { - const marker = new Marker(value, markups); - marker.builder = this; - return marker; + createMarker(value, markups = []) { + const marker = new Marker(value, markups) + marker.builder = this + return marker } /** @@ -139,10 +132,10 @@ class PostNodeBuilder { * @param {Markup[]} [markups=[]] * @return {Atom} */ - createAtom(name, value='', payload={}, markups=[]) { - const atom = new Atom(name, value, payload, markups); - atom.builder = this; - return atom; + createAtom(name, value = '', payload = {}, markups = []) { + const atom = new Atom(name, value, payload, markups) + atom.builder = this + return atom } /** @@ -150,18 +143,18 @@ class PostNodeBuilder { * @param {Object} attributes Key-value pairs of attributes for the markup * @return {Markup} */ - createMarkup(tagName, attributes={}) { - tagName = normalizeTagName(tagName); + createMarkup(tagName, attributes = {}) { + tagName = normalizeTagName(tagName) - let markup = findMarkupInCache(this.markupCache, tagName, attributes); + let markup = findMarkupInCache(this.markupCache, tagName, attributes) if (!markup) { - markup = new Markup(tagName, attributes); - markup.builder = this; - addMarkupToCache(this.markupCache, markup); + markup = new Markup(tagName, attributes) + markup.builder = this + addMarkupToCache(this.markupCache, markup) } - return markup; + return markup } } -export default PostNodeBuilder; +export default PostNodeBuilder diff --git a/src/js/models/post.js b/src/js/models/post.js index 18772dd29..4117cfc48 100644 --- a/src/js/models/post.js +++ b/src/js/models/post.js @@ -1,9 +1,9 @@ -import { POST_TYPE } from './types'; -import LinkedList from 'mobiledoc-kit/utils/linked-list'; -import { forEach } from 'mobiledoc-kit/utils/array-utils'; -import Set from 'mobiledoc-kit/utils/set'; -import Position from 'mobiledoc-kit/utils/cursor/position'; -import assert from 'mobiledoc-kit/utils/assert'; +import { POST_TYPE } from './types' +import LinkedList from 'mobiledoc-kit/utils/linked-list' +import { forEach } from 'mobiledoc-kit/utils/array-utils' +import Set from 'mobiledoc-kit/utils/set' +import Position from 'mobiledoc-kit/utils/cursor/position' +import assert from 'mobiledoc-kit/utils/assert' /** * The Post is an in-memory representation of an editor's document. @@ -18,11 +18,11 @@ class Post { * @private */ constructor() { - this.type = POST_TYPE; + this.type = POST_TYPE this.sections = new LinkedList({ - adoptItem: s => s.post = s.parent = this, - freeItem: s => s.post = s.parent = null - }); + adoptItem: s => (s.post = s.parent = this), + freeItem: s => (s.post = s.parent = null), + }) } /** @@ -32,9 +32,9 @@ class Post { */ headPosition() { if (this.isBlank) { - return Position.blankPosition(); + return Position.blankPosition() } else { - return this.sections.head.headPosition(); + return this.sections.head.headPosition() } } @@ -45,9 +45,9 @@ class Post { */ tailPosition() { if (this.isBlank) { - return Position.blankPosition(); + return Position.blankPosition() } else { - return this.sections.tail.tailPosition(); + return this.sections.tail.tailPosition() } } @@ -56,11 +56,11 @@ class Post { * @public */ toRange() { - return this.headPosition().toRange(this.tailPosition()); + return this.headPosition().toRange(this.tailPosition()) } get isBlank() { - return this.sections.isEmpty; + return this.sections.isEmpty } /** @@ -70,11 +70,10 @@ class Post { * @public */ get hasContent() { - if ((this.sections.length > 1) || - (this.sections.length === 1 && !this.sections.head.isBlank)) { - return true; + if (this.sections.length > 1 || (this.sections.length === 1 && !this.sections.head.isBlank)) { + return true } else { - return false; + return false } } @@ -83,75 +82,73 @@ class Post { * @return {Array} markers that are completely contained by the range */ markersContainedByRange(range) { - const markers = []; + const markers = [] this.walkMarkerableSections(range, section => { - section._markersInRange( - range.trimTo(section), - (m, {isContained}) => { if (isContained) { markers.push(m); } } - ); - }); + section._markersInRange(range.trimTo(section), (m, { isContained }) => { + if (isContained) { + markers.push(m) + } + }) + }) - return markers; + return markers } markupsInRange(range) { - const markups = new Set(); + const markups = new Set() if (range.isCollapsed) { - let pos = range.head; + let pos = range.head if (pos.isMarkerable) { - let [back, forward] = [pos.markerIn(-1), pos.markerIn(1)]; + let [back, forward] = [pos.markerIn(-1), pos.markerIn(1)] if (back && forward && back === forward) { - back.markups.forEach(m => markups.add(m)); + back.markups.forEach(m => markups.add(m)) } else { - (back && back.markups || []).forEach(m => { + ;((back && back.markups) || []).forEach(m => { if (m.isForwardInclusive()) { - markups.add(m); + markups.add(m) } - }); - (forward && forward.markups || []).forEach(m => { + }) + ;((forward && forward.markups) || []).forEach(m => { if (m.isBackwardInclusive()) { - markups.add(m); + markups.add(m) } - }); + }) } } } else { - this.walkMarkerableSections(range, (section) => { - forEach( - section.markupsInRange(range.trimTo(section)), - m => markups.add(m) - ); - }); + this.walkMarkerableSections(range, section => { + forEach(section.markupsInRange(range.trimTo(section)), m => markups.add(m)) + }) } - return markups.toArray(); + return markups.toArray() } walkAllLeafSections(callback) { - let range = this.headPosition().toRange(this.tailPosition()); - return this.walkLeafSections(range, callback); + let range = this.headPosition().toRange(this.tailPosition()) + return this.walkLeafSections(range, callback) } walkLeafSections(range, callback) { - const { head, tail } = range; + const { head, tail } = range - let index = 0; - let nextSection, shouldStop; - let currentSection = head.section; + let index = 0 + let nextSection, shouldStop + let currentSection = head.section while (currentSection) { - nextSection = this._nextLeafSection(currentSection); - shouldStop = currentSection === tail.section; + nextSection = this._nextLeafSection(currentSection) + shouldStop = currentSection === tail.section - callback(currentSection, index); - index++; + callback(currentSection, index) + index++ if (shouldStop) { - break; + break } else { - currentSection = nextSection; + currentSection = nextSection } } } @@ -159,30 +156,32 @@ class Post { walkMarkerableSections(range, callback) { this.walkLeafSections(range, section => { if (section.isMarkerable) { - callback(section); + callback(section) } - }); + }) } // return the next section that has markers after this one, // possibly skipping non-markerable sections _nextLeafSection(section) { - if (!section) { return null; } + if (!section) { + return null + } - const next = section.next; + const next = section.next if (next) { if (next.isLeafSection) { - return next; + return next } else if (next.items) { - return next.items.head; + return next.items.head } else { - assert('Cannot determine next section from non-leaf-section', false); + assert('Cannot determine next section from non-leaf-section', false) } } else if (section.isNested) { // if there is no section after this, but this section is a child // (e.g. a ListItem inside a ListSection), check for a markerable // section after its parent - return this._nextLeafSection(section.parent); + return this._nextLeafSection(section.parent) } } @@ -191,53 +190,48 @@ class Post { * @return {Post} A new post, constrained to {range} */ trimTo(range) { - const post = this.builder.createPost(); - const { builder } = this; - const { head, tail } = range; - const tailNotSelected = tail.offset === 0 && head.section !== tail.section; + const post = this.builder.createPost() + const { builder } = this + const { head, tail } = range + const tailNotSelected = tail.offset === 0 && head.section !== tail.section let sectionParent = post, - listParent = null; + listParent = null this.walkLeafSections(range, section => { - let newSection; + let newSection if (section.isMarkerable) { if (section.isListItem) { if (listParent) { - sectionParent = null; + sectionParent = null } else { - listParent = builder.createListSection(section.parent.tagName); - post.sections.append(listParent); - sectionParent = null; + listParent = builder.createListSection(section.parent.tagName) + post.sections.append(listParent) + sectionParent = null } - newSection = builder.createListItem(); - listParent.items.append(newSection); + newSection = builder.createListItem() + listParent.items.append(newSection) } else { - listParent = null; - sectionParent = post; - const tagName = tailNotSelected && tail.section === section ? - 'p' : - section.tagName; - newSection = builder.createMarkupSection(tagName); + listParent = null + sectionParent = post + const tagName = tailNotSelected && tail.section === section ? 'p' : section.tagName + newSection = builder.createMarkupSection(tagName) } - let currentRange = range.trimTo(section); - forEach( - section.markersFor(currentRange.headSectionOffset, currentRange.tailSectionOffset), - m => newSection.markers.append(m) - ); + let currentRange = range.trimTo(section) + forEach(section.markersFor(currentRange.headSectionOffset, currentRange.tailSectionOffset), m => + newSection.markers.append(m) + ) } else { - newSection = tailNotSelected && tail.section === section ? - builder.createMarkupSection('p') : - section.clone(); + newSection = tailNotSelected && tail.section === section ? builder.createMarkupSection('p') : section.clone() - sectionParent = post; + sectionParent = post } if (sectionParent) { - sectionParent.sections.append(newSection); + sectionParent.sections.append(newSection) } - }); - return post; + }) + return post } } -export default Post; +export default Post diff --git a/src/js/models/render-node.js b/src/js/models/render-node.js index 6807e0a7c..8ad21aa9b 100644 --- a/src/js/models/render-node.js +++ b/src/js/models/render-node.js @@ -1,92 +1,95 @@ -import LinkedItem from 'mobiledoc-kit/utils/linked-item'; -import LinkedList from 'mobiledoc-kit/utils/linked-list'; -import { containsNode } from 'mobiledoc-kit/utils/dom-utils'; -import assert from 'mobiledoc-kit/utils/assert'; +import LinkedItem from 'mobiledoc-kit/utils/linked-item' +import LinkedList from 'mobiledoc-kit/utils/linked-list' +import { containsNode } from 'mobiledoc-kit/utils/dom-utils' +import assert from 'mobiledoc-kit/utils/assert' export default class RenderNode extends LinkedItem { constructor(postNode, renderTree) { - super(); - this.parent = null; - this.isDirty = true; - this.isRemoved = false; - this.postNode = postNode; - this._childNodes = null; - this._element = null; - this._cursorElement = null; // blank render nodes need a cursor element - this.renderTree = renderTree; + super() + this.parent = null + this.isDirty = true + this.isRemoved = false + this.postNode = postNode + this._childNodes = null + this._element = null + this._cursorElement = null // blank render nodes need a cursor element + this.renderTree = renderTree // RenderNodes for Markers keep track of their markupElement - this.markupElement = null; + this.markupElement = null // RenderNodes for Atoms use these properties - this.headTextNode = null; - this.tailTextNode = null; - this.atomNode = null; + this.headTextNode = null + this.tailTextNode = null + this.atomNode = null // RenderNodes for cards use this property - this.cardNode = null; + this.cardNode = null } isAttached() { - assert('Cannot check if a renderNode is attached without an element.', - !!this.element); - return containsNode(this.renderTree.rootElement, this.element); + assert('Cannot check if a renderNode is attached without an element.', !!this.element) + return containsNode(this.renderTree.rootElement, this.element) } get childNodes() { if (!this._childNodes) { this._childNodes = new LinkedList({ - adoptItem: item => item.parent = this, - freeItem: item => item.destroy() - }); + adoptItem: item => (item.parent = this), + freeItem: item => item.destroy(), + }) } - return this._childNodes; + return this._childNodes } scheduleForRemoval() { - this.isRemoved = true; - if (this.parent) { this.parent.markDirty(); } + this.isRemoved = true + if (this.parent) { + this.parent.markDirty() + } } markDirty() { - this.isDirty = true; - if (this.parent) { this.parent.markDirty(); } + this.isDirty = true + if (this.parent) { + this.parent.markDirty() + } } get isRendered() { - return !!this.element; + return !!this.element } markClean() { - this.isDirty = false; + this.isDirty = false } set element(element) { - const currentElement = this._element; - this._element = element; + const currentElement = this._element + this._element = element if (currentElement) { - this.renderTree.removeElementRenderNode(currentElement); + this.renderTree.removeElementRenderNode(currentElement) } if (element) { - this.renderTree.setElementRenderNode(element, this); + this.renderTree.setElementRenderNode(element, this) } } get element() { - return this._element; + return this._element } set cursorElement(cursorElement) { - this._cursorElement = cursorElement; + this._cursorElement = cursorElement } get cursorElement() { - return this._cursorElement || this.element; + return this._cursorElement || this.element } destroy() { - this.element = null; - this.parent = null; - this.postNode = null; - this.renderTree = null; + this.element = null + this.parent = null + this.postNode = null + this.renderTree = null } reparsesMutationOfChildNode(node) { if (this.postNode.isCardSection) { - return !containsNode(this.cardNode.element, node); + return !containsNode(this.cardNode.element, node) } else if (this.postNode.isAtom) { - return !containsNode(this.atomNode.element, node); + return !containsNode(this.atomNode.element, node) } - return true; + return true } } diff --git a/src/js/models/render-tree.js b/src/js/models/render-tree.js index dbfc67fb2..d4d3d9195 100644 --- a/src/js/models/render-tree.js +++ b/src/js/models/render-tree.js @@ -1,70 +1,70 @@ -import RenderNode from 'mobiledoc-kit/models/render-node'; -import ElementMap from '../utils/element-map'; +import RenderNode from 'mobiledoc-kit/models/render-node' +import ElementMap from '../utils/element-map' export default class RenderTree { constructor(rootPostNode) { - this._rootNode = this.buildRenderNode(rootPostNode); - this._elements = new ElementMap(); + this._rootNode = this.buildRenderNode(rootPostNode) + this._elements = new ElementMap() } /* * @return {RenderNode} The root render node in this tree */ get rootNode() { - return this._rootNode; + return this._rootNode } /** * @return {Boolean} */ get isDirty() { - return this.rootNode && this.rootNode.isDirty; + return this.rootNode && this.rootNode.isDirty } /* * @return {DOMNode} The root DOM element in this tree */ get rootElement() { - return this.rootNode.element; + return this.rootNode.element } /* * @param {DOMNode} element * @return {RenderNode} The renderNode for this element, if any */ getElementRenderNode(element) { - return this._elements.get(element); + return this._elements.get(element) } setElementRenderNode(element, renderNode) { - this._elements.set(element, renderNode); + this._elements.set(element, renderNode) } removeElementRenderNode(element) { - this._elements.remove(element); + this._elements.remove(element) } /** * @param {DOMNode} element * Walk up from the dom element until we find a renderNode element */ - findRenderNodeFromElement(element, conditionFn=()=>true) { - let renderNode; + findRenderNodeFromElement(element, conditionFn = () => true) { + let renderNode while (element) { - renderNode = this.getElementRenderNode(element); + renderNode = this.getElementRenderNode(element) if (renderNode && conditionFn(renderNode)) { - return renderNode; + return renderNode } // continue loop - element = element.parentNode; + element = element.parentNode // stop if we are at the root element if (element === this.rootElement) { if (conditionFn(this.rootNode)) { - return this.rootNode; + return this.rootNode } else { - return; + return } } } } buildRenderNode(postNode) { - const renderNode = new RenderNode(postNode, this); - postNode.renderNode = renderNode; - return renderNode; + const renderNode = new RenderNode(postNode, this) + postNode.renderNode = renderNode + return renderNode } } diff --git a/src/js/models/types.js b/src/js/models/types.js index aa2f6f0c9..4105d6297 100644 --- a/src/js/models/types.js +++ b/src/js/models/types.js @@ -1,9 +1,9 @@ -export const MARKUP_SECTION_TYPE = 'markup-section'; -export const LIST_SECTION_TYPE = 'list-section'; -export const MARKUP_TYPE = 'markup'; -export const MARKER_TYPE = 'marker'; -export const POST_TYPE = 'post'; -export const LIST_ITEM_TYPE = 'list-item'; -export const CARD_TYPE = 'card-section'; -export const IMAGE_SECTION_TYPE = 'image-section'; -export const ATOM_TYPE = 'atom'; \ No newline at end of file +export const MARKUP_SECTION_TYPE = 'markup-section' +export const LIST_SECTION_TYPE = 'list-section' +export const MARKUP_TYPE = 'markup' +export const MARKER_TYPE = 'marker' +export const POST_TYPE = 'post' +export const LIST_ITEM_TYPE = 'list-item' +export const CARD_TYPE = 'card-section' +export const IMAGE_SECTION_TYPE = 'image-section' +export const ATOM_TYPE = 'atom' diff --git a/src/js/parsers/dom.js b/src/js/parsers/dom.js index 91447d772..d20318c37 100644 --- a/src/js/parsers/dom.js +++ b/src/js/parsers/dom.js @@ -1,99 +1,77 @@ -import { - NO_BREAK_SPACE, - TAB_CHARACTER, - ATOM_CLASS_NAME -} from '../renderers/editor-dom'; -import { - MARKUP_SECTION_TYPE, - LIST_SECTION_TYPE, - LIST_ITEM_TYPE -} from '../models/types'; -import { - isTextNode, - isCommentNode, - isElementNode, - getAttributes, - normalizeTagName -} from '../utils/dom-utils'; -import { - any, - detect, - forEach -} from '../utils/array-utils'; -import { TAB } from 'mobiledoc-kit/utils/characters'; -import { ZWNJ } from 'mobiledoc-kit/renderers/editor-dom'; - -import SectionParser from 'mobiledoc-kit/parsers/section'; -import Markup from 'mobiledoc-kit/models/markup'; - -const GOOGLE_DOCS_CONTAINER_ID_REGEX = /^docs\-internal\-guid/; - -const NO_BREAK_SPACE_REGEX = new RegExp(NO_BREAK_SPACE, 'g'); -const TAB_CHARACTER_REGEX = new RegExp(TAB_CHARACTER, 'g'); +import { NO_BREAK_SPACE, TAB_CHARACTER, ATOM_CLASS_NAME } from '../renderers/editor-dom' +import { MARKUP_SECTION_TYPE, LIST_SECTION_TYPE, LIST_ITEM_TYPE } from '../models/types' +import { isTextNode, isCommentNode, isElementNode, getAttributes, normalizeTagName } from '../utils/dom-utils' +import { any, detect, forEach } from '../utils/array-utils' +import { TAB } from 'mobiledoc-kit/utils/characters' +import { ZWNJ } from 'mobiledoc-kit/renderers/editor-dom' + +import SectionParser from 'mobiledoc-kit/parsers/section' +import Markup from 'mobiledoc-kit/models/markup' + +const GOOGLE_DOCS_CONTAINER_ID_REGEX = /^docs\-internal\-guid/ + +const NO_BREAK_SPACE_REGEX = new RegExp(NO_BREAK_SPACE, 'g') +const TAB_CHARACTER_REGEX = new RegExp(TAB_CHARACTER, 'g') export function transformHTMLText(textContent) { - let text = textContent; - text = text.replace(NO_BREAK_SPACE_REGEX, ' '); - text = text.replace(TAB_CHARACTER_REGEX, TAB); - return text; + let text = textContent + text = text.replace(NO_BREAK_SPACE_REGEX, ' ') + text = text.replace(TAB_CHARACTER_REGEX, TAB) + return text } export function trimSectionText(section) { if (section.isMarkerable && section.markers.length) { - let { head, tail } = section.markers; - head.value = head.value.replace(/^\s+/, ''); - tail.value = tail.value.replace(/\s+$/, ''); + let { head, tail } = section.markers + head.value = head.value.replace(/^\s+/, '') + tail.value = tail.value.replace(/\s+$/, '') } } function isGoogleDocsContainer(element) { - return !isTextNode(element) && - !isCommentNode(element) && - normalizeTagName(element.tagName) === normalizeTagName('b') && - GOOGLE_DOCS_CONTAINER_ID_REGEX.test(element.id); + return ( + !isTextNode(element) && + !isCommentNode(element) && + normalizeTagName(element.tagName) === normalizeTagName('b') && + GOOGLE_DOCS_CONTAINER_ID_REGEX.test(element.id) + ) } function detectRootElement(element) { - let childNodes = element.childNodes || []; - let googleDocsContainer = detect(childNodes, isGoogleDocsContainer); + let childNodes = element.childNodes || [] + let googleDocsContainer = detect(childNodes, isGoogleDocsContainer) if (googleDocsContainer) { - return googleDocsContainer; + return googleDocsContainer } else { - return element; + return element } } const TAG_REMAPPING = { - 'b': 'strong', - 'i': 'em' -}; + b: 'strong', + i: 'em', +} function remapTagName(tagName) { - let normalized = normalizeTagName(tagName); - let remapped = TAG_REMAPPING[normalized]; - return remapped || normalized; + let normalized = normalizeTagName(tagName) + let remapped = TAG_REMAPPING[normalized] + return remapped || normalized } function trim(str) { - return str.replace(/^\s+/, '').replace(/\s+$/, ''); + return str.replace(/^\s+/, '').replace(/\s+$/, '') } function walkMarkerableNodes(parent, callback) { - let currentNode = parent; - - if ( - isTextNode(currentNode) || - ( - isElementNode(currentNode) && - currentNode.classList.contains(ATOM_CLASS_NAME) - ) - ) { - callback(currentNode); + let currentNode = parent + + if (isTextNode(currentNode) || (isElementNode(currentNode) && currentNode.classList.contains(ATOM_CLASS_NAME))) { + callback(currentNode) } else { - currentNode = currentNode.firstChild; + currentNode = currentNode.firstChild while (currentNode) { - walkMarkerableNodes(currentNode, callback); - currentNode = currentNode.nextSibling; + walkMarkerableNodes(currentNode, callback) + currentNode = currentNode.nextSibling } } } @@ -103,83 +81,83 @@ function walkMarkerableNodes(parent, callback) { * @private */ class DOMParser { - constructor(builder, options={}) { - this.builder = builder; - this.sectionParser = new SectionParser(this.builder, options); + constructor(builder, options = {}) { + this.builder = builder + this.sectionParser = new SectionParser(this.builder, options) } parse(element) { - const post = this.builder.createPost(); - let rootElement = detectRootElement(element); + const post = this.builder.createPost() + let rootElement = detectRootElement(element) this._eachChildNode(rootElement, child => { - let sections = this.parseSections(child); - this.appendSections(post, sections); - }); + let sections = this.parseSections(child) + this.appendSections(post, sections) + }) // trim leading/trailing whitespace of markerable sections to avoid // unnessary whitespace from indented HTML input - forEach(post.sections, section => trimSectionText(section)); + forEach(post.sections, section => trimSectionText(section)) - return post; + return post } appendSections(post, sections) { - forEach(sections, section => this.appendSection(post, section)); + forEach(sections, section => this.appendSection(post, section)) } appendSection(post, section) { if ( section.isBlank || - (section.isMarkerable && - trim(section.text) === "" && - !any(section.markers, marker => marker.isAtom)) + (section.isMarkerable && trim(section.text) === '' && !any(section.markers, marker => marker.isAtom)) ) { - return; + return } - let lastSection = post.sections.tail; - if (lastSection && - lastSection._inferredTagName && - section._inferredTagName && - lastSection.tagName === section.tagName) { - lastSection.join(section); + let lastSection = post.sections.tail + if ( + lastSection && + lastSection._inferredTagName && + section._inferredTagName && + lastSection.tagName === section.tagName + ) { + lastSection.join(section) } else { - post.sections.append(section); + post.sections.append(section) } } _eachChildNode(element, callback) { - let nodes = isTextNode(element) ? [element] : element.childNodes; - forEach(nodes, node => callback(node)); + let nodes = isTextNode(element) ? [element] : element.childNodes + forEach(nodes, node => callback(node)) } parseSections(element) { - return this.sectionParser.parse(element); + return this.sectionParser.parse(element) } // walk up from the textNode until the rootNode, converting each // parentNode into a markup collectMarkups(textNode, rootNode) { - let markups = []; - let currentNode = textNode.parentNode; + let markups = [] + let currentNode = textNode.parentNode while (currentNode && currentNode !== rootNode) { - let markup = this.markupFromNode(currentNode); + let markup = this.markupFromNode(currentNode) if (markup) { - markups.push(markup); + markups.push(markup) } - currentNode = currentNode.parentNode; + currentNode = currentNode.parentNode } - return markups; + return markups } // Turn an element node into a markup markupFromNode(node) { if (Markup.isValidElement(node)) { - let tagName = remapTagName(node.tagName); - let attributes = getAttributes(node); - return this.builder.createMarkup(tagName, attributes); + let tagName = remapTagName(node.tagName) + let attributes = getAttributes(node) + return this.builder.createMarkup(tagName, attributes) } } @@ -188,135 +166,134 @@ class DOMParser { reparseSection(section, renderTree) { switch (section.type) { case LIST_SECTION_TYPE: - return this.reparseListSection(section, renderTree); + return this.reparseListSection(section, renderTree) case LIST_ITEM_TYPE: - return this.reparseListItem(section, renderTree); + return this.reparseListItem(section, renderTree) case MARKUP_SECTION_TYPE: - return this.reparseMarkupSection(section, renderTree); + return this.reparseMarkupSection(section, renderTree) default: - return; // can only parse the above types + return // can only parse the above types } } reparseMarkupSection(section, renderTree) { - return this._reparseSectionContainingMarkers(section, renderTree); + return this._reparseSectionContainingMarkers(section, renderTree) } reparseListItem(listItem, renderTree) { - return this._reparseSectionContainingMarkers(listItem, renderTree); + return this._reparseSectionContainingMarkers(listItem, renderTree) } reparseListSection(listSection, renderTree) { - listSection.items.forEach(li => this.reparseListItem(li, renderTree)); + listSection.items.forEach(li => this.reparseListItem(li, renderTree)) } _reparseSectionContainingMarkers(section, renderTree) { - let element = section.renderNode.element; - let seenRenderNodes = []; - let previousMarker; + let element = section.renderNode.element + let seenRenderNodes = [] + let previousMarker - walkMarkerableNodes(element, (node) => { - let marker; - let renderNode = renderTree.getElementRenderNode(node); + walkMarkerableNodes(element, node => { + let marker + let renderNode = renderTree.getElementRenderNode(node) if (renderNode) { if (renderNode.postNode.isMarker) { - let text = transformHTMLText(node.textContent); - let markups = this.collectMarkups(node, element); + let text = transformHTMLText(node.textContent) + let markups = this.collectMarkups(node, element) if (text.length) { - marker = renderNode.postNode; - marker.value = text; - marker.markups = markups; + marker = renderNode.postNode + marker.value = text + marker.markups = markups } else { - renderNode.scheduleForRemoval(); + renderNode.scheduleForRemoval() } } else if (renderNode.postNode.isAtom) { - let { headTextNode, tailTextNode } = renderNode; + let { headTextNode, tailTextNode } = renderNode if (headTextNode.textContent !== ZWNJ) { - let value = headTextNode.textContent.replace(new RegExp(ZWNJ, 'g'), ''); - headTextNode.textContent = ZWNJ; + let value = headTextNode.textContent.replace(new RegExp(ZWNJ, 'g'), '') + headTextNode.textContent = ZWNJ if (previousMarker && previousMarker.isMarker) { - previousMarker.value += value; + previousMarker.value += value if (previousMarker.renderNode) { - previousMarker.renderNode.markDirty(); + previousMarker.renderNode.markDirty() } } else { - let postNode = renderNode.postNode; - let newMarkups = postNode.markups.slice(); - let newPreviousMarker = this.builder.createMarker(value, newMarkups); - section.markers.insertBefore(newPreviousMarker, postNode); - - let newPreviousRenderNode = renderTree.buildRenderNode(newPreviousMarker); - newPreviousRenderNode.markDirty(); - section.renderNode.markDirty(); - - seenRenderNodes.push(newPreviousRenderNode); - section.renderNode.childNodes.insertBefore(newPreviousRenderNode, - renderNode); + let postNode = renderNode.postNode + let newMarkups = postNode.markups.slice() + let newPreviousMarker = this.builder.createMarker(value, newMarkups) + section.markers.insertBefore(newPreviousMarker, postNode) + + let newPreviousRenderNode = renderTree.buildRenderNode(newPreviousMarker) + newPreviousRenderNode.markDirty() + section.renderNode.markDirty() + + seenRenderNodes.push(newPreviousRenderNode) + section.renderNode.childNodes.insertBefore(newPreviousRenderNode, renderNode) } } if (tailTextNode.textContent !== ZWNJ) { - let value = tailTextNode.textContent.replace(new RegExp(ZWNJ, 'g'), ''); - tailTextNode.textContent = ZWNJ; + let value = tailTextNode.textContent.replace(new RegExp(ZWNJ, 'g'), '') + tailTextNode.textContent = ZWNJ if (renderNode.postNode.next && renderNode.postNode.next.isMarker) { - let nextMarker = renderNode.postNode.next; + let nextMarker = renderNode.postNode.next if (nextMarker.renderNode) { - let nextValue = nextMarker.renderNode.element.textContent; - nextMarker.renderNode.element.textContent = value + nextValue; + let nextValue = nextMarker.renderNode.element.textContent + nextMarker.renderNode.element.textContent = value + nextValue } else { - let nextValue = value + nextMarker.value; - nextMarker.value = nextValue; + let nextValue = value + nextMarker.value + nextMarker.value = nextValue } } else { - let postNode = renderNode.postNode; - let newMarkups = postNode.markups.slice(); - let newMarker = this.builder.createMarker(value, newMarkups); + let postNode = renderNode.postNode + let newMarkups = postNode.markups.slice() + let newMarker = this.builder.createMarker(value, newMarkups) - section.markers.insertAfter(newMarker, postNode); + section.markers.insertAfter(newMarker, postNode) - let newRenderNode = renderTree.buildRenderNode(newMarker); - seenRenderNodes.push(newRenderNode); + let newRenderNode = renderTree.buildRenderNode(newMarker) + seenRenderNodes.push(newRenderNode) - newRenderNode.markDirty(); - section.renderNode.markDirty(); + newRenderNode.markDirty() + section.renderNode.markDirty() - section.renderNode.childNodes.insertAfter(newRenderNode, renderNode); + section.renderNode.childNodes.insertAfter(newRenderNode, renderNode) } } if (renderNode) { - marker = renderNode.postNode; + marker = renderNode.postNode } } } else if (isTextNode(node)) { - let text = transformHTMLText(node.textContent); - let markups = this.collectMarkups(node, element); - marker = this.builder.createMarker(text, markups); - - renderNode = renderTree.buildRenderNode(marker); - renderNode.element = node; - renderNode.markClean(); - section.renderNode.markDirty(); - - let previousRenderNode = previousMarker && previousMarker.renderNode; - section.markers.insertAfter(marker, previousMarker); - section.renderNode.childNodes.insertAfter(renderNode, previousRenderNode); + let text = transformHTMLText(node.textContent) + let markups = this.collectMarkups(node, element) + marker = this.builder.createMarker(text, markups) + + renderNode = renderTree.buildRenderNode(marker) + renderNode.element = node + renderNode.markClean() + section.renderNode.markDirty() + + let previousRenderNode = previousMarker && previousMarker.renderNode + section.markers.insertAfter(marker, previousMarker) + section.renderNode.childNodes.insertAfter(renderNode, previousRenderNode) } if (renderNode) { - seenRenderNodes.push(renderNode); + seenRenderNodes.push(renderNode) } - previousMarker = marker; - }); + previousMarker = marker + }) - let renderNode = section.renderNode.childNodes.head; + let renderNode = section.renderNode.childNodes.head while (renderNode) { if (seenRenderNodes.indexOf(renderNode) === -1) { - renderNode.scheduleForRemoval(); + renderNode.scheduleForRemoval() } - renderNode = renderNode.next; + renderNode = renderNode.next } } } -export default DOMParser; +export default DOMParser diff --git a/src/js/parsers/html.js b/src/js/parsers/html.js index b12472761..20cb92b43 100644 --- a/src/js/parsers/html.js +++ b/src/js/parsers/html.js @@ -1,12 +1,12 @@ -import { parseHTML } from '../utils/dom-utils'; -import assert from '../utils/assert'; -import DOMParser from './dom'; +import { parseHTML } from '../utils/dom-utils' +import assert from '../utils/assert' +import DOMParser from './dom' export default class HTMLParser { - constructor(builder, options={}) { - assert('Must pass builder to HTMLParser', builder); - this.builder = builder; - this.options = options; + constructor(builder, options = {}) { + assert('Must pass builder to HTMLParser', builder) + this.builder = builder + this.options = options } /** @@ -14,8 +14,8 @@ export default class HTMLParser { * @return {Post} A post abstract */ parse(html) { - let dom = parseHTML(html); - let parser = new DOMParser(this.builder, this.options); - return parser.parse(dom); + let dom = parseHTML(html) + let parser = new DOMParser(this.builder, this.options) + return parser.parse(dom) } } diff --git a/src/js/parsers/mobiledoc/0-2.js b/src/js/parsers/mobiledoc/0-2.js index 9e77348e4..3410c7f00 100644 --- a/src/js/parsers/mobiledoc/0-2.js +++ b/src/js/parsers/mobiledoc/0-2.js @@ -2,120 +2,120 @@ import { MOBILEDOC_MARKUP_SECTION_TYPE, MOBILEDOC_IMAGE_SECTION_TYPE, MOBILEDOC_LIST_SECTION_TYPE, - MOBILEDOC_CARD_SECTION_TYPE -} from 'mobiledoc-kit/renderers/mobiledoc/0-2'; -import { kvArrayToObject, filter } from "../../utils/array-utils"; -import assert from 'mobiledoc-kit/utils/assert'; + MOBILEDOC_CARD_SECTION_TYPE, +} from 'mobiledoc-kit/renderers/mobiledoc/0-2' +import { kvArrayToObject, filter } from '../../utils/array-utils' +import assert from 'mobiledoc-kit/utils/assert' /* * Parses from mobiledoc -> post */ export default class MobiledocParser { constructor(builder) { - this.builder = builder; + this.builder = builder } /** * @param {Mobiledoc} * @return {Post} */ - parse({sections: sectionData}) { + parse({ sections: sectionData }) { try { - const markerTypes = sectionData[0]; - const sections = sectionData[1]; + const markerTypes = sectionData[0] + const sections = sectionData[1] - const post = this.builder.createPost(); + const post = this.builder.createPost() - this.markups = []; - this.markerTypes = this.parseMarkerTypes(markerTypes); - this.parseSections(sections, post); + this.markups = [] + this.markerTypes = this.parseMarkerTypes(markerTypes) + this.parseSections(sections, post) - return post; + return post } catch (e) { - assert(`Unable to parse mobiledoc: ${e.message}`, false); + assert(`Unable to parse mobiledoc: ${e.message}`, false) } } parseMarkerTypes(markerTypes) { - return markerTypes.map((markerType) => this.parseMarkerType(markerType)); + return markerTypes.map(markerType => this.parseMarkerType(markerType)) } parseMarkerType([tagName, attributesArray]) { - const attributesObject = kvArrayToObject(attributesArray || []); - return this.builder.createMarkup(tagName, attributesObject); + const attributesObject = kvArrayToObject(attributesArray || []) + return this.builder.createMarkup(tagName, attributesObject) } parseSections(sections, post) { - sections.forEach((section) => this.parseSection(section, post)); + sections.forEach(section => this.parseSection(section, post)) } parseSection(section, post) { - let [type] = section; - switch(type) { + let [type] = section + switch (type) { case MOBILEDOC_MARKUP_SECTION_TYPE: - this.parseMarkupSection(section, post); - break; + this.parseMarkupSection(section, post) + break case MOBILEDOC_IMAGE_SECTION_TYPE: - this.parseImageSection(section, post); - break; + this.parseImageSection(section, post) + break case MOBILEDOC_CARD_SECTION_TYPE: - this.parseCardSection(section, post); - break; + this.parseCardSection(section, post) + break case MOBILEDOC_LIST_SECTION_TYPE: - this.parseListSection(section, post); - break; + this.parseListSection(section, post) + break default: - assert(`Unexpected section type ${type}`, false); + assert(`Unexpected section type ${type}`, false) } } parseCardSection([, name, payload], post) { - const section = this.builder.createCardSection(name, payload); - post.sections.append(section); + const section = this.builder.createCardSection(name, payload) + post.sections.append(section) } parseImageSection([, src], post) { - const section = this.builder.createImageSection(src); - post.sections.append(section); + const section = this.builder.createImageSection(src) + post.sections.append(section) } parseMarkupSection([, tagName, markers], post) { - const section = this.builder.createMarkupSection(tagName.toLowerCase() === 'pull-quote' ? 'aside' : tagName); - post.sections.append(section); - this.parseMarkers(markers, section); + const section = this.builder.createMarkupSection(tagName.toLowerCase() === 'pull-quote' ? 'aside' : tagName) + post.sections.append(section) + this.parseMarkers(markers, section) // Strip blank markers after they have been created. This ensures any // markup they include has been correctly populated. filter(section.markers, m => m.isBlank).forEach(m => { - section.markers.remove(m); - }); + section.markers.remove(m) + }) } parseListSection([, tagName, items], post) { - const section = this.builder.createListSection(tagName); - post.sections.append(section); - this.parseListItems(items, section); + const section = this.builder.createListSection(tagName) + post.sections.append(section) + this.parseListItems(items, section) } parseListItems(items, section) { - items.forEach(i => this.parseListItem(i, section)); + items.forEach(i => this.parseListItem(i, section)) } parseListItem(markers, section) { - const item = this.builder.createListItem(); - this.parseMarkers(markers, item); - section.items.append(item); + const item = this.builder.createListItem() + this.parseMarkers(markers, item) + section.items.append(item) } parseMarkers(markers, parent) { - markers.forEach(m => this.parseMarker(m, parent)); + markers.forEach(m => this.parseMarker(m, parent)) } parseMarker([markerTypeIndexes, closeCount, value], parent) { markerTypeIndexes.forEach(index => { - this.markups.push(this.markerTypes[index]); - }); - const marker = this.builder.createMarker(value, this.markups.slice()); - parent.markers.append(marker); - this.markups = this.markups.slice(0, this.markups.length-closeCount); + this.markups.push(this.markerTypes[index]) + }) + const marker = this.builder.createMarker(value, this.markups.slice()) + parent.markers.append(marker) + this.markups = this.markups.slice(0, this.markups.length - closeCount) } } diff --git a/src/js/parsers/mobiledoc/0-3-1.js b/src/js/parsers/mobiledoc/0-3-1.js index 398cfa0fb..80c810767 100644 --- a/src/js/parsers/mobiledoc/0-3-1.js +++ b/src/js/parsers/mobiledoc/0-3-1.js @@ -4,17 +4,17 @@ import { MOBILEDOC_LIST_SECTION_TYPE, MOBILEDOC_CARD_SECTION_TYPE, MOBILEDOC_MARKUP_MARKER_TYPE, - MOBILEDOC_ATOM_MARKER_TYPE -} from 'mobiledoc-kit/renderers/mobiledoc/0-3-1'; -import { kvArrayToObject, filter } from "../../utils/array-utils"; -import assert from 'mobiledoc-kit/utils/assert'; + MOBILEDOC_ATOM_MARKER_TYPE, +} from 'mobiledoc-kit/renderers/mobiledoc/0-3-1' +import { kvArrayToObject, filter } from '../../utils/array-utils' +import assert from 'mobiledoc-kit/utils/assert' /* * Parses from mobiledoc -> post */ export default class MobiledocParser { constructor(builder) { - this.builder = builder; + this.builder = builder } /** @@ -23,144 +23,144 @@ export default class MobiledocParser { */ parse({ sections, markups: markerTypes, cards: cardTypes, atoms: atomTypes }) { try { - const post = this.builder.createPost(); + const post = this.builder.createPost() - this.markups = []; - this.markerTypes = this.parseMarkerTypes(markerTypes); - this.cardTypes = this.parseCardTypes(cardTypes); - this.atomTypes = this.parseAtomTypes(atomTypes); - this.parseSections(sections, post); + this.markups = [] + this.markerTypes = this.parseMarkerTypes(markerTypes) + this.cardTypes = this.parseCardTypes(cardTypes) + this.atomTypes = this.parseAtomTypes(atomTypes) + this.parseSections(sections, post) - return post; + return post } catch (e) { - assert(`Unable to parse mobiledoc: ${e.message}`, false); + assert(`Unable to parse mobiledoc: ${e.message}`, false) } } parseMarkerTypes(markerTypes) { - return markerTypes.map((markerType) => this.parseMarkerType(markerType)); + return markerTypes.map(markerType => this.parseMarkerType(markerType)) } parseMarkerType([tagName, attributesArray]) { - const attributesObject = kvArrayToObject(attributesArray || []); - return this.builder.createMarkup(tagName, attributesObject); + const attributesObject = kvArrayToObject(attributesArray || []) + return this.builder.createMarkup(tagName, attributesObject) } parseCardTypes(cardTypes) { - return cardTypes.map((cardType) => this.parseCardType(cardType)); + return cardTypes.map(cardType => this.parseCardType(cardType)) } parseCardType([cardName, cardPayload]) { - return [cardName, cardPayload]; + return [cardName, cardPayload] } parseAtomTypes(atomTypes) { - return atomTypes.map((atomType) => this.parseAtomType(atomType)); + return atomTypes.map(atomType => this.parseAtomType(atomType)) } parseAtomType([atomName, atomValue, atomPayload]) { - return [atomName, atomValue, atomPayload]; + return [atomName, atomValue, atomPayload] } parseSections(sections, post) { - sections.forEach((section) => this.parseSection(section, post)); + sections.forEach(section => this.parseSection(section, post)) } parseSection(section, post) { - let [type] = section; - switch(type) { + let [type] = section + switch (type) { case MOBILEDOC_MARKUP_SECTION_TYPE: - this.parseMarkupSection(section, post); - break; + this.parseMarkupSection(section, post) + break case MOBILEDOC_IMAGE_SECTION_TYPE: - this.parseImageSection(section, post); - break; + this.parseImageSection(section, post) + break case MOBILEDOC_CARD_SECTION_TYPE: - this.parseCardSection(section, post); - break; + this.parseCardSection(section, post) + break case MOBILEDOC_LIST_SECTION_TYPE: - this.parseListSection(section, post); - break; + this.parseListSection(section, post) + break default: - assert('Unexpected section type ${type}', false); + assert('Unexpected section type ${type}', false) } } getAtomTypeFromIndex(index) { - const atomType = this.atomTypes[index]; - assert(`No atom definition found at index ${index}`, !!atomType); - return atomType; + const atomType = this.atomTypes[index] + assert(`No atom definition found at index ${index}`, !!atomType) + return atomType } getCardTypeFromIndex(index) { - const cardType = this.cardTypes[index]; - assert(`No card definition found at index ${index}`, !!cardType); - return cardType; + const cardType = this.cardTypes[index] + assert(`No card definition found at index ${index}`, !!cardType) + return cardType } parseCardSection([, cardIndex], post) { - const [name, payload] = this.getCardTypeFromIndex(cardIndex); - const section = this.builder.createCardSection(name, payload); - post.sections.append(section); + const [name, payload] = this.getCardTypeFromIndex(cardIndex) + const section = this.builder.createCardSection(name, payload) + post.sections.append(section) } parseImageSection([, src], post) { - const section = this.builder.createImageSection(src); - post.sections.append(section); + const section = this.builder.createImageSection(src) + post.sections.append(section) } parseMarkupSection([, tagName, markers], post) { - const section = this.builder.createMarkupSection(tagName); - post.sections.append(section); - this.parseMarkers(markers, section); + const section = this.builder.createMarkupSection(tagName) + post.sections.append(section) + this.parseMarkers(markers, section) // Strip blank markers after they have been created. This ensures any // markup they include has been correctly populated. filter(section.markers, m => m.isBlank).forEach(m => { - section.markers.remove(m); - }); + section.markers.remove(m) + }) } parseListSection([, tagName, items], post) { - const section = this.builder.createListSection(tagName); - post.sections.append(section); - this.parseListItems(items, section); + const section = this.builder.createListSection(tagName) + post.sections.append(section) + this.parseListItems(items, section) } parseListItems(items, section) { - items.forEach(i => this.parseListItem(i, section)); + items.forEach(i => this.parseListItem(i, section)) } parseListItem(markers, section) { - const item = this.builder.createListItem(); - this.parseMarkers(markers, item); - section.items.append(item); + const item = this.builder.createListItem() + this.parseMarkers(markers, item) + section.items.append(item) } parseMarkers(markers, parent) { - markers.forEach(m => this.parseMarker(m, parent)); + markers.forEach(m => this.parseMarker(m, parent)) } parseMarker([type, markerTypeIndexes, closeCount, value], parent) { markerTypeIndexes.forEach(index => { - this.markups.push(this.markerTypes[index]); - }); + this.markups.push(this.markerTypes[index]) + }) - const marker = this.buildMarkerType(type, value); - parent.markers.append(marker); + const marker = this.buildMarkerType(type, value) + parent.markers.append(marker) - this.markups = this.markups.slice(0, this.markups.length-closeCount); + this.markups = this.markups.slice(0, this.markups.length - closeCount) } buildMarkerType(type, value) { switch (type) { case MOBILEDOC_MARKUP_MARKER_TYPE: - return this.builder.createMarker(value, this.markups.slice()); + return this.builder.createMarker(value, this.markups.slice()) case MOBILEDOC_ATOM_MARKER_TYPE: { - const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value); - return this.builder.createAtom(atomName, atomValue, atomPayload, this.markups.slice()); + const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value) + return this.builder.createAtom(atomName, atomValue, atomPayload, this.markups.slice()) } default: - assert(`Unexpected marker type ${type}`, false); + assert(`Unexpected marker type ${type}`, false) } } } diff --git a/src/js/parsers/mobiledoc/0-3-2.js b/src/js/parsers/mobiledoc/0-3-2.js index 7f9c65344..1b5d6e74d 100644 --- a/src/js/parsers/mobiledoc/0-3-2.js +++ b/src/js/parsers/mobiledoc/0-3-2.js @@ -4,19 +4,19 @@ import { MOBILEDOC_LIST_SECTION_TYPE, MOBILEDOC_CARD_SECTION_TYPE, MOBILEDOC_MARKUP_MARKER_TYPE, - MOBILEDOC_ATOM_MARKER_TYPE -} from '../../renderers/mobiledoc/0-3-2'; + MOBILEDOC_ATOM_MARKER_TYPE, +} from '../../renderers/mobiledoc/0-3-2' -import { kvArrayToObject, filter } from '../../utils/array-utils'; -import assert from '../../utils/assert'; -import { entries } from '../../utils/object-utils'; +import { kvArrayToObject, filter } from '../../utils/array-utils' +import assert from '../../utils/assert' +import { entries } from '../../utils/object-utils' /* * Parses from mobiledoc -> post */ export default class MobiledocParser { constructor(builder) { - this.builder = builder; + this.builder = builder } /** @@ -25,154 +25,154 @@ export default class MobiledocParser { */ parse({ sections, markups: markerTypes, cards: cardTypes, atoms: atomTypes }) { try { - const post = this.builder.createPost(); + const post = this.builder.createPost() - this.markups = []; - this.markerTypes = this.parseMarkerTypes(markerTypes); - this.cardTypes = this.parseCardTypes(cardTypes); - this.atomTypes = this.parseAtomTypes(atomTypes); - this.parseSections(sections, post); + this.markups = [] + this.markerTypes = this.parseMarkerTypes(markerTypes) + this.cardTypes = this.parseCardTypes(cardTypes) + this.atomTypes = this.parseAtomTypes(atomTypes) + this.parseSections(sections, post) - return post; + return post } catch (e) { - assert(`Unable to parse mobiledoc: ${e.message}`, false); + assert(`Unable to parse mobiledoc: ${e.message}`, false) } } parseMarkerTypes(markerTypes) { - return markerTypes.map((markerType) => this.parseMarkerType(markerType)); + return markerTypes.map(markerType => this.parseMarkerType(markerType)) } parseMarkerType([tagName, attributesArray]) { - const attributesObject = kvArrayToObject(attributesArray || []); - return this.builder.createMarkup(tagName, attributesObject); + const attributesObject = kvArrayToObject(attributesArray || []) + return this.builder.createMarkup(tagName, attributesObject) } parseCardTypes(cardTypes) { - return cardTypes.map((cardType) => this.parseCardType(cardType)); + return cardTypes.map(cardType => this.parseCardType(cardType)) } parseCardType([cardName, cardPayload]) { - return [cardName, cardPayload]; + return [cardName, cardPayload] } parseAtomTypes(atomTypes) { - return atomTypes.map((atomType) => this.parseAtomType(atomType)); + return atomTypes.map(atomType => this.parseAtomType(atomType)) } parseAtomType([atomName, atomValue, atomPayload]) { - return [atomName, atomValue, atomPayload]; + return [atomName, atomValue, atomPayload] } parseSections(sections, post) { - sections.forEach((section) => this.parseSection(section, post)); + sections.forEach(section => this.parseSection(section, post)) } parseSection(section, post) { - let [type] = section; - switch(type) { + let [type] = section + switch (type) { case MOBILEDOC_MARKUP_SECTION_TYPE: - this.parseMarkupSection(section, post); - break; + this.parseMarkupSection(section, post) + break case MOBILEDOC_IMAGE_SECTION_TYPE: - this.parseImageSection(section, post); - break; + this.parseImageSection(section, post) + break case MOBILEDOC_CARD_SECTION_TYPE: - this.parseCardSection(section, post); - break; + this.parseCardSection(section, post) + break case MOBILEDOC_LIST_SECTION_TYPE: - this.parseListSection(section, post); - break; + this.parseListSection(section, post) + break default: - assert('Unexpected section type ${type}', false); + assert('Unexpected section type ${type}', false) } } getAtomTypeFromIndex(index) { - const atomType = this.atomTypes[index]; - assert(`No atom definition found at index ${index}`, !!atomType); - return atomType; + const atomType = this.atomTypes[index] + assert(`No atom definition found at index ${index}`, !!atomType) + return atomType } getCardTypeFromIndex(index) { - const cardType = this.cardTypes[index]; - assert(`No card definition found at index ${index}`, !!cardType); - return cardType; + const cardType = this.cardTypes[index] + assert(`No card definition found at index ${index}`, !!cardType) + return cardType } parseCardSection([, cardIndex], post) { - const [name, payload] = this.getCardTypeFromIndex(cardIndex); - const section = this.builder.createCardSection(name, payload); - post.sections.append(section); + const [name, payload] = this.getCardTypeFromIndex(cardIndex) + const section = this.builder.createCardSection(name, payload) + post.sections.append(section) } parseImageSection([, src], post) { - const section = this.builder.createImageSection(src); - post.sections.append(section); + const section = this.builder.createImageSection(src) + post.sections.append(section) } parseMarkupSection([, tagName, markers, attributesArray], post) { - const section = this.builder.createMarkupSection(tagName); - post.sections.append(section); + const section = this.builder.createMarkupSection(tagName) + post.sections.append(section) if (attributesArray) { entries(kvArrayToObject(attributesArray)).forEach(([key, value]) => { - section.setAttribute(key, value); - }); + section.setAttribute(key, value) + }) } - this.parseMarkers(markers, section); + this.parseMarkers(markers, section) // Strip blank markers after they have been created. This ensures any // markup they include has been correctly populated. filter(section.markers, m => m.isBlank).forEach(m => { - section.markers.remove(m); - }); + section.markers.remove(m) + }) } parseListSection([, tagName, items, attributesArray], post) { - const section = this.builder.createListSection(tagName); - post.sections.append(section); + const section = this.builder.createListSection(tagName) + post.sections.append(section) if (attributesArray) { entries(kvArrayToObject(attributesArray)).forEach(([key, value]) => { - section.setAttribute(key, value); - }); + section.setAttribute(key, value) + }) } - this.parseListItems(items, section); + this.parseListItems(items, section) } parseListItems(items, section) { - items.forEach(i => this.parseListItem(i, section)); + items.forEach(i => this.parseListItem(i, section)) } parseListItem(markers, section) { - const item = this.builder.createListItem(); - this.parseMarkers(markers, item); - section.items.append(item); + const item = this.builder.createListItem() + this.parseMarkers(markers, item) + section.items.append(item) } parseMarkers(markers, parent) { - markers.forEach(m => this.parseMarker(m, parent)); + markers.forEach(m => this.parseMarker(m, parent)) } parseMarker([type, markerTypeIndexes, closeCount, value], parent) { markerTypeIndexes.forEach(index => { - this.markups.push(this.markerTypes[index]); - }); + this.markups.push(this.markerTypes[index]) + }) - const marker = this.buildMarkerType(type, value); - parent.markers.append(marker); + const marker = this.buildMarkerType(type, value) + parent.markers.append(marker) - this.markups = this.markups.slice(0, this.markups.length-closeCount); + this.markups = this.markups.slice(0, this.markups.length - closeCount) } buildMarkerType(type, value) { switch (type) { case MOBILEDOC_MARKUP_MARKER_TYPE: - return this.builder.createMarker(value, this.markups.slice()); + return this.builder.createMarker(value, this.markups.slice()) case MOBILEDOC_ATOM_MARKER_TYPE: { - const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value); - return this.builder.createAtom(atomName, atomValue, atomPayload, this.markups.slice()); + const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value) + return this.builder.createAtom(atomName, atomValue, atomPayload, this.markups.slice()) } default: - assert(`Unexpected marker type ${type}`, false); + assert(`Unexpected marker type ${type}`, false) } } } diff --git a/src/js/parsers/mobiledoc/0-3.js b/src/js/parsers/mobiledoc/0-3.js index 00addadfd..03b6f00c1 100644 --- a/src/js/parsers/mobiledoc/0-3.js +++ b/src/js/parsers/mobiledoc/0-3.js @@ -4,17 +4,17 @@ import { MOBILEDOC_LIST_SECTION_TYPE, MOBILEDOC_CARD_SECTION_TYPE, MOBILEDOC_MARKUP_MARKER_TYPE, - MOBILEDOC_ATOM_MARKER_TYPE -} from 'mobiledoc-kit/renderers/mobiledoc/0-3'; -import { kvArrayToObject, filter } from "../../utils/array-utils"; -import assert from 'mobiledoc-kit/utils/assert'; + MOBILEDOC_ATOM_MARKER_TYPE, +} from 'mobiledoc-kit/renderers/mobiledoc/0-3' +import { kvArrayToObject, filter } from '../../utils/array-utils' +import assert from 'mobiledoc-kit/utils/assert' /* * Parses from mobiledoc -> post */ export default class MobiledocParser { constructor(builder) { - this.builder = builder; + this.builder = builder } /** @@ -23,144 +23,144 @@ export default class MobiledocParser { */ parse({ sections, markups: markerTypes, cards: cardTypes, atoms: atomTypes }) { try { - const post = this.builder.createPost(); + const post = this.builder.createPost() - this.markups = []; - this.markerTypes = this.parseMarkerTypes(markerTypes); - this.cardTypes = this.parseCardTypes(cardTypes); - this.atomTypes = this.parseAtomTypes(atomTypes); - this.parseSections(sections, post); + this.markups = [] + this.markerTypes = this.parseMarkerTypes(markerTypes) + this.cardTypes = this.parseCardTypes(cardTypes) + this.atomTypes = this.parseAtomTypes(atomTypes) + this.parseSections(sections, post) - return post; + return post } catch (e) { - assert(`Unable to parse mobiledoc: ${e.message}`, false); + assert(`Unable to parse mobiledoc: ${e.message}`, false) } } parseMarkerTypes(markerTypes) { - return markerTypes.map((markerType) => this.parseMarkerType(markerType)); + return markerTypes.map(markerType => this.parseMarkerType(markerType)) } parseMarkerType([tagName, attributesArray]) { - const attributesObject = kvArrayToObject(attributesArray || []); - return this.builder.createMarkup(tagName, attributesObject); + const attributesObject = kvArrayToObject(attributesArray || []) + return this.builder.createMarkup(tagName, attributesObject) } parseCardTypes(cardTypes) { - return cardTypes.map((cardType) => this.parseCardType(cardType)); + return cardTypes.map(cardType => this.parseCardType(cardType)) } parseCardType([cardName, cardPayload]) { - return [cardName, cardPayload]; + return [cardName, cardPayload] } parseAtomTypes(atomTypes) { - return atomTypes.map((atomType) => this.parseAtomType(atomType)); + return atomTypes.map(atomType => this.parseAtomType(atomType)) } parseAtomType([atomName, atomValue, atomPayload]) { - return [atomName, atomValue, atomPayload]; + return [atomName, atomValue, atomPayload] } parseSections(sections, post) { - sections.forEach((section) => this.parseSection(section, post)); + sections.forEach(section => this.parseSection(section, post)) } parseSection(section, post) { - let [type] = section; - switch(type) { + let [type] = section + switch (type) { case MOBILEDOC_MARKUP_SECTION_TYPE: - this.parseMarkupSection(section, post); - break; + this.parseMarkupSection(section, post) + break case MOBILEDOC_IMAGE_SECTION_TYPE: - this.parseImageSection(section, post); - break; + this.parseImageSection(section, post) + break case MOBILEDOC_CARD_SECTION_TYPE: - this.parseCardSection(section, post); - break; + this.parseCardSection(section, post) + break case MOBILEDOC_LIST_SECTION_TYPE: - this.parseListSection(section, post); - break; + this.parseListSection(section, post) + break default: - assert('Unexpected section type ${type}', false); + assert('Unexpected section type ${type}', false) } } getAtomTypeFromIndex(index) { - const atomType = this.atomTypes[index]; - assert(`No atom definition found at index ${index}`, !!atomType); - return atomType; + const atomType = this.atomTypes[index] + assert(`No atom definition found at index ${index}`, !!atomType) + return atomType } getCardTypeFromIndex(index) { - const cardType = this.cardTypes[index]; - assert(`No card definition found at index ${index}`, !!cardType); - return cardType; + const cardType = this.cardTypes[index] + assert(`No card definition found at index ${index}`, !!cardType) + return cardType } parseCardSection([, cardIndex], post) { - const [name, payload] = this.getCardTypeFromIndex(cardIndex); - const section = this.builder.createCardSection(name, payload); - post.sections.append(section); + const [name, payload] = this.getCardTypeFromIndex(cardIndex) + const section = this.builder.createCardSection(name, payload) + post.sections.append(section) } parseImageSection([, src], post) { - const section = this.builder.createImageSection(src); - post.sections.append(section); + const section = this.builder.createImageSection(src) + post.sections.append(section) } parseMarkupSection([, tagName, markers], post) { - const section = this.builder.createMarkupSection(tagName.toLowerCase() === 'pull-quote' ? 'aside' : tagName); - post.sections.append(section); - this.parseMarkers(markers, section); + const section = this.builder.createMarkupSection(tagName.toLowerCase() === 'pull-quote' ? 'aside' : tagName) + post.sections.append(section) + this.parseMarkers(markers, section) // Strip blank markers after they have been created. This ensures any // markup they include has been correctly populated. filter(section.markers, m => m.isBlank).forEach(m => { - section.markers.remove(m); - }); + section.markers.remove(m) + }) } parseListSection([, tagName, items], post) { - const section = this.builder.createListSection(tagName); - post.sections.append(section); - this.parseListItems(items, section); + const section = this.builder.createListSection(tagName) + post.sections.append(section) + this.parseListItems(items, section) } parseListItems(items, section) { - items.forEach(i => this.parseListItem(i, section)); + items.forEach(i => this.parseListItem(i, section)) } parseListItem(markers, section) { - const item = this.builder.createListItem(); - this.parseMarkers(markers, item); - section.items.append(item); + const item = this.builder.createListItem() + this.parseMarkers(markers, item) + section.items.append(item) } parseMarkers(markers, parent) { - markers.forEach(m => this.parseMarker(m, parent)); + markers.forEach(m => this.parseMarker(m, parent)) } parseMarker([type, markerTypeIndexes, closeCount, value], parent) { markerTypeIndexes.forEach(index => { - this.markups.push(this.markerTypes[index]); - }); + this.markups.push(this.markerTypes[index]) + }) - const marker = this.buildMarkerType(type, value); - parent.markers.append(marker); + const marker = this.buildMarkerType(type, value) + parent.markers.append(marker) - this.markups = this.markups.slice(0, this.markups.length-closeCount); + this.markups = this.markups.slice(0, this.markups.length - closeCount) } buildMarkerType(type, value) { switch (type) { case MOBILEDOC_MARKUP_MARKER_TYPE: - return this.builder.createMarker(value, this.markups.slice()); + return this.builder.createMarker(value, this.markups.slice()) case MOBILEDOC_ATOM_MARKER_TYPE: { - const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value); - return this.builder.createAtom(atomName, atomValue, atomPayload, this.markups.slice()); + const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value) + return this.builder.createAtom(atomName, atomValue, atomPayload, this.markups.slice()) } default: - assert(`Unexpected marker type ${type}`, false); + assert(`Unexpected marker type ${type}`, false) } } } diff --git a/src/js/parsers/mobiledoc/index.js b/src/js/parsers/mobiledoc/index.js index 5c111ca72..50194df92 100644 --- a/src/js/parsers/mobiledoc/index.js +++ b/src/js/parsers/mobiledoc/index.js @@ -1,33 +1,32 @@ -import MobiledocParser_0_2 from './0-2'; -import MobiledocParser_0_3 from './0-3'; -import MobiledocParser_0_3_1 from './0-3-1'; -import MobiledocParser_0_3_2 from './0-3-2'; +import MobiledocParser_0_2 from './0-2' +import MobiledocParser_0_3 from './0-3' +import MobiledocParser_0_3_1 from './0-3-1' +import MobiledocParser_0_3_2 from './0-3-2' -import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_2 } from 'mobiledoc-kit/renderers/mobiledoc/0-2'; -import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3 } from 'mobiledoc-kit/renderers/mobiledoc/0-3'; -import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_1 } from 'mobiledoc-kit/renderers/mobiledoc/0-3-1'; -import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_2 } from 'mobiledoc-kit/renderers/mobiledoc/0-3-2'; -import assert from 'mobiledoc-kit/utils/assert'; +import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_2 } from 'mobiledoc-kit/renderers/mobiledoc/0-2' +import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3 } from 'mobiledoc-kit/renderers/mobiledoc/0-3' +import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_1 } from 'mobiledoc-kit/renderers/mobiledoc/0-3-1' +import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_2 } from 'mobiledoc-kit/renderers/mobiledoc/0-3-2' +import assert from 'mobiledoc-kit/utils/assert' function parseVersion(mobiledoc) { - return mobiledoc.version; + return mobiledoc.version } export default { parse(builder, mobiledoc) { - let version = parseVersion(mobiledoc); + let version = parseVersion(mobiledoc) switch (version) { case MOBILEDOC_VERSION_0_2: - return new MobiledocParser_0_2(builder).parse(mobiledoc); + return new MobiledocParser_0_2(builder).parse(mobiledoc) case MOBILEDOC_VERSION_0_3: - return new MobiledocParser_0_3(builder).parse(mobiledoc); + return new MobiledocParser_0_3(builder).parse(mobiledoc) case MOBILEDOC_VERSION_0_3_1: - return new MobiledocParser_0_3_1(builder).parse(mobiledoc); + return new MobiledocParser_0_3_1(builder).parse(mobiledoc) case MOBILEDOC_VERSION_0_3_2: - return new MobiledocParser_0_3_2(builder).parse(mobiledoc); + return new MobiledocParser_0_3_2(builder).parse(mobiledoc) default: - assert(`Unknown version of mobiledoc parser requested: ${version}`, - false); + assert(`Unknown version of mobiledoc parser requested: ${version}`, false) } - } -}; + }, +} diff --git a/src/js/parsers/section.js b/src/js/parsers/section.js index 93d6fcc29..3a1c70f0b 100644 --- a/src/js/parsers/section.js +++ b/src/js/parsers/section.js @@ -1,54 +1,26 @@ -import { - DEFAULT_TAG_NAME, - VALID_MARKUP_SECTION_TAGNAMES -} from 'mobiledoc-kit/models/markup-section'; - -import { - VALID_LIST_SECTION_TAGNAMES -} from 'mobiledoc-kit/models/list-section'; - -import { - VALID_LIST_ITEM_TAGNAMES -} from 'mobiledoc-kit/models/list-item'; - -import { - LIST_SECTION_TYPE, - LIST_ITEM_TYPE, - MARKUP_SECTION_TYPE -} from 'mobiledoc-kit/models/types'; - -import { - VALID_MARKUP_TAGNAMES -} from 'mobiledoc-kit/models/markup'; - -import { - getAttributes, - normalizeTagName, - isTextNode, - isCommentNode, - NODE_TYPES -} from 'mobiledoc-kit/utils/dom-utils'; - -import { - any, - forEach, - contains -} from 'mobiledoc-kit/utils/array-utils'; - -import { - transformHTMLText, - trimSectionText -} from '../parsers/dom'; - -import assert from '../utils/assert'; - -const SKIPPABLE_ELEMENT_TAG_NAMES = [ - 'style', 'head', 'title', 'meta' -].map(normalizeTagName); - -const NEWLINES = /\s*\n\s*/g; +import { DEFAULT_TAG_NAME, VALID_MARKUP_SECTION_TAGNAMES } from 'mobiledoc-kit/models/markup-section' + +import { VALID_LIST_SECTION_TAGNAMES } from 'mobiledoc-kit/models/list-section' + +import { VALID_LIST_ITEM_TAGNAMES } from 'mobiledoc-kit/models/list-item' + +import { LIST_SECTION_TYPE, LIST_ITEM_TYPE, MARKUP_SECTION_TYPE } from 'mobiledoc-kit/models/types' + +import { VALID_MARKUP_TAGNAMES } from 'mobiledoc-kit/models/markup' + +import { getAttributes, normalizeTagName, isTextNode, isCommentNode, NODE_TYPES } from 'mobiledoc-kit/utils/dom-utils' + +import { any, forEach, contains } from 'mobiledoc-kit/utils/array-utils' + +import { transformHTMLText, trimSectionText } from '../parsers/dom' + +import assert from '../utils/assert' + +const SKIPPABLE_ELEMENT_TAG_NAMES = ['style', 'head', 'title', 'meta'].map(normalizeTagName) + +const NEWLINES = /\s*\n\s*/g function sanitize(text) { - return text.replace(NEWLINES, ' '); + return text.replace(NEWLINES, ' ') } /** @@ -57,124 +29,124 @@ function sanitize(text) { * @private */ class SectionParser { - constructor(builder, options={}) { - this.builder = builder; - this.plugins = options.plugins || []; + constructor(builder, options = {}) { + this.builder = builder + this.plugins = options.plugins || [] } parse(element) { if (this._isSkippable(element)) { - return []; + return [] } - this.sections = []; - this.state = {}; + this.sections = [] + this.state = {} - this._updateStateFromElement(element); + this._updateStateFromElement(element) - let finished = false; + let finished = false // top-level text nodes will be run through parseNode later so avoid running // the node through parserPlugins twice if (!isTextNode(element)) { - finished = this.runPlugins(element); + finished = this.runPlugins(element) } if (!finished) { - let childNodes = isTextNode(element) ? [element] : element.childNodes; + let childNodes = isTextNode(element) ? [element] : element.childNodes forEach(childNodes, el => { - this.parseNode(el); - }); + this.parseNode(el) + }) } - this._closeCurrentSection(); + this._closeCurrentSection() - return this.sections; + return this.sections } runPlugins(node) { - let isNodeFinished = false; + let isNodeFinished = false let env = { - addSection: (section) => { + addSection: section => { // avoid creating empty paragraphs due to wrapper elements around // parser-plugin-handled elements if (this.state.section && this.state.section.isMarkerable && !this.state.section.text && !this.state.text) { - this.state.section = null; + this.state.section = null } else { - this._closeCurrentSection(); + this._closeCurrentSection() } - this.sections.push(section); + this.sections.push(section) }, - addMarkerable: (marker) => { - let { state } = this; - let { section } = state; + addMarkerable: marker => { + let { state } = this + let { section } = state // if the first element doesn't create it's own state and it's plugin // handler uses `addMarkerable` we won't have a section yet if (!section) { - state.text = ''; - state.section = this.builder.createMarkupSection(normalizeTagName('p')); - section = state.section; + state.text = '' + state.section = this.builder.createMarkupSection(normalizeTagName('p')) + section = state.section } assert( 'Markerables can only be appended to markup sections and list item sections', section && section.isMarkerable - ); + ) if (state.text) { - this._createMarker(); + this._createMarker() } - section.markers.append(marker); + section.markers.append(marker) }, nodeFinished() { - isNodeFinished = true; - } - }; - for (let i=0; i. // deals with typical case of
  • Text

  • Text

  • if ( @@ -212,99 +180,99 @@ class SectionParser { tagName === 'p' && !node.nextSibling && contains(VALID_LIST_ITEM_TAGNAMES, normalizeTagName(node.parentElement.tagName)) - ) { - this.parseElementNode(node); - return; + ) { + this.parseElementNode(node) + return } // avoid creating empty paragraphs due to wrapper elements around // section-creating elements if (this.state.section.isMarkerable && !this.state.text && this.state.section.markers.length === 0) { - this.state.section = null; + this.state.section = null } else { - this._closeCurrentSection(); + this._closeCurrentSection() } - this._updateStateFromElement(node); + this._updateStateFromElement(node) } if (this.state.section.isListSection) { // ensure the list section is closed and added to the sections list. // _closeCurrentSection handles pushing list items onto the list section - this._closeCurrentSection(); + this._closeCurrentSection() - forEach(node.childNodes, (node) => { - this.parseNode(node); - }); - return; + forEach(node.childNodes, node => { + this.parseNode(node) + }) + return } } switch (node.nodeType) { case NODE_TYPES.TEXT: - this.parseTextNode(node); - break; + this.parseTextNode(node) + break case NODE_TYPES.ELEMENT: - this.parseElementNode(node); - break; + this.parseElementNode(node) + break } } parseElementNode(element) { - let { state } = this; + let { state } = this - const markups = this._markupsFromElement(element); + const markups = this._markupsFromElement(element) if (markups.length && state.text.length && state.section.isMarkerable) { - this._createMarker(); + this._createMarker() } - state.markups.push(...markups); + state.markups.push(...markups) - forEach(element.childNodes, (node) => { - this.parseNode(node); - }); + forEach(element.childNodes, node => { + this.parseNode(node) + }) if (markups.length && state.text.length && state.section.isMarkerable) { // create the marker started for this node - this._createMarker(); + this._createMarker() } // pop the current markups from the stack - state.markups.splice(-markups.length, markups.length); + state.markups.splice(-markups.length, markups.length) } parseTextNode(textNode) { - let { state } = this; - state.text += sanitize(textNode.textContent); + let { state } = this + state.text += sanitize(textNode.textContent) } _updateStateFromElement(element) { if (isCommentNode(element)) { - return; + return } - let { state } = this; - state.section = this._createSectionFromElement(element); - state.markups = this._markupsFromElement(element); - state.text = ''; + let { state } = this + state.section = this._createSectionFromElement(element) + state.markups = this._markupsFromElement(element) + state.text = '' } _closeCurrentSection() { - let { sections, state } = this; - let lastSection = sections[sections.length - 1]; + let { sections, state } = this + let lastSection = sections[sections.length - 1] if (!state.section) { - return; + return } // close a trailing text node if it exists if (state.text.length && state.section.isMarkerable) { - this._createMarker(); + this._createMarker() } // push listItems onto the listSection or add a new section if (state.section.isListItem && lastSection && lastSection.isListSection) { - trimSectionText(state.section); - lastSection.items.append(state.section); + trimSectionText(state.section) + lastSection.items.append(state.section) } else { // avoid creating empty markup sections, especially useful for indented source if ( @@ -312,85 +280,83 @@ class SectionParser { !state.section.text.trim() && !any(state.section.markers, marker => marker.isAtom) ) { - state.section = null; - state.text = ''; - return; + state.section = null + state.text = '' + return } // remove empty list sections before creating a new section if (lastSection && lastSection.isListSection && lastSection.items.length === 0) { - sections.pop(); + sections.pop() } - sections.push(state.section); + sections.push(state.section) } - state.section = null; - state.text = ''; + state.section = null + state.text = '' } _markupsFromElement(element) { - let { builder } = this; - let markups = []; + let { builder } = this + let markups = [] if (isTextNode(element)) { - return markups; + return markups } - const tagName = normalizeTagName(element.tagName); + const tagName = normalizeTagName(element.tagName) if (this._isValidMarkupForElement(tagName, element)) { - markups.push(builder.createMarkup(tagName, getAttributes(element))); + markups.push(builder.createMarkup(tagName, getAttributes(element))) } - this._markupsFromElementStyle(element).forEach( - markup => markups.push(markup) - ); + this._markupsFromElementStyle(element).forEach(markup => markups.push(markup)) - return markups; + return markups } _isValidMarkupForElement(tagName, element) { if (VALID_MARKUP_TAGNAMES.indexOf(tagName) === -1) { - return false; + return false } else if (tagName === 'b') { // google docs add a that should not // create a "b" markup - return element.style.fontWeight !== 'normal'; + return element.style.fontWeight !== 'normal' } - return true; + return true } _markupsFromElementStyle(element) { - let { builder } = this; - let markups = []; - let { fontStyle, fontWeight } = element.style; + let { builder } = this + let markups = [] + let { fontStyle, fontWeight } = element.style if (fontStyle === 'italic') { - markups.push(builder.createMarkup('em')); + markups.push(builder.createMarkup('em')) } if (fontWeight === 'bold' || fontWeight === '700') { - markups.push(builder.createMarkup('strong')); + markups.push(builder.createMarkup('strong')) } - return markups; + return markups } _createMarker() { - let { state } = this; - let text = transformHTMLText(state.text); - let marker = this.builder.createMarker(text, state.markups); - state.section.markers.append(marker); - state.text = ''; + let { state } = this + let text = transformHTMLText(state.text) + let marker = this.builder.createMarker(text, state.markups) + state.section.markers.append(marker) + state.text = '' } _getSectionDetails(element) { let sectionType, - tagName, - inferredTagName = false; + tagName, + inferredTagName = false if (isTextNode(element)) { - tagName = DEFAULT_TAG_NAME; - sectionType = MARKUP_SECTION_TYPE; - inferredTagName = true; + tagName = DEFAULT_TAG_NAME + sectionType = MARKUP_SECTION_TYPE + inferredTagName = true } else { - tagName = normalizeTagName(element.tagName); + tagName = normalizeTagName(element.tagName) // blockquote>p is valid html and should be treated as a blockquote section // rather than a plain markup section @@ -399,58 +365,58 @@ class SectionParser { element.parentElement && normalizeTagName(element.parentElement.tagName) === 'blockquote' ) { - tagName = 'blockquote'; + tagName = 'blockquote' } if (contains(VALID_LIST_SECTION_TAGNAMES, tagName)) { - sectionType = LIST_SECTION_TYPE; + sectionType = LIST_SECTION_TYPE } else if (contains(VALID_LIST_ITEM_TAGNAMES, tagName)) { - sectionType = LIST_ITEM_TYPE; + sectionType = LIST_ITEM_TYPE } else if (contains(VALID_MARKUP_SECTION_TAGNAMES, tagName)) { - sectionType = MARKUP_SECTION_TYPE; + sectionType = MARKUP_SECTION_TYPE } else { - sectionType = MARKUP_SECTION_TYPE; - tagName = DEFAULT_TAG_NAME; - inferredTagName = true; + sectionType = MARKUP_SECTION_TYPE + tagName = DEFAULT_TAG_NAME + inferredTagName = true } } - return {sectionType, tagName, inferredTagName}; + return { sectionType, tagName, inferredTagName } } _createSectionFromElement(element) { if (isCommentNode(element)) { - return; + return } - let { builder } = this; - let section; - let {tagName, sectionType, inferredTagName} = - this._getSectionDetails(element); + let { builder } = this + let section + let { tagName, sectionType, inferredTagName } = this._getSectionDetails(element) switch (sectionType) { case LIST_SECTION_TYPE: - section = builder.createListSection(tagName); - break; + section = builder.createListSection(tagName) + break case LIST_ITEM_TYPE: - section = builder.createListItem(); - break; + section = builder.createListItem() + break case MARKUP_SECTION_TYPE: - section = builder.createMarkupSection(tagName); - section._inferredTagName = inferredTagName; - break; + section = builder.createMarkupSection(tagName) + section._inferredTagName = inferredTagName + break default: - assert('Cannot parse section from element', false); + assert('Cannot parse section from element', false) } - return section; + return section } _isSkippable(element) { - return element.nodeType === NODE_TYPES.ELEMENT && - contains(SKIPPABLE_ELEMENT_TAG_NAMES, - normalizeTagName(element.tagName)); + return ( + element.nodeType === NODE_TYPES.ELEMENT && + contains(SKIPPABLE_ELEMENT_TAG_NAMES, normalizeTagName(element.tagName)) + ) } } -export default SectionParser; +export default SectionParser diff --git a/src/js/parsers/text.js b/src/js/parsers/text.js index 6c4a9546d..65cbe72ad 100644 --- a/src/js/parsers/text.js +++ b/src/js/parsers/text.js @@ -1,33 +1,27 @@ -import assert from 'mobiledoc-kit/utils/assert'; -import { - MARKUP_SECTION_TYPE, - LIST_SECTION_TYPE -} from 'mobiledoc-kit/models/types'; -import { - DEFAULT_TAG_NAME as DEFAULT_MARKUP_SECTION_TAG_NAME -} from 'mobiledoc-kit/models/markup-section'; +import assert from 'mobiledoc-kit/utils/assert' +import { MARKUP_SECTION_TYPE, LIST_SECTION_TYPE } from 'mobiledoc-kit/models/types' +import { DEFAULT_TAG_NAME as DEFAULT_MARKUP_SECTION_TAG_NAME } from 'mobiledoc-kit/models/markup-section' -const UL_LI_REGEX = /^\* (.*)$/; -const OL_LI_REGEX = /^\d\.? (.*)$/; -const CR = '\r'; -const LF = '\n'; -const CR_REGEX = new RegExp(CR, 'g'); -const CR_LF_REGEX = new RegExp(CR+LF, 'g'); +const UL_LI_REGEX = /^\* (.*)$/ +const OL_LI_REGEX = /^\d\.? (.*)$/ +const CR = '\r' +const LF = '\n' +const CR_REGEX = new RegExp(CR, 'g') +const CR_LF_REGEX = new RegExp(CR + LF, 'g') -export const SECTION_BREAK = LF; +export const SECTION_BREAK = LF function normalizeLineEndings(text) { - return text.replace(CR_LF_REGEX, LF) - .replace(CR_REGEX, LF); + return text.replace(CR_LF_REGEX, LF).replace(CR_REGEX, LF) } export default class TextParser { constructor(builder, options) { - this.builder = builder; - this.options = options; + this.builder = builder + this.options = options - this.post = this.builder.createPost(); - this.prevSection = null; + this.post = this.builder.createPost() + this.prevSection = null } /** @@ -35,62 +29,63 @@ export default class TextParser { * @return {Post} a post abstract */ parse(text) { - text = normalizeLineEndings(text); + text = normalizeLineEndings(text) text.split(SECTION_BREAK).forEach(text => { - let section = this._parseSection(text); - this._appendSection(section); - }); + let section = this._parseSection(text) + this._appendSection(section) + }) - return this.post; + return this.post } _parseSection(text) { let tagName = DEFAULT_MARKUP_SECTION_TAG_NAME, - type = MARKUP_SECTION_TYPE, - section; + type = MARKUP_SECTION_TYPE, + section if (UL_LI_REGEX.test(text)) { - tagName = 'ul'; - type = LIST_SECTION_TYPE; - text = text.match(UL_LI_REGEX)[1]; + tagName = 'ul' + type = LIST_SECTION_TYPE + text = text.match(UL_LI_REGEX)[1] } else if (OL_LI_REGEX.test(text)) { - tagName = 'ol'; - type = LIST_SECTION_TYPE; - text = text.match(OL_LI_REGEX)[1]; + tagName = 'ol' + type = LIST_SECTION_TYPE + text = text.match(OL_LI_REGEX)[1] } - let markers = [this.builder.createMarker(text)]; + let markers = [this.builder.createMarker(text)] switch (type) { case LIST_SECTION_TYPE: { - let item = this.builder.createListItem(markers); - let list = this.builder.createListSection(tagName, [item]); - section = list; - break; + let item = this.builder.createListItem(markers) + let list = this.builder.createListSection(tagName, [item]) + section = list + break } case MARKUP_SECTION_TYPE: - section = this.builder.createMarkupSection(tagName, markers); - break; + section = this.builder.createMarkupSection(tagName, markers) + break default: - assert(`Unknown type encountered ${type}`, false); + assert(`Unknown type encountered ${type}`, false) } - return section; + return section } _appendSection(section) { let isSameListSection = section.isListSection && - this.prevSection && this.prevSection.isListSection && - this.prevSection.tagName === section.tagName; + this.prevSection && + this.prevSection.isListSection && + this.prevSection.tagName === section.tagName if (isSameListSection) { section.items.forEach(item => { - this.prevSection.items.append(item.clone()); - }); + this.prevSection.items.append(item.clone()) + }) } else { - this.post.sections.insertAfter(section, this.prevSection); - this.prevSection = section; + this.post.sections.insertAfter(section, this.prevSection) + this.prevSection = section } } } diff --git a/src/js/renderers/editor-dom.js b/src/js/renderers/editor-dom.js index 3d4bb83e4..95f001dbf 100644 --- a/src/js/renderers/editor-dom.js +++ b/src/js/renderers/editor-dom.js @@ -1,6 +1,6 @@ -import CardNode from 'mobiledoc-kit/models/card-node'; -import { detect, forEach } from 'mobiledoc-kit/utils/array-utils'; -import AtomNode from 'mobiledoc-kit/models/atom-node'; +import CardNode from 'mobiledoc-kit/models/card-node' +import { detect, forEach } from 'mobiledoc-kit/utils/array-utils' +import AtomNode from 'mobiledoc-kit/models/atom-node' import { POST_TYPE, MARKUP_SECTION_TYPE, @@ -9,48 +9,47 @@ import { MARKER_TYPE, IMAGE_SECTION_TYPE, CARD_TYPE, - ATOM_TYPE -} from '../models/types'; -import { startsWith, endsWith } from '../utils/string-utils'; -import { addClassName, removeClassName } from '../utils/dom-utils'; -import { MARKUP_SECTION_ELEMENT_NAMES } from '../models/markup-section'; -import assert from '../utils/assert'; -import { TAB } from 'mobiledoc-kit/utils/characters'; - -export const CARD_ELEMENT_CLASS_NAME = '__mobiledoc-card'; -export const NO_BREAK_SPACE = '\u00A0'; -export const TAB_CHARACTER = '\u2003'; -export const SPACE = ' '; -export const ZWNJ = '\u200c'; -export const ATOM_CLASS_NAME = '-mobiledoc-kit__atom'; -export const EDITOR_HAS_NO_CONTENT_CLASS_NAME = '__has-no-content'; -export const EDITOR_ELEMENT_CLASS_NAME = '__mobiledoc-editor'; + ATOM_TYPE, +} from '../models/types' +import { startsWith, endsWith } from '../utils/string-utils' +import { addClassName, removeClassName } from '../utils/dom-utils' +import { MARKUP_SECTION_ELEMENT_NAMES } from '../models/markup-section' +import assert from '../utils/assert' +import { TAB } from 'mobiledoc-kit/utils/characters' + +export const CARD_ELEMENT_CLASS_NAME = '__mobiledoc-card' +export const NO_BREAK_SPACE = '\u00A0' +export const TAB_CHARACTER = '\u2003' +export const SPACE = ' ' +export const ZWNJ = '\u200c' +export const ATOM_CLASS_NAME = '-mobiledoc-kit__atom' +export const EDITOR_HAS_NO_CONTENT_CLASS_NAME = '__has-no-content' +export const EDITOR_ELEMENT_CLASS_NAME = '__mobiledoc-editor' function createElementFromMarkup(doc, markup) { - let element = doc.createElement(markup.tagName); + let element = doc.createElement(markup.tagName) Object.keys(markup.attributes).forEach(k => { - element.setAttribute(k, markup.attributes[k]); - }); - return element; + element.setAttribute(k, markup.attributes[k]) + }) + return element } -const TWO_SPACES = `${SPACE}${SPACE}`; -const SPACE_AND_NO_BREAK = `${SPACE}${NO_BREAK_SPACE}`; -const SPACES_REGEX = new RegExp(TWO_SPACES, 'g'); -const TAB_REGEX = new RegExp(TAB, 'g'); -const endsWithSpace = function(text) { - return endsWith(text, SPACE); -}; -const startsWithSpace = function(text) { - return startsWith(text, SPACE); -}; +const TWO_SPACES = `${SPACE}${SPACE}` +const SPACE_AND_NO_BREAK = `${SPACE}${NO_BREAK_SPACE}` +const SPACES_REGEX = new RegExp(TWO_SPACES, 'g') +const TAB_REGEX = new RegExp(TAB, 'g') +const endsWithSpace = function (text) { + return endsWith(text, SPACE) +} +const startsWithSpace = function (text) { + return startsWith(text, SPACE) +} // FIXME: This can be done more efficiently with a single pass // building a correct string based on the original. function renderHTMLText(marker) { - let text = marker.value; - text = text.replace(SPACES_REGEX, SPACE_AND_NO_BREAK) - .replace(TAB_REGEX, TAB_CHARACTER); + let text = marker.value + text = text.replace(SPACES_REGEX, SPACE_AND_NO_BREAK).replace(TAB_REGEX, TAB_CHARACTER) // If the first marker has a leading space or the last marker has a // trailing space, the browser will collapse the space when we position @@ -58,76 +57,80 @@ function renderHTMLText(marker) { // See https://github.com/bustle/mobiledoc-kit/issues/68 // and https://github.com/bustle/mobiledoc-kit/issues/75 if (marker.isMarker && endsWithSpace(text) && !marker.next) { - text = text.substr(0, text.length - 1) + NO_BREAK_SPACE; + text = text.substr(0, text.length - 1) + NO_BREAK_SPACE } - if (marker.isMarker && startsWithSpace(text) && - (!marker.prev || (marker.prev.isMarker && endsWithSpace(marker.prev.value)))) { - text = NO_BREAK_SPACE + text.substr(1); + if ( + marker.isMarker && + startsWithSpace(text) && + (!marker.prev || (marker.prev.isMarker && endsWithSpace(marker.prev.value))) + ) { + text = NO_BREAK_SPACE + text.substr(1) } - return text; + return text } // ascends from element upward, returning the last parent node that is not // parentElement function penultimateParentOf(element, parentElement) { - while (parentElement && - element.parentNode !== parentElement && - element.parentNode !== document.body // ensure the while loop stops - ) { - element = element.parentNode; + while ( + parentElement && + element.parentNode !== parentElement && + element.parentNode !== document.body // ensure the while loop stops + ) { + element = element.parentNode } - return element; + return element } function setSectionAttributesOnElement(section, element) { section.eachAttribute((key, value) => { - element.setAttribute(key, value); - }); + element.setAttribute(key, value) + }) } function renderMarkupSection(section) { - let element; + let element if (MARKUP_SECTION_ELEMENT_NAMES.indexOf(section.tagName) !== -1) { - element = document.createElement(section.tagName); + element = document.createElement(section.tagName) } else { - element = document.createElement('div'); - addClassName(element, section.tagName); + element = document.createElement('div') + addClassName(element, section.tagName) } - setSectionAttributesOnElement(section, element); + setSectionAttributesOnElement(section, element) - return element; + return element } function renderListSection(section) { - let element = document.createElement(section.tagName); + let element = document.createElement(section.tagName) - setSectionAttributesOnElement(section, element); + setSectionAttributesOnElement(section, element) - return element; + return element } function renderListItem() { - return document.createElement('li'); + return document.createElement('li') } function renderCursorPlaceholder() { - return document.createElement('br'); + return document.createElement('br') } function renderInlineCursorPlaceholder() { - return document.createTextNode(ZWNJ); + return document.createTextNode(ZWNJ) } function renderCard() { - let wrapper = document.createElement('div'); - let cardElement = document.createElement('div'); - cardElement.contentEditable = false; - addClassName(cardElement, CARD_ELEMENT_CLASS_NAME); - wrapper.appendChild(renderInlineCursorPlaceholder()); - wrapper.appendChild(cardElement); - wrapper.appendChild(renderInlineCursorPlaceholder()); - return { wrapper, cardElement }; + let wrapper = document.createElement('div') + let cardElement = document.createElement('div') + cardElement.contentEditable = false + addClassName(cardElement, CARD_ELEMENT_CLASS_NAME) + wrapper.appendChild(renderInlineCursorPlaceholder()) + wrapper.appendChild(cardElement) + wrapper.appendChild(renderInlineCursorPlaceholder()) + return { wrapper, cardElement } } /** @@ -136,65 +139,64 @@ function renderCard() { * @private */ function wrapElement(element, openedMarkups) { - let wrappedElement = element; + let wrappedElement = element - for (let i=openedMarkups.length - 1; i>=0; i--) { - let markup = openedMarkups[i]; - let openedElement = createElementFromMarkup(document, markup); - openedElement.appendChild(wrappedElement); - wrappedElement = openedElement; + for (let i = openedMarkups.length - 1; i >= 0; i--) { + let markup = openedMarkups[i] + let openedElement = createElementFromMarkup(document, markup) + openedElement.appendChild(wrappedElement) + wrappedElement = openedElement } - return wrappedElement; + return wrappedElement } // Attach the element to its parent element at the correct position based on the // previousRenderNode -function attachElementToParent(element, parentElement, previousRenderNode=null) { +function attachElementToParent(element, parentElement, previousRenderNode = null) { if (previousRenderNode) { - let previousSibling = previousRenderNode.element; - let previousSiblingPenultimate = penultimateParentOf(previousSibling, - parentElement); - parentElement.insertBefore(element, previousSiblingPenultimate.nextSibling); + let previousSibling = previousRenderNode.element + let previousSiblingPenultimate = penultimateParentOf(previousSibling, parentElement) + parentElement.insertBefore(element, previousSiblingPenultimate.nextSibling) } else { - parentElement.insertBefore(element, parentElement.firstChild); + parentElement.insertBefore(element, parentElement.firstChild) } } function renderAtom(atom, element, previousRenderNode) { - let atomElement = document.createElement('span'); - atomElement.contentEditable = false; + let atomElement = document.createElement('span') + atomElement.contentEditable = false - let wrapper = document.createElement('span'); - addClassName(wrapper, ATOM_CLASS_NAME); - let headTextNode = renderInlineCursorPlaceholder(); - let tailTextNode = renderInlineCursorPlaceholder(); + let wrapper = document.createElement('span') + addClassName(wrapper, ATOM_CLASS_NAME) + let headTextNode = renderInlineCursorPlaceholder() + let tailTextNode = renderInlineCursorPlaceholder() - wrapper.appendChild(headTextNode); - wrapper.appendChild(atomElement); - wrapper.appendChild(tailTextNode); + wrapper.appendChild(headTextNode) + wrapper.appendChild(atomElement) + wrapper.appendChild(tailTextNode) - let wrappedElement = wrapElement(wrapper, atom.openedMarkups); - attachElementToParent(wrappedElement, element, previousRenderNode); + let wrappedElement = wrapElement(wrapper, atom.openedMarkups) + attachElementToParent(wrappedElement, element, previousRenderNode) return { markupElement: wrappedElement, wrapper, atomElement, headTextNode, - tailTextNode - }; + tailTextNode, + } } function getNextMarkerElement(renderNode) { - let element = renderNode.element.parentNode; - let marker = renderNode.postNode; - let closedCount = marker.closedMarkups.length; + let element = renderNode.element.parentNode + let marker = renderNode.postNode + let closedCount = marker.closedMarkups.length while (closedCount--) { - element = element.parentNode; + element = element.parentNode } - return element; + return element } /** @@ -210,294 +212,272 @@ function getNextMarkerElement(renderNode) { * @private */ function renderMarker(marker, parentElement, previousRenderNode) { - let text = renderHTMLText(marker); + let text = renderHTMLText(marker) - let element = document.createTextNode(text); - let markupElement = wrapElement(element, marker.openedMarkups); - attachElementToParent(markupElement, parentElement, previousRenderNode); + let element = document.createTextNode(text) + let markupElement = wrapElement(element, marker.openedMarkups) + attachElementToParent(markupElement, parentElement, previousRenderNode) - return { element, markupElement }; + return { element, markupElement } } // Attach the render node's element to the DOM, // replacing the originalElement if it exists -function attachRenderNodeElementToDOM(renderNode, originalElement=null) { - const element = renderNode.element; - const hasRendered = !!originalElement; +function attachRenderNodeElementToDOM(renderNode, originalElement = null) { + const element = renderNode.element + const hasRendered = !!originalElement if (hasRendered) { - let parentElement = renderNode.parent.element; - parentElement.replaceChild(element, originalElement); + let parentElement = renderNode.parent.element + parentElement.replaceChild(element, originalElement) } else { - let parentElement, nextSiblingElement; + let parentElement, nextSiblingElement if (renderNode.prev) { - let previousElement = renderNode.prev.element; - parentElement = previousElement.parentNode; - nextSiblingElement = previousElement.nextSibling; + let previousElement = renderNode.prev.element + parentElement = previousElement.parentNode + nextSiblingElement = previousElement.nextSibling } else { - parentElement = renderNode.parent.element; - nextSiblingElement = parentElement.firstChild; + parentElement = renderNode.parent.element + nextSiblingElement = parentElement.firstChild } - parentElement.insertBefore(element, nextSiblingElement); + parentElement.insertBefore(element, nextSiblingElement) } } function removeRenderNodeSectionFromParent(renderNode, section) { - const parent = renderNode.parent.postNode; - parent.sections.remove(section); + const parent = renderNode.parent.postNode + parent.sections.remove(section) } function removeRenderNodeElementFromParent(renderNode) { if (renderNode.element && renderNode.element.parentNode) { - renderNode.element.parentNode.removeChild(renderNode.element); + renderNode.element.parentNode.removeChild(renderNode.element) } } -function validateCards(cards=[]) { +function validateCards(cards = []) { forEach(cards, card => { - assert( - `Card "${card.name}" must define type "dom", has: "${card.type}"`, - card.type === 'dom' - ); - assert( - `Card "${card.name}" must define \`render\` method`, - !!card.render - ); - }); - return cards; + assert(`Card "${card.name}" must define type "dom", has: "${card.type}"`, card.type === 'dom') + assert(`Card "${card.name}" must define \`render\` method`, !!card.render) + }) + return cards } -function validateAtoms(atoms=[]) { +function validateAtoms(atoms = []) { forEach(atoms, atom => { - assert( - `Atom "${atom.name}" must define type "dom", has: "${atom.type}"`, - atom.type === 'dom' - ); - assert( - `Atom "${atom.name}" must define \`render\` method`, - !!atom.render - ); - }); - return atoms; + assert(`Atom "${atom.name}" must define type "dom", has: "${atom.type}"`, atom.type === 'dom') + assert(`Atom "${atom.name}" must define \`render\` method`, !!atom.render) + }) + return atoms } class Visitor { constructor(editor, cards, atoms, unknownCardHandler, unknownAtomHandler, options) { - this.editor = editor; - this.cards = validateCards(cards); - this.atoms = validateAtoms(atoms); - this.unknownCardHandler = unknownCardHandler; - this.unknownAtomHandler = unknownAtomHandler; - this.options = options; + this.editor = editor + this.cards = validateCards(cards) + this.atoms = validateAtoms(atoms) + this.unknownCardHandler = unknownCardHandler + this.unknownAtomHandler = unknownAtomHandler + this.options = options } _findCard(cardName) { - let card = detect(this.cards, card => card.name === cardName); - return card || this._createUnknownCard(cardName); + let card = detect(this.cards, card => card.name === cardName) + return card || this._createUnknownCard(cardName) } _createUnknownCard(cardName) { - assert( - `Unknown card "${cardName}" found, but no unknownCardHandler is defined`, - !!this.unknownCardHandler - ); + assert(`Unknown card "${cardName}" found, but no unknownCardHandler is defined`, !!this.unknownCardHandler) return { name: cardName, type: 'dom', render: this.unknownCardHandler, - edit: this.unknownCardHandler - }; + edit: this.unknownCardHandler, + } } _findAtom(atomName) { - let atom = detect(this.atoms, atom => atom.name === atomName); - return atom || this._createUnknownAtom(atomName); + let atom = detect(this.atoms, atom => atom.name === atomName) + return atom || this._createUnknownAtom(atomName) } _createUnknownAtom(atomName) { - assert( - `Unknown atom "${atomName}" found, but no unknownAtomHandler is defined`, - !!this.unknownAtomHandler - ); + assert(`Unknown atom "${atomName}" found, but no unknownAtomHandler is defined`, !!this.unknownAtomHandler) return { name: atomName, type: 'dom', - render: this.unknownAtomHandler - }; + render: this.unknownAtomHandler, + } } [POST_TYPE](renderNode, post, visit) { if (!renderNode.element) { - renderNode.element = document.createElement('div'); + renderNode.element = document.createElement('div') } - addClassName(renderNode.element, EDITOR_ELEMENT_CLASS_NAME); + addClassName(renderNode.element, EDITOR_ELEMENT_CLASS_NAME) if (post.hasContent) { - removeClassName(renderNode.element, EDITOR_HAS_NO_CONTENT_CLASS_NAME); + removeClassName(renderNode.element, EDITOR_HAS_NO_CONTENT_CLASS_NAME) } else { - addClassName(renderNode.element, EDITOR_HAS_NO_CONTENT_CLASS_NAME); + addClassName(renderNode.element, EDITOR_HAS_NO_CONTENT_CLASS_NAME) } - visit(renderNode, post.sections); + visit(renderNode, post.sections) } [MARKUP_SECTION_TYPE](renderNode, section, visit) { - const originalElement = renderNode.element; + const originalElement = renderNode.element // Always rerender the section -- its tag name or attributes may have changed. // TODO make this smarter, only rerendering and replacing the element when necessary - renderNode.element = renderMarkupSection(section); - renderNode.cursorElement = null; - attachRenderNodeElementToDOM(renderNode, originalElement); + renderNode.element = renderMarkupSection(section) + renderNode.cursorElement = null + attachRenderNodeElementToDOM(renderNode, originalElement) if (section.isBlank) { - let cursorPlaceholder = renderCursorPlaceholder(); - renderNode.element.appendChild(cursorPlaceholder); - renderNode.cursorElement = cursorPlaceholder; + let cursorPlaceholder = renderCursorPlaceholder() + renderNode.element.appendChild(cursorPlaceholder) + renderNode.cursorElement = cursorPlaceholder } else { - const visitAll = true; - visit(renderNode, section.markers, visitAll); + const visitAll = true + visit(renderNode, section.markers, visitAll) } } [LIST_SECTION_TYPE](renderNode, section, visit) { - const originalElement = renderNode.element; + const originalElement = renderNode.element - renderNode.element = renderListSection(section); - attachRenderNodeElementToDOM(renderNode, originalElement); + renderNode.element = renderListSection(section) + attachRenderNodeElementToDOM(renderNode, originalElement) - const visitAll = true; - visit(renderNode, section.items, visitAll); + const visitAll = true + visit(renderNode, section.items, visitAll) } [LIST_ITEM_TYPE](renderNode, item, visit) { // FIXME do we need to do anything special for rerenders? - renderNode.element = renderListItem(); - renderNode.cursorElement = null; - attachRenderNodeElementToDOM(renderNode, null); + renderNode.element = renderListItem() + renderNode.cursorElement = null + attachRenderNodeElementToDOM(renderNode, null) if (item.isBlank) { - let cursorPlaceholder = renderCursorPlaceholder(); - renderNode.element.appendChild(cursorPlaceholder); - renderNode.cursorElement = cursorPlaceholder; + let cursorPlaceholder = renderCursorPlaceholder() + renderNode.element.appendChild(cursorPlaceholder) + renderNode.cursorElement = cursorPlaceholder } else { - const visitAll = true; - visit(renderNode, item.markers, visitAll); + const visitAll = true + visit(renderNode, item.markers, visitAll) } } [MARKER_TYPE](renderNode, marker) { - let parentElement; + let parentElement if (renderNode.prev) { - parentElement = getNextMarkerElement(renderNode.prev); + parentElement = getNextMarkerElement(renderNode.prev) } else { - parentElement = renderNode.parent.element; + parentElement = renderNode.parent.element } - let { element, markupElement } = - renderMarker(marker, parentElement, renderNode.prev); + let { element, markupElement } = renderMarker(marker, parentElement, renderNode.prev) - renderNode.element = element; - renderNode.markupElement = markupElement; + renderNode.element = element + renderNode.markupElement = markupElement } [IMAGE_SECTION_TYPE](renderNode, section) { if (renderNode.element) { if (renderNode.element.src !== section.src) { - renderNode.element.src = section.src; + renderNode.element.src = section.src } } else { - let element = document.createElement('img'); - element.src = section.src; + let element = document.createElement('img') + element.src = section.src if (renderNode.prev) { - let previousElement = renderNode.prev.element; - let nextElement = previousElement.nextSibling; + let previousElement = renderNode.prev.element + let nextElement = previousElement.nextSibling if (nextElement) { - nextElement.parentNode.insertBefore(element, nextElement); + nextElement.parentNode.insertBefore(element, nextElement) } } if (!element.parentNode) { - renderNode.parent.element.appendChild(element); + renderNode.parent.element.appendChild(element) } - renderNode.element = element; + renderNode.element = element } } [CARD_TYPE](renderNode, section) { - const originalElement = renderNode.element; - const {editor, options} = this; + const originalElement = renderNode.element + const { editor, options } = this - const card = this._findCard(section.name); + const card = this._findCard(section.name) - let { wrapper, cardElement } = renderCard(); - renderNode.element = wrapper; - attachRenderNodeElementToDOM(renderNode, originalElement); + let { wrapper, cardElement } = renderCard() + renderNode.element = wrapper + attachRenderNodeElementToDOM(renderNode, originalElement) - const cardNode = new CardNode( - editor, card, section, cardElement, options); - renderNode.cardNode = cardNode; + const cardNode = new CardNode(editor, card, section, cardElement, options) + renderNode.cardNode = cardNode - const initialMode = section._initialMode; - cardNode[initialMode](); + const initialMode = section._initialMode + cardNode[initialMode]() } [ATOM_TYPE](renderNode, atomModel) { - let parentElement; + let parentElement if (renderNode.prev) { - parentElement = getNextMarkerElement(renderNode.prev); + parentElement = getNextMarkerElement(renderNode.prev) } else { - parentElement = renderNode.parent.element; + parentElement = renderNode.parent.element } - const { editor, options } = this; - const { - wrapper, - markupElement, - atomElement, - headTextNode, - tailTextNode - } = renderAtom(atomModel, parentElement, renderNode.prev); - const atom = this._findAtom(atomModel.name); - - let atomNode = renderNode.atomNode; + const { editor, options } = this + const { wrapper, markupElement, atomElement, headTextNode, tailTextNode } = renderAtom( + atomModel, + parentElement, + renderNode.prev + ) + const atom = this._findAtom(atomModel.name) + + let atomNode = renderNode.atomNode if (!atomNode) { // create new AtomNode - atomNode = new AtomNode(editor, atom, atomModel, atomElement, options); + atomNode = new AtomNode(editor, atom, atomModel, atomElement, options) } else { // retarget atomNode to new atom element - atomNode.element = atomElement; + atomNode.element = atomElement } - atomNode.render(); + atomNode.render() - renderNode.atomNode = atomNode; - renderNode.element = wrapper; - renderNode.headTextNode = headTextNode; - renderNode.tailTextNode = tailTextNode; - renderNode.markupElement = markupElement; + renderNode.atomNode = atomNode + renderNode.element = wrapper + renderNode.headTextNode = headTextNode + renderNode.tailTextNode = tailTextNode + renderNode.markupElement = markupElement } } let destroyHooks = { [POST_TYPE](/*renderNode, post*/) { - assert('post destruction is not supported by the renderer', false); + assert('post destruction is not supported by the renderer', false) }, [MARKUP_SECTION_TYPE](renderNode, section) { - removeRenderNodeSectionFromParent(renderNode, section); - removeRenderNodeElementFromParent(renderNode); + removeRenderNodeSectionFromParent(renderNode, section) + removeRenderNodeElementFromParent(renderNode) }, [LIST_SECTION_TYPE](renderNode, section) { - removeRenderNodeSectionFromParent(renderNode, section); - removeRenderNodeElementFromParent(renderNode); + removeRenderNodeSectionFromParent(renderNode, section) + removeRenderNodeElementFromParent(renderNode) }, [LIST_ITEM_TYPE](renderNode, li) { - removeRenderNodeSectionFromParent(renderNode, li); - removeRenderNodeElementFromParent(renderNode); + removeRenderNodeSectionFromParent(renderNode, li) + removeRenderNodeElementFromParent(renderNode) }, [MARKER_TYPE](renderNode, marker) { @@ -507,57 +487,57 @@ let destroyHooks = { // If an atom throws during render we may end up later destroying a renderNode // that has not rendered yet, so exit early here if so. if (!renderNode.isRendered) { - return; + return } - let { markupElement } = renderNode; + let { markupElement } = renderNode if (marker.section) { - marker.section.markers.remove(marker); + marker.section.markers.remove(marker) } if (markupElement.parentNode) { // if no parentNode, the browser already removed this element - markupElement.parentNode.removeChild(markupElement); + markupElement.parentNode.removeChild(markupElement) } }, [IMAGE_SECTION_TYPE](renderNode, section) { - removeRenderNodeSectionFromParent(renderNode, section); - removeRenderNodeElementFromParent(renderNode); + removeRenderNodeSectionFromParent(renderNode, section) + removeRenderNodeElementFromParent(renderNode) }, [CARD_TYPE](renderNode, section) { if (renderNode.cardNode) { - renderNode.cardNode.teardown(); + renderNode.cardNode.teardown() } - removeRenderNodeSectionFromParent(renderNode, section); - removeRenderNodeElementFromParent(renderNode); + removeRenderNodeSectionFromParent(renderNode, section) + removeRenderNodeElementFromParent(renderNode) }, [ATOM_TYPE](renderNode, atom) { if (renderNode.atomNode) { - renderNode.atomNode.teardown(); + renderNode.atomNode.teardown() } // an atom is a kind of marker so just call its destroy hook vs copying here - destroyHooks[MARKER_TYPE](renderNode, atom); - } -}; + destroyHooks[MARKER_TYPE](renderNode, atom) + }, +} // removes children from parentNode (a RenderNode) that are scheduled for removal -function removeDestroyedChildren(parentNode, forceRemoval=false) { - let child = parentNode.childNodes.head; - let nextChild, method; +function removeDestroyedChildren(parentNode, forceRemoval = false) { + let child = parentNode.childNodes.head + let nextChild, method while (child) { - nextChild = child.next; + nextChild = child.next if (child.isRemoved || forceRemoval) { - removeDestroyedChildren(child, true); - method = child.postNode.type; - assert(`editor-dom cannot destroy "${method}"`, !!destroyHooks[method]); - destroyHooks[method](child, child.postNode); - parentNode.childNodes.remove(child); + removeDestroyedChildren(child, true) + method = child.postNode.type + assert(`editor-dom cannot destroy "${method}"`, !!destroyHooks[method]) + destroyHooks[method](child, child.postNode) + parentNode.childNodes.remove(child) } - child = nextChild; + child = nextChild } } @@ -565,58 +545,57 @@ function removeDestroyedChildren(parentNode, forceRemoval=false) { // create one, insert it into the tree, and return it function lookupNode(renderTree, parentNode, postNode, previousNode) { if (postNode.renderNode) { - return postNode.renderNode; + return postNode.renderNode } else { - const renderNode = renderTree.buildRenderNode(postNode); - parentNode.childNodes.insertAfter(renderNode, previousNode); - return renderNode; + const renderNode = renderTree.buildRenderNode(postNode) + parentNode.childNodes.insertAfter(renderNode, previousNode) + return renderNode } } export default class Renderer { constructor(editor, cards, atoms, unknownCardHandler, unknownAtomHandler, options) { - this.editor = editor; - this.visitor = new Visitor(editor, cards, atoms, unknownCardHandler, unknownAtomHandler, options); - this.nodes = []; - this.hasRendered = false; + this.editor = editor + this.visitor = new Visitor(editor, cards, atoms, unknownCardHandler, unknownAtomHandler, options) + this.nodes = [] + this.hasRendered = false } destroy() { if (!this.hasRendered) { - return; + return } - let renderNode = this.renderTree.rootNode; - let force = true; - removeDestroyedChildren(renderNode, force); + let renderNode = this.renderTree.rootNode + let force = true + removeDestroyedChildren(renderNode, force) } - visit(renderTree, parentNode, postNodes, visitAll=false) { - let previousNode; + visit(renderTree, parentNode, postNodes, visitAll = false) { + let previousNode postNodes.forEach(postNode => { - let node = lookupNode(renderTree, parentNode, postNode, previousNode); + let node = lookupNode(renderTree, parentNode, postNode, previousNode) if (node.isDirty || visitAll) { - this.nodes.push(node); + this.nodes.push(node) } - previousNode = node; - }); + previousNode = node + }) } render(renderTree) { - this.hasRendered = true; - this.renderTree = renderTree; - let renderNode = renderTree.rootNode; - let method, postNode; + this.hasRendered = true + this.renderTree = renderTree + let renderNode = renderTree.rootNode + let method, postNode while (renderNode) { - removeDestroyedChildren(renderNode); - postNode = renderNode.postNode; - - method = postNode.type; - assert(`EditorDom visitor cannot handle type ${method}`, !!this.visitor[method]); - this.visitor[method](renderNode, postNode, - (...args) => this.visit(renderTree, ...args)); - renderNode.markClean(); - renderNode = this.nodes.shift(); + removeDestroyedChildren(renderNode) + postNode = renderNode.postNode + + method = postNode.type + assert(`EditorDom visitor cannot handle type ${method}`, !!this.visitor[method]) + this.visitor[method](renderNode, postNode, (...args) => this.visit(renderTree, ...args)) + renderNode.markClean() + renderNode = this.nodes.shift() } } } diff --git a/src/js/renderers/mobiledoc/0-2.js b/src/js/renderers/mobiledoc/0-2.js index 5e85061bd..a01c7b9ee 100644 --- a/src/js/renderers/mobiledoc/0-2.js +++ b/src/js/renderers/mobiledoc/0-2.js @@ -1,5 +1,5 @@ -import {visit, visitArray, compile} from '../../utils/compiler'; -import { objectToSortedKVArray } from '../../utils/array-utils'; +import { visit, visitArray, compile } from '../../utils/compiler' +import { objectToSortedKVArray } from '../../utils/array-utils' import { POST_TYPE, MARKUP_SECTION_TYPE, @@ -8,103 +8,103 @@ import { MARKER_TYPE, MARKUP_TYPE, IMAGE_SECTION_TYPE, - CARD_TYPE -} from '../../models/types'; + CARD_TYPE, +} from '../../models/types' -export const MOBILEDOC_VERSION = '0.2.0'; -export const MOBILEDOC_MARKUP_SECTION_TYPE = 1; -export const MOBILEDOC_IMAGE_SECTION_TYPE = 2; -export const MOBILEDOC_LIST_SECTION_TYPE = 3; -export const MOBILEDOC_CARD_SECTION_TYPE = 10; +export const MOBILEDOC_VERSION = '0.2.0' +export const MOBILEDOC_MARKUP_SECTION_TYPE = 1 +export const MOBILEDOC_IMAGE_SECTION_TYPE = 2 +export const MOBILEDOC_LIST_SECTION_TYPE = 3 +export const MOBILEDOC_CARD_SECTION_TYPE = 10 const visitor = { [POST_TYPE](node, opcodes) { - opcodes.push(['openPost']); - visitArray(visitor, node.sections, opcodes); + opcodes.push(['openPost']) + visitArray(visitor, node.sections, opcodes) }, [MARKUP_SECTION_TYPE](node, opcodes) { - opcodes.push(['openMarkupSection', node.tagName]); - visitArray(visitor, node.markers, opcodes); + opcodes.push(['openMarkupSection', node.tagName]) + visitArray(visitor, node.markers, opcodes) }, [LIST_SECTION_TYPE](node, opcodes) { - opcodes.push(['openListSection', node.tagName]); - visitArray(visitor, node.items, opcodes); + opcodes.push(['openListSection', node.tagName]) + visitArray(visitor, node.items, opcodes) }, [LIST_ITEM_TYPE](node, opcodes) { - opcodes.push(['openListItem']); - visitArray(visitor, node.markers, opcodes); + opcodes.push(['openListItem']) + visitArray(visitor, node.markers, opcodes) }, [IMAGE_SECTION_TYPE](node, opcodes) { - opcodes.push(['openImageSection', node.src]); + opcodes.push(['openImageSection', node.src]) }, [CARD_TYPE](node, opcodes) { - opcodes.push(['openCardSection', node.name, node.payload]); + opcodes.push(['openCardSection', node.name, node.payload]) }, [MARKER_TYPE](node, opcodes) { - opcodes.push(['openMarker', node.closedMarkups.length, node.value]); - visitArray(visitor, node.openedMarkups, opcodes); + opcodes.push(['openMarker', node.closedMarkups.length, node.value]) + visitArray(visitor, node.openedMarkups, opcodes) }, [MARKUP_TYPE](node, opcodes) { - opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]); - } -}; + opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]) + }, +} const postOpcodeCompiler = { openMarker(closeCount, value) { - this.markupMarkerIds = []; - this.markers.push([ - this.markupMarkerIds, - closeCount, - value || '' - ]); + this.markupMarkerIds = [] + this.markers.push([this.markupMarkerIds, closeCount, value || '']) }, openMarkupSection(tagName) { - this.markers = []; - this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]); + this.markers = [] + this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]) }, openListSection(tagName) { - this.items = []; - this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]); + this.items = [] + this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]) }, openListItem() { - this.markers = []; - this.items.push(this.markers); + this.markers = [] + this.items.push(this.markers) }, openImageSection(url) { - this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]); + this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]) }, openCardSection(name, payload) { - this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, name, payload]); + this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, name, payload]) }, openPost() { - this.markerTypes = []; - this.sections = []; + this.markerTypes = [] + this.sections = [] this.result = { version: MOBILEDOC_VERSION, - sections: [this.markerTypes, this.sections] - }; + sections: [this.markerTypes, this.sections], + } }, openMarkup(tagName, attributes) { - const index = this._findOrAddMarkerTypeIndex(tagName, attributes); - this.markupMarkerIds.push(index); + const index = this._findOrAddMarkerTypeIndex(tagName, attributes) + this.markupMarkerIds.push(index) }, _findOrAddMarkerTypeIndex(tagName, attributesArray) { - if (!this._markerTypeCache) { this._markerTypeCache = {}; } - const key = `${tagName}-${attributesArray.join('-')}`; + if (!this._markerTypeCache) { + this._markerTypeCache = {} + } + const key = `${tagName}-${attributesArray.join('-')}` - let index = this._markerTypeCache[key]; + let index = this._markerTypeCache[key] if (index === undefined) { - let markerType = [tagName]; - if (attributesArray.length) { markerType.push(attributesArray); } - this.markerTypes.push(markerType); + let markerType = [tagName] + if (attributesArray.length) { + markerType.push(attributesArray) + } + this.markerTypes.push(markerType) - index = this.markerTypes.length - 1; - this._markerTypeCache[key] = index; + index = this.markerTypes.length - 1 + this._markerTypeCache[key] = index } - return index; - } -}; + return index + }, +} /** * Render from post -> mobiledoc @@ -115,10 +115,10 @@ export default { * @return {Mobiledoc} */ render(post) { - let opcodes = []; - visit(visitor, post, opcodes); - let compiler = Object.create(postOpcodeCompiler); - compile(compiler, opcodes); - return compiler.result; - } -}; + let opcodes = [] + visit(visitor, post, opcodes) + let compiler = Object.create(postOpcodeCompiler) + compile(compiler, opcodes) + return compiler.result + }, +} diff --git a/src/js/renderers/mobiledoc/0-3-1.js b/src/js/renderers/mobiledoc/0-3-1.js index 74c109ff4..170a6eb44 100644 --- a/src/js/renderers/mobiledoc/0-3-1.js +++ b/src/js/renderers/mobiledoc/0-3-1.js @@ -1,5 +1,5 @@ -import {visit, visitArray, compile} from '../../utils/compiler'; -import { objectToSortedKVArray } from '../../utils/array-utils'; +import { visit, visitArray, compile } from '../../utils/compiler' +import { objectToSortedKVArray } from '../../utils/array-utils' import { POST_TYPE, MARKUP_SECTION_TYPE, @@ -9,137 +9,131 @@ import { MARKUP_TYPE, IMAGE_SECTION_TYPE, CARD_TYPE, - ATOM_TYPE -} from '../../models/types'; + ATOM_TYPE, +} from '../../models/types' -export const MOBILEDOC_VERSION = '0.3.1'; -export const MOBILEDOC_MARKUP_SECTION_TYPE = 1; -export const MOBILEDOC_IMAGE_SECTION_TYPE = 2; -export const MOBILEDOC_LIST_SECTION_TYPE = 3; -export const MOBILEDOC_CARD_SECTION_TYPE = 10; +export const MOBILEDOC_VERSION = '0.3.1' +export const MOBILEDOC_MARKUP_SECTION_TYPE = 1 +export const MOBILEDOC_IMAGE_SECTION_TYPE = 2 +export const MOBILEDOC_LIST_SECTION_TYPE = 3 +export const MOBILEDOC_CARD_SECTION_TYPE = 10 -export const MOBILEDOC_MARKUP_MARKER_TYPE = 0; -export const MOBILEDOC_ATOM_MARKER_TYPE = 1; +export const MOBILEDOC_MARKUP_MARKER_TYPE = 0 +export const MOBILEDOC_ATOM_MARKER_TYPE = 1 const visitor = { [POST_TYPE](node, opcodes) { - opcodes.push(['openPost']); - visitArray(visitor, node.sections, opcodes); + opcodes.push(['openPost']) + visitArray(visitor, node.sections, opcodes) }, [MARKUP_SECTION_TYPE](node, opcodes) { - opcodes.push(['openMarkupSection', node.tagName]); - visitArray(visitor, node.markers, opcodes); + opcodes.push(['openMarkupSection', node.tagName]) + visitArray(visitor, node.markers, opcodes) }, [LIST_SECTION_TYPE](node, opcodes) { - opcodes.push(['openListSection', node.tagName]); - visitArray(visitor, node.items, opcodes); + opcodes.push(['openListSection', node.tagName]) + visitArray(visitor, node.items, opcodes) }, [LIST_ITEM_TYPE](node, opcodes) { - opcodes.push(['openListItem']); - visitArray(visitor, node.markers, opcodes); + opcodes.push(['openListItem']) + visitArray(visitor, node.markers, opcodes) }, [IMAGE_SECTION_TYPE](node, opcodes) { - opcodes.push(['openImageSection', node.src]); + opcodes.push(['openImageSection', node.src]) }, [CARD_TYPE](node, opcodes) { - opcodes.push(['openCardSection', node.name, node.payload]); + opcodes.push(['openCardSection', node.name, node.payload]) }, [MARKER_TYPE](node, opcodes) { - opcodes.push(['openMarker', node.closedMarkups.length, node.value]); - visitArray(visitor, node.openedMarkups, opcodes); + opcodes.push(['openMarker', node.closedMarkups.length, node.value]) + visitArray(visitor, node.openedMarkups, opcodes) }, [MARKUP_TYPE](node, opcodes) { - opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]); + opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]) }, [ATOM_TYPE](node, opcodes) { - opcodes.push(['openAtom', node.closedMarkups.length, node.name, node.value, node.payload]); - visitArray(visitor, node.openedMarkups, opcodes); - } -}; + opcodes.push(['openAtom', node.closedMarkups.length, node.name, node.value, node.payload]) + visitArray(visitor, node.openedMarkups, opcodes) + }, +} const postOpcodeCompiler = { openMarker(closeCount, value) { - this.markupMarkerIds = []; - this.markers.push([ - MOBILEDOC_MARKUP_MARKER_TYPE, - this.markupMarkerIds, - closeCount, - value || '' - ]); + this.markupMarkerIds = [] + this.markers.push([MOBILEDOC_MARKUP_MARKER_TYPE, this.markupMarkerIds, closeCount, value || '']) }, openMarkupSection(tagName) { - this.markers = []; - this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]); + this.markers = [] + this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]) }, openListSection(tagName) { - this.items = []; - this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]); + this.items = [] + this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]) }, openListItem() { - this.markers = []; - this.items.push(this.markers); + this.markers = [] + this.items.push(this.markers) }, openImageSection(url) { - this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]); + this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]) }, openCardSection(name, payload) { - const index = this._addCardTypeIndex(name, payload); - this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index]); + const index = this._addCardTypeIndex(name, payload) + this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index]) }, openAtom(closeCount, name, value, payload) { - const index = this._addAtomTypeIndex(name, value, payload); - this.markupMarkerIds = []; - this.markers.push([ - MOBILEDOC_ATOM_MARKER_TYPE, - this.markupMarkerIds, - closeCount, - index - ]); + const index = this._addAtomTypeIndex(name, value, payload) + this.markupMarkerIds = [] + this.markers.push([MOBILEDOC_ATOM_MARKER_TYPE, this.markupMarkerIds, closeCount, index]) }, openPost() { - this.atomTypes = []; - this.cardTypes = []; - this.markerTypes = []; - this.sections = []; + this.atomTypes = [] + this.cardTypes = [] + this.markerTypes = [] + this.sections = [] this.result = { version: MOBILEDOC_VERSION, atoms: this.atomTypes, cards: this.cardTypes, markups: this.markerTypes, - sections: this.sections - }; + sections: this.sections, + } }, openMarkup(tagName, attributes) { - const index = this._findOrAddMarkerTypeIndex(tagName, attributes); - this.markupMarkerIds.push(index); + const index = this._findOrAddMarkerTypeIndex(tagName, attributes) + this.markupMarkerIds.push(index) }, _addCardTypeIndex(cardName, payload) { - let cardType = [cardName, payload]; - this.cardTypes.push(cardType); - return this.cardTypes.length - 1; + let cardType = [cardName, payload] + this.cardTypes.push(cardType) + return this.cardTypes.length - 1 }, _addAtomTypeIndex(atomName, atomValue, payload) { - let atomType = [atomName, atomValue, payload]; - this.atomTypes.push(atomType); - return this.atomTypes.length - 1; + let atomType = [atomName, atomValue, payload] + this.atomTypes.push(atomType) + return this.atomTypes.length - 1 }, _findOrAddMarkerTypeIndex(tagName, attributesArray) { - if (!this._markerTypeCache) { this._markerTypeCache = {}; } - const key = `${tagName}-${attributesArray.join('-')}`; + if (!this._markerTypeCache) { + this._markerTypeCache = {} + } + const key = `${tagName}-${attributesArray.join('-')}` - let index = this._markerTypeCache[key]; + let index = this._markerTypeCache[key] if (index === undefined) { - let markerType = [tagName]; - if (attributesArray.length) { markerType.push(attributesArray); } - this.markerTypes.push(markerType); + let markerType = [tagName] + if (attributesArray.length) { + markerType.push(attributesArray) + } + this.markerTypes.push(markerType) - index = this.markerTypes.length - 1; - this._markerTypeCache[key] = index; + index = this.markerTypes.length - 1 + this._markerTypeCache[key] = index } - return index; - } -}; + return index + }, +} /** * Render from post -> mobiledoc @@ -150,10 +144,10 @@ export default { * @return {Mobiledoc} */ render(post) { - let opcodes = []; - visit(visitor, post, opcodes); - let compiler = Object.create(postOpcodeCompiler); - compile(compiler, opcodes); - return compiler.result; - } -}; + let opcodes = [] + visit(visitor, post, opcodes) + let compiler = Object.create(postOpcodeCompiler) + compile(compiler, opcodes) + return compiler.result + }, +} diff --git a/src/js/renderers/mobiledoc/0-3-2.js b/src/js/renderers/mobiledoc/0-3-2.js index 9656fb80f..b9f3edae0 100644 --- a/src/js/renderers/mobiledoc/0-3-2.js +++ b/src/js/renderers/mobiledoc/0-3-2.js @@ -1,5 +1,5 @@ -import {visit, visitArray, compile} from '../../utils/compiler'; -import { objectToSortedKVArray } from '../../utils/array-utils'; +import { visit, visitArray, compile } from '../../utils/compiler' +import { objectToSortedKVArray } from '../../utils/array-utils' import { POST_TYPE, MARKUP_SECTION_TYPE, @@ -9,145 +9,139 @@ import { MARKUP_TYPE, IMAGE_SECTION_TYPE, CARD_TYPE, - ATOM_TYPE -} from '../../models/types'; + ATOM_TYPE, +} from '../../models/types' -export const MOBILEDOC_VERSION = '0.3.2'; -export const MOBILEDOC_MARKUP_SECTION_TYPE = 1; -export const MOBILEDOC_IMAGE_SECTION_TYPE = 2; -export const MOBILEDOC_LIST_SECTION_TYPE = 3; -export const MOBILEDOC_CARD_SECTION_TYPE = 10; +export const MOBILEDOC_VERSION = '0.3.2' +export const MOBILEDOC_MARKUP_SECTION_TYPE = 1 +export const MOBILEDOC_IMAGE_SECTION_TYPE = 2 +export const MOBILEDOC_LIST_SECTION_TYPE = 3 +export const MOBILEDOC_CARD_SECTION_TYPE = 10 -export const MOBILEDOC_MARKUP_MARKER_TYPE = 0; -export const MOBILEDOC_ATOM_MARKER_TYPE = 1; +export const MOBILEDOC_MARKUP_MARKER_TYPE = 0 +export const MOBILEDOC_ATOM_MARKER_TYPE = 1 const visitor = { [POST_TYPE](node, opcodes) { - opcodes.push(['openPost']); - visitArray(visitor, node.sections, opcodes); + opcodes.push(['openPost']) + visitArray(visitor, node.sections, opcodes) }, [MARKUP_SECTION_TYPE](node, opcodes) { - opcodes.push(['openMarkupSection', node.tagName, objectToSortedKVArray(node.attributes)]); - visitArray(visitor, node.markers, opcodes); + opcodes.push(['openMarkupSection', node.tagName, objectToSortedKVArray(node.attributes)]) + visitArray(visitor, node.markers, opcodes) }, [LIST_SECTION_TYPE](node, opcodes) { - opcodes.push(['openListSection', node.tagName, objectToSortedKVArray(node.attributes)]); - visitArray(visitor, node.items, opcodes); + opcodes.push(['openListSection', node.tagName, objectToSortedKVArray(node.attributes)]) + visitArray(visitor, node.items, opcodes) }, [LIST_ITEM_TYPE](node, opcodes) { - opcodes.push(['openListItem']); - visitArray(visitor, node.markers, opcodes); + opcodes.push(['openListItem']) + visitArray(visitor, node.markers, opcodes) }, [IMAGE_SECTION_TYPE](node, opcodes) { - opcodes.push(['openImageSection', node.src]); + opcodes.push(['openImageSection', node.src]) }, [CARD_TYPE](node, opcodes) { - opcodes.push(['openCardSection', node.name, node.payload]); + opcodes.push(['openCardSection', node.name, node.payload]) }, [MARKER_TYPE](node, opcodes) { - opcodes.push(['openMarker', node.closedMarkups.length, node.value]); - visitArray(visitor, node.openedMarkups, opcodes); + opcodes.push(['openMarker', node.closedMarkups.length, node.value]) + visitArray(visitor, node.openedMarkups, opcodes) }, [MARKUP_TYPE](node, opcodes) { - opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]); + opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]) }, [ATOM_TYPE](node, opcodes) { - opcodes.push(['openAtom', node.closedMarkups.length, node.name, node.value, node.payload]); - visitArray(visitor, node.openedMarkups, opcodes); - } -}; + opcodes.push(['openAtom', node.closedMarkups.length, node.name, node.value, node.payload]) + visitArray(visitor, node.openedMarkups, opcodes) + }, +} const postOpcodeCompiler = { openMarker(closeCount, value) { - this.markupMarkerIds = []; - this.markers.push([ - MOBILEDOC_MARKUP_MARKER_TYPE, - this.markupMarkerIds, - closeCount, - value || '' - ]); + this.markupMarkerIds = [] + this.markers.push([MOBILEDOC_MARKUP_MARKER_TYPE, this.markupMarkerIds, closeCount, value || '']) }, openMarkupSection(tagName, attributes) { - this.markers = []; + this.markers = [] if (attributes && attributes.length !== 0) { - this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers, attributes]); + this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers, attributes]) } else { - this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]); + this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]) } }, openListSection(tagName, attributes) { - this.items = []; + this.items = [] if (attributes && attributes.length !== 0) { - this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items, attributes]); + this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items, attributes]) } else { - this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]); + this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]) } }, openListItem() { - this.markers = []; - this.items.push(this.markers); + this.markers = [] + this.items.push(this.markers) }, openImageSection(url) { - this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]); + this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]) }, openCardSection(name, payload) { - const index = this._addCardTypeIndex(name, payload); - this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index]); + const index = this._addCardTypeIndex(name, payload) + this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index]) }, openAtom(closeCount, name, value, payload) { - const index = this._addAtomTypeIndex(name, value, payload); - this.markupMarkerIds = []; - this.markers.push([ - MOBILEDOC_ATOM_MARKER_TYPE, - this.markupMarkerIds, - closeCount, - index - ]); + const index = this._addAtomTypeIndex(name, value, payload) + this.markupMarkerIds = [] + this.markers.push([MOBILEDOC_ATOM_MARKER_TYPE, this.markupMarkerIds, closeCount, index]) }, openPost() { - this.atomTypes = []; - this.cardTypes = []; - this.markerTypes = []; - this.sections = []; + this.atomTypes = [] + this.cardTypes = [] + this.markerTypes = [] + this.sections = [] this.result = { version: MOBILEDOC_VERSION, atoms: this.atomTypes, cards: this.cardTypes, markups: this.markerTypes, - sections: this.sections - }; + sections: this.sections, + } }, openMarkup(tagName, attributes) { - const index = this._findOrAddMarkerTypeIndex(tagName, attributes); - this.markupMarkerIds.push(index); + const index = this._findOrAddMarkerTypeIndex(tagName, attributes) + this.markupMarkerIds.push(index) }, _addCardTypeIndex(cardName, payload) { - let cardType = [cardName, payload]; - this.cardTypes.push(cardType); - return this.cardTypes.length - 1; + let cardType = [cardName, payload] + this.cardTypes.push(cardType) + return this.cardTypes.length - 1 }, _addAtomTypeIndex(atomName, atomValue, payload) { - let atomType = [atomName, atomValue, payload]; - this.atomTypes.push(atomType); - return this.atomTypes.length - 1; + let atomType = [atomName, atomValue, payload] + this.atomTypes.push(atomType) + return this.atomTypes.length - 1 }, _findOrAddMarkerTypeIndex(tagName, attributesArray) { - if (!this._markerTypeCache) { this._markerTypeCache = {}; } - const key = `${tagName}-${attributesArray.join('-')}`; + if (!this._markerTypeCache) { + this._markerTypeCache = {} + } + const key = `${tagName}-${attributesArray.join('-')}` - let index = this._markerTypeCache[key]; + let index = this._markerTypeCache[key] if (index === undefined) { - let markerType = [tagName]; - if (attributesArray.length) { markerType.push(attributesArray); } - this.markerTypes.push(markerType); + let markerType = [tagName] + if (attributesArray.length) { + markerType.push(attributesArray) + } + this.markerTypes.push(markerType) - index = this.markerTypes.length - 1; - this._markerTypeCache[key] = index; + index = this.markerTypes.length - 1 + this._markerTypeCache[key] = index } - return index; - } -}; + return index + }, +} /** * Render from post -> mobiledoc @@ -158,10 +152,10 @@ export default { * @return {Mobiledoc} */ render(post) { - let opcodes = []; - visit(visitor, post, opcodes); - let compiler = Object.create(postOpcodeCompiler); - compile(compiler, opcodes); - return compiler.result; - } -}; + let opcodes = [] + visit(visitor, post, opcodes) + let compiler = Object.create(postOpcodeCompiler) + compile(compiler, opcodes) + return compiler.result + }, +} diff --git a/src/js/renderers/mobiledoc/0-3.js b/src/js/renderers/mobiledoc/0-3.js index b11b2a855..cdd4a793b 100644 --- a/src/js/renderers/mobiledoc/0-3.js +++ b/src/js/renderers/mobiledoc/0-3.js @@ -1,5 +1,5 @@ -import {visit, visitArray, compile} from '../../utils/compiler'; -import { objectToSortedKVArray } from '../../utils/array-utils'; +import { visit, visitArray, compile } from '../../utils/compiler' +import { objectToSortedKVArray } from '../../utils/array-utils' import { POST_TYPE, MARKUP_SECTION_TYPE, @@ -9,137 +9,131 @@ import { MARKUP_TYPE, IMAGE_SECTION_TYPE, CARD_TYPE, - ATOM_TYPE -} from '../../models/types'; + ATOM_TYPE, +} from '../../models/types' -export const MOBILEDOC_VERSION = '0.3.0'; -export const MOBILEDOC_MARKUP_SECTION_TYPE = 1; -export const MOBILEDOC_IMAGE_SECTION_TYPE = 2; -export const MOBILEDOC_LIST_SECTION_TYPE = 3; -export const MOBILEDOC_CARD_SECTION_TYPE = 10; +export const MOBILEDOC_VERSION = '0.3.0' +export const MOBILEDOC_MARKUP_SECTION_TYPE = 1 +export const MOBILEDOC_IMAGE_SECTION_TYPE = 2 +export const MOBILEDOC_LIST_SECTION_TYPE = 3 +export const MOBILEDOC_CARD_SECTION_TYPE = 10 -export const MOBILEDOC_MARKUP_MARKER_TYPE = 0; -export const MOBILEDOC_ATOM_MARKER_TYPE = 1; +export const MOBILEDOC_MARKUP_MARKER_TYPE = 0 +export const MOBILEDOC_ATOM_MARKER_TYPE = 1 const visitor = { [POST_TYPE](node, opcodes) { - opcodes.push(['openPost']); - visitArray(visitor, node.sections, opcodes); + opcodes.push(['openPost']) + visitArray(visitor, node.sections, opcodes) }, [MARKUP_SECTION_TYPE](node, opcodes) { - opcodes.push(['openMarkupSection', node.tagName]); - visitArray(visitor, node.markers, opcodes); + opcodes.push(['openMarkupSection', node.tagName]) + visitArray(visitor, node.markers, opcodes) }, [LIST_SECTION_TYPE](node, opcodes) { - opcodes.push(['openListSection', node.tagName]); - visitArray(visitor, node.items, opcodes); + opcodes.push(['openListSection', node.tagName]) + visitArray(visitor, node.items, opcodes) }, [LIST_ITEM_TYPE](node, opcodes) { - opcodes.push(['openListItem']); - visitArray(visitor, node.markers, opcodes); + opcodes.push(['openListItem']) + visitArray(visitor, node.markers, opcodes) }, [IMAGE_SECTION_TYPE](node, opcodes) { - opcodes.push(['openImageSection', node.src]); + opcodes.push(['openImageSection', node.src]) }, [CARD_TYPE](node, opcodes) { - opcodes.push(['openCardSection', node.name, node.payload]); + opcodes.push(['openCardSection', node.name, node.payload]) }, [MARKER_TYPE](node, opcodes) { - opcodes.push(['openMarker', node.closedMarkups.length, node.value]); - visitArray(visitor, node.openedMarkups, opcodes); + opcodes.push(['openMarker', node.closedMarkups.length, node.value]) + visitArray(visitor, node.openedMarkups, opcodes) }, [MARKUP_TYPE](node, opcodes) { - opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]); + opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]) }, [ATOM_TYPE](node, opcodes) { - opcodes.push(['openAtom', node.closedMarkups.length, node.name, node.value, node.payload]); - visitArray(visitor, node.openedMarkups, opcodes); - } -}; + opcodes.push(['openAtom', node.closedMarkups.length, node.name, node.value, node.payload]) + visitArray(visitor, node.openedMarkups, opcodes) + }, +} const postOpcodeCompiler = { openMarker(closeCount, value) { - this.markupMarkerIds = []; - this.markers.push([ - MOBILEDOC_MARKUP_MARKER_TYPE, - this.markupMarkerIds, - closeCount, - value || '' - ]); + this.markupMarkerIds = [] + this.markers.push([MOBILEDOC_MARKUP_MARKER_TYPE, this.markupMarkerIds, closeCount, value || '']) }, openMarkupSection(tagName) { - this.markers = []; - this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]); + this.markers = [] + this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]) }, openListSection(tagName) { - this.items = []; - this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]); + this.items = [] + this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]) }, openListItem() { - this.markers = []; - this.items.push(this.markers); + this.markers = [] + this.items.push(this.markers) }, openImageSection(url) { - this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]); + this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]) }, openCardSection(name, payload) { - const index = this._addCardTypeIndex(name, payload); - this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index]); + const index = this._addCardTypeIndex(name, payload) + this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index]) }, openAtom(closeCount, name, value, payload) { - const index = this._addAtomTypeIndex(name, value, payload); - this.markupMarkerIds = []; - this.markers.push([ - MOBILEDOC_ATOM_MARKER_TYPE, - this.markupMarkerIds, - closeCount, - index - ]); + const index = this._addAtomTypeIndex(name, value, payload) + this.markupMarkerIds = [] + this.markers.push([MOBILEDOC_ATOM_MARKER_TYPE, this.markupMarkerIds, closeCount, index]) }, openPost() { - this.atomTypes = []; - this.cardTypes = []; - this.markerTypes = []; - this.sections = []; + this.atomTypes = [] + this.cardTypes = [] + this.markerTypes = [] + this.sections = [] this.result = { version: MOBILEDOC_VERSION, atoms: this.atomTypes, cards: this.cardTypes, markups: this.markerTypes, - sections: this.sections - }; + sections: this.sections, + } }, openMarkup(tagName, attributes) { - const index = this._findOrAddMarkerTypeIndex(tagName, attributes); - this.markupMarkerIds.push(index); + const index = this._findOrAddMarkerTypeIndex(tagName, attributes) + this.markupMarkerIds.push(index) }, _addCardTypeIndex(cardName, payload) { - let cardType = [cardName, payload]; - this.cardTypes.push(cardType); - return this.cardTypes.length - 1; + let cardType = [cardName, payload] + this.cardTypes.push(cardType) + return this.cardTypes.length - 1 }, _addAtomTypeIndex(atomName, atomValue, payload) { - let atomType = [atomName, atomValue, payload]; - this.atomTypes.push(atomType); - return this.atomTypes.length - 1; + let atomType = [atomName, atomValue, payload] + this.atomTypes.push(atomType) + return this.atomTypes.length - 1 }, _findOrAddMarkerTypeIndex(tagName, attributesArray) { - if (!this._markerTypeCache) { this._markerTypeCache = {}; } - const key = `${tagName}-${attributesArray.join('-')}`; + if (!this._markerTypeCache) { + this._markerTypeCache = {} + } + const key = `${tagName}-${attributesArray.join('-')}` - let index = this._markerTypeCache[key]; + let index = this._markerTypeCache[key] if (index === undefined) { - let markerType = [tagName]; - if (attributesArray.length) { markerType.push(attributesArray); } - this.markerTypes.push(markerType); + let markerType = [tagName] + if (attributesArray.length) { + markerType.push(attributesArray) + } + this.markerTypes.push(markerType) - index = this.markerTypes.length - 1; - this._markerTypeCache[key] = index; + index = this.markerTypes.length - 1 + this._markerTypeCache[key] = index } - return index; - } -}; + return index + }, +} /** * Render from post -> mobiledoc @@ -150,10 +144,10 @@ export default { * @return {Mobiledoc} */ render(post) { - let opcodes = []; - visit(visitor, post, opcodes); - let compiler = Object.create(postOpcodeCompiler); - compile(compiler, opcodes); - return compiler.result; - } -}; + let opcodes = [] + visit(visitor, post, opcodes) + let compiler = Object.create(postOpcodeCompiler) + compile(compiler, opcodes) + return compiler.result + }, +} diff --git a/src/js/renderers/mobiledoc/index.js b/src/js/renderers/mobiledoc/index.js index da1622900..a0b86ae8a 100644 --- a/src/js/renderers/mobiledoc/index.js +++ b/src/js/renderers/mobiledoc/index.js @@ -1,26 +1,26 @@ -import MobiledocRenderer_0_2, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_2 } from './0-2'; -import MobiledocRenderer_0_3, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3 } from './0-3'; -import MobiledocRenderer_0_3_1, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_1 } from './0-3-1'; -import MobiledocRenderer_0_3_2, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_2 } from './0-3-2'; -import assert from 'mobiledoc-kit/utils/assert'; +import MobiledocRenderer_0_2, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_2 } from './0-2' +import MobiledocRenderer_0_3, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3 } from './0-3' +import MobiledocRenderer_0_3_1, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_1 } from './0-3-1' +import MobiledocRenderer_0_3_2, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_2 } from './0-3-2' +import assert from 'mobiledoc-kit/utils/assert' -export const MOBILEDOC_VERSION = MOBILEDOC_VERSION_0_3_2; +export const MOBILEDOC_VERSION = MOBILEDOC_VERSION_0_3_2 export default { render(post, version) { switch (version) { case MOBILEDOC_VERSION_0_2: - return MobiledocRenderer_0_2.render(post); + return MobiledocRenderer_0_2.render(post) case MOBILEDOC_VERSION_0_3: - return MobiledocRenderer_0_3.render(post); + return MobiledocRenderer_0_3.render(post) case MOBILEDOC_VERSION_0_3_1: - return MobiledocRenderer_0_3_1.render(post); + return MobiledocRenderer_0_3_1.render(post) case undefined: case null: case MOBILEDOC_VERSION_0_3_2: - return MobiledocRenderer_0_3_2.render(post); + return MobiledocRenderer_0_3_2.render(post) default: - assert(`Unknown version of mobiledoc renderer requested: ${version}`, false); + assert(`Unknown version of mobiledoc renderer requested: ${version}`, false) } - } -}; + }, +} diff --git a/src/js/utils/array-utils.js b/src/js/utils/array-utils.js index 9ab77d945..54380d4a5 100644 --- a/src/js/utils/array-utils.js +++ b/src/js/utils/array-utils.js @@ -1,40 +1,44 @@ function detect(enumerable, callback) { if (enumerable.detect) { - return enumerable.detect(callback); + return enumerable.detect(callback) } else { - for (let i=0; i { - if (conditionFn(i)) { filtered.push(i); } - }); - return filtered; + if (conditionFn(i)) { + filtered.push(i) + } + }) + return filtered } /** @@ -65,14 +71,14 @@ function filter(enumerable, conditionFn) { * @private */ function commonItemLength(listA, listB) { - let offset = 0; + let offset = 0 while (offset < listA.length && offset < listB.length) { if (listA[offset] !== listB[offset]) { - break; + break } - offset++; + offset++ } - return offset; + return offset } /** @@ -80,27 +86,27 @@ function commonItemLength(listA, listB) { * @private */ function commonItems(listA, listB) { - let offset = 0; + let offset = 0 while (offset < listA.length && offset < listB.length) { if (listA[offset] !== listB[offset]) { - break; + break } - offset++; + offset++ } - return listA.slice(0, offset); + return listA.slice(0, offset) } // return new array without falsy items like ruby's `compact` function compact(enumerable) { - return filter(enumerable, i => !!i); + return filter(enumerable, i => !!i) } function reduce(enumerable, callback, initialValue) { - let previousValue = initialValue; + let previousValue = initialValue forEach(enumerable, (val, index) => { - previousValue = callback(previousValue, val, index); - }); - return previousValue; + previousValue = callback(previousValue, val, index) + }) + return previousValue } /** @@ -109,51 +115,56 @@ function reduce(enumerable, callback, initialValue) { * @private */ function kvArrayToObject(array) { - const obj = {}; - for (let i = 0; i < array.length; i+=2) { - let [key, value] = [array[i], array[i+1]]; - obj[key] = value; + const obj = {} + for (let i = 0; i < array.length; i += 2) { + let [key, value] = [array[i], array[i + 1]] + obj[key] = value } - return obj; + return obj } function objectToSortedKVArray(obj) { - const keys = Object.keys(obj).sort(); - const result = []; + const keys = Object.keys(obj).sort() + const result = [] keys.forEach(k => { - result.push(k); - result.push(obj[k]); - }); - return result; + result.push(k) + result.push(obj[k]) + }) + return result } // check shallow equality of two non-nested arrays function isArrayEqual(arr1, arr2) { - let l1 = arr1.length, l2 = arr2.length; - if (l1 !== l2) { return false; } + let l1 = arr1.length, + l2 = arr2.length + if (l1 !== l2) { + return false + } - for (let i=0; i < l1; i++) { - if (arr1[i] !== arr2[i]) { return false; } + for (let i = 0; i < l1; i++) { + if (arr1[i] !== arr2[i]) { + return false + } } - return true; + return true } // return an object with only the valid keys -function filterObject(object, validKeys=[]) { - let result = {}; +function filterObject(object, validKeys = []) { + let result = {} forEach( filter(Object.keys(object), key => validKeys.indexOf(key) !== -1), - key => result[key] = object[key] - ); - return result; + key => (result[key] = object[key]) + ) + return result } function contains(array, item) { - return array.indexOf(item) !== -1; + return array.indexOf(item) !== -1 } function values(object) { - return Object.keys(object).map(key => object[key]); + return Object.keys(object).map(key => object[key]) } export { @@ -172,5 +183,5 @@ export { toArray, filterObject, contains, - values -}; + values, +} diff --git a/src/js/utils/assert.js b/src/js/utils/assert.js index 3e3df8f0d..b35ec961c 100644 --- a/src/js/utils/assert.js +++ b/src/js/utils/assert.js @@ -1,7 +1,7 @@ -import MobiledocError from './mobiledoc-error'; +import MobiledocError from './mobiledoc-error' -export default function(message, conditional) { +export default function (message, conditional) { if (!conditional) { - throw new MobiledocError(message); + throw new MobiledocError(message) } } diff --git a/src/js/utils/browser.js b/src/js/utils/browser.js index 5c296f0b0..559b05b8c 100644 --- a/src/js/utils/browser.js +++ b/src/js/utils/browser.js @@ -1,8 +1,8 @@ export default { isMac() { - return (typeof window !== 'undefined') && window.navigator && /Mac/.test(window.navigator.platform); + return typeof window !== 'undefined' && window.navigator && /Mac/.test(window.navigator.platform) }, isWin() { - return (typeof window !== 'undefined') && window.navigator && /Win/.test(window.navigator.platform); - } -}; + return typeof window !== 'undefined' && window.navigator && /Win/.test(window.navigator.platform) + }, +} diff --git a/src/js/utils/characters.js b/src/js/utils/characters.js index d906126d8..f3f373d01 100644 --- a/src/js/utils/characters.js +++ b/src/js/utils/characters.js @@ -1,3 +1,3 @@ -export const TAB = '\t'; -export const ENTER = '\n'; -export const SPACE = ' '; +export const TAB = '\t' +export const ENTER = '\n' +export const SPACE = ' ' diff --git a/src/js/utils/compiler.js b/src/js/utils/compiler.js index 2b549d903..d2e23967e 100644 --- a/src/js/utils/compiler.js +++ b/src/js/utils/compiler.js @@ -1,33 +1,33 @@ -import { forEach } from './array-utils'; -import assert from './assert'; +import { forEach } from './array-utils' +import assert from './assert' export function visit(visitor, node, opcodes) { - const method = node.type; - assert(`Cannot visit unknown type ${method}`, !!visitor[method]); - visitor[method](node, opcodes); + const method = node.type + assert(`Cannot visit unknown type ${method}`, !!visitor[method]) + visitor[method](node, opcodes) } export function compile(compiler, opcodes) { - for (var i=0, l=opcodes.length; i { - visit(visitor, node, opcodes); - }); + visit(visitor, node, opcodes) + }) } diff --git a/src/js/utils/copy.js b/src/js/utils/copy.js index a5005b4ad..3a28e9023 100644 --- a/src/js/utils/copy.js +++ b/src/js/utils/copy.js @@ -1,11 +1,9 @@ function shallowCopyObject(object) { - let copy = {}; + let copy = {} Object.keys(object).forEach(key => { - copy[key] = object[key]; - }); - return copy; + copy[key] = object[key] + }) + return copy } -export { - shallowCopyObject -}; +export { shallowCopyObject } diff --git a/src/js/utils/cursor.js b/src/js/utils/cursor.js index 15d3d145a..858f8c4bd 100644 --- a/src/js/utils/cursor.js +++ b/src/js/utils/cursor.js @@ -1,24 +1,21 @@ -import { - clearSelection, - comparePosition -} from '../utils/selection-utils'; -import { containsNode } from '../utils/dom-utils'; -import Position from './cursor/position'; -import Range from './cursor/range'; -import { DIRECTION } from '../utils/key'; -import { constrainSelectionTo } from '../utils/selection-utils'; - -export { Position, Range }; +import { clearSelection, comparePosition } from '../utils/selection-utils' +import { containsNode } from '../utils/dom-utils' +import Position from './cursor/position' +import Range from './cursor/range' +import { DIRECTION } from '../utils/key' +import { constrainSelectionTo } from '../utils/selection-utils' + +export { Position, Range } const Cursor = class Cursor { constructor(editor) { - this.editor = editor; - this.renderTree = editor._renderTree; - this.post = editor.post; + this.editor = editor + this.renderTree = editor._renderTree + this.post = editor.post } clearSelection() { - clearSelection(); + clearSelection() } /** @@ -26,111 +23,111 @@ const Cursor = class Cursor { * editor's element or a selection that is contained in the editor's element */ hasCursor() { - return this.editor.hasRendered && - (this._hasCollapsedSelection() || this._hasSelection()); + return this.editor.hasRendered && (this._hasCollapsedSelection() || this._hasSelection()) } hasSelection() { - return this.editor.hasRendered && - this._hasSelection(); + return this.editor.hasRendered && this._hasSelection() } /** * @return {Boolean} Can the cursor be on this element? */ isAddressable(element) { - let { renderTree } = this; - let renderNode = renderTree.findRenderNodeFromElement(element); + let { renderTree } = this + let renderNode = renderTree.findRenderNodeFromElement(element) if (renderNode && renderNode.postNode.isCardSection) { - let renderedElement = renderNode.element; + let renderedElement = renderNode.element // card sections have addressable text nodes containing ‌ // as their first and last child - if (element !== renderedElement && - element !== renderedElement.firstChild && - element !== renderedElement.lastChild) { - return false; + if ( + element !== renderedElement && + element !== renderedElement.firstChild && + element !== renderedElement.lastChild + ) { + return false } } - return !!renderNode; + return !!renderNode } /* * @return {Range} Cursor#Range object */ get offsets() { - if (!this.hasCursor()) { return Range.blankRange(); } + if (!this.hasCursor()) { + return Range.blankRange() + } - let { selection, renderTree } = this; - let parentNode = this.editor.element; - selection = constrainSelectionTo(selection, parentNode); + let { selection, renderTree } = this + let parentNode = this.editor.element + selection = constrainSelectionTo(selection, parentNode) - const { - headNode, headOffset, tailNode, tailOffset, direction - } = comparePosition(selection); + const { headNode, headOffset, tailNode, tailOffset, direction } = comparePosition(selection) - const headPosition = Position.fromNode(renderTree, headNode, headOffset); - const tailPosition = Position.fromNode(renderTree, tailNode, tailOffset); + const headPosition = Position.fromNode(renderTree, headNode, headOffset) + const tailPosition = Position.fromNode(renderTree, tailNode, tailOffset) - return new Range(headPosition, tailPosition, direction); + return new Range(headPosition, tailPosition, direction) } _findNodeForPosition(position) { - let { section } = position; - let node, offset; + let { section } = position + let node, offset if (section.isCardSection) { - offset = 0; + offset = 0 if (position.offset === 0) { - node = section.renderNode.element.firstChild; + node = section.renderNode.element.firstChild } else { - node = section.renderNode.element.lastChild; + node = section.renderNode.element.lastChild } } else if (section.isBlank) { - node = section.renderNode.cursorElement; - offset = 0; + node = section.renderNode.cursorElement + offset = 0 } else { - let {marker, offsetInMarker} = position; + let { marker, offsetInMarker } = position if (marker.isAtom) { if (offsetInMarker > 0) { // FIXME -- if there is a next marker, focus on it? - offset = 0; - node = marker.renderNode.tailTextNode; + offset = 0 + node = marker.renderNode.tailTextNode } else { - offset = 0; - node = marker.renderNode.headTextNode; + offset = 0 + node = marker.renderNode.headTextNode } } else { - node = marker.renderNode.element; - offset = offsetInMarker; + node = marker.renderNode.element + offset = offsetInMarker } } - return {node, offset}; + return { node, offset } } selectRange(range) { if (range.isBlank) { - this.clearSelection(); - return; + this.clearSelection() + return } - const { head, tail, direction } = range; - const { node:headNode, offset:headOffset } = this._findNodeForPosition(head), - { node:tailNode, offset:tailOffset } = this._findNodeForPosition(tail); - this._moveToNode(headNode, headOffset, tailNode, tailOffset, direction); + const { head, tail, direction } = range + const { node: headNode, offset: headOffset } = this._findNodeForPosition(head), + { node: tailNode, offset: tailOffset } = this._findNodeForPosition(tail) + this._moveToNode(headNode, headOffset, tailNode, tailOffset, direction) // Firefox sometimes doesn't keep focus in the editor after adding a card - this.editor._ensureFocus(); + this.editor._ensureFocus() } get selection() { - return window.getSelection(); + return window.getSelection() } selectedText() { // FIXME remove this - return this.selection.toString(); + return this.selection.toString() } /** @@ -141,46 +138,51 @@ const Cursor = class Cursor { * @param {integer} direction forward or backward, default forward * @private */ - _moveToNode(node, offset, endNode, endOffset, direction=DIRECTION.FORWARD) { - this.clearSelection(); + _moveToNode(node, offset, endNode, endOffset, direction = DIRECTION.FORWARD) { + this.clearSelection() if (direction === DIRECTION.BACKWARD) { - [node, offset, endNode, endOffset] = [ endNode, endOffset, node, offset ]; + ;[node, offset, endNode, endOffset] = [endNode, endOffset, node, offset] } - const range = document.createRange(); - range.setStart(node, offset); + const range = document.createRange() + range.setStart(node, offset) if (direction === DIRECTION.BACKWARD && !!this.selection.extend) { - this.selection.addRange(range); - this.selection.extend(endNode, endOffset); + this.selection.addRange(range) + this.selection.extend(endNode, endOffset) } else { - range.setEnd(endNode, endOffset); - this.selection.addRange(range); + range.setEnd(endNode, endOffset) + this.selection.addRange(range) } } _hasSelection() { - const element = this.editor.element; - const { _selectionRange } = this; - if (!_selectionRange || _selectionRange.collapsed) { return false; } + const element = this.editor.element + const { _selectionRange } = this + if (!_selectionRange || _selectionRange.collapsed) { + return false + } - return containsNode(element, this.selection.anchorNode) && - containsNode(element, this.selection.focusNode); + return containsNode(element, this.selection.anchorNode) && containsNode(element, this.selection.focusNode) } _hasCollapsedSelection() { - const { _selectionRange } = this; - if (!_selectionRange) { return false; } + const { _selectionRange } = this + if (!_selectionRange) { + return false + } - const element = this.editor.element; - return containsNode(element, this.selection.anchorNode); + const element = this.editor.element + return containsNode(element, this.selection.anchorNode) } get _selectionRange() { - const { selection } = this; - if (selection.rangeCount === 0) { return null; } - return selection.getRangeAt(0); + const { selection } = this + if (selection.rangeCount === 0) { + return null + } + return selection.getRangeAt(0) } -}; +} -export default Cursor; +export default Cursor diff --git a/src/js/utils/cursor/position.js b/src/js/utils/cursor/position.js index 14847c0b1..bff47ac76 100644 --- a/src/js/utils/cursor/position.js +++ b/src/js/utils/cursor/position.js @@ -1,68 +1,61 @@ -import { isTextNode } from 'mobiledoc-kit/utils/dom-utils'; -import assert from 'mobiledoc-kit/utils/assert'; -import { - HIGH_SURROGATE_RANGE, - LOW_SURROGATE_RANGE -} from 'mobiledoc-kit/models/marker'; -import { containsNode } from 'mobiledoc-kit/utils/dom-utils'; -import { findOffsetInNode } from 'mobiledoc-kit/utils/selection-utils'; -import { DIRECTION } from 'mobiledoc-kit/utils/key'; -import Range from './range'; - -const { FORWARD, BACKWARD } = DIRECTION; +import { isTextNode } from 'mobiledoc-kit/utils/dom-utils' +import assert from 'mobiledoc-kit/utils/assert' +import { HIGH_SURROGATE_RANGE, LOW_SURROGATE_RANGE } from 'mobiledoc-kit/models/marker' +import { containsNode } from 'mobiledoc-kit/utils/dom-utils' +import { findOffsetInNode } from 'mobiledoc-kit/utils/selection-utils' +import { DIRECTION } from 'mobiledoc-kit/utils/key' +import Range from './range' + +const { FORWARD, BACKWARD } = DIRECTION // generated via http://xregexp.com/ to cover chars that \w misses // (new XRegExp('\\p{Alphabetic}|[0-9]|_|:')).toString() -const WORD_CHAR_REGEX = /[A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͅͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևְ-ׇֽֿׁׂׅׄא-תװ-ײؐ-ؚؠ-ٗٙ-ٟٮ-ۓە-ۜۡ-ۭۨ-ۯۺ-ۼۿܐ-ܿݍ-ޱߊ-ߪߴߵߺࠀ-ࠗࠚ-ࠬࡀ-ࡘࢠ-ࢴࣣ-ࣰࣩ-ऻऽ-ौॎ-ॐॕ-ॣॱ-ঃঅ-ঌএঐও-নপ-রলশ-হঽ-ৄেৈোৌৎৗড়ঢ়য়-ৣৰৱਁ-ਃਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਾ-ੂੇੈੋੌੑਖ਼-ੜਫ਼ੰ-ੵઁ-ઃઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽ-ૅે-ૉોૌૐૠ-ૣૹଁ-ଃଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽ-ୄେୈୋୌୖୗଡ଼ଢ଼ୟ-ୣୱஂஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹா-ூெ-ைொ-ௌௐௗఀ-ఃఅ-ఌఎ-ఐఒ-నప-హఽ-ౄె-ైొ-ౌౕౖౘ-ౚౠ-ౣಁ-ಃಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽ-ೄೆ-ೈೊ-ೌೕೖೞೠ-ೣೱೲഁ-ഃഅ-ഌഎ-ഐഒ-ഺഽ-ൄെ-ൈൊ-ൌൎൗൟ-ൣൺ-ൿංඃඅ-ඖක-නඳ-රලව-ෆා-ුූෘ-ෟෲෳก-ฺเ-ๆํກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ູົ-ຽເ-ໄໆໍໜ-ໟༀཀ-ཇཉ-ཬཱ-ཱྀྈ-ྗྙ-ྼက-ံးျ-ဿၐ-ၢၥ-ၨၮ-ႆႎႜႝႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚ፟ᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜌᜎ-ᜓᜠ-ᜳᝀ-ᝓᝠ-ᝬᝮ-ᝰᝲᝳក-ឳា-ៈៗៜᠠ-ᡷᢀ-ᢪᢰ-ᣵᤀ-ᤞᤠ-ᤫᤰ-ᤸᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨛᨠ-ᩞᩡ-ᩴᪧᬀ-ᬳᬵ-ᭃᭅ-ᭋᮀ-ᮩᮬ-ᮯᮺ-ᯥᯧ-ᯱᰀ-ᰵᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳳᳵᳶᴀ-ᶿᷧ-ᷴḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⒶ-ⓩⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⷠ-ⷿⸯ々-〇〡-〩〱-〵〸-〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿕ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙴ-ꙻꙿ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꞭꞰ-ꞷꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠧꡀ-ꡳꢀ-ꣃꣲ-ꣷꣻꣽꤊ-ꤪꤰ-ꥒꥠ-ꥼꦀ-ꦲꦴ-ꦿꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨶꩀ-ꩍꩠ-ꩶꩺꩾ-ꪾꫀꫂꫛ-ꫝꫠ-ꫯꫲ-ꫵꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭥꭰ-ꯪ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ]|[0-9]|_|:/; +const WORD_CHAR_REGEX = /[A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͅͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևְ-ׇֽֿׁׂׅׄא-תװ-ײؐ-ؚؠ-ٗٙ-ٟٮ-ۓە-ۜۡ-ۭۨ-ۯۺ-ۼۿܐ-ܿݍ-ޱߊ-ߪߴߵߺࠀ-ࠗࠚ-ࠬࡀ-ࡘࢠ-ࢴࣣ-ࣰࣩ-ऻऽ-ौॎ-ॐॕ-ॣॱ-ঃঅ-ঌএঐও-নপ-রলশ-হঽ-ৄেৈোৌৎৗড়ঢ়য়-ৣৰৱਁ-ਃਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਾ-ੂੇੈੋੌੑਖ਼-ੜਫ਼ੰ-ੵઁ-ઃઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽ-ૅે-ૉોૌૐૠ-ૣૹଁ-ଃଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽ-ୄେୈୋୌୖୗଡ଼ଢ଼ୟ-ୣୱஂஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹா-ூெ-ைொ-ௌௐௗఀ-ఃఅ-ఌఎ-ఐఒ-నప-హఽ-ౄె-ైొ-ౌౕౖౘ-ౚౠ-ౣಁ-ಃಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽ-ೄೆ-ೈೊ-ೌೕೖೞೠ-ೣೱೲഁ-ഃഅ-ഌഎ-ഐഒ-ഺഽ-ൄെ-ൈൊ-ൌൎൗൟ-ൣൺ-ൿංඃඅ-ඖක-නඳ-රලව-ෆා-ුූෘ-ෟෲෳก-ฺเ-ๆํກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ູົ-ຽເ-ໄໆໍໜ-ໟༀཀ-ཇཉ-ཬཱ-ཱྀྈ-ྗྙ-ྼက-ံးျ-ဿၐ-ၢၥ-ၨၮ-ႆႎႜႝႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚ፟ᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜌᜎ-ᜓᜠ-ᜳᝀ-ᝓᝠ-ᝬᝮ-ᝰᝲᝳក-ឳា-ៈៗៜᠠ-ᡷᢀ-ᢪᢰ-ᣵᤀ-ᤞᤠ-ᤫᤰ-ᤸᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨛᨠ-ᩞᩡ-ᩴᪧᬀ-ᬳᬵ-ᭃᭅ-ᭋᮀ-ᮩᮬ-ᮯᮺ-ᯥᯧ-ᯱᰀ-ᰵᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳳᳵᳶᴀ-ᶿᷧ-ᷴḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⒶ-ⓩⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⷠ-ⷿⸯ々-〇〡-〩〱-〵〸-〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿕ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙴ-ꙻꙿ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꞭꞰ-ꞷꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠧꡀ-ꡳꢀ-ꣃꣲ-ꣷꣻꣽꤊ-ꤪꤰ-ꥒꥠ-ꥼꦀ-ꦲꦴ-ꦿꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨶꩀ-ꩍꩠ-ꩶꩺꩾ-ꪾꫀꫂꫛ-ꫝꫠ-ꫯꫲ-ꫵꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭥꭰ-ꯪ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ]|[0-9]|_|:/ function findParentSectionFromNode(renderTree, node) { - let renderNode = renderTree.findRenderNodeFromElement( - node, - (renderNode) => renderNode.postNode.isSection - ); + let renderNode = renderTree.findRenderNodeFromElement(node, renderNode => renderNode.postNode.isSection) - return renderNode && renderNode.postNode; + return renderNode && renderNode.postNode } function findOffsetInMarkerable(markerable, node, offset) { - let offsetInSection = 0; - let marker = markerable.markers.head; + let offsetInSection = 0 + let marker = markerable.markers.head while (marker) { - let markerNode = marker.renderNode.element; + let markerNode = marker.renderNode.element if (markerNode === node) { - return offsetInSection + offset; + return offsetInSection + offset } else if (marker.isAtom) { if (marker.renderNode.headTextNode === node) { - return offsetInSection; + return offsetInSection } else if (marker.renderNode.tailTextNode === node) { - return offsetInSection + 1; + return offsetInSection + 1 } } - offsetInSection += marker.length; - marker = marker.next; + offsetInSection += marker.length + marker = marker.next } - return offsetInSection; + return offsetInSection } function findOffsetInSection(section, node, offset) { if (section.isMarkerable) { - return findOffsetInMarkerable(section, node, offset); + return findOffsetInMarkerable(section, node, offset) } else { - assert('findOffsetInSection must be called with markerable or card section', - section.isCardSection); + assert('findOffsetInSection must be called with markerable or card section', section.isCardSection) - let wrapperNode = section.renderNode.element; - let endTextNode = wrapperNode.lastChild; + let wrapperNode = section.renderNode.element + let endTextNode = wrapperNode.lastChild if (node === endTextNode) { - return 1; + return 1 } - return 0; + return 0 } } -let Position, BlankPosition; +let Position, BlankPosition Position = class Position { /** @@ -71,17 +64,15 @@ Position = class Position { * Two positions (a head and a tail) make up a {@link Range}. * @constructor */ - constructor(section, offset=0, isBlank=false) { + constructor(section, offset = 0, isBlank = false) { if (!isBlank) { - assert('Position must have a section that is addressable by the cursor', - (section && section.isLeafSection)); - assert('Position must have numeric offset', - (typeof offset === 'number')); + assert('Position must have a section that is addressable by the cursor', section && section.isLeafSection) + assert('Position must have numeric offset', typeof offset === 'number') } - this.section = section; - this.offset = offset; - this.isBlank = isBlank; + this.section = section + this.offset = offset + this.isBlank = isBlank } /** @@ -91,18 +82,18 @@ Position = class Position { * @return {Position|null} */ static atPoint(x, y, editor) { - let { _renderTree, element: rootElement } = editor; - let elementFromPoint = document.elementFromPoint(x, y); + let { _renderTree, element: rootElement } = editor + let elementFromPoint = document.elementFromPoint(x, y) if (!containsNode(rootElement, elementFromPoint)) { - return; + return } - let { node, offset } = findOffsetInNode(elementFromPoint, {left: x, top: y}); - return Position.fromNode(_renderTree, node, offset); + let { node, offset } = findOffsetInNode(elementFromPoint, { left: x, top: y }) + return Position.fromNode(_renderTree, node, offset) } static blankPosition() { - return new BlankPosition(); + return new BlankPosition() } /** @@ -112,23 +103,23 @@ Position = class Position { * @return {Range} * @public */ - toRange(tail=this, direction=null) { - return new Range(this, tail, direction); + toRange(tail = this, direction = null) { + return new Range(this, tail, direction) } get leafSectionIndex() { - let post = this.section.post; - let leafSectionIndex; + let post = this.section.post + let leafSectionIndex post.walkAllLeafSections((section, index) => { if (section === this.section) { - leafSectionIndex = index; + leafSectionIndex = index } - }); - return leafSectionIndex; + }) + return leafSectionIndex } get isMarkerable() { - return this.section && this.section.isMarkerable; + return this.section && this.section.isMarkerable } /** @@ -137,7 +128,7 @@ Position = class Position { * @return {Marker|undefined} */ get marker() { - return this.isMarkerable && this.markerPosition.marker; + return this.isMarkerable && this.markerPosition.marker } /** @@ -150,55 +141,58 @@ Position = class Position { * @return {Marker|undefined} */ markerIn(direction) { - if (!this.isMarkerable) { return; } + if (!this.isMarkerable) { + return + } - let { marker, offsetInMarker } = this; - if (!marker) { return; } + let { marker, offsetInMarker } = this + if (!marker) { + return + } if (offsetInMarker > 0 && offsetInMarker < marker.length) { - return marker; + return marker } else if (offsetInMarker === 0) { - return direction === BACKWARD ? marker : marker.prev; + return direction === BACKWARD ? marker : marker.prev } else if (offsetInMarker === marker.length) { - return direction === FORWARD ? marker.next : marker; + return direction === FORWARD ? marker.next : marker } } get offsetInMarker() { - return this.markerPosition.offset; + return this.markerPosition.offset } isEqual(position) { - return this.section === position.section && - this.offset === position.offset; + return this.section === position.section && this.offset === position.offset } /** * @return {Boolean} If this position is at the head of the post */ isHeadOfPost() { - return this.move(BACKWARD).isEqual(this); + return this.move(BACKWARD).isEqual(this) } /** * @return {Boolean} If this position is at the tail of the post */ isTailOfPost() { - return this.move(FORWARD).isEqual(this); + return this.move(FORWARD).isEqual(this) } /** * @return {Boolean} If this position is at the head of its section */ isHead() { - return this.isEqual(this.section.headPosition()); + return this.isEqual(this.section.headPosition()) } /** * @return {Boolean} If this position is at the tail of its section */ isTail() { - return this.isEqual(this.section.tailPosition()); + return this.isEqual(this.section.tailPosition()) } /** @@ -211,14 +205,14 @@ Position = class Position { * at the end of the post. */ move(units) { - assert('Must pass integer to Position#move', typeof units === 'number'); + assert('Must pass integer to Position#move', typeof units === 'number') if (units < 0) { - return this.moveLeft().move(++units); + return this.moveLeft().move(++units) } else if (units > 0) { - return this.moveRight().move(--units); + return this.moveRight().move(--units) } else { - return this; + return this } } @@ -227,72 +221,68 @@ Position = class Position { * @return {Position} The result of moving 1 "word" unit in `direction` */ moveWord(direction) { - let isPostBoundary = direction === BACKWARD ? this.isHeadOfPost() : this.isTailOfPost(); + let isPostBoundary = direction === BACKWARD ? this.isHeadOfPost() : this.isTailOfPost() if (isPostBoundary) { - return this; + return this } if (!this.isMarkerable) { - return this.move(direction); + return this.move(direction) } - let pos = this; + let pos = this // Helper fn to check if the pos is at the `dir` boundary of its section let isBoundary = (pos, dir) => { - return dir === BACKWARD ? pos.isHead() : pos.isTail(); - }; + return dir === BACKWARD ? pos.isHead() : pos.isTail() + } // Get the char at this position (looking forward/right) - let getChar = (pos) => { - let { marker, offsetInMarker } = pos; - return marker.charAt(offsetInMarker); - }; + let getChar = pos => { + let { marker, offsetInMarker } = pos + return marker.charAt(offsetInMarker) + } // Get the char in `dir` at this position let peekChar = (pos, dir) => { - return dir === BACKWARD ? getChar(pos.move(BACKWARD)) : getChar(pos); - }; + return dir === BACKWARD ? getChar(pos.move(BACKWARD)) : getChar(pos) + } // Whether there is an atom in `dir` from this position let isAtom = (pos, dir) => { // Special case when position is at end, the marker associated with it is // the marker to its left. Normally `pos#marker` is the marker to the right of the pos's offset. if (dir === BACKWARD && pos.isTail() && pos.marker.isAtom) { - return true; + return true } - return dir === BACKWARD ? pos.move(BACKWARD).marker.isAtom : pos.marker.isAtom; - }; + return dir === BACKWARD ? pos.move(BACKWARD).marker.isAtom : pos.marker.isAtom + } if (isBoundary(pos, direction)) { // extend movement into prev/next section - return pos.move(direction).moveWord(direction); + return pos.move(direction).moveWord(direction) } - let seekWord = (pos) => { - return !isBoundary(pos, direction) && - !isAtom(pos, direction) && - !WORD_CHAR_REGEX.test(peekChar(pos, direction)); - }; + let seekWord = pos => { + return !isBoundary(pos, direction) && !isAtom(pos, direction) && !WORD_CHAR_REGEX.test(peekChar(pos, direction)) + } // move(dir) while we are seeking the first word char while (seekWord(pos)) { - pos = pos.move(direction); + pos = pos.move(direction) } if (isAtom(pos, direction)) { - return pos.move(direction); + return pos.move(direction) } - let seekBoundary = (pos) => { - return !isBoundary(pos, direction) && - !isAtom(pos, direction) && - WORD_CHAR_REGEX.test(peekChar(pos, direction)); - }; + let seekBoundary = pos => { + return !isBoundary(pos, direction) && !isAtom(pos, direction) && WORD_CHAR_REGEX.test(peekChar(pos, direction)) + } // move(dir) while we are seeking the first boundary position while (seekBoundary(pos)) { - pos = pos.move(direction); + pos = pos.move(direction) } - return pos; + return pos } /** @@ -303,17 +293,17 @@ Position = class Position { */ moveLeft() { if (this.isHead()) { - let prev = this.section.previousLeafSection(); - return prev ? prev.tailPosition() : this; + let prev = this.section.previousLeafSection() + return prev ? prev.tailPosition() : this } else { - let offset = this.offset - 1; + let offset = this.offset - 1 if (this.isMarkerable && this.marker) { - let code = this.marker.value.charCodeAt(offset); + let code = this.marker.value.charCodeAt(offset) if (code >= LOW_SURROGATE_RANGE[0] && code <= LOW_SURROGATE_RANGE[1]) { - offset = offset - 1; + offset = offset - 1 } } - return new Position(this.section, offset); + return new Position(this.section, offset) } } @@ -325,66 +315,64 @@ Position = class Position { */ moveRight() { if (this.isTail()) { - let next = this.section.nextLeafSection(); - return next ? next.headPosition() : this; + let next = this.section.nextLeafSection() + return next ? next.headPosition() : this } else { - let offset = this.offset + 1; + let offset = this.offset + 1 if (this.isMarkerable && this.marker) { - let code = this.marker.value.charCodeAt(offset - 1); + let code = this.marker.value.charCodeAt(offset - 1) if (code >= HIGH_SURROGATE_RANGE[0] && code <= HIGH_SURROGATE_RANGE[1]) { - offset = offset + 1; + offset = offset + 1 } } - return new Position(this.section, offset); + return new Position(this.section, offset) } } static fromNode(renderTree, node, offset) { if (isTextNode(node)) { - return Position.fromTextNode(renderTree, node, offset); + return Position.fromTextNode(renderTree, node, offset) } else { - return Position.fromElementNode(renderTree, node, offset); + return Position.fromElementNode(renderTree, node, offset) } } static fromTextNode(renderTree, textNode, offsetInNode) { - const renderNode = renderTree.getElementRenderNode(textNode); - let section, offsetInSection; + const renderNode = renderTree.getElementRenderNode(textNode) + let section, offsetInSection if (renderNode) { - const marker = renderNode.postNode; - section = marker.section; + const marker = renderNode.postNode + section = marker.section - assert(`Could not find parent section for mapped text node "${textNode.textContent}"`, - !!section); - offsetInSection = section.offsetOfMarker(marker, offsetInNode); + assert(`Could not find parent section for mapped text node "${textNode.textContent}"`, !!section) + offsetInSection = section.offsetOfMarker(marker, offsetInNode) } else { // all text nodes should be rendered by markers except: // * text nodes inside cards // * text nodes created by the browser during text input // both of these should have rendered parent sections, though - section = findParentSectionFromNode(renderTree, textNode); - assert(`Could not find parent section for un-mapped text node "${textNode.textContent}"`, - !!section); + section = findParentSectionFromNode(renderTree, textNode) + assert(`Could not find parent section for un-mapped text node "${textNode.textContent}"`, !!section) - offsetInSection = findOffsetInSection(section, textNode, offsetInNode); + offsetInSection = findOffsetInSection(section, textNode, offsetInNode) } - return new Position(section, offsetInSection); + return new Position(section, offsetInSection) } static fromElementNode(renderTree, elementNode, offset) { - let position; + let position // The browser may change the reported selection to equal the editor's root // element if the user clicks an element that is immediately removed, // which can happen when clicking to remove a card. if (elementNode === renderTree.rootElement) { - let post = renderTree.rootNode.postNode; - position = offset === 0 ? post.headPosition() : post.tailPosition(); + let post = renderTree.rootNode.postNode + position = offset === 0 ? post.headPosition() : post.tailPosition() } else { - let section = findParentSectionFromNode(renderTree, elementNode); - assert('Could not find parent section from element node', !!section); + let section = findParentSectionFromNode(renderTree, elementNode) + assert('Could not find parent section from element node', !!section) if (section.isCardSection) { // Selections in cards are usually made on a text node @@ -394,72 +382,92 @@ Position = class Position { // the final zwnj and should consider the cursor at the // end of the card (offset 1). Otherwise, the cursor is at // the start of the card - position = offset < 2 ? section.headPosition() : section.tailPosition(); + position = offset < 2 ? section.headPosition() : section.tailPosition() } else { - // In Firefox it is possible for the cursor to be on an atom's wrapper // element. (In Chrome/Safari, the browser corrects this to be on // one of the text nodes surrounding the wrapper). // This code corrects for when the browser reports the cursor position // to be on the wrapper element itself - let renderNode = renderTree.getElementRenderNode(elementNode); - let postNode = renderNode && renderNode.postNode; + let renderNode = renderTree.getElementRenderNode(elementNode) + let postNode = renderNode && renderNode.postNode if (postNode && postNode.isAtom) { - let sectionOffset = section.offsetOfMarker(postNode); + let sectionOffset = section.offsetOfMarker(postNode) if (offset > 1) { // we are on the tail side of the atom - sectionOffset += postNode.length; + sectionOffset += postNode.length } - position = new Position(section, sectionOffset); + position = new Position(section, sectionOffset) } else if (offset >= elementNode.childNodes.length) { - // This is to deal with how Firefox handles triple-click selections. // See https://stackoverflow.com/a/21234837/1269194 for an // explanation. - position = section.tailPosition(); + position = section.tailPosition() } else { // The offset is 0 if the cursor is on a non-atom-wrapper element node // (e.g., a
    tag in a blank markup section) - position = section.headPosition(); + position = section.headPosition() } } } - return position; + return position } /** * @private */ get markerPosition() { - assert('Cannot get markerPosition without a section', !!this.section); - assert('cannot get markerPosition of a non-markerable', !!this.section.isMarkerable); - return this.section.markerPositionAtOffset(this.offset); + assert('Cannot get markerPosition without a section', !!this.section) + assert('cannot get markerPosition of a non-markerable', !!this.section.isMarkerable) + return this.section.markerPositionAtOffset(this.offset) } -}; +} BlankPosition = class BlankPosition extends Position { constructor() { - super(null, 0, true); + super(null, 0, true) } isEqual(other) { - return other && other.isBlank; + return other && other.isBlank } - toRange() { return Range.blankRange(); } - get leafSectionIndex() { assert('must implement get leafSectionIndex', false); } + toRange() { + return Range.blankRange() + } + get leafSectionIndex() { + assert('must implement get leafSectionIndex', false) + } - get isMarkerable() { return false; } - get marker() { return false; } - isHeadOfPost() { return false; } - isTailOfPost() { return false; } - isHead() { return false; } - isTail() { return false; } - move() { return this; } - moveWord() { return this; } + get isMarkerable() { + return false + } + get marker() { + return false + } + isHeadOfPost() { + return false + } + isTailOfPost() { + return false + } + isHead() { + return false + } + isTail() { + return false + } + move() { + return this + } + moveWord() { + return this + } - get markerPosition() { return {}; } -}; + get markerPosition() { + return {} + } +} -export default Position; +export default Position diff --git a/src/js/utils/cursor/range.js b/src/js/utils/cursor/range.js index 00a2726b6..4de098e5d 100644 --- a/src/js/utils/cursor/range.js +++ b/src/js/utils/cursor/range.js @@ -1,6 +1,6 @@ -import Position from './position'; -import { DIRECTION } from '../key'; -import assert from 'mobiledoc-kit/utils/assert'; +import Position from './position' +import { DIRECTION } from '../key' +import assert from 'mobiledoc-kit/utils/assert' /** * A logical range of a {@link Post}. @@ -15,15 +15,15 @@ class Range { * @return {Range} * @private */ - constructor(head, tail=head, direction=null) { + constructor(head, tail = head, direction = null) { /** @property {Position} head */ - this.head = head; + this.head = head /** @property {Position} tail */ - this.tail = tail; + this.tail = tail /** @property {Direction} direction */ - this.direction = direction; + this.direction = direction } /** @@ -36,16 +36,12 @@ class Range { * @param {Direction} [direction=null] * @return {Range} */ - static create(headSection, headOffset, tailSection=headSection, tailOffset=headOffset, direction=null) { - return new Range( - new Position(headSection, headOffset), - new Position(tailSection, tailOffset), - direction - ); + static create(headSection, headOffset, tailSection = headSection, tailOffset = headOffset, direction = null) { + return new Range(new Position(headSection, headOffset), new Position(tailSection, tailOffset), direction) } static blankRange() { - return new Range(Position.blankPosition(), Position.blankPosition()); + return new Range(Position.blankPosition(), Position.blankPosition()) } /** @@ -59,14 +55,12 @@ class Range { * @private */ trimTo(section) { - const length = section.length; + const length = section.length - let headOffset = section === this.head.section ? - Math.min(this.head.offset, length) : 0; - let tailOffset = section === this.tail.section ? - Math.min(this.tail.offset, length) : length; + let headOffset = section === this.head.section ? Math.min(this.head.offset, length) : 0 + let tailOffset = section === this.tail.section ? Math.min(this.tail.offset, length) : length - return Range.create(section, headOffset, section, tailOffset); + return Range.create(section, headOffset, section, tailOffset) } /** @@ -79,19 +73,21 @@ class Range { * @public */ extend(units) { - assert(`Must pass integer to Range#extend`, typeof units === 'number'); + assert(`Must pass integer to Range#extend`, typeof units === 'number') - if (units === 0) { return this; } + if (units === 0) { + return this + } - let { head, tail, direction: currentDirection } = this; + let { head, tail, direction: currentDirection } = this switch (currentDirection) { case DIRECTION.FORWARD: - return new Range(head, tail.move(units), currentDirection); + return new Range(head, tail.move(units), currentDirection) case DIRECTION.BACKWARD: - return new Range(head.move(units), tail, currentDirection); + return new Range(head.move(units), tail, currentDirection) default: { - let newDirection = units > 0 ? DIRECTION.FORWARD : DIRECTION.BACKWARD; - return new Range(head, tail, newDirection).extend(units); + let newDirection = units > 0 ? DIRECTION.FORWARD : DIRECTION.BACKWARD + return new Range(head, tail, newDirection).extend(units) } } } @@ -106,15 +102,17 @@ class Range { * @public */ move(direction) { - assert(`Must pass DIRECTION.FORWARD (${DIRECTION.FORWARD}) or DIRECTION.BACKWARD (${DIRECTION.BACKWARD}) to Range#move`, - direction === DIRECTION.FORWARD || direction === DIRECTION.BACKWARD); + assert( + `Must pass DIRECTION.FORWARD (${DIRECTION.FORWARD}) or DIRECTION.BACKWARD (${DIRECTION.BACKWARD}) to Range#move`, + direction === DIRECTION.FORWARD || direction === DIRECTION.BACKWARD + ) - let { focusedPosition, isCollapsed } = this; + let { focusedPosition, isCollapsed } = this if (isCollapsed) { - return new Range(focusedPosition.move(direction)); + return new Range(focusedPosition.move(direction)) } else { - return this._collapse(direction); + return this._collapse(direction) } } @@ -127,85 +125,81 @@ class Range { * @public */ expandByMarker(detectMarker) { - let { - head, - tail, - direction - } = this; - let {section: headSection} = head; + let { head, tail, direction } = this + let { section: headSection } = head if (headSection !== tail.section) { - throw new Error('#expandByMarker does not work across sections. Perhaps you should confirm the range is collapsed'); + throw new Error( + '#expandByMarker does not work across sections. Perhaps you should confirm the range is collapsed' + ) } let firstNotMatchingDetect = i => { - return !detectMarker(i); - }; + return !detectMarker(i) + } - let headMarker = headSection.markers.detect(firstNotMatchingDetect, head.marker, true); + let headMarker = headSection.markers.detect(firstNotMatchingDetect, head.marker, true) if (!headMarker && detectMarker(headSection.markers.head)) { - headMarker = headSection.markers.head; + headMarker = headSection.markers.head } else { - headMarker = headMarker.next || head.marker; + headMarker = headMarker.next || head.marker } - let headPosition = new Position(headSection, headSection.offsetOfMarker(headMarker)); + let headPosition = new Position(headSection, headSection.offsetOfMarker(headMarker)) - let tailMarker = tail.section.markers.detect(firstNotMatchingDetect, tail.marker); + let tailMarker = tail.section.markers.detect(firstNotMatchingDetect, tail.marker) if (!tailMarker && detectMarker(headSection.markers.tail)) { - tailMarker = headSection.markers.tail; + tailMarker = headSection.markers.tail } else { - tailMarker = tailMarker.prev || tail.marker; + tailMarker = tailMarker.prev || tail.marker } - let tailPosition = new Position(tail.section, tail.section.offsetOfMarker(tailMarker) + tailMarker.length); + let tailPosition = new Position(tail.section, tail.section.offsetOfMarker(tailMarker) + tailMarker.length) - return headPosition.toRange(tailPosition, direction); + return headPosition.toRange(tailPosition, direction) } _collapse(direction) { - return new Range(direction === DIRECTION.BACKWARD ? this.head : this.tail); + return new Range(direction === DIRECTION.BACKWARD ? this.head : this.tail) } get focusedPosition() { - return this.direction === DIRECTION.BACKWARD ? this.head : this.tail; + return this.direction === DIRECTION.BACKWARD ? this.head : this.tail } isEqual(other) { - return other && - this.head.isEqual(other.head) && - this.tail.isEqual(other.tail); + return other && this.head.isEqual(other.head) && this.tail.isEqual(other.tail) } get isBlank() { - return this.head.isBlank && this.tail.isBlank; + return this.head.isBlank && this.tail.isBlank } // "legacy" APIs get headSection() { - return this.head.section; + return this.head.section } get tailSection() { - return this.tail.section; + return this.tail.section } get headSectionOffset() { - return this.head.offset; + return this.head.offset } get tailSectionOffset() { - return this.tail.offset; + return this.tail.offset } get isCollapsed() { - return this.head.isEqual(this.tail); + return this.head.isEqual(this.tail) } get headMarker() { - return this.head.marker; + return this.head.marker } get tailMarker() { - return this.tail.marker; + return this.tail.marker } get headMarkerOffset() { - return this.head.offsetInMarker; + return this.head.offsetInMarker } get tailMarkerOffset() { - return this.tail.offsetInMarker; + return this.tail.offsetInMarker } } -export default Range; +export default Range diff --git a/src/js/utils/deprecate.js b/src/js/utils/deprecate.js index e8c2fe35b..11da51f75 100644 --- a/src/js/utils/deprecate.js +++ b/src/js/utils/deprecate.js @@ -7,9 +7,9 @@ * conditional is false: * `deprecate('Deprecated only if foo !== bar', foo === bar)` */ -export default function deprecate(message, conditional=false) { +export default function deprecate(message, conditional = false) { if (!conditional) { // eslint-disable-next-line no-console - console.log(`[mobiledoc-kit] [DEPRECATED]: ${message}`); + console.log(`[mobiledoc-kit] [DEPRECATED]: ${message}`) } } diff --git a/src/js/utils/dom-utils.js b/src/js/utils/dom-utils.js index 0206ed310..34fe21834 100644 --- a/src/js/utils/dom-utils.js +++ b/src/js/utils/dom-utils.js @@ -1,49 +1,48 @@ -import { forEach } from './array-utils'; +import { forEach } from './array-utils' export const NODE_TYPES = { ELEMENT: 1, TEXT: 3, - COMMENT: 8 -}; + COMMENT: 8, +} function isTextNode(node) { - return node.nodeType === NODE_TYPES.TEXT; + return node.nodeType === NODE_TYPES.TEXT } function isCommentNode(node) { - return node.nodeType === NODE_TYPES.COMMENT; + return node.nodeType === NODE_TYPES.COMMENT } function isElementNode(node) { - return node.nodeType === NODE_TYPES.ELEMENT; + return node.nodeType === NODE_TYPES.ELEMENT } // perform a pre-order tree traversal of the dom, calling `callbackFn(node)` // for every node for which `conditionFn(node)` is true -function walkDOM(topNode, callbackFn=()=>{}, conditionFn=()=>true) { - let currentNode = topNode; +function walkDOM(topNode, callbackFn = () => {}, conditionFn = () => true) { + let currentNode = topNode if (conditionFn(currentNode)) { - callbackFn(currentNode); + callbackFn(currentNode) } - currentNode = currentNode.firstChild; + currentNode = currentNode.firstChild while (currentNode) { - walkDOM(currentNode, callbackFn, conditionFn); - currentNode = currentNode.nextSibling; + walkDOM(currentNode, callbackFn, conditionFn) + currentNode = currentNode.nextSibling } } -function walkTextNodes(topNode, callbackFn=()=>{}) { - const conditionFn = (node) => isTextNode(node); - walkDOM(topNode, callbackFn, conditionFn); +function walkTextNodes(topNode, callbackFn = () => {}) { + const conditionFn = node => isTextNode(node) + walkDOM(topNode, callbackFn, conditionFn) } - function clearChildNodes(element) { while (element.childNodes.length) { - element.removeChild(element.childNodes[0]); + element.removeChild(element.childNodes[0]) } } @@ -56,10 +55,10 @@ function clearChildNodes(element) { */ function containsNode(parentNode, childNode) { if (parentNode === childNode) { - return true; + return true } - const position = parentNode.compareDocumentPosition(childNode); - return !!(position & Node.DOCUMENT_POSITION_CONTAINED_BY); + const position = parentNode.compareDocumentPosition(childNode) + return !!(position & Node.DOCUMENT_POSITION_CONTAINED_BY) } /** @@ -70,37 +69,37 @@ function containsNode(parentNode, childNode) { * @private */ function getAttributes(element) { - const result = {}; + const result = {} if (element.hasAttributes()) { - forEach(element.attributes, ({name,value}) => { - result[name] = value; - }); + forEach(element.attributes, ({ name, value }) => { + result[name] = value + }) } - return result; + return result } function addClassName(element, className) { - element.classList.add(className); + element.classList.add(className) } function removeClassName(element, className) { - element.classList.remove(className); + element.classList.remove(className) } function normalizeTagName(tagName) { - return tagName.toLowerCase(); + return tagName.toLowerCase() } function parseHTML(html) { - const div = document.createElement('div'); - div.innerHTML = html; - return div; + const div = document.createElement('div') + div.innerHTML = html + return div } function serializeHTML(node) { - const div = document.createElement('div'); - div.appendChild(node); - return div.innerHTML; + const div = document.createElement('div') + div.appendChild(node) + return div.innerHTML } export { @@ -116,5 +115,5 @@ export { isCommentNode, isElementNode, parseHTML, - serializeHTML -}; + serializeHTML, +} diff --git a/src/js/utils/element-map.js b/src/js/utils/element-map.js index f33767b99..36af65133 100644 --- a/src/js/utils/element-map.js +++ b/src/js/utils/element-map.js @@ -1,30 +1,29 @@ -import assert from 'mobiledoc-kit/utils/assert'; +import assert from 'mobiledoc-kit/utils/assert' // start at one to make the falsy semantics easier -let uuidGenerator = 1; +let uuidGenerator = 1 class ElementMap { constructor() { - this._map = {}; + this._map = {} } set(key, value) { - let uuid = key._uuid; + let uuid = key._uuid if (!uuid) { - key._uuid = uuid = '' + uuidGenerator++; + key._uuid = uuid = '' + uuidGenerator++ } - this._map[uuid] = value; + this._map[uuid] = value } get(key) { if (key._uuid) { - return this._map[key._uuid]; + return this._map[key._uuid] } - return null; + return null } remove(key) { - assert('tried to fetch a value for an element not seen before', !!key._uuid); - delete this._map[key._uuid]; + assert('tried to fetch a value for an element not seen before', !!key._uuid) + delete this._map[key._uuid] } - } -export default ElementMap; +export default ElementMap diff --git a/src/js/utils/element-utils.js b/src/js/utils/element-utils.js index cb0bd8df3..423f885ed 100644 --- a/src/js/utils/element-utils.js +++ b/src/js/utils/element-utils.js @@ -1,83 +1,83 @@ -import { dasherize } from 'mobiledoc-kit/utils/string-utils'; -import { - normalizeTagName -} from 'mobiledoc-kit/utils/dom-utils'; +import { dasherize } from 'mobiledoc-kit/utils/string-utils' +import { normalizeTagName } from 'mobiledoc-kit/utils/dom-utils' function getEventTargetMatchingTag(tagName, target, container) { - tagName = normalizeTagName(tagName); + tagName = normalizeTagName(tagName) // Traverses up DOM from an event target to find the node matching specifed tag while (target && target !== container) { if (normalizeTagName(target.tagName) === tagName) { - return target; + return target } - target = target.parentNode; + target = target.parentNode } } function getElementRelativeOffset(element) { - var offset = { left: 0, top: -window.pageYOffset }; - var offsetParent = element.offsetParent; - var offsetParentPosition = window.getComputedStyle(offsetParent).position; - var offsetParentRect; + var offset = { left: 0, top: -window.pageYOffset } + var offsetParent = element.offsetParent + var offsetParentPosition = window.getComputedStyle(offsetParent).position + var offsetParentRect if (offsetParentPosition === 'relative') { - offsetParentRect = offsetParent.getBoundingClientRect(); - offset.left = offsetParentRect.left; - offset.top = offsetParentRect.top; + offsetParentRect = offsetParent.getBoundingClientRect() + offset.left = offsetParentRect.left + offset.top = offsetParentRect.top } - return offset; + return offset } function getElementComputedStyleNumericProp(element, prop) { - return parseFloat(window.getComputedStyle(element)[prop]); + return parseFloat(window.getComputedStyle(element)[prop]) } function positionElementToRect(element, rect, topOffset, leftOffset) { - var relativeOffset = getElementRelativeOffset(element); - var style = element.style; - var round = Math.round; - var left, top; + var relativeOffset = getElementRelativeOffset(element) + var style = element.style + var round = Math.round + var left, top - topOffset = topOffset || 0; - leftOffset = leftOffset || 0; - left = round(rect.left - relativeOffset.left - leftOffset); - top = round(rect.top + rect.height - relativeOffset.top - topOffset); - style.left = left + 'px'; - style.top = top + 'px'; - return { left: left, top: top }; + topOffset = topOffset || 0 + leftOffset = leftOffset || 0 + left = round(rect.left - relativeOffset.left - leftOffset) + top = round(rect.top + rect.height - relativeOffset.top - topOffset) + style.left = left + 'px' + style.top = top + 'px' + return { left: left, top: top } } function positionElementHorizontallyCenteredToRect(element, rect, topOffset) { - var horizontalCenter = (element.offsetWidth / 2) - (rect.width / 2); - return positionElementToRect(element, rect, topOffset, horizontalCenter); + var horizontalCenter = element.offsetWidth / 2 - rect.width / 2 + return positionElementToRect(element, rect, topOffset, horizontalCenter) } function positionElementCenteredBelow(element, belowElement) { - var elementMargin = getElementComputedStyleNumericProp(element, 'marginTop'); - return positionElementHorizontallyCenteredToRect(element, belowElement.getBoundingClientRect(), -elementMargin); + var elementMargin = getElementComputedStyleNumericProp(element, 'marginTop') + return positionElementHorizontallyCenteredToRect(element, belowElement.getBoundingClientRect(), -elementMargin) } function setData(element, name, value) { if (element.dataset) { - element.dataset[name] = value; + element.dataset[name] = value } else { - const dataName = dasherize(name); - return element.setAttribute(dataName, value); + const dataName = dasherize(name) + return element.setAttribute(dataName, value) } } function whenElementIsNotInDOM(element, callback) { - let isCanceled = false; + let isCanceled = false const observerFn = () => { - if (isCanceled) { return; } + if (isCanceled) { + return + } if (!element.parentNode) { - callback(); + callback() } else { - window.requestAnimationFrame(observerFn); + window.requestAnimationFrame(observerFn) } - }; - observerFn(); - return { cancel: () => isCanceled = true }; + } + observerFn() + return { cancel: () => (isCanceled = true) } } export { @@ -88,5 +88,5 @@ export { positionElementToRect, positionElementHorizontallyCenteredToRect, positionElementCenteredBelow, - whenElementIsNotInDOM -}; + whenElementIsNotInDOM, +} diff --git a/src/js/utils/environment.js b/src/js/utils/environment.js index 157cb8420..66f34fa04 100644 --- a/src/js/utils/environment.js +++ b/src/js/utils/environment.js @@ -1,5 +1,5 @@ export default { hasDOM() { - return typeof document !== 'undefined'; - } -}; + return typeof document !== 'undefined' + }, +} diff --git a/src/js/utils/fixed-queue.js b/src/js/utils/fixed-queue.js index 287bd6577..2a130b15b 100644 --- a/src/js/utils/fixed-queue.js +++ b/src/js/utils/fixed-queue.js @@ -1,29 +1,29 @@ export default class FixedQueue { - constructor(length=0) { - this._maxLength = length; - this._items = []; + constructor(length = 0) { + this._maxLength = length + this._items = [] } get length() { - return this._items.length; + return this._items.length } pop() { - return this._items.pop(); + return this._items.pop() } push(item) { - this._items.push(item); + this._items.push(item) if (this.length > this._maxLength) { - this._items.shift(); + this._items.shift() } } clear() { - this._items = []; + this._items = [] } toArray() { - return this._items; + return this._items } } diff --git a/src/js/utils/key.js b/src/js/utils/key.js index e6c8cfbb0..cd8b80e31 100644 --- a/src/js/utils/key.js +++ b/src/js/utils/key.js @@ -1,6 +1,6 @@ -import Keycodes from './keycodes'; -import Keys from './keys'; -import { TAB } from 'mobiledoc-kit/utils/characters'; +import Keycodes from './keycodes' +import Keys from './keys' +import { TAB } from 'mobiledoc-kit/utils/characters' /** * @typedef Direction @@ -10,55 +10,55 @@ import { TAB } from 'mobiledoc-kit/utils/characters'; */ export const DIRECTION = { FORWARD: 1, - BACKWARD: -1 -}; -import assert from './assert'; + BACKWARD: -1, +} +import assert from './assert' export const MODIFIERS = { META: 1, // also called "command" on OS X CTRL: 2, SHIFT: 4, - ALT: 8 // also called "option" on OS X -}; + ALT: 8, // also called "option" on OS X +} export function modifierMask(event) { - let { - metaKey, shiftKey, ctrlKey, altKey - } = event; + let { metaKey, shiftKey, ctrlKey, altKey } = event let modVal = (val, modifier) => { - return (val && modifier) || 0; - }; - return modVal(metaKey, MODIFIERS.META) + - modVal(shiftKey, MODIFIERS.SHIFT) + - modVal(ctrlKey, MODIFIERS.CTRL) + - modVal(altKey, MODIFIERS.ALT); + return (val && modifier) || 0 + } + return ( + modVal(metaKey, MODIFIERS.META) + + modVal(shiftKey, MODIFIERS.SHIFT) + + modVal(ctrlKey, MODIFIERS.CTRL) + + modVal(altKey, MODIFIERS.ALT) + ) } const SPECIAL_KEYS = { BACKSPACE: Keycodes.BACKSPACE, - TAB: Keycodes.TAB, - ENTER: Keycodes.ENTER, - ESC: Keycodes.ESC, - SPACE: Keycodes.SPACE, - PAGEUP: Keycodes.PAGEUP, - PAGEDOWN: Keycodes.PAGEDOWN, - END: Keycodes.END, - HOME: Keycodes.HOME, - LEFT: Keycodes.LEFT, - UP: Keycodes.UP, - RIGHT: Keycodes.RIGHT, - DOWN: Keycodes.DOWN, - INS: Keycodes.INS, - DEL: Keycodes.DELETE -}; + TAB: Keycodes.TAB, + ENTER: Keycodes.ENTER, + ESC: Keycodes.ESC, + SPACE: Keycodes.SPACE, + PAGEUP: Keycodes.PAGEUP, + PAGEDOWN: Keycodes.PAGEDOWN, + END: Keycodes.END, + HOME: Keycodes.HOME, + LEFT: Keycodes.LEFT, + UP: Keycodes.UP, + RIGHT: Keycodes.RIGHT, + DOWN: Keycodes.DOWN, + INS: Keycodes.INS, + DEL: Keycodes.DELETE, +} export function specialCharacterToCode(specialCharacter) { - return SPECIAL_KEYS[specialCharacter]; + return SPECIAL_KEYS[specialCharacter] } // heuristic for determining if `event` is a key event function isKeyEvent(event) { - return /^key/.test(event.type); + return /^key/.test(event.type) } /** @@ -68,106 +68,106 @@ function isKeyEvent(event) { */ const Key = class Key { constructor(event) { - this.key = event.key; - this.keyCode = event.keyCode; - this.charCode = event.charCode; - this.event = event; - this.modifierMask = modifierMask(event); + this.key = event.key + this.keyCode = event.keyCode + this.charCode = event.charCode + this.event = event + this.modifierMask = modifierMask(event) } static fromEvent(event) { - assert('Must pass a Key event to Key.fromEvent', - event && isKeyEvent(event)); - return new Key(event); + assert('Must pass a Key event to Key.fromEvent', event && isKeyEvent(event)) + return new Key(event) } toString() { - if (this.isTab()) { return TAB; } - return String.fromCharCode(this.charCode); + if (this.isTab()) { + return TAB + } + return String.fromCharCode(this.charCode) } // See https://caniuse.com/#feat=keyboardevent-key for browser support. isKeySupported() { - return this.key; + return this.key } isKey(identifier) { if (this.isKeySupported()) { - assert(`Must define Keys.${identifier}.`, Keys[identifier]); - return this.key === Keys[identifier]; + assert(`Must define Keys.${identifier}.`, Keys[identifier]) + return this.key === Keys[identifier] } else { - assert(`Must define Keycodes.${identifier}.`, Keycodes[identifier]); - return this.keyCode === Keycodes[identifier]; + assert(`Must define Keycodes.${identifier}.`, Keycodes[identifier]) + return this.keyCode === Keycodes[identifier] } } isEscape() { - return this.isKey('ESC'); + return this.isKey('ESC') } isDelete() { - return this.isKey('BACKSPACE') || this.isForwardDelete(); + return this.isKey('BACKSPACE') || this.isForwardDelete() } isForwardDelete() { - return this.isKey('DELETE'); + return this.isKey('DELETE') } isArrow() { - return this.isHorizontalArrow() || this.isVerticalArrow(); + return this.isHorizontalArrow() || this.isVerticalArrow() } isHorizontalArrow() { - return this.isLeftArrow() || this.isRightArrow(); + return this.isLeftArrow() || this.isRightArrow() } isHorizontalArrowWithoutModifiersOtherThanShift() { - return this.isHorizontalArrow() && - !(this.ctrlKey || this.metaKey || this.altKey); + return this.isHorizontalArrow() && !(this.ctrlKey || this.metaKey || this.altKey) } isVerticalArrow() { - return this.isKey('UP') || this.isKey('DOWN'); + return this.isKey('UP') || this.isKey('DOWN') } isLeftArrow() { - return this.isKey('LEFT'); + return this.isKey('LEFT') } isRightArrow() { - return this.isKey('RIGHT'); + return this.isKey('RIGHT') } isHome() { - return this.isKey('HOME'); + return this.isKey('HOME') } isEnd() { - return this.isKey('END'); + return this.isKey('END') } isPageUp() { - return this.isKey('PAGEUP'); + return this.isKey('PAGEUP') } isPageDown() { - return this.isKey('PAGEDOWN'); + return this.isKey('PAGEDOWN') } isInsert() { - return this.isKey('INS'); + return this.isKey('INS') } isClear() { - return this.isKey('CLEAR'); + return this.isKey('CLEAR') } isPause() { - return this.isKey('PAUSE'); + return this.isKey('PAUSE') } isSpace() { - return this.isKey('SPACE'); + return this.isKey('SPACE') } // In Firefox, pressing ctrl-TAB will switch to another open browser tab, but @@ -178,11 +178,11 @@ const Key = class Key { // modifier. // See: https://github.com/bustle/mobiledoc-kit/issues/565 isTab() { - return !this.hasAnyModifier() && this.isKey('TAB'); + return !this.hasAnyModifier() && this.isKey('TAB') } isEnter() { - return this.isKey('ENTER'); + return this.isKey('ENTER') } /* @@ -192,7 +192,7 @@ const Key = class Key { * @return {bool} */ isShiftKey() { - return this.isKey('SHIFT'); + return this.isKey('SHIFT') } /* @@ -201,7 +201,7 @@ const Key = class Key { * @return {bool} */ isAltKey() { - return this.isKey('ALT'); + return this.isKey('ALT') } /* @@ -210,21 +210,21 @@ const Key = class Key { * @return {bool} */ isCtrlKey() { - return this.isKey('CTRL'); + return this.isKey('CTRL') } isIME() { // FIXME the IME action seems to get lost when we issue an // `editor.deleteSelection` before it (in Chrome) - return this.keyCode === Keycodes.IME; + return this.keyCode === Keycodes.IME } get direction() { switch (true) { case this.isDelete(): - return this.isForwardDelete() ? DIRECTION.FORWARD : DIRECTION.BACKWARD; + return this.isForwardDelete() ? DIRECTION.FORWARD : DIRECTION.BACKWARD case this.isHorizontalArrow(): - return this.isRightArrow() ? DIRECTION.FORWARD : DIRECTION.BACKWARD; + return this.isRightArrow() ? DIRECTION.FORWARD : DIRECTION.BACKWARD } } @@ -237,74 +237,75 @@ const Key = class Key { * @return {bool} */ isShift() { - return this.shiftKey; + return this.shiftKey } hasModifier(modifier) { - return modifier & this.modifierMask; + return modifier & this.modifierMask } hasAnyModifier() { - return !!this.modifierMask; + return !!this.modifierMask } get ctrlKey() { - return MODIFIERS.CTRL & this.modifierMask; + return MODIFIERS.CTRL & this.modifierMask } get metaKey() { - return MODIFIERS.META & this.modifierMask; + return MODIFIERS.META & this.modifierMask } get shiftKey() { - return MODIFIERS.SHIFT & this.modifierMask; + return MODIFIERS.SHIFT & this.modifierMask } get altKey() { - return MODIFIERS.ALT & this.modifierMask; + return MODIFIERS.ALT & this.modifierMask } isPrintableKey() { return !( this.isArrow() || - this.isHome() || this.isEnd() || - this.isPageUp() || this.isPageDown() || - this.isInsert() || this.isClear() || this.isPause() || + this.isHome() || + this.isEnd() || + this.isPageUp() || + this.isPageDown() || + this.isInsert() || + this.isClear() || + this.isPause() || this.isEscape() - ); + ) } isNumberKey() { if (this.isKeySupported()) { - return this.key >= '0' && this.key <= '9'; + return this.key >= '0' && this.key <= '9' } else { - const code = this.keyCode; - return (code >= Keycodes['0'] && code <= Keycodes['9']) || - (code >= Keycodes.NUMPAD_0 && code <= Keycodes.NUMPAD_9); // numpad keys + const code = this.keyCode + return ( + (code >= Keycodes['0'] && code <= Keycodes['9']) || (code >= Keycodes.NUMPAD_0 && code <= Keycodes.NUMPAD_9) + ) // numpad keys } } isLetterKey() { if (this.isKeySupported()) { - const key = this.key; - return (key >= 'a' && key <= 'z') || - (key >= 'A' && key <= 'Z'); + const key = this.key + return (key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') } else { - const code = this.keyCode; - return (code >= Keycodes.A && code <= Keycodes.Z) || - (code >= Keycodes.a && code <= Keycodes.z); + const code = this.keyCode + return (code >= Keycodes.A && code <= Keycodes.Z) || (code >= Keycodes.a && code <= Keycodes.z) } } isPunctuation() { if (this.isKeySupported()) { - const key = this.key; - return (key >= ';' && key <= '`') || - (key >= '[' && key <= '"'); + const key = this.key + return (key >= ';' && key <= '`') || (key >= '[' && key <= '"') } else { - const code = this.keyCode; - return (code >= Keycodes[';'] && code <= Keycodes['`']) || - (code >= Keycodes['['] && code <= Keycodes['"']); + const code = this.keyCode + return (code >= Keycodes[';'] && code <= Keycodes['`']) || (code >= Keycodes['['] && code <= Keycodes['"']) } } @@ -314,12 +315,12 @@ const Key = class Key { */ isPrintable() { if (this.ctrlKey || this.metaKey) { - return false; + return false } // Firefox calls keypress events for some keys that should not be printable if (!this.isPrintableKey()) { - return false; + return false } return ( @@ -327,13 +328,13 @@ const Key = class Key { this.toString().length > 0 || this.isNumberKey() || this.isSpace() || - this.isTab() || + this.isTab() || this.isEnter() || this.isLetterKey() || this.isPunctuation() || this.isIME() - ); + ) } -}; +} -export default Key; +export default Key diff --git a/src/js/utils/keycodes.js b/src/js/utils/keycodes.js index d7d0fa50a..d79aaded7 100644 --- a/src/js/utils/keycodes.js +++ b/src/js/utils/keycodes.js @@ -1,43 +1,43 @@ export default { - BACKSPACE: 8, - SPACE: 32, - ENTER: 13, - SHIFT: 16, - ESC: 27, - DELETE: 46, - '0': 48, - '9': 57, - A: 65, - Z: 90, - a: 97, - z: 122, - 'NUMPAD_0': 186, - 'NUMPAD_9': 111, - ';': 186, - '.': 190, - '`': 192, - '[': 219, - '"': 222, + BACKSPACE: 8, + SPACE: 32, + ENTER: 13, + SHIFT: 16, + ESC: 27, + DELETE: 46, + '0': 48, + '9': 57, + A: 65, + Z: 90, + a: 97, + z: 122, + NUMPAD_0: 186, + NUMPAD_9: 111, + ';': 186, + '.': 190, + '`': 192, + '[': 219, + '"': 222, // Input Method Editor uses multiple keystrokes to display characters. // Example on mac: press option-i then i. This fires 2 key events in Chrome // with keyCode 229 and displays ˆ and then î. // See http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html#fixed-virtual-key-codes - IME: 229, + IME: 229, - TAB: 9, - CLEAR: 12, - PAUSE: 19, - PAGEUP: 33, - PAGEDOWN: 34, - END: 35, - HOME: 36, - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40, - INS: 45, - META: 91, - ALT: 18, - CTRL: 17 -}; + TAB: 9, + CLEAR: 12, + PAUSE: 19, + PAGEUP: 33, + PAGEDOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + INS: 45, + META: 91, + ALT: 18, + CTRL: 17, +} diff --git a/src/js/utils/keys.js b/src/js/utils/keys.js index 1bbb47f61..6b79e0653 100644 --- a/src/js/utils/keys.js +++ b/src/js/utils/keys.js @@ -1,23 +1,23 @@ export default { - BACKSPACE: 'Backspace', - SPACE: ' ', - ENTER: 'Enter', - SHIFT: 'Shift', - ESC: 'Escape', - DELETE: 'Delete', - INS: 'Insert', - HOME: 'Home', - END: 'End', - PAGEUP: 'PageUp', - PAGEDOWN: 'PageDown', - CLEAR: 'Clear', - PAUSE: 'Pause', - TAB: 'Tab', - ALT: 'Alt', - CTRL: 'Control', + BACKSPACE: 'Backspace', + SPACE: ' ', + ENTER: 'Enter', + SHIFT: 'Shift', + ESC: 'Escape', + DELETE: 'Delete', + INS: 'Insert', + HOME: 'Home', + END: 'End', + PAGEUP: 'PageUp', + PAGEDOWN: 'PageDown', + CLEAR: 'Clear', + PAUSE: 'Pause', + TAB: 'Tab', + ALT: 'Alt', + CTRL: 'Control', - LEFT: 'ArrowLeft', - RIGHT: 'ArrowRight', - UP: 'ArrowUp', - DOWN: 'ArrowDown' -}; + LEFT: 'ArrowLeft', + RIGHT: 'ArrowRight', + UP: 'ArrowUp', + DOWN: 'ArrowDown', +} diff --git a/src/js/utils/linked-item.js b/src/js/utils/linked-item.js index 923ff20ab..ac2b86c28 100644 --- a/src/js/utils/linked-item.js +++ b/src/js/utils/linked-item.js @@ -1,6 +1,6 @@ export default class LinkedItem { constructor() { - this.next = null; - this.prev = null; + this.next = null + this.prev = null } } diff --git a/src/js/utils/linked-list.js b/src/js/utils/linked-list.js index f95240996..fea82bfcb 100644 --- a/src/js/utils/linked-list.js +++ b/src/js/utils/linked-list.js @@ -1,210 +1,212 @@ -import assert from './assert'; +import assert from './assert' -const PARENT_PROP = '__parent'; +const PARENT_PROP = '__parent' export default class LinkedList { constructor(options) { - this.head = null; - this.tail = null; - this.length = 0; + this.head = null + this.tail = null + this.length = 0 if (options) { - const {adoptItem, freeItem} = options; - this._adoptItem = adoptItem; - this._freeItem = freeItem; + const { adoptItem, freeItem } = options + this._adoptItem = adoptItem + this._freeItem = freeItem } } adoptItem(item) { - item[PARENT_PROP]= this; - this.length++; - if (this._adoptItem) { this._adoptItem(item); } + item[PARENT_PROP] = this + this.length++ + if (this._adoptItem) { + this._adoptItem(item) + } } freeItem(item) { - item[PARENT_PROP] = null; - this.length--; - if (this._freeItem) { this._freeItem(item); } + item[PARENT_PROP] = null + this.length-- + if (this._freeItem) { + this._freeItem(item) + } } get isEmpty() { - return this.length === 0; + return this.length === 0 } prepend(item) { - this.insertBefore(item, this.head); + this.insertBefore(item, this.head) } append(item) { - this.insertBefore(item, null); + this.insertBefore(item, null) } insertAfter(item, prevItem) { - let nextItem = prevItem ? prevItem.next : this.head; - this.insertBefore(item, nextItem); + let nextItem = prevItem ? prevItem.next : this.head + this.insertBefore(item, nextItem) } - _ensureItemIsNotAlreadyInList(item){ + _ensureItemIsNotAlreadyInList(item) { assert( 'Cannot insert an item into a list if it is already in a list', !item.next && !item.prev && this.head !== item - ); + ) } insertBefore(item, nextItem) { - this._ensureItemIsNotInList(item); - this.adoptItem(item); + this._ensureItemIsNotInList(item) + this.adoptItem(item) - let insertPos; + let insertPos if (nextItem && nextItem.prev) { - insertPos = 'middle'; + insertPos = 'middle' } else if (nextItem) { - insertPos = 'start'; + insertPos = 'start' } else { - insertPos = 'end'; + insertPos = 'end' } switch (insertPos) { case 'start': if (this.head) { - item.next = this.head; - this.head.prev = item; + item.next = this.head + this.head.prev = item } - this.head = item; + this.head = item - break; + break case 'middle': { - let prevItem = nextItem.prev; - item.next = nextItem; - item.prev = prevItem; - nextItem.prev = item; - prevItem.next = item; + let prevItem = nextItem.prev + item.next = nextItem + item.prev = prevItem + nextItem.prev = item + prevItem.next = item - break; + break } case 'end': { - let tail = this.tail; - item.prev = tail; + let tail = this.tail + item.prev = tail if (tail) { - tail.next = item; + tail.next = item } else { - this.head = item; + this.head = item } - this.tail = item; + this.tail = item - break; + break } } } remove(item) { if (!item[PARENT_PROP]) { - return; + return } - this._ensureItemIsInThisList(item); - this.freeItem(item); + this._ensureItemIsInThisList(item) + this.freeItem(item) - let [prev, next] = [item.prev, item.next]; - item.prev = null; - item.next = null; + let [prev, next] = [item.prev, item.next] + item.prev = null + item.next = null if (prev) { - prev.next = next; + prev.next = next } else { - this.head = next; + this.head = next } if (next) { - next.prev = prev; + next.prev = prev } else { - this.tail = prev; + this.tail = prev } } forEach(callback) { - let item = this.head; - let index = 0; + let item = this.head + let index = 0 while (item) { - callback(item, index++); - item = item.next; + callback(item, index++) + item = item.next } } map(callback) { - let result = []; - this.forEach(i => result.push(callback(i))); - return result; + let result = [] + this.forEach(i => result.push(callback(i))) + return result } walk(startItem, endItem, callback) { - let item = startItem || this.head; + let item = startItem || this.head while (item) { - callback(item); + callback(item) if (item === endItem) { - break; + break } - item = item.next; + item = item.next } } readRange(startItem, endItem) { - let items = []; - this.walk(startItem, endItem, (item) => { - items.push(item); - }); - return items; + let items = [] + this.walk(startItem, endItem, item => { + items.push(item) + }) + return items } toArray() { - return this.readRange(); + return this.readRange() } - detect(callback, item=this.head, reverse=false) { + detect(callback, item = this.head, reverse = false) { while (item) { if (callback(item)) { - return item; + return item } - item = reverse ? item.prev : item.next; + item = reverse ? item.prev : item.next } } any(callback) { - return !!this.detect(callback); + return !!this.detect(callback) } every(callback) { - let item = this.head; + let item = this.head while (item) { if (!callback(item)) { - return false; + return false } - item = item.next; + item = item.next } - return true; + return true } objectAt(targetIndex) { - let index = -1; + let index = -1 return this.detect(() => { - index++; - return (targetIndex === index); - }); + index++ + return targetIndex === index + }) } splice(targetItem, removalCount, newItems) { - let item = targetItem; - let nextItem = item.next; - let count = 0; + let item = targetItem + let nextItem = item.next + let count = 0 while (item && count < removalCount) { - count++; - nextItem = item.next; - this.remove(item); - item = nextItem; + count++ + nextItem = item.next + this.remove(item) + item = nextItem } - newItems.forEach((newItem) => { - this.insertBefore(newItem, nextItem); - }); + newItems.forEach(newItem => { + this.insertBefore(newItem, nextItem) + }) } removeBy(conditionFn) { - let item = this.head; + let item = this.head while (item) { - let nextItem = item.next; + let nextItem = item.next if (conditionFn(item)) { - this.remove(item); + this.remove(item) } - item = nextItem; + item = nextItem } } _ensureItemIsNotInList(item) { - assert('Cannot insert an item into a list if it is already in a list', - !item[PARENT_PROP]); + assert('Cannot insert an item into a list if it is already in a list', !item[PARENT_PROP]) } _ensureItemIsInThisList(item) { - assert('Cannot remove item that is in another list', - item[PARENT_PROP] === this); + assert('Cannot remove item that is in another list', item[PARENT_PROP] === this) } } diff --git a/src/js/utils/log-manager.js b/src/js/utils/log-manager.js index b39a4b33d..92cdac8d9 100644 --- a/src/js/utils/log-manager.js +++ b/src/js/utils/log-manager.js @@ -1,47 +1,47 @@ class Logger { constructor(type, manager) { - this.type = type; - this.manager = manager; + this.type = type + this.manager = manager } isEnabled() { - return this.manager.isEnabled(this.type); + return this.manager.isEnabled(this.type) } log(...args) { - args.unshift(`[${this.type}]`); + args.unshift(`[${this.type}]`) if (this.isEnabled()) { - window.console.log(...args); + window.console.log(...args) } } } class LogManager { constructor() { - this.enabledTypes = []; - this.allEnabled = false; + this.enabledTypes = [] + this.allEnabled = false } for(type) { - return new Logger(type, this); + return new Logger(type, this) } enableAll() { - this.allEnabled = true; + this.allEnabled = true } enableTypes(types) { - this.enabledTypes = this.enabledTypes.concat(types); + this.enabledTypes = this.enabledTypes.concat(types) } disable() { - this.enabledTypes = []; - this.allEnabled = false; + this.enabledTypes = [] + this.allEnabled = false } isEnabled(type) { - return this.allEnabled || this.enabledTypes.indexOf(type) !== -1; + return this.allEnabled || this.enabledTypes.indexOf(type) !== -1 } } -export default LogManager; +export default LogManager diff --git a/src/js/utils/markuperable.js b/src/js/utils/markuperable.js index d54753aa6..42c8cd9de 100644 --- a/src/js/utils/markuperable.js +++ b/src/js/utils/markuperable.js @@ -1,71 +1,67 @@ -import { normalizeTagName } from '../utils/dom-utils'; -import { detect, commonItemLength, forEach, filter } from '../utils/array-utils'; +import { normalizeTagName } from '../utils/dom-utils' +import { detect, commonItemLength, forEach, filter } from '../utils/array-utils' export default class Markerupable { - clearMarkups() { - this.markups = []; + this.markups = [] } addMarkup(markup) { - this.markups.push(markup); + this.markups.push(markup) } addMarkupAtIndex(markup, index) { - this.markups.splice(index, 0, markup); + this.markups.splice(index, 0, markup) } removeMarkup(markupOrMarkupCallback) { - let callback; + let callback if (typeof markupOrMarkupCallback === 'function') { - callback = markupOrMarkupCallback; + callback = markupOrMarkupCallback } else { - let markup = markupOrMarkupCallback; - callback = (_markup) => _markup === markup; + let markup = markupOrMarkupCallback + callback = _markup => _markup === markup } - forEach( - filter(this.markups, callback), - m => this._removeMarkup(m) - ); + forEach(filter(this.markups, callback), m => this._removeMarkup(m)) } _removeMarkup(markup) { - const index = this.markups.indexOf(markup); + const index = this.markups.indexOf(markup) if (index !== -1) { - this.markups.splice(index, 1); + this.markups.splice(index, 1) } } hasMarkup(tagNameOrMarkup) { - return !!this.getMarkup(tagNameOrMarkup); + return !!this.getMarkup(tagNameOrMarkup) } getMarkup(tagNameOrMarkup) { if (typeof tagNameOrMarkup === 'string') { - let tagName = normalizeTagName(tagNameOrMarkup); - return detect(this.markups, markup => markup.tagName === tagName); + let tagName = normalizeTagName(tagNameOrMarkup) + return detect(this.markups, markup => markup.tagName === tagName) } else { - let targetMarkup = tagNameOrMarkup; - return detect(this.markups, markup => markup === targetMarkup); + let targetMarkup = tagNameOrMarkup + return detect(this.markups, markup => markup === targetMarkup) } } get openedMarkups() { - let count = 0; + let count = 0 if (this.prev) { - count = commonItemLength(this.markups, this.prev.markups); + count = commonItemLength(this.markups, this.prev.markups) } - return this.markups.slice(count); + return this.markups.slice(count) } get closedMarkups() { - let count = 0; + let count = 0 if (this.next) { - count = commonItemLength(this.markups, this.next.markups); + count = commonItemLength(this.markups, this.next.markups) } - return this.markups.slice(count); + return this.markups.slice(count) } } diff --git a/src/js/utils/merge.js b/src/js/utils/merge.js index 193091a73..1c3ef103c 100644 --- a/src/js/utils/merge.js +++ b/src/js/utils/merge.js @@ -1,13 +1,13 @@ function mergeWithOptions(original, updates, options) { - options = options || {}; - for(var prop in updates) { + options = options || {} + for (var prop in updates) { if (options.hasOwnProperty(prop)) { - original[prop] = options[prop]; + original[prop] = options[prop] } else if (updates.hasOwnProperty(prop)) { - original[prop] = updates[prop]; + original[prop] = updates[prop] } } - return original; + return original } /** @@ -15,7 +15,7 @@ function mergeWithOptions(original, updates, options) { * @private */ function merge(original, updates) { - return mergeWithOptions(original, updates); + return mergeWithOptions(original, updates) } -export { mergeWithOptions, merge }; +export { mergeWithOptions, merge } diff --git a/src/js/utils/mixin.js b/src/js/utils/mixin.js index e798ca71e..676f530c3 100644 --- a/src/js/utils/mixin.js +++ b/src/js/utils/mixin.js @@ -1,15 +1,15 @@ -const CONSTRUCTOR_FN_NAME = 'constructor'; +const CONSTRUCTOR_FN_NAME = 'constructor' export default function mixin(target, source) { - target = target.prototype; + target = target.prototype // Fallback to just `source` to allow mixing in a plain object (pojo) - source = source.prototype || source; + source = source.prototype || source - Object.getOwnPropertyNames(source).forEach((name) => { + Object.getOwnPropertyNames(source).forEach(name => { if (name !== CONSTRUCTOR_FN_NAME) { - const descriptor = Object.getOwnPropertyDescriptor(source, name); + const descriptor = Object.getOwnPropertyDescriptor(source, name) - Object.defineProperty(target, name, descriptor); + Object.defineProperty(target, name, descriptor) } - }); + }) } diff --git a/src/js/utils/mobiledoc-error.js b/src/js/utils/mobiledoc-error.js index 46c9f5f4f..83a2c6c1e 100644 --- a/src/js/utils/mobiledoc-error.js +++ b/src/js/utils/mobiledoc-error.js @@ -1,25 +1,17 @@ -var errorProps = [ - 'description', - 'fileName', - 'lineNumber', - 'message', - 'name', - 'number', - 'stack' -]; +var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'] function MobiledocError() { - let tmp = Error.apply(this, arguments); + let tmp = Error.apply(this, arguments) if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); + Error.captureStackTrace(this, this.constructor) } // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. for (let idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; + this[errorProps[idx]] = tmp[errorProps[idx]] } } -MobiledocError.prototype = Object.create(Error.prototype); +MobiledocError.prototype = Object.create(Error.prototype) -export default MobiledocError; +export default MobiledocError diff --git a/src/js/utils/object-utils.js b/src/js/utils/object-utils.js index 1f5b5ff14..3bfb759b6 100644 --- a/src/js/utils/object-utils.js +++ b/src/js/utils/object-utils.js @@ -1,11 +1,11 @@ export function entries(obj) { - const ownProps = Object.keys(obj); - let i = ownProps.length; - const resArray = new Array(i); + const ownProps = Object.keys(obj) + let i = ownProps.length + const resArray = new Array(i) while (i--) { - resArray[i] = [ownProps[i], obj[ownProps[i]]]; + resArray[i] = [ownProps[i], obj[ownProps[i]]] } - return resArray; + return resArray } diff --git a/src/js/utils/parse-utils.js b/src/js/utils/parse-utils.js index 6c013f539..a2ccd9d32 100644 --- a/src/js/utils/parse-utils.js +++ b/src/js/utils/parse-utils.js @@ -1,30 +1,30 @@ /* global JSON */ -import mobiledocParsers from '../parsers/mobiledoc'; -import HTMLParser from '../parsers/html'; -import TextParser from '../parsers/text'; +import mobiledocParsers from '../parsers/mobiledoc' +import HTMLParser from '../parsers/html' +import TextParser from '../parsers/text' -export const MIME_TEXT_PLAIN = 'text/plain'; -export const MIME_TEXT_HTML = 'text/html'; -export const NONSTANDARD_IE_TEXT_TYPE = 'Text'; +export const MIME_TEXT_PLAIN = 'text/plain' +export const MIME_TEXT_HTML = 'text/html' +export const NONSTANDARD_IE_TEXT_TYPE = 'Text' -const MOBILEDOC_REGEX = new RegExp(/data\-mobiledoc='(.*?)'>/); +const MOBILEDOC_REGEX = new RegExp(/data\-mobiledoc='(.*?)'>/) /** * @return {Post} * @private */ function parsePostFromHTML(html, builder, plugins) { - let post; + let post if (MOBILEDOC_REGEX.test(html)) { - let mobiledocString = html.match(MOBILEDOC_REGEX)[1]; - let mobiledoc = JSON.parse(mobiledocString); - post = mobiledocParsers.parse(builder, mobiledoc); + let mobiledocString = html.match(MOBILEDOC_REGEX)[1] + let mobiledoc = JSON.parse(mobiledocString) + post = mobiledocParsers.parse(builder, mobiledoc) } else { - post = new HTMLParser(builder, {plugins}).parse(html); + post = new HTMLParser(builder, { plugins }).parse(html) } - return post; + return post } /** @@ -32,9 +32,9 @@ function parsePostFromHTML(html, builder, plugins) { * @private */ function parsePostFromText(text, builder, plugins) { - let parser = new TextParser(builder, {plugins}); - let post = parser.parse(text); - return post; + let parser = new TextParser(builder, { plugins }) + let post = parser.parse(text) + return post } /** @@ -42,21 +42,23 @@ function parsePostFromText(text, builder, plugins) { * @private */ export function getContentFromPasteEvent(event, window) { - let html = '', text = ''; + let html = '', + text = '' - let { clipboardData } = event; + let { clipboardData } = event if (clipboardData && clipboardData.getData) { - html = clipboardData.getData(MIME_TEXT_HTML); - text = clipboardData.getData(MIME_TEXT_PLAIN); - } else if (window.clipboardData && window.clipboardData.getData) { // IE + html = clipboardData.getData(MIME_TEXT_HTML) + text = clipboardData.getData(MIME_TEXT_PLAIN) + } else if (window.clipboardData && window.clipboardData.getData) { + // IE // The Internet Explorers (including Edge) have a non-standard way of interacting with the // Clipboard API (see http://caniuse.com/#feat=clipboard). In short, they expose a global window.clipboardData // object instead of the per-event event.clipboardData object on the other browsers. - html = window.clipboardData.getData(NONSTANDARD_IE_TEXT_TYPE); + html = window.clipboardData.getData(NONSTANDARD_IE_TEXT_TYPE) } - return { html, text }; + return { html, text } } /** @@ -64,21 +66,22 @@ export function getContentFromPasteEvent(event, window) { * @private */ function getContentFromDropEvent(event, logger) { - let html = '', text = ''; + let html = '', + text = '' try { - html = event.dataTransfer.getData(MIME_TEXT_HTML); - text = event.dataTransfer.getData(MIME_TEXT_PLAIN); + html = event.dataTransfer.getData(MIME_TEXT_HTML) + text = event.dataTransfer.getData(MIME_TEXT_PLAIN) } catch (e) { // FIXME IE11 does not include any data in the 'text/html' or 'text/plain' // mimetypes. It throws an error 'Invalid argument' when attempting to read // these properties. if (logger) { - logger.log('Error getting drop data: ', e); + logger.log('Error getting drop data: ', e) } } - return { html, text }; + return { html, text } } /** @@ -87,22 +90,22 @@ function getContentFromDropEvent(event, logger) { * @param {Window} * @private */ -export function setClipboardData(event, {mobiledoc, html, text}, window) { +export function setClipboardData(event, { mobiledoc, html, text }, window) { if (mobiledoc && html) { - html = `
    ${html}
    `; + html = `
    ${html}
    ` } - let { clipboardData } = event; - let { clipboardData: nonstandardClipboardData } = window; + let { clipboardData } = event + let { clipboardData: nonstandardClipboardData } = window if (clipboardData && clipboardData.setData) { - clipboardData.setData(MIME_TEXT_HTML, html); - clipboardData.setData(MIME_TEXT_PLAIN, text); + clipboardData.setData(MIME_TEXT_HTML, html) + clipboardData.setData(MIME_TEXT_PLAIN, text) } else if (nonstandardClipboardData && nonstandardClipboardData.setData) { // The Internet Explorers (including Edge) have a non-standard way of interacting with the // Clipboard API (see http://caniuse.com/#feat=clipboard). In short, they expose a global window.clipboardData // object instead of the per-event event.clipboardData object on the other browsers. - nonstandardClipboardData.setData(NONSTANDARD_IE_TEXT_TYPE, html); + nonstandardClipboardData.setData(NONSTANDARD_IE_TEXT_TYPE, html) } } @@ -112,13 +115,17 @@ export function setClipboardData(event, {mobiledoc, html, text}, window) { * @return {Post} * @private */ -export function parsePostFromPaste(pasteEvent, {builder, _parserPlugins: plugins}, {targetFormat}={targetFormat:'html'}) { - let { html, text } = getContentFromPasteEvent(pasteEvent, window); +export function parsePostFromPaste( + pasteEvent, + { builder, _parserPlugins: plugins }, + { targetFormat } = { targetFormat: 'html' } +) { + let { html, text } = getContentFromPasteEvent(pasteEvent, window) if (targetFormat === 'html' && html && html.length) { - return parsePostFromHTML(html, builder, plugins); + return parsePostFromHTML(html, builder, plugins) } else if (text && text.length) { - return parsePostFromText(text, builder, plugins); + return parsePostFromText(text, builder, plugins) } } @@ -129,13 +136,13 @@ export function parsePostFromPaste(pasteEvent, {builder, _parserPlugins: plugins * @return {Post} * @private */ -export function parsePostFromDrop(dropEvent, editor, {logger}={}) { - let { builder, _parserPlugins: plugins } = editor; - let { html, text } = getContentFromDropEvent(dropEvent, logger); +export function parsePostFromDrop(dropEvent, editor, { logger } = {}) { + let { builder, _parserPlugins: plugins } = editor + let { html, text } = getContentFromDropEvent(dropEvent, logger) if (html && html.length) { - return parsePostFromHTML(html, builder, plugins); + return parsePostFromHTML(html, builder, plugins) } else if (text && text.length) { - return parsePostFromText(text, builder, plugins); + return parsePostFromText(text, builder, plugins) } } diff --git a/src/js/utils/placeholder-image-src.js b/src/js/utils/placeholder-image-src.js index 4ac64acec..67b6ae34c 100644 --- a/src/js/utils/placeholder-image-src.js +++ b/src/js/utils/placeholder-image-src.js @@ -1,3 +1,4 @@ -const placeholderImageSrc = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAMFBMVEXp7vG6vsHm6+63u77Hy868wMPe4+bO09bh5unr8fTR1djAxMfM0NPX3N/c4eTBxcjXRf5TAAACh0lEQVR4nO3b6ZKqMBSFUSQMYZL3f9tbBq/NEEDiqUqOfusn1ZXKbjcQlGQZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACC6RkbsGHuabChEtHmiGYfS3EQYM+Sxw/gMQvmcNnYaj6oTDHi73WPn2eqnj9B8zo3TJXcq5uNjXmVff86VwSR3JtryMa1BYqi7S1hJDCVpSigyLcGhJJEwzlCSNtPKrbVhVwsdCfOhH7uuaG3ARV9DwsaOzxt3N1yPqCHhvXytTUz92VDpmE/LLhZwl++R6Sds6sUa/PL6K/2E2fIhw1xdRKefsFolrPc+xNx/N0k/4fpBsdhL2HfeiN+TsDCms8dDpeRyS3P3QDl6Iqaf8L0rTf+80m6Lmn7Ct+4Wxf+/2RY1/YRv3PHz/u+fsCmqgoTnq7Z+8SGviqoh4dnKu1ieqauiakh4/PQ0r6ivqDoSHj0B97eNRVG1JNxV+L4bnxdVecJtRTdFVZ7QU9F1UXUn9FZ0VVRlCav5ob2KLouqKmFjy676u2HsVnRRVFUJq3J+8KCi86IqSthMvyl209Hjijqm3RsqAZ5pNfa5PJ2KelJRjQmr1/r7cfy0ouoSNvOfvbvhvKLaEr4qOin9kTQnrN7LpDZhE/Zmhp6Eq4p+YcKgiipKGFhRRQkDK6ooYfgLbiSMioQkJGF8P5XwHv4O+7AaKiXzaeXh1kMl5AffTUxiKEm/krD94BR8Gdxl1fceSlR58ZhXKbEpyD2amNiBtmrJLTMHL1LF8/rpXkSZXEmz8K8uvAFFNm6Iq0aBLUFOmeCuJ6exrcCmoLpN7kYx891bSAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgh/wDdr8peyRHLogAAAAASUVORK5CYII="; +const placeholderImageSrc = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAMFBMVEXp7vG6vsHm6+63u77Hy868wMPe4+bO09bh5unr8fTR1djAxMfM0NPX3N/c4eTBxcjXRf5TAAACh0lEQVR4nO3b6ZKqMBSFUSQMYZL3f9tbBq/NEEDiqUqOfusn1ZXKbjcQlGQZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACC6RkbsGHuabChEtHmiGYfS3EQYM+Sxw/gMQvmcNnYaj6oTDHi73WPn2eqnj9B8zo3TJXcq5uNjXmVff86VwSR3JtryMa1BYqi7S1hJDCVpSigyLcGhJJEwzlCSNtPKrbVhVwsdCfOhH7uuaG3ARV9DwsaOzxt3N1yPqCHhvXytTUz92VDpmE/LLhZwl++R6Sds6sUa/PL6K/2E2fIhw1xdRKefsFolrPc+xNx/N0k/4fpBsdhL2HfeiN+TsDCms8dDpeRyS3P3QDl6Iqaf8L0rTf+80m6Lmn7Ct+4Wxf+/2RY1/YRv3PHz/u+fsCmqgoTnq7Z+8SGviqoh4dnKu1ieqauiakh4/PQ0r6ivqDoSHj0B97eNRVG1JNxV+L4bnxdVecJtRTdFVZ7QU9F1UXUn9FZ0VVRlCav5ob2KLouqKmFjy676u2HsVnRRVFUJq3J+8KCi86IqSthMvyl209Hjijqm3RsqAZ5pNfa5PJ2KelJRjQmr1/r7cfy0ouoSNvOfvbvhvKLaEr4qOin9kTQnrN7LpDZhE/Zmhp6Eq4p+YcKgiipKGFhRRQkDK6ooYfgLbiSMioQkJGF8P5XwHv4O+7AaKiXzaeXh1kMl5AffTUxiKEm/krD94BR8Gdxl1fceSlR58ZhXKbEpyD2amNiBtmrJLTMHL1LF8/rpXkSZXEmz8K8uvAFFNm6Iq0aBLUFOmeCuJ6exrcCmoLpN7kYx891bSAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgh/wDdr8peyRHLogAAAAASUVORK5CYII=' -export default placeholderImageSrc; +export default placeholderImageSrc diff --git a/src/js/utils/selection-utils.js b/src/js/utils/selection-utils.js index d932c5c93..cb595c6ca 100644 --- a/src/js/utils/selection-utils.js +++ b/src/js/utils/selection-utils.js @@ -1,33 +1,32 @@ -import { DIRECTION } from '../utils/key'; -import { isTextNode, isElementNode } from 'mobiledoc-kit/utils/dom-utils'; +import { DIRECTION } from '../utils/key' +import { isTextNode, isElementNode } from 'mobiledoc-kit/utils/dom-utils' function clearSelection() { - window.getSelection().removeAllRanges(); + window.getSelection().removeAllRanges() } function textNodeRects(node) { - let range = document.createRange(); - range.setEnd(node, node.nodeValue.length); - range.setStart(node, 0); - return range.getClientRects(); + let range = document.createRange() + range.setEnd(node, node.nodeValue.length) + range.setStart(node, 0) + return range.getClientRects() } function findOffsetInTextNode(node, coords) { - let len = node.nodeValue.length; - let range = document.createRange(); + let len = node.nodeValue.length + let range = document.createRange() for (let i = 0; i < len; i++) { - range.setEnd(node, i + 1); - range.setStart(node, i); - let rect = range.getBoundingClientRect(); + range.setEnd(node, i + 1) + range.setStart(node, i) + let rect = range.getBoundingClientRect() if (rect.top === rect.bottom) { - continue; + continue } - if (rect.left <= coords.left && rect.right >= coords.left && - rect.top <= coords.top && rect.bottom >= coords.top) { - return {node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)}; + if (rect.left <= coords.left && rect.right >= coords.left && rect.top <= coords.top && rect.bottom >= coords.top) { + return { node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0) } } } - return {node, offset: 0}; + return { node, offset: 0 } } /* @@ -37,77 +36,78 @@ function findOffsetInTextNode(node, coords) { */ /* eslint-disable complexity */ function findOffsetInNode(node, coords) { - let closest, dyClosest = 1e8, coordsClosest, offset = 0; + let closest, + dyClosest = 1e8, + coordsClosest, + offset = 0 for (let child = node.firstChild; child; child = child.nextSibling) { - let rects; + let rects if (isElementNode(child)) { - rects = child.getClientRects(); + rects = child.getClientRects() } else if (isTextNode(child)) { - rects = textNodeRects(child); + rects = textNodeRects(child) } else { - continue; + continue } for (let i = 0; i < rects.length; i++) { - let rect = rects[i]; + let rect = rects[i] if (rect.left <= coords.left && rect.right >= coords.left) { - let dy = rect.top > coords.top ? rect.top - coords.top - : rect.bottom < coords.top ? coords.top - rect.bottom : 0; + let dy = rect.top > coords.top ? rect.top - coords.top : rect.bottom < coords.top ? coords.top - rect.bottom : 0 if (dy < dyClosest) { - closest = child; - dyClosest = dy; - coordsClosest = dy ? {left: coords.left, top: rect.top} : coords; + closest = child + dyClosest = dy + coordsClosest = dy ? { left: coords.left, top: rect.top } : coords if (isElementNode(child) && !child.firstChild) { - offset = i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0); + offset = i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0) } - continue; + continue } } - if (!closest && - (coords.top >= rect.bottom || coords.top >= rect.top && coords.left >= rect.right)) { - offset = i + 1; + if (!closest && (coords.top >= rect.bottom || (coords.top >= rect.top && coords.left >= rect.right))) { + offset = i + 1 } } } if (!closest) { - return {node, offset}; + return { node, offset } } if (isTextNode(closest)) { - return findOffsetInTextNode(closest, coordsClosest); + return findOffsetInTextNode(closest, coordsClosest) } if (closest.firstChild) { - return findOffsetInNode(closest, coordsClosest); + return findOffsetInNode(closest, coordsClosest) } - return {node, offset}; + return { node, offset } } /* eslint-enable complexity */ function constrainNodeTo(node, parentNode, existingOffset) { - let compare = parentNode.compareDocumentPosition(node); + let compare = parentNode.compareDocumentPosition(node) if (compare & Node.DOCUMENT_POSITION_CONTAINED_BY) { // the node is inside parentNode, do nothing - return { node, offset: existingOffset}; + return { node, offset: existingOffset } } else if (compare & Node.DOCUMENT_POSITION_CONTAINS) { // the node contains parentNode. This shouldn't happen. - return { node, offset: existingOffset}; + return { node, offset: existingOffset } } else if (compare & Node.DOCUMENT_POSITION_PRECEDING) { // node is before parentNode. return start of deepest first child - let child = parentNode.firstChild; + let child = parentNode.firstChild while (child.firstChild) { - child = child.firstChild; + child = child.firstChild } - return { node: child, offset: 0}; + return { node: child, offset: 0 } } else if (compare & Node.DOCUMENT_POSITION_FOLLOWING) { // node is after parentNode. return end of deepest last child - let child = parentNode.lastChild; + let child = parentNode.lastChild while (child.lastChild) { - child = child.lastChild; + child = child.lastChild } - let offset = isTextNode(child) ? child.textContent.length : 1; - return {node: child, offset}; + let offset = isTextNode(child) ? child.textContent.length : 1 + return { node: child, offset } } else { - return { node, offset: existingOffset}; + return { node, offset: existingOffset } } } @@ -117,23 +117,21 @@ function constrainNodeTo(node, parentNode, existingOffset) { * or end of the parentNode's children */ function constrainSelectionTo(selection, parentNode) { - let { - node: anchorNode, - offset: anchorOffset - } = constrainNodeTo(selection.anchorNode, parentNode, selection.anchorOffset); - let { - node: focusNode, - offset: focusOffset - } = constrainNodeTo(selection.focusNode, parentNode, selection.focusOffset); + let { node: anchorNode, offset: anchorOffset } = constrainNodeTo( + selection.anchorNode, + parentNode, + selection.anchorOffset + ) + let { node: focusNode, offset: focusOffset } = constrainNodeTo(selection.focusNode, parentNode, selection.focusOffset) - return { anchorNode, anchorOffset, focusNode, focusOffset }; + return { anchorNode, anchorOffset, focusNode, focusOffset } } function comparePosition(selection) { - let { anchorNode, focusNode, anchorOffset, focusOffset } = selection; - let headNode, tailNode, headOffset, tailOffset, direction; + let { anchorNode, focusNode, anchorOffset, focusOffset } = selection + let headNode, tailNode, headOffset, tailOffset, direction - const position = anchorNode.compareDocumentPosition(focusNode); + const position = anchorNode.compareDocumentPosition(focusNode) // IE may select return focus and anchor nodes far up the DOM tree instead of // picking the deepest, most specific possible node. For example in @@ -149,63 +147,65 @@ function comparePosition(selection) { // if (position & Node.DOCUMENT_POSITION_CONTAINS) { if (focusOffset < focusNode.childNodes.length) { - focusNode = focusNode.childNodes[focusOffset]; - focusOffset = 0; + focusNode = focusNode.childNodes[focusOffset] + focusOffset = 0 } else { // This situation happens on IE when triple-clicking to select. // Set the focus to the very last character inside the node. while (focusNode.lastChild) { - focusNode = focusNode.lastChild; + focusNode = focusNode.lastChild } - focusOffset = focusNode.textContent.length; + focusOffset = focusNode.textContent.length } return comparePosition({ focusNode, focusOffset, - anchorNode, anchorOffset - }); + anchorNode, + anchorOffset, + }) } else if (position & Node.DOCUMENT_POSITION_CONTAINED_BY) { - let offset = anchorOffset - 1; + let offset = anchorOffset - 1 if (offset < 0) { - offset = 0; + offset = 0 } return comparePosition({ anchorNode: anchorNode.childNodes[offset], anchorOffset: 0, - focusNode, focusOffset - }); - // The meat of translating anchor and focus nodes to head and tail nodes + focusNode, + focusOffset, + }) + // The meat of translating anchor and focus nodes to head and tail nodes } else if (position & Node.DOCUMENT_POSITION_FOLLOWING) { - headNode = anchorNode; tailNode = focusNode; - headOffset = anchorOffset; tailOffset = focusOffset; - direction = DIRECTION.FORWARD; + headNode = anchorNode + tailNode = focusNode + headOffset = anchorOffset + tailOffset = focusOffset + direction = DIRECTION.FORWARD } else if (position & Node.DOCUMENT_POSITION_PRECEDING) { - headNode = focusNode; tailNode = anchorNode; - headOffset = focusOffset; tailOffset = anchorOffset; - direction = DIRECTION.BACKWARD; - } else { // same node - headNode = tailNode = anchorNode; - headOffset = anchorOffset; - tailOffset = focusOffset; + headNode = focusNode + tailNode = anchorNode + headOffset = focusOffset + tailOffset = anchorOffset + direction = DIRECTION.BACKWARD + } else { + // same node + headNode = tailNode = anchorNode + headOffset = anchorOffset + tailOffset = focusOffset if (tailOffset < headOffset) { // Swap the offset order - headOffset = focusOffset; - tailOffset = anchorOffset; - direction = DIRECTION.BACKWARD; + headOffset = focusOffset + tailOffset = anchorOffset + direction = DIRECTION.BACKWARD } else if (headOffset < tailOffset) { - direction = DIRECTION.FORWARD; + direction = DIRECTION.FORWARD } else { - direction = null; + direction = null } } - return {headNode, headOffset, tailNode, tailOffset, direction}; + return { headNode, headOffset, tailNode, tailOffset, direction } } -export { - clearSelection, - comparePosition, - findOffsetInNode, - constrainSelectionTo -}; +export { clearSelection, comparePosition, findOffsetInNode, constrainSelectionTo } diff --git a/src/js/utils/set.js b/src/js/utils/set.js index 599673fae..1a514ccaf 100644 --- a/src/js/utils/set.js +++ b/src/js/utils/set.js @@ -1,24 +1,24 @@ export default class Set { - constructor(items=[]) { - this.items = []; - items.forEach(i => this.add(i)); + constructor(items = []) { + this.items = [] + items.forEach(i => this.add(i)) } add(item) { if (!this.has(item)) { - this.items.push(item); + this.items.push(item) } } get length() { - return this.items.length; + return this.items.length } has(item) { - return this.items.indexOf(item) !== -1; + return this.items.indexOf(item) !== -1 } toArray() { - return this.items; + return this.items } } diff --git a/src/js/utils/string-utils.js b/src/js/utils/string-utils.js index d76137d65..9942a18a3 100644 --- a/src/js/utils/string-utils.js +++ b/src/js/utils/string-utils.js @@ -4,21 +4,21 @@ */ export function dasherize(string) { return string.replace(/[A-Z]/g, (match, offset) => { - const lower = match.toLowerCase(); + const lower = match.toLowerCase() - return (offset === 0 ? lower : '-' + lower); - }); + return offset === 0 ? lower : '-' + lower + }) } export function capitalize(string) { - return string.charAt(0).toUpperCase() + string.slice(1); + return string.charAt(0).toUpperCase() + string.slice(1) } export function startsWith(string, character) { - return string.charAt(0) === character; + return string.charAt(0) === character } export function endsWith(string, endString) { - let index = string.lastIndexOf(endString); - return index !== -1 && index === string.length - endString.length; + let index = string.lastIndexOf(endString) + return index !== -1 && index === string.length - endString.length } diff --git a/src/js/utils/to-range.js b/src/js/utils/to-range.js index 1fe59660b..086baf4fe 100644 --- a/src/js/utils/to-range.js +++ b/src/js/utils/to-range.js @@ -1,15 +1,15 @@ -import Range from 'mobiledoc-kit/utils/cursor/range'; -import Position from 'mobiledoc-kit/utils/cursor/position'; -import assert from 'mobiledoc-kit/utils/assert'; +import Range from 'mobiledoc-kit/utils/cursor/range' +import Position from 'mobiledoc-kit/utils/cursor/position' +import assert from 'mobiledoc-kit/utils/assert' export default function toRange(rangeLike) { - assert(`Must pass non-blank object to "toRange"`, !!rangeLike); + assert(`Must pass non-blank object to "toRange"`, !!rangeLike) if (rangeLike instanceof Range) { - return rangeLike; + return rangeLike } else if (rangeLike instanceof Position) { - return rangeLike.toRange(); + return rangeLike.toRange() } - assert(`Incorrect structure for rangeLike: ${rangeLike}`, false); + assert(`Incorrect structure for rangeLike: ${rangeLike}`, false) } diff --git a/src/js/version.js b/src/js/version.js index 2034b2955..55b1d631e 100644 --- a/src/js/version.js +++ b/src/js/version.js @@ -1 +1 @@ -export default '##VERSION##'; +export default '##VERSION##' diff --git a/src/js/views/tooltip.js b/src/js/views/tooltip.js index 7c1e4d2dc..944a90bef 100644 --- a/src/js/views/tooltip.js +++ b/src/js/views/tooltip.js @@ -1,88 +1,86 @@ -import View from './view'; -import { - positionElementCenteredBelow, - getEventTargetMatchingTag, - whenElementIsNotInDOM -} from '../utils/element-utils'; -import { editLink } from '../editor/ui'; +import View from './view' +import { positionElementCenteredBelow, getEventTargetMatchingTag, whenElementIsNotInDOM } from '../utils/element-utils' +import { editLink } from '../editor/ui' -const SHOW_DELAY = 200; -const HIDE_DELAY = 600; +const SHOW_DELAY = 200 +const HIDE_DELAY = 600 export default class Tooltip extends View { constructor(options) { - options.classNames = ['__mobiledoc-tooltip']; - super(options); + options.classNames = ['__mobiledoc-tooltip'] + super(options) - this.rootElement = options.rootElement; - this.editor = options.editor; + this.rootElement = options.rootElement + this.editor = options.editor - this.addListeners(options); + this.addListeners(options) } showLink(linkEl) { - const { editor, element: tooltipEl } = this; - const { tooltipPlugin } = editor; + const { editor, element: tooltipEl } = this + const { tooltipPlugin } = editor tooltipPlugin.renderLink(tooltipEl, linkEl, { editLink: () => { - editLink(linkEl, editor); - this.hide(); - } - }); + editLink(linkEl, editor) + this.hide() + }, + }) - this.show(); - positionElementCenteredBelow(this.element, linkEl); + this.show() + positionElementCenteredBelow(this.element, linkEl) - this.elementObserver = whenElementIsNotInDOM(linkEl, () => this.hide()); + this.elementObserver = whenElementIsNotInDOM(linkEl, () => this.hide()) } addListeners(options) { - const { rootElement, element: tooltipElement } = this; - let showTimeout, hideTimeout; + const { rootElement, element: tooltipElement } = this + let showTimeout, hideTimeout const scheduleHide = () => { - clearTimeout(hideTimeout); + clearTimeout(hideTimeout) hideTimeout = setTimeout(() => { - this.hide(); - }, HIDE_DELAY); - }; + this.hide() + }, HIDE_DELAY) + } this.addEventListener(tooltipElement, 'mouseenter', e => { - clearTimeout(hideTimeout); - }); + clearTimeout(hideTimeout) + }) this.addEventListener(tooltipElement, 'mouseleave', e => { - scheduleHide(); - }); + scheduleHide() + }) - this.addEventListener(rootElement, 'mouseover', (e) => { - let target = getEventTargetMatchingTag(options.showForTag, e.target, rootElement); + this.addEventListener(rootElement, 'mouseover', e => { + let target = getEventTargetMatchingTag(options.showForTag, e.target, rootElement) if (target && target.isContentEditable) { - clearTimeout(hideTimeout); + clearTimeout(hideTimeout) showTimeout = setTimeout(() => { - this.showLink(target); - }, SHOW_DELAY); + this.showLink(target) + }, SHOW_DELAY) } - }); + }) - this.addEventListener(rootElement, 'mouseout', (e) => { - clearTimeout(showTimeout); - if (this.elementObserver) { this.elementObserver.cancel(); } - scheduleHide(); - }); + this.addEventListener(rootElement, 'mouseout', e => { + clearTimeout(showTimeout) + if (this.elementObserver) { + this.elementObserver.cancel() + } + scheduleHide() + }) } } export const DEFAULT_TOOLTIP_PLUGIN = { renderLink(tooltipEl, linkEl, { editLink }) { - const { href } = linkEl; - tooltipEl.innerHTML = `${href}`; - const button = document.createElement('button'); + const { href } = linkEl + tooltipEl.innerHTML = `${href}` + const button = document.createElement('button') button.classList.add('__mobiledoc-tooltip__edit-link') - button.innerText = 'Edit Link'; - button.addEventListener('click', editLink); - tooltipEl.append(button); - } -}; + button.innerText = 'Edit Link' + button.addEventListener('click', editLink) + tooltipEl.append(button) + }, +} diff --git a/src/js/views/view.js b/src/js/views/view.js index a356095b1..1e85a613d 100644 --- a/src/js/views/view.js +++ b/src/js/views/view.js @@ -1,51 +1,51 @@ -import { addClassName } from '../utils/dom-utils'; +import { addClassName } from '../utils/dom-utils' class View { - constructor(options={}) { - options.tagName = options.tagName || 'div'; - options.container = options.container || document.body; + constructor(options = {}) { + options.tagName = options.tagName || 'div' + options.container = options.container || document.body - this.element = document.createElement(options.tagName); - this.container = options.container; - this.isShowing = false; + this.element = document.createElement(options.tagName) + this.container = options.container + this.isShowing = false - let classNames = options.classNames || []; - classNames.forEach(name => addClassName(this.element, name)); - this._eventListeners = []; + let classNames = options.classNames || [] + classNames.forEach(name => addClassName(this.element, name)) + this._eventListeners = [] } addEventListener(element, type, listener) { - element.addEventListener(type, listener); - this._eventListeners.push([element, type, listener]); + element.addEventListener(type, listener) + this._eventListeners.push([element, type, listener]) } removeAllEventListeners() { this._eventListeners.forEach(([element, type, listener]) => { - element.removeEventListener(type, listener); - }); + element.removeEventListener(type, listener) + }) } show() { - if(!this.isShowing) { - this.container.appendChild(this.element); - this.isShowing = true; - return true; + if (!this.isShowing) { + this.container.appendChild(this.element) + this.isShowing = true + return true } } hide() { if (this.isShowing) { - this.container.removeChild(this.element); - this.isShowing = false; - return true; + this.container.removeChild(this.element) + this.isShowing = false + return true } } destroy() { - this.removeAllEventListeners(); - this.hide(); - this.isDestroyed = true; + this.removeAllEventListeners() + this.hide() + this.isDestroyed = true } } -export default View; +export default View diff --git a/yarn.lock b/yarn.lock index 1aebcc8fd..b573366d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2571,6 +2571,11 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= +prettier@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" + integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== + printf@^0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/printf/-/printf-0.5.2.tgz#8546e01a1f647b1dff510ae92bdc92beb8c9b2f9"