ForceBlocks.js 3.7 KB
/**
 * ForceBlocks.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Makes sure that everything gets wrapped in paragraphs.
 *
 * @private
 * @class tinymce.ForceBlocks
 */
define("tinymce/ForceBlocks", [], function() {
	return function(editor) {
		var settings = editor.settings, dom = editor.dom, selection = editor.selection;
		var schema = editor.schema, blockElements = schema.getBlockElements();

		function addRootBlocks() {
			var node = selection.getStart(), rootNode = editor.getBody(), rng;
			var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
			var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
			var tmpRng, rootNodeName, forcedRootBlock;

			forcedRootBlock = settings.forced_root_block;

			if (!node || node.nodeType !== 1 || !forcedRootBlock) {
				return;
			}

			// Check if node is wrapped in block
			while (node && node != rootNode) {
				if (blockElements[node.nodeName]) {
					return;
				}

				node = node.parentNode;
			}

			// Get current selection
			rng = selection.getRng();
			if (rng.setStart) {
				startContainer = rng.startContainer;
				startOffset = rng.startOffset;
				endContainer = rng.endContainer;
				endOffset = rng.endOffset;

				try {
					restoreSelection = editor.getDoc().activeElement === rootNode;
				} catch (ex) {
					// IE throws unspecified error here sometimes
				}
			} else {
				// Force control range into text range
				if (rng.item) {
					node = rng.item(0);
					rng = editor.getDoc().body.createTextRange();
					rng.moveToElementText(node);
				}

				restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
				tmpRng = rng.duplicate();
				tmpRng.collapse(true);
				startOffset = tmpRng.move('character', offset) * -1;

				if (!tmpRng.collapsed) {
					tmpRng = rng.duplicate();
					tmpRng.collapse(false);
					endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
				}
			}

			// Wrap non block elements and text nodes
			node = rootNode.firstChild;
			rootNodeName = rootNode.nodeName.toLowerCase();
			while (node) {
				// TODO: Break this up, too complex
				if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
					schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
					// Remove empty text nodes
					if (node.nodeType === 3 && node.nodeValue.length === 0) {
						tempNode = node;
						node = node.nextSibling;
						dom.remove(tempNode);
						continue;
					}

					if (!rootBlockNode) {
						rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs);
						node.parentNode.insertBefore(rootBlockNode, node);
						wrapped = true;
					}

					tempNode = node;
					node = node.nextSibling;
					rootBlockNode.appendChild(tempNode);
				} else {
					rootBlockNode = null;
					node = node.nextSibling;
				}
			}

			if (wrapped && restoreSelection) {
				if (rng.setStart) {
					rng.setStart(startContainer, startOffset);
					rng.setEnd(endContainer, endOffset);
					selection.setRng(rng);
				} else {
					// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
					try {
						rng = editor.getDoc().body.createTextRange();
						rng.moveToElementText(rootNode);
						rng.collapse(true);
						rng.moveStart('character', startOffset);

						if (endOffset > 0) {
							rng.moveEnd('character', endOffset);
						}

						rng.select();
					} catch (ex) {
						// Ignore
					}
				}

				editor.nodeChanged();
			}
		}

		// Force root blocks
		if (settings.forced_root_block) {
			editor.on('NodeChange', addRootBlocks);
		}
	};
});