From b53df8a58d8dafedf5daf3275243acb8ae3c99f0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 9 Aug 2013 14:00:41 +0200 Subject: [PATCH] Changing Zero Width Space handling strategy ZWS allows to put caret into empty inline node in Chrome This change goes from: - content observer puts ZWS on nodes set to '' - ZWS stays in nodes and is handled accordingly (selection, arrow keys, backspace/delete keys etc.) to: - content observer puts ZWS on nodes set to '' - content observer removes ZWS from nodes which .data changes from 'ZWS' to other string - special treatment of ZWS is necessary only on "empty" document text elements, ie those whose container .data equals ZWS --- modules/documentCanvas/canvas/canvas.js | 47 +++++------- modules/documentCanvas/canvas/canvas.test3.js | 71 ++++++++++++++----- 2 files changed, 70 insertions(+), 48 deletions(-) diff --git a/modules/documentCanvas/canvas/canvas.js b/modules/documentCanvas/canvas/canvas.js index 8ba871b..7428e10 100644 --- a/modules/documentCanvas/canvas/canvas.js +++ b/modules/documentCanvas/canvas/canvas.js @@ -320,8 +320,14 @@ $.extend(Canvas.prototype, { var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if(documentElement.DocumentTextElement.isContentContainer(mutation.target)) { + observer.disconnect(); if(mutation.target.data === '') mutation.target.data = utils.unicode.ZWS; + else if(mutation.oldValue === utils.unicode.ZWS) { + mutation.target.data = mutation.target.data.replace(utils.unicode.ZWS, ''); + canvas._moveCaretToTextElement(canvas.getDocumentElement(mutation.target), 'end'); + } + observer.observe(canvas.d[0], config); canvas.publisher('contentChanged'); } }); @@ -739,40 +745,23 @@ $.extend(Cursor.prototype, { anchorElement = this.canvas.getDocumentElement(selection.anchorNode), focusElement = this.canvas.getDocumentElement(selection.focusNode); - var getOffset = function(where) { - var toret, node; - if(where === 'anchor') { - node = selection.anchorNode; - toret = selection.anchorOffset; - } else { - node = selection.focusNode; - toret = selection.focusOffset; - } - - if(toret === 1 && node.data.charAt(0) === utils.unicode.ZWS) - toret = 0; - else if((toret === node.data.length - 1) && (node.data.charAt(node.data.length - 1) === utils.unicode.ZWS)) - toret++; - return toret; - } - if((!anchorElement) || (anchorElement instanceof documentElement.DocumentNodeElement) || (!focusElement) || focusElement instanceof documentElement.DocumentNodeElement) return {}; if(which === 'anchor') { return { element: anchorElement, - offset: getOffset('anchor'), - offsetAtBeginning: getOffset('anchor') === 0, - offsetAtEnd: selection.anchorNode.data.length === getOffset('anchor') + offset: selection.anchorOffset, + offsetAtBeginning: selection.anchorOffset === 0, + offsetAtEnd: selection.anchorNode.data.length === selection.anchorOffset }; } if(which === 'focus') { return { element: focusElement, - offset: getOffset('focus'), - offsetAtBeginning: getOffset('focus') === 0, - offsetAtEnd: selection.focusNode.data.length === getOffset('focus') + offset: selection.focusOffset, + offsetAtBeginning: selection.focusOffset === 0, + offsetAtEnd: selection.focusNode.data.length === selection.focusOffset }; } @@ -785,30 +774,30 @@ $.extend(Cursor.prototype, { if(anchorFirst) { if(which === 'start') { element = anchorElement; - offset = getOffset('anchor') + offset = selection.anchorOffset } else if(which === 'end') { element = focusElement, - offset = getOffset('focus') + offset = selection.focusOffset } } else { if(which === 'start') { element = focusElement, - offset = getOffset('focus') + offset = selection.focusOffset } else if(which === 'end') { element = anchorElement; - offset = getOffset('anchor') + offset = selection.anchorOffset } } } else { // TODO: Handle order via https://developer.mozilla.org/en-US/docs/Web/API/Node.compareDocumentPosition if(which === 'start') { element = anchorElement; - offset = getOffset('anchor') + offset = selection.anchorOffset } else { element = focusElement; - offset = getOffset('focus') + offset = selection.focusOffset } } diff --git a/modules/documentCanvas/canvas/canvas.test3.js b/modules/documentCanvas/canvas/canvas.test3.js index 477aca0..f96e0ff 100644 --- a/modules/documentCanvas/canvas/canvas.test3.js +++ b/modules/documentCanvas/canvas/canvas.test3.js @@ -13,6 +13,58 @@ var expect = chai.expect; describe('Canvas', function() { + + + describe('ZWS', function() { + var view, section, textElement; + + beforeEach(function() { + var c = canvas.fromXML('
'); + + section = c.doc(); + textElement = section.children()[0]; + view = c.view()[0]; + document.getElementsByTagName('body')[0].appendChild(view); + }); + + afterEach(function() { + view.parentNode.removeChild(view); + }); + + var getTextContainerNode = function(textElement) { + return textElement.dom().contents()[0]; + } + + it('is set automatically on all empty DocumentTextElements', function() { + expect(getTextContainerNode(textElement).data).to.equal(utils.unicode.ZWS); + + var header = section.append({tag: 'header'}), + newText = header.append({text: ''}), + textNode = getTextContainerNode(textElement); + + expect(textNode.data).to.equal(utils.unicode.ZWS); + }); + + it('is added automatically when whole text gets deleted', function() { + getTextContainerNode(textElement).data = ''; + + window.setTimeout(function() { + expect(getTextContainerNode(textElement).data).to.equal(utils.unicode.ZWS); + }, 0) + + var header = section.append({tag: 'header'}), + newText = header.append({text: 'Alice'}), + textNode = getTextContainerNode(newText); + + expect(textNode.data).to.have.length('Alice'.length); + textNode.data = ''; + + window.setTimeout(function() { + expect(textNode.data).to.equal(utils.unicode.ZWS); + }, 0) + }); + }); + describe('Internal HTML representation of a DocumentNodeElement', function() { it('is always a div tag', function() { ['section', 'header', 'span', 'aside', 'figure'].forEach(function(tagName) { @@ -1271,25 +1323,6 @@ describe('Canvas', function() { expect(cursor.isSelectingSiblings()).to.equal(false, '"has" and "big" are not children'); }) - - describe('zero width space handling', function() { - it('position range includes ZWS at the boundries of text in case when native selection api doesn\'t', function() { - var c = canvas.fromXML("
Alice
"), - dom = c.doc().dom(), - textNode = findTextNode(dom, 'Alice'), - cursor = c.getCursor(); - - textNode.data = utils.unicode.ZWS + 'Alice'; - getSelection.returns({anchorNode: textNode, anchorOffset: 1, focusNode: textNode, focusOffset: 1}); - expect(cursor.getPosition().offset).to.equal(0); - expect(cursor.getPosition().offsetAtBeginning).to.equal(true, 'offset at beginning'); - - textNode.data = 'Alice' + utils.unicode.ZWS; - getSelection.returns({anchorNode: textNode, anchorOffset: 5, focusNode: textNode, focusOffset: 5}); - expect(cursor.getPosition().offset).to.equal(6); - expect(cursor.getPosition().offsetAtEnd).to.equal(true, 'offset at end'); - }); - }); }); describe('Serializing document to WLXML', function() { -- 2.20.1