1 /* block-jump.js is part of the Aloha Editor project http://aloha-editor.org
  2  *
  3  * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
  4  * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
  5  * Contributors http://aloha-editor.org/contribution.php 
  6  * 
  7  * Aloha Editor is free software; you can redistribute it and/or
  8  * modify it under the terms of the GNU General Public License
  9  * as published by the Free Software Foundation; either version 2
 10  * of the License, or any later version.
 11  *
 12  * Aloha Editor is distributed in the hope that it will be useful,
 13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU General Public License
 18  * along with this program; if not, write to the Free Software
 19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 20  * 
 21  * As an additional permission to the GNU GPL version 2, you may distribute
 22  * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 23  * source code without the copy of the GNU GPL normally required,
 24  * provided you include this license notice and a URL through which
 25  * recipients can access the Corresponding Source.
 26  */
 27 /**
 28  * Implements some logic related to moving the cursor keys across blocks.
 29  * 
 30  * In the following example
 31  *
 32  * "some text<span class="aloha-block ..." contenteditable="false" ...>...</span>[]some text"
 33  *
 34  * when one moves the cursor indicated by "[]" to the left, the entire
 35  * non-contenteditable block is skipped. The same for moving the cursor
 36  * right across the block.
 37  *
 38  * TODO: actually, the block shouldn't be skipped, it should be
 39  *       selected/highlighted first.
 40  * TODO: this file currently doesn't contain all the code to implement
 41  *       block jumping. Some of it is currently implemented in markup.js.
 42  */
 43 define([
 44 	'aloha/core',
 45 	'jquery',
 46 	'aloha/console'
 47 ], function (
 48 	Aloha,
 49 	$,
 50 	console
 51 ) {
 52 	'use strict';
 53 
 54 	var zeroWidthNode = null;
 55 
 56 	/**
 57 	 * Replaces the text in given text with the given text.
 58 	 *
 59 	 * @param node
 60 	 *        A text node attached to the DOM.
 61 	 * @param text
 62 	 *        A string that is to replace the text of the given text node.
 63 	 */
 64 	function replaceMergeTextNode(node, text) {
 65 		node.deleteData(0, node.length);
 66 		if ('' !== text) {
 67 			if (node.nextSibling && 3 === node.nextSibling.nodeType) {
 68 				node.nextSibling.insertData(0, text);
 69 			} else if (node.previousSibling && 3 === node.previousSibling.nodeType) {
 70 				node.previousSibling.insertData(node.previousSibling.length, text);
 71 			} else {
 72 				node.insertData(0, text);
 73 			}
 74 		}
 75 		// We don't remove the node immediately to avoid intefering with a
 76 		// caller's range object that may have a start or end containers
 77 		// equal to this node. Removing it in a timeout may still interfere
 78 		// with the selection, but that was not a problem during testing.
 79 		setTimeout(function () {
 80 			if (0 === node.length) {
 81 				$(node).remove();
 82 			}
 83 		}, 0);
 84 	}
 85 
 86 	/**
 87 	 * Removes a previously inserted zero width text node.
 88 	 * See insertZeroWidthTextNodeFix().
 89 	 */
 90 	function removeZeroWidthTextNodeFix() {
 91 		if (!zeroWidthNode) {
 92 			return;
 93 		}
 94 		// We want to only replace a single zero-width character to avoid
 95 		// interfering with the other zero-width whitespace hack that makes
 96 		// empty lines visible in IE7.
 97 		var text = zeroWidthNode.nodeValue.replace(/\u200b/, '');
 98 		if (text === zeroWidthNode.nodeValue) {
 99 			console.warn('Expected to remove the zero width text node fix, but couldn\'t find it');
100 		}
101 		replaceMergeTextNode(zeroWidthNode, text);
102 		zeroWidthNode = null;
103 	}
104 
105 	/**
106 	 * Inserts a zero width text node before or after a block.
107 	 *
108 	 * There is a problem where some browsers can't select the boundary
109 	 * between some contenteditable content and non-contenteditable
110 	 * content. For example, if in the example at the top of the file
111 	 * the selection were one step to the right "...</span>s[]ome..."
112 	 * and the left cursor key were pressed, then the selection would
113 	 * just disappear or be stuck between the span and the text node.
114 	 *
115 	 * To work around this problem a zero width text node is inserted
116 	 * before or after a block.
117 	 *
118 	 * The inserted zero width text node will be removed automatically
119 	 * when it isn't necessary any more (on selection change or on
120 	 * editable.getContents()).
121 	 *
122 	 * TODO: In retrospect, a better alternative may be to simply wrap
123 	 *       every inlin-block with an editable span.
124 	 * @param block
125 	 *        The DOM element for a block before or after which the zero
126 	 *        width text node will be inserted.
127 	 * @param isGoingLeft
128 	 *        True if the zero width text node is to be inserted after
129 	 *        the block element, or false if the zero width text node is
130 	 *        to be inserted before the block element.
131 	 * @return
132 	 *        The text node that was inserted.
133 	 */
134 	function insertZeroWidthTextNodeFix(block, isGoingLeft) {
135 		removeZeroWidthTextNodeFix();
136 		zeroWidthNode = document.createTextNode("\u200b");
137 		if (isGoingLeft) {
138 			$(block).after(zeroWidthNode);
139 		} else {
140 			$(block).before(zeroWidthNode);
141 		}
142 		Aloha.bind('aloha-selection-changed', function (event) {
143 			removeZeroWidthTextNodeFix();
144 			Aloha.unbind(event);
145 		});
146 		return zeroWidthNode;
147 	}
148 
149 	return {
150 		removeZeroWidthTextNodeFix: removeZeroWidthTextNodeFix,
151 		insertZeroWidthTextNodeFix: insertZeroWidthTextNodeFix
152 	};
153 });
154