From 7b5c2726141f9b72213007c49943d695849f755b Mon Sep 17 00:00:00 2001 From: Matthew Beale Date: Wed, 10 Feb 2016 15:35:22 -0500 Subject: [PATCH] Do not permit changes inside cards to reparse * Limit it to card elements, not the wrappers/zwnj * Refactor section removal and migration to postEditor * Refactor post reparse to use `run`, fire hooks --- src/js/editor/edit-history.js | 14 +++-------- src/js/editor/editor.js | 9 ++++--- src/js/editor/mutation-handler.js | 15 ++++++----- src/js/editor/post.js | 13 ++++++++++ src/js/models/render-node.js | 6 +++++ tests/acceptance/editor-reparse-test.js | 33 +++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 21 deletions(-) diff --git a/src/js/editor/edit-history.js b/src/js/editor/edit-history.js index aed17e6fa..63862e021 100644 --- a/src/js/editor/edit-history.js +++ b/src/js/editor/edit-history.js @@ -23,7 +23,7 @@ export class Snapshot { snapshotRange() { let { range, cursor } = this.editor; - if (cursor.hasCursor()) { + if (cursor.hasCursor() && !range.isBlank) { let { head, tail } = range; this.range = { head: [head.leafSectionIndex, head.offset], @@ -99,16 +99,8 @@ export default class EditHistory { let { builder, post } = editor; let restoredPost = mobiledocParsers.parse(builder, mobiledoc); - // remove existing sections - post.sections.toArray().forEach(section => { - postEditor.removeSection(section); - }); - - // append restored sections - restoredPost.sections.toArray().forEach(section => { - restoredPost.sections.remove(section); - postEditor.insertSectionBefore(post.sections, section, null); - }); + postEditor.removeAllSections(); + postEditor.migrateSectionsFromPost(restoredPost); // resurrect snapshotted range if it exists let newRange = snapshot.getRange(post); diff --git a/src/js/editor/editor.js b/src/js/editor/editor.js index 9c1d3cb9d..fcce49553 100644 --- a/src/js/editor/editor.js +++ b/src/js/editor/editor.js @@ -327,10 +327,11 @@ class Editor { } _reparsePost() { - this.post = this._parser.parse(this.element); - this._renderTree = new RenderTree(this.post); - clearChildNodes(this.element); - this.rerender(); + let post = this._parser.parse(this.element); + this.run(postEditor => { + postEditor.removeAllSections(); + postEditor.migrateSectionsFromPost(post); + }); this.runCallbacks(CALLBACK_QUEUES.DID_REPARSE); this.didUpdate(); diff --git a/src/js/editor/mutation-handler.js b/src/js/editor/mutation-handler.js index e41b163fe..07cd9d5fd 100644 --- a/src/js/editor/mutation-handler.js +++ b/src/js/editor/mutation-handler.js @@ -85,9 +85,12 @@ export default class MutationHandler { let nodes = this._findTargetNodes(mutations[i]); for (let j=0; j < nodes.length; j++) { - let section = this._findSectionFromNode(nodes[j]); - if (section) { - sections.add(section); + let node = nodes[j]; + let renderNode = this._findSectionRenderNodeFromNode(node); + if (renderNode) { + if (renderNode.reparsesMutationOfChildNode(node)) { + sections.add(renderNode.postNode); + } } else { reparsePost = true; break; @@ -121,10 +124,10 @@ export default class MutationHandler { return attachedNodes; } - _findSectionFromNode(node) { - let rn = this.renderTree.findRenderNodeFromElement(node, (rn) => { + _findSectionRenderNodeFromNode(node) { + return this.renderTree.findRenderNodeFromElement(node, (rn) => { return rn.postNode.isSection; }); - return rn && rn.postNode; } + } diff --git a/src/js/editor/post.js b/src/js/editor/post.js index 3b0c7748b..a5810f5af 100644 --- a/src/js/editor/post.js +++ b/src/js/editor/post.js @@ -1188,6 +1188,19 @@ class PostEditor { } } + removeAllSections() { + this.editor.post.sections.toArray().forEach(section => { + this.removeSection(section); + }); + } + + migrateSectionsFromPost(post) { + post.sections.toArray().forEach(section => { + 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, diff --git a/src/js/models/render-node.js b/src/js/models/render-node.js index c585fb032..41ca7f466 100644 --- a/src/js/models/render-node.js +++ b/src/js/models/render-node.js @@ -74,4 +74,10 @@ export default class RenderNode extends LinkedItem { this.postNode = null; this.renderTree = null; } + reparsesMutationOfChildNode(node) { + if (this.postNode.isCardSection) { + return !this.cardNode.element.contains(node); + } + return true; + } } diff --git a/tests/acceptance/editor-reparse-test.js b/tests/acceptance/editor-reparse-test.js index df48f4d8d..cc0474949 100644 --- a/tests/acceptance/editor-reparse-test.js +++ b/tests/acceptance/editor-reparse-test.js @@ -238,3 +238,36 @@ test('inserting text into text node on left/right of atom is reparsed correctly' }); }); }); + +test('mutation inside card element does not cause reparse', (assert) => { + let done = assert.async(); + let parseCount = 0; + let myCard = { + name: 'my-card', + type: 'dom', + render() { + return document.createTextNode('howdy'); + } + }; + + editor = Helpers.mobiledoc.renderInto(editorElement, ({post, cardSection}) => { + return post([ + cardSection('my-card', {}) + ]); + }, { + cards: [myCard] + }); + + editor.didUpdatePost(() => { + parseCount++; + }); + + let textNode = Helpers.dom.findTextNode(editorElement, 'howdy'); + textNode.textContent = 'adios'; + + // Allow the mutation observer to fire then... + setTimeout(function() { + assert.equal(0, parseCount); + done(); + }, 0); +});