diff --git a/spec/selection.spec.js b/spec/selection.spec.js index 81fd5b2af..7e99e17f8 100644 --- a/spec/selection.spec.js +++ b/spec/selection.spec.js @@ -109,6 +109,7 @@ describe('Selection TestCase', function () { var exportedSelection = editor.exportSelection(); expect(exportedSelection.emptyBlocksIndex).toEqual(undefined); }); + it('should not export a position indicating the cursor is after an empty paragraph ' + '(in a complicated markup with selection on the element)', function () { this.el.innerHTML = '
www.google.com
Hello
<' + tagName + ' />
World
'; + var editor = this.newMediumEditor('.editor', { + buttons: ['italic', 'underline', 'strikethrough'] + }); + editor.importSelection({ + 'start': 5, + 'end': 5, + 'emptyBlocksIndex': 1 + }); + + var innerElement = window.getSelection().getRangeAt(0).startContainer; + expect(innerElement.nodeName.toLowerCase()).toBe('p', 'focused element nodeName'); + expect(innerElement).toBe(window.getSelection().getRangeAt(0).endContainer); + expect(innerElement.previousSibling.nodeName.toLowerCase()).toBe('p', 'previous sibling name'); + expect(innerElement.nextSibling.nodeName.toLowerCase()).toBe('p', 'next sibling name'); + }); + }); + + it('should not import a selection into focusing on an empty element in a table', function () { + this.el.innerHTML = '
Hello
Head |
---|
Body |
World
'; + var editor = this.newMediumEditor('.editor', { + buttons: ['italic', 'underline', 'strikethrough'] + }); + editor.importSelection({ + 'start': 5, + 'end': 5, + 'emptyBlocksIndex': 1 + }); + + var innerElement = window.getSelection().getRangeAt(0).startContainer; + // The behavior varies from browser to browser for this case, some select TH, some #textNode + expect(Util.isDescendant(editor.elements[0].querySelector('th'), innerElement, true)) + .toBe(true, 'expect selection to be of TH or a descendant'); + expect(innerElement).toBe(window.getSelection().getRangeAt(0).endContainer); + }); + + it('should not import a selection beyond any block elements that have text, even when emptyBlocksIndex indicates it should ', function () { this.el.innerHTML = '
www.google.com
Whatever
'; var editor = this.newMediumEditor('.editor', { buttons: ['italic', 'underline', 'strikethrough'] diff --git a/src/js/core.js b/src/js/core.js index 492e92992..41e7b8855 100644 --- a/src/js/core.js +++ b/src/js/core.js @@ -1014,7 +1014,7 @@ function MediumEditor(elements, options) { // We're selecting a high-level block node, so make sure the cursor gets moved into the deepest // element at the beginning of the block - range.setStart(Util.getFirstLeafNode(targetNode), 0); + range.setStart(Util.getFirstSelectableLeafNode(targetNode), 0); range.collapse(true); } diff --git a/src/js/util.js b/src/js/util.js index cf22f6cf6..966867ab2 100644 --- a/src/js/util.js +++ b/src/js/util.js @@ -672,10 +672,22 @@ var Util; }); }, - getFirstLeafNode: function (element) { + getFirstSelectableLeafNode: function (element) { while (element && element.firstChild) { element = element.firstChild; } + var emptyElements = ['br', 'col', 'colgroup', 'hr', 'img', 'input', 'source', 'wbr']; + while (emptyElements.indexOf(element.nodeName.toLowerCase()) !== -1) { + // We don't want to set the selection to an element that can't have children, this messes up Gecko. + element = element.parentNode; + } + // Selecting at the beginning of a table doesn't work in PhantomJS. + if (element.nodeName.toLowerCase() === 'table') { + var firstCell = element.querySelector('th, td'); + if (firstCell) { + element = firstCell; + } + } return element; },