From 543abca02249b912160979cd96207ff2b94c125c Mon Sep 17 00:00:00 2001 From: Neil Jenkins Date: Mon, 2 Apr 2012 15:53:24 +1000 Subject: [PATCH] Workaround WebKit/IE can't focus empty text nodes. --- source/Editor.js | 58 ++++++++++++++++++++++++++++++++++++++++++++++-- source/Node.js | 16 ++++++++++--- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/source/Editor.js b/source/Editor.js index aa861c2..5623806 100644 --- a/source/Editor.js +++ b/source/Editor.js @@ -19,11 +19,15 @@ var win = doc.defaultView, body = doc.body; + // Browser sniffing. Unfortunately necessary. var isOpera = !!win.opera; var isIE = !!win.ie; var isGecko = /Gecko\//.test( navigator.userAgent ); + var isWebKit = /WebKit/.test( navigator.userAgent ); var isIOS = /iP(?:ad|hone|od)/.test( navigator.userAgent ); + var useTextFixer = isIE || isOpera; + var cantFocusEmptyTextNodes = isIE || isWebKit; // --- DOM Sugar --- @@ -150,7 +154,42 @@ var lastFocusNode; var path = ''; + // --- Workaround for browsers that can't focus empty text nodes --- + + // WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256 + + var placeholderTextNode = null; + var setPlaceholderTextNode = function ( node ) { + if ( placeholderTextNode ) { + removePlaceholderTextNode( getSelection(), true ); + } + placeholderTextNode = node; + }; + var removePlaceholderTextNode = function ( range, force ) { + var node = placeholderTextNode, + index; + if ( !force && node.data === '\u200B' && + ( range.startContainer === node || range.endContainer === node ) ) { + return; + } + placeholderTextNode = null; + if ( node.parentNode ) { + while ( ( index = node.data.indexOf( '\u200B' ) ) > -1 ) { + node.deleteData( index, 1 ); + } + if ( !node.data && !node.nextSibling && !node.previousSibling && + node.parentNode.isInline() ) { + node.parentNode.detach(); + } + } + }; + + // --- Path change events --- + var updatePath = function ( range, force ) { + if ( placeholderTextNode ) { + removePlaceholderTextNode( range ); + } var anchor = range.startContainer, focus = range.endContainer, newPath; @@ -457,7 +496,8 @@ if ( range.collapsed ) { var el = createElement( tag, attributes ).fixCursor(); range._insertNode( el ); - range.selectNodeContents( el ); + range.setStart( el.firstChild, el.firstChild.length ); + range.collapse( true ); } // Otherwise we find all the textnodes in the range (splitting // partially selected nodes) and if they're not already formatted @@ -526,8 +566,17 @@ // We need a node in the selection to break the surrounding // formatted text. + var fixer; if ( range.collapsed ) { - range._insertNode( doc.createTextNode( '' ) ); + if ( cantFocusEmptyTextNodes ) { + fixer = doc.createTextNode( '\u200B' ); + setTimeout( function () { + setPlaceholderTextNode( fixer ); + }, 0 ); + } else { + fixer = doc.createTextNode( '' ); + } + range._insertNode( fixer ); } // Find block-level ancestor of selection @@ -609,6 +658,9 @@ // Merge adjacent inlines: range = getRangeAndRemoveBookmark(); + if ( fixer ) { + range.collapse( false ); + } var _range = { startContainer: range.startContainer, startOffset: range.startOffset, @@ -1485,6 +1537,8 @@ win.editor = { + _setPlaceholderTextNode: setPlaceholderTextNode, + addEventListener: chain( addEventListener ), removeEventListener: chain( removeEventListener ), diff --git a/source/Node.js b/source/Node.js index 60b18cc..7f6ead4 100644 --- a/source/Node.js +++ b/source/Node.js @@ -2,7 +2,8 @@ ( function () { -/*global Node, Text, Element, HTMLDocument, window, document */ +/*global Node, Text, Element, HTMLDocument, window, document, navigator, + setTimeout, editor */ "use strict"; @@ -56,6 +57,8 @@ var isBlock = function ( el ) { return el.isBlock() ? FILTER_ACCEPT : FILTER_SKIP; }; var useTextFixer = !!( window.opera || window.ie ); +var cantFocusEmptyTextNodes = + /WebKit/.test( navigator.userAgent ) || !!window.ie; implement( window.Node ? [ Node ] : [ Text, Element, HTMLDocument ], { isInline: $False, @@ -376,7 +379,14 @@ implement([ Element ], { if ( el.isInline() ) { if ( !el.firstChild ) { - fixer = doc.createTextNode( /* isWebkit ? '\u200B' :*/ '' ); + if ( cantFocusEmptyTextNodes ) { + fixer = doc.createTextNode( '\u200B' ); + setTimeout( function () { + editor._setPlaceholderTextNode( fixer ); + }, 0 ); + } else { + fixer = doc.createTextNode( '' ); + } } } else { if ( useTextFixer ) { @@ -415,7 +425,7 @@ implement([ Element ], { } }); -// Fix IE9's buggy implementation of Text#splitText. +// Fix IE8/9's buggy implementation of Text#splitText. // If the split is at the end of the node, it doesn't insert the newly split // node into the document, and sets its value to undefined rather than ''. // And even if the split is not at the end, the original node is removed from