vendor/assets/javascripts/aloha/lib/util/dom.js in locomotive-aloha-rails-0.20.1.5 vs vendor/assets/javascripts/aloha/lib/util/dom.js in locomotive-aloha-rails-0.23.2.1
- old
+ new
@@ -1,1685 +1,1706 @@
-/*!
-* This file is part of Aloha Editor Project http://aloha-editor.org
-* Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com
-* Contributors http://aloha-editor.org/contribution.php
-* Licensed unter the terms of http://www.aloha-editor.org/license.html
-*//*
-* Aloha Editor is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as published by
-* the Free Software Foundation, either version 3 of the License, or
-* (at your option) any later version.*
-*
-* Aloha Editor is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
-
+/* dom.js is part of Aloha Editor project http://aloha-editor.org
+ *
+ * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
+ * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
+ * Contributors http://aloha-editor.org/contribution.php
+ *
+ * Aloha Editor is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or any later version.
+ *
+ * Aloha Editor is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * As an additional permission to the GNU GPL version 2, you may distribute
+ * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
+ * source code without the copy of the GNU GPL normally required,
+ * provided you include this license notice and a URL through which
+ * recipients can access the Corresponding Source.
+ */
// Ensure GENTICS Namespace
-GENTICS = window.GENTICS || {};
-GENTICS.Utils = GENTICS.Utils || {};
+window.GENTICS = window.GENTICS || {};
+window.GENTICS.Utils = window.GENTICS.Utils || {};
-define(
-['aloha/jquery', 'util/class', 'aloha/ecma5shims'],
-function(jQuery, Class, $_) {
-
-
- var
- GENTICS = window.GENTICS,
-// Class = window.Class,
+define(['jquery', 'util/class', 'aloha/ecma5shims'], function (jQuery, Class, $_) {
+ "use strict";
+
+ var GENTICS = window.GENTICS,
+ // Class = window.Class,
// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1841493061
Node = {
- 'ELEMENT_NODE' : 1,
- 'ATTRIBUTE_NODE': 2,
- 'TEXT_NODE': 3,
- 'CDATA_SECTION_NODE': 4,
- 'ENTITY_REFERENCE_NODE': 5,
- 'ENTITY_NODE': 6,
- 'PROCESSING_INSTRUCTION_NODE': 7,
- 'COMMENT_NODE': 8,
- 'DOCUMENT_NODE': 9,
- 'DOCUMENT_TYPE_NODE': 10,
- 'DOCUMENT_FRAGMENT_NODE': 11,
- 'NOTATION_NODE': 12,
- //The two nodes are disconnected. Order between disconnected nodes is always implementation-specific.
- 'DOCUMENT_POSITION_DISCONNECTED': 0x01,
- //The second node precedes the reference node.
- 'DOCUMENT_POSITION_PRECEDING': 0x02,
- //The node follows the reference node.
- 'DOCUMENT_POSITION_FOLLOWING': 0x04,
- //The node contains the reference node. A node which contains is always preceding, too.
- 'DOCUMENT_POSITION_CONTAINS': 0x08,
- //The node is contained by the reference node. A node which is contained is always following, too.
- 'DOCUMENT_POSITION_CONTAINED_BY': 0x10,
- //The determination of preceding versus following is implementation-specific.
- 'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 0x20
- };
+ 'ELEMENT_NODE': 1,
+ 'ATTRIBUTE_NODE': 2,
+ 'TEXT_NODE': 3,
+ 'CDATA_SECTION_NODE': 4,
+ 'ENTITY_REFERENCE_NODE': 5,
+ 'ENTITY_NODE': 6,
+ 'PROCESSING_INSTRUCTION_NODE': 7,
+ 'COMMENT_NODE': 8,
+ 'DOCUMENT_NODE': 9,
+ 'DOCUMENT_TYPE_NODE': 10,
+ 'DOCUMENT_FRAGMENT_NODE': 11,
+ 'NOTATION_NODE': 12,
+ //The two nodes are disconnected. Order between disconnected nodes is always implementation-specific.
+ 'DOCUMENT_POSITION_DISCONNECTED': 0x01,
+ //The second node precedes the reference node.
+ 'DOCUMENT_POSITION_PRECEDING': 0x02,
+ //The node follows the reference node.
+ 'DOCUMENT_POSITION_FOLLOWING': 0x04,
+ //The node contains the reference node. A node which contains is always preceding, too.
+ 'DOCUMENT_POSITION_CONTAINS': 0x08,
+ //The node is contained by the reference node. A node which is contained is always following, too.
+ 'DOCUMENT_POSITION_CONTAINED_BY': 0x10,
+ //The determination of preceding versus following is implementation-specific.
+ 'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 0x20
+ },
+ blockElementNames = {
+ 'P': true,
+ 'H1': true,
+ 'H2': true,
+ 'H3': true,
+ 'H4': true,
+ 'H5': true,
+ 'H6': true,
+ 'LI': true
+ };
-
-
-/**
- * @namespace GENTICS.Utils
- * @class Dom provides methods to get information about the DOM and to manipulate it
- * @singleton
- */
-var Dom = Class.extend({
/**
- * Regex to find word characters.
+ * @namespace GENTICS.Utils
+ * @class Dom provides methods to get information about the DOM and to manipulate it
+ * @singleton
*/
- wordRegex: /[\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0525\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971\u0972\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3D\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8B\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA65F\uA662-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B\uA78C\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/,
+ var Dom = Class.extend({
+ /**
+ * Regex to find word characters.
+ */
+ wordRegex: /[\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0525\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971\u0972\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3D\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8B\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA65F\uA662-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B\uA78C\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/,
- /**
- * Regex to find non-word characters.
- */
- nonWordRegex: /[^\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0525\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971\u0972\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3D\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8B\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA65F\uA662-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B\uA78C\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/,
+ /**
+ * Regex to find non-word characters.
+ */
+ nonWordRegex: /[^\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0525\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971\u0972\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3D\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8B\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA65F\uA662-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B\uA78C\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/,
- /**
- * Tags which can safely be merged
- * @hide
- */
- mergeableTags: ['b', 'code', 'del', 'em', 'i', 'ins', 'strong', 'sub', 'sup', '#text'],
+ /**
+ * Tags which can safely be merged
+ * @hide
+ */
+ mergeableTags: ['b', 'code', 'del', 'em', 'i', 'ins', 'strong', 'sub', 'sup', '#text'],
- /**
- * Tags which do not mark word boundaries
- * @hide
- */
- nonWordBoundaryTags: ['a', 'b', 'code', 'del', 'em', 'i', 'ins', 'span', 'strong', 'sub', 'sup', '#text'],
+ /**
+ * Tags which do not mark word boundaries
+ * @hide
+ */
+ nonWordBoundaryTags: ['a', 'b', 'code', 'del', 'em', 'i', 'ins', 'span', 'strong', 'sub', 'sup', '#text'],
- /**
- * Tags which are considered 'nonempty', even if they have no children (or not data)
- * TODO: finish this list
- * @hide
- */
- nonEmptyTags: ['br'],
+ /**
+ * Tags which are considered 'nonempty', even if they have no children (or not data)
+ * TODO: finish this list
+ * @hide
+ */
+ nonEmptyTags: ['br'],
- /**
- * Tags which make up Flow Content or Phrasing Content, according to the HTML 5 specification,
- * @see http://dev.w3.org/html5/spec/Overview.html#flow-content
- * @see http://dev.w3.org/html5/spec/Overview.html#phrasing-content
- * @hide
- */
- tags: {
- 'flow' : [ 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio',
- 'b', 'bdi','bdo', 'blockquote', 'br', 'button', 'canvas', 'cite', 'code',
- 'command', 'datalist', 'del', 'details', 'dfn', 'div', 'dl', 'em',
- 'embed', 'fieldset', 'figure', 'footer', 'form', 'h1', 'h2', 'h3',
- 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'iframe', 'img',
- 'input', 'ins', 'kbd', 'keygen', 'label', 'map', 'mark', 'math',
- 'menu', 'meter', 'nav', 'noscript', 'object', 'ol', 'output', 'p',
- 'pre', 'progress', 'q', 'ruby', 's', 'samp', 'script', 'section',
- 'select', 'small', 'span', 'strong', 'style', 'sub', 'sup', 'svg',
- 'table', 'textarea', 'time', 'u', 'ul', 'var', 'video', 'wbr', '#text' ],
- 'phrasing' : [ 'a', 'abbr', 'area', 'audio', 'b', 'bdi', 'bdo', 'br', 'button',
- 'canvas', 'cite', 'code', 'command', 'datalist', 'del', 'dfn',
- 'em', 'embed', 'i', 'iframe', 'img', 'input', 'ins', 'kbd',
- 'keygen', 'label', 'map', 'mark', 'math', 'meter', 'noscript',
- 'object', 'output', 'progress', 'q', 'ruby', 'samp', 'script',
- 'select', 'small', 'span', 'strong', 'sub', 'sup', 'svg',
- 'textarea', 'time', 'u', 'var', 'video', 'wbr', '#text' ]
- },
+ /**
+ * Tags which make up Flow Content or Phrasing Content, according to the HTML 5 specification,
+ * @see http://dev.w3.org/html5/spec/Overview.html#flow-content
+ * @see http://dev.w3.org/html5/spec/Overview.html#phrasing-content
+ * @hide
+ */
+ tags: {
+ 'flow': ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'blockquote', 'br', 'button', 'canvas', 'cite', 'code', 'command', 'datalist', 'del', 'details', 'dfn', 'div', 'dl', 'em', 'embed', 'fieldset', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'map', 'mark', 'math', 'menu', 'meter', 'nav', 'noscript', 'object', 'ol', 'output', 'p', 'pre', 'progress', 'q', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'span', 'strong', 'style', 'sub', 'sup', 'svg', 'table', 'textarea', 'time', 'u', 'ul', 'var', 'video', 'wbr', '#text'],
+ 'phrasing': ['a', 'abbr', 'area', 'audio', 'b', 'bdi', 'bdo', 'br', 'button', 'canvas', 'cite', 'code', 'command', 'datalist', 'del', 'dfn', 'em', 'embed', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'map', 'mark', 'math', 'meter', 'noscript', 'object', 'output', 'progress', 'q', 'ruby', 'samp', 'script', 'select', 'small', 'span', 'strong', 'sub', 'sup', 'svg', 'textarea', 'time', 'u', 'var', 'video', 'wbr', '#text']
+ },
- /**
- * Possible children of tags, according to the HTML 5
- * specification.
- * See http://dev.w3.org/html5/spec/Overview.html#elements-1
- * Moved to http://www.whatwg.org/specs/web-apps/current-work/#elements-1
- * @hide
- */
- children: {
- 'a' : 'phrasing', // transparent
- 'abbr' : 'phrasing',
- 'address' : 'flow',
- 'area' : 'empty',
- 'article' : 'flow',
- 'aside' : 'flow',
- 'audio' : 'source', // transparent
- 'b' : 'phrasing',
- 'base' : 'empty',
- 'bdo' : 'phrasing',
- 'blockquote' : 'phrasing',
- 'body' : 'flow',
- 'br' : 'empty',
- 'button' : 'phrasing',
- 'canvas' : 'phrasing', // transparent
- 'caption' : 'flow',
- 'cite' : 'phrasing',
- 'code' : 'phrasing',
- 'col' : 'empty',
- 'colgroup' : 'col',
- 'command' : 'empty',
- 'datalist' : ['phrasing', 'option'],
- 'dd' : 'flow',
- 'del' : 'phrasing',
- 'div' : 'flow',
- 'details' : ['summary', 'flow'],
- 'dfn' : 'flow',
- 'dl' : ['dt','dd'],
- 'dt' : 'phrasing', // varies
- 'em' : 'phrasing',
- 'embed' : 'empty',
- 'fieldset' : ['legend', 'flow'],
- 'figcaption': 'flow',
- 'figure' : ['figcaption', 'flow'],
- 'footer' : 'flow',
- 'form' : 'flow',
- 'h1' : 'phrasing',
- 'h2' : 'phrasing',
- 'h3' : 'phrasing',
- 'h4' : 'phrasing',
- 'h5' : 'phrasing',
- 'h6' : 'phrasing',
- //head
- 'header' : 'flow',
- 'hgroup' : ['h1','h2','h3','h4','h5','h6'],
- 'hr' : 'empty',
- //html :)
- 'i' : 'phrasing',
- 'iframe' : '#text',
- 'img' : 'empty',
- 'input' : 'empty',
- 'ins' : 'phrasing', // transparent
- 'kbd' : 'phrasing',
- 'keygen' : 'empty',
- 'label' : 'phrasing',
- 'legend' : 'phrasing',
- 'li' : 'flow',
- 'link' : 'empty',
- 'map' : 'area', // transparent
- 'mark' : 'phrasing',
- 'menu' : ['li', 'flow'],
- 'meta' : 'empty',
- 'meter' : 'phrasing',
- 'nav' : 'flow',
- 'noscript' : 'phrasing', // varies
- 'object' : 'param', // transparent
- 'ol' : 'li',
- 'optgroup' : 'option',
- 'option' : '#text',
- 'output' : 'phrasing',
- 'p' : 'phrasing',
- 'param' : 'empty',
- 'pre' : 'phrasing',
- 'progress' : 'phrasing',
- 'q' : 'phrasing',
- 'rp' : 'phrasing',
- 'rt' : 'phrasing',
- 'ruby' : ['phrasing', 'rt', 'rp'],
- 's' : 'phrasing',
- 'samp' : 'pharsing',
- 'script' : '#script', //script
- 'section' : 'flow',
- 'select' : ['option', 'optgroup'],
- 'small' : 'phrasing',
- 'source' : 'empty',
- 'span' : 'phrasing',
- 'strong' : 'phrasing',
- 'style' : 'phrasing', // varies
- 'sub' : 'phrasing',
- 'summary' : 'phrasing',
- 'sup' : 'phrasing',
- 'table' : ['caption', 'colgroup', 'thead', 'tbody', 'tfoot', 'tr'],
- 'tbody' : 'tr',
- 'td' : 'flow',
- 'textarea' : '#text',
- 'tfoot' : 'tr',
- 'th' : 'phrasing',
- 'thead' : 'tr',
- 'time' : 'phrasing',
- 'title' : '#text',
- 'tr' : ['th', 'td'],
- 'track' : 'empty',
- 'u' : 'phrasing',
- 'ul' : 'li',
- 'var' : 'phrasing',
- 'video' : 'source', // transparent
- 'wbr' : 'empty'
- },
+ /**
+ * Possible children of tags, according to the HTML 5
+ * specification.
+ * See http://dev.w3.org/html5/spec/Overview.html#elements-1
+ * Moved to http://www.whatwg.org/specs/web-apps/current-work/#elements-1
+ * @hide
+ */
+ children: {
+ 'a': 'phrasing', // transparent
+ 'abbr': 'phrasing',
+ 'address': 'flow',
+ 'area': 'empty',
+ 'article': 'flow',
+ 'aside': 'flow',
+ 'audio': 'source', // transparent
+ 'b': 'phrasing',
+ 'base': 'empty',
+ 'bdo': 'phrasing',
+ 'blockquote': 'phrasing',
+ 'body': 'flow',
+ 'br': 'empty',
+ 'button': 'phrasing',
+ 'canvas': 'phrasing', // transparent
+ 'caption': 'flow',
+ 'cite': 'phrasing',
+ 'code': 'phrasing',
+ 'col': 'empty',
+ 'colgroup': 'col',
+ 'command': 'empty',
+ 'datalist': ['phrasing', 'option'],
+ 'dd': 'flow',
+ 'del': 'phrasing',
+ 'div': 'flow',
+ 'details': ['summary', 'flow'],
+ 'dfn': 'flow',
+ 'dl': ['dt', 'dd'],
+ 'dt': 'phrasing', // varies
+ 'em': 'phrasing',
+ 'embed': 'empty',
+ 'fieldset': ['legend', 'flow'],
+ 'figcaption': 'flow',
+ 'figure': ['figcaption', 'flow'],
+ 'footer': 'flow',
+ 'form': 'flow',
+ 'h1': 'phrasing',
+ 'h2': 'phrasing',
+ 'h3': 'phrasing',
+ 'h4': 'phrasing',
+ 'h5': 'phrasing',
+ 'h6': 'phrasing',
+ //head
+ 'header': 'flow',
+ 'hgroup': ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
+ 'hr': 'empty',
+ //html :)
+ 'i': 'phrasing',
+ 'iframe': '#text',
+ 'img': 'empty',
+ 'input': 'empty',
+ 'ins': 'phrasing', // transparent
+ 'kbd': 'phrasing',
+ 'keygen': 'empty',
+ 'label': 'phrasing',
+ 'legend': 'phrasing',
+ 'li': 'flow',
+ 'link': 'empty',
+ 'map': 'area', // transparent
+ 'mark': 'phrasing',
+ 'menu': ['li', 'flow'],
+ 'meta': 'empty',
+ 'meter': 'phrasing',
+ 'nav': 'flow',
+ 'noscript': 'phrasing', // varies
+ 'object': 'param', // transparent
+ 'ol': 'li',
+ 'optgroup': 'option',
+ 'option': '#text',
+ 'output': 'phrasing',
+ 'p': 'phrasing',
+ 'param': 'empty',
+ 'pre': 'phrasing',
+ 'progress': 'phrasing',
+ 'q': 'phrasing',
+ 'rp': 'phrasing',
+ 'rt': 'phrasing',
+ 'ruby': ['phrasing', 'rt', 'rp'],
+ 's': 'phrasing',
+ 'samp': 'pharsing',
+ 'script': '#script', //script
+ 'section': 'flow',
+ 'select': ['option', 'optgroup'],
+ 'small': 'phrasing',
+ 'source': 'empty',
+ 'span': 'phrasing',
+ 'strong': 'phrasing',
+ 'style': 'phrasing', // varies
+ 'sub': 'phrasing',
+ 'summary': 'phrasing',
+ 'sup': 'phrasing',
+ 'table': ['caption', 'colgroup', 'thead', 'tbody', 'tfoot', 'tr'],
+ 'tbody': 'tr',
+ 'td': 'flow',
+ 'textarea': '#text',
+ 'tfoot': 'tr',
+ 'th': 'phrasing',
+ 'thead': 'tr',
+ 'time': 'phrasing',
+ 'title': '#text',
+ 'tr': ['th', 'td'],
+ 'track': 'empty',
+ 'u': 'phrasing',
+ 'ul': 'li',
+ 'var': 'phrasing',
+ 'video': 'source', // transparent
+ 'wbr': 'empty'
+ },
- /**
- * List of nodenames of blocklevel elements
- * TODO: finish this list
- * @hide
- */
- blockLevelElements: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'div', 'pre'],
+ /**
+ * List of nodenames of blocklevel elements
+ * TODO: finish this list
+ * @hide
+ */
+ blockLevelElements: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'div', 'pre'],
- /**
- * List of nodenames of list elements
- * @hide
- */
- listElements: ['li', 'ol', 'ul'],
+ /**
+ * List of nodenames of list elements
+ * @hide
+ */
+ listElements: ['li', 'ol', 'ul'],
- /**
- * Splits a DOM element at the given position up until the limiting object(s), so that it is valid HTML again afterwards.
- * @param {RangeObject} range Range object that indicates the position of the splitting.
- * This range will be updated, so that it represents the same range as before the split.
- * @param {jQuery} limit Limiting node(s) for the split.
- * The limiting node will not be included in the split itself.
- * If no limiting object is set, the document body will be the limiting object.
- * @param {boolean} atEnd If set to true, the DOM will be splitted at the end of the range otherwise at the start.
- * @return {object} jQuery object containing the two root DOM objects of the split, true if the DOM did not need to be split or false if the DOM could not be split
- * @method
- */
- split: function (range, limit, atEnd) {
- var
- splitElement = jQuery(range.startContainer),
- splitPosition = range.startOffset,
- updateRange, path, parents,
- newDom, insertElement, secondPart,
- i, pathLength, element, jqelement, children, newElement,
- next, prev, offset;
+ /**
+ * Splits a DOM element at the given position up until the limiting object(s), so that it is valid HTML again afterwards.
+ * @param {RangeObject} range Range object that indicates the position of the splitting.
+ * This range will be updated, so that it represents the same range as before the split.
+ * @param {jQuery} limit Limiting node(s) for the split.
+ * The limiting node will not be included in the split itself.
+ * If no limiting object is set, the document body will be the limiting object.
+ * @param {boolean} atEnd If set to true, the DOM will be splitted at the end of the range otherwise at the start.
+ * @return {object} jQuery object containing the two root DOM objects of the split, true if the DOM did not need to be split or false if the DOM could not be split
+ * @method
+ */
+ split: function (range, limit, atEnd) {
+ var splitElement = jQuery(range.startContainer),
+ splitPosition = range.startOffset,
+ updateRange,
+ path,
+ parents,
+ newDom,
+ insertElement,
+ secondPart,
+ i,
+ pathLength,
+ element,
+ jqelement,
+ children,
+ newElement,
+ next,
+ prev,
+ offset;
+ if (atEnd) {
+ splitElement = jQuery(range.endContainer);
+ splitPosition = range.endOffset;
+ }
- if (atEnd) {
- splitElement = jQuery(range.endContainer);
- splitPosition = range.endOffset;
- }
+ if (limit.length < 1) {
+ limit = jQuery(document.body);
+ }
- if (limit.length < 1) {
- limit = jQuery(document.body);
- }
+ // we may have to update the range if it is not collapsed and we are splitting at the start
+ updateRange = (!range.isCollapsed() && !atEnd);
- // we may have to update the range if it is not collapsed and we are splitting at the start
- updateRange = (!range.isCollapsed() && !atEnd);
+ // find the path up to the highest object that will be splitted
+ parents = splitElement.parents().get();
+ parents.unshift(splitElement.get(0));
- // find the path up to the highest object that will be splitted
- parents = splitElement.parents().get();
- parents.unshift(splitElement.get(0));
-
- jQuery.each(parents, function(index, element) {
- var isLimit = limit.filter(
- function(){
+ jQuery.each(parents, function (index, element) {
+ var isLimit = limit.filter(
+ function () {
return this == element;
- }).length;
- if (isLimit) {
- if (index > 0) {
- path = parents.slice(0, index);
+ }
+ ).length;
+ if (isLimit) {
+ if (index > 0) {
+ path = parents.slice(0, index);
+ }
+ return false;
}
- return false;
+ });
+
+ // nothing found to split -> return here
+ if (!path) {
+ return true;
}
- });
- // nothing found to split -> return here
- if (! path) {
- return true;
- }
+ path = path.reverse();
- path = path.reverse();
+ // iterate over the path, create new dom nodes for every element and move
+ // the contents right of the split to the new element
+ for (i = 0, pathLength = path.length; i < pathLength; ++i) {
+ element = path[i];
+ if (i === pathLength - 1) {
+ // last element in the path -> we have to split it
- // iterate over the path, create new dom nodes for every element and move
- // the contents right of the split to the new element
- for( i=0, pathLength = path.length; i < pathLength; ++i) {
- element = path[i];
- if (i === pathLength - 1) {
- // last element in the path -> we have to split it
+ // split the last part into two parts
+ if (element.nodeType === 3) {
+ // text node
+ secondPart = document.createTextNode(element.data.substring(splitPosition, element.data.length));
+ element.data = element.data.substring(0, splitPosition);
+ } else {
+ // other nodes
+ jqelement = jQuery(element);
+ children = jqelement.contents();
+ newElement = jqelement.clone(false).empty();
+ secondPart = newElement.append(children.slice(splitPosition, children.length)).get(0);
+ }
- // split the last part into two parts
- if (element.nodeType === 3) {
- // text node
- secondPart = document.createTextNode(element.data.substring(splitPosition, element.data.length));
- element.data = element.data.substring(0, splitPosition);
- } else {
- // other nodes
- jqelement = jQuery(element);
- children = jqelement.contents();
- newElement = jqelement.clone(false).empty();
- secondPart = newElement.append(children.slice(splitPosition, children.length)).get(0);
- }
+ // update the range if necessary
+ if (updateRange && range.endContainer === element) {
+ range.endContainer = secondPart;
+ range.endOffset -= splitPosition;
+ range.clearCaches();
+ }
- // update the range if necessary
- if (updateRange && range.endContainer === element) {
- range.endContainer = secondPart;
- range.endOffset -= splitPosition;
- range.clearCaches();
- }
-
- // add the second part
- if (insertElement) {
- insertElement.prepend(secondPart);
+ // add the second part
+ if (insertElement) {
+ insertElement.prepend(secondPart);
+ } else {
+ jQuery(element).after(secondPart);
+ }
} else {
- jQuery(element).after(secondPart);
- }
- } else {
- // create the new element of the same type and prepend it to the previously created element
- newElement = jQuery(element).clone(false).empty();
+ // create the new element of the same type and prepend it to the previously created element
+ newElement = jQuery(element).clone(false).empty();
- if (!newDom) {
- newDom = newElement;
- } else {
- insertElement.prepend(newElement);
- }
- insertElement = newElement;
+ if (!newDom) {
+ newDom = newElement;
+ } else {
+ insertElement.prepend(newElement);
+ }
+ insertElement = newElement;
- // move all contents right of the split to the new element
- while ( true ) {
- next = path[i+1].nextSibling;
- if ( !next ) { break; }
- insertElement.append(next);
- }
+ // move all contents right of the split to the new element
+ while (true) {
+ next = path[i + 1].nextSibling;
+ if (!next) {
+ break;
+ }
+ insertElement.append(next);
+ }
- // update the range if necessary
- if (updateRange && range.endContainer === element) {
- range.endContainer = newElement.get(0);
- prev = path[i+1];
- offset = 0;
- while ( true ) {
- prev = prev.previousSibling;
- if ( !prev ) { break; }
- offset++;
+ // update the range if necessary
+ if (updateRange && range.endContainer === element) {
+ range.endContainer = newElement.get(0);
+ prev = path[i + 1];
+ offset = 0;
+ while (true) {
+ prev = prev.previousSibling;
+ if (!prev) {
+ break;
+ }
+ offset++;
+ }
+ range.endOffset -= offset;
+ range.clearCaches();
}
- range.endOffset -= offset;
- range.clearCaches();
}
}
- }
- // append the new dom
- jQuery(path[0]).after(newDom);
+ // append the new dom
+ jQuery(path[0]).after(newDom);
- return jQuery([path[0], newDom ? newDom.get(0) : secondPart]);
- },
+ return jQuery([path[0], newDom ? newDom.get(0) : secondPart]);
+ },
- /**
- * Check whether the HTML 5 specification allows direct nesting of the given DOM
- * objects.
- * @param {object} outerDOMObject
- * outer (nesting) DOM Object
- * @param {object} innerDOMObject
- * inner (nested) DOM Object
- * @return {boolean} true when the nesting is allowed, false if not
- * @method
- */
- allowsNesting: function (outerDOMObject, innerDOMObject) {
- if (!outerDOMObject || !outerDOMObject.nodeName || !innerDOMObject
- || !innerDOMObject.nodeName) {
- return false;
- }
+ /**
+ * Check whether the HTML 5 specification allows direct nesting of the given DOM
+ * objects.
+ * @param {object} outerDOMObject
+ * outer (nesting) DOM Object
+ * @param {object} innerDOMObject
+ * inner (nested) DOM Object
+ * @return {boolean} true when the nesting is allowed, false if not
+ * @method
+ */
+ allowsNesting: function (outerDOMObject, innerDOMObject) {
+ if (!outerDOMObject || !outerDOMObject.nodeName || !innerDOMObject || !innerDOMObject.nodeName) {
+ return false;
+ }
- var outerNodeName = outerDOMObject.nodeName.toLowerCase(),
- innerNodeName = innerDOMObject.nodeName.toLowerCase();
+ var outerNodeName = outerDOMObject.nodeName.toLowerCase(),
+ innerNodeName = innerDOMObject.nodeName.toLowerCase();
- if (!this.children[outerNodeName]) {
- return false;
- }
+ if (!this.children[outerNodeName]) {
+ return false;
+ }
- // check whether the nesting is configured by node names (like for table)
- if (this.children[outerNodeName] == innerNodeName) {
- return true;
- }
- if (jQuery.isArray(this.children[outerNodeName])
- && jQuery.inArray(innerNodeName, this.children[outerNodeName]) >= 0) {
- return true;
- }
+ // check whether the nesting is configured by node names (like for table)
+ if (this.children[outerNodeName] == innerNodeName) {
+ return true;
+ }
+ if (jQuery.isArray(this.children[outerNodeName]) && jQuery.inArray(innerNodeName, this.children[outerNodeName]) >= 0) {
+ return true;
+ }
- if (jQuery.isArray(this.tags[this.children[outerNodeName]])
- && jQuery.inArray(innerNodeName,
- this.tags[this.children[outerNodeName]]) >= 0) {
- return true;
- }
+ if (jQuery.isArray(this.tags[this.children[outerNodeName]])
+ && jQuery.inArray(innerNodeName, this.tags[this.children[outerNodeName]]) >= 0) {
+ return true;
+ }
- return false;
- },
+ return false;
+ },
- /**
- * Apply the given markup additively to the given range. The given rangeObject will be modified if necessary
- * @param {GENTICS.Utils.RangeObject} rangeObject range to which the markup shall be added
- * @param {jQuery} markup markup to be applied as jQuery object
- * @param {boolean} allownesting true when nesting of the added markup is allowed, false if not (default: false)
- * @method
- */
- addMarkup: function (rangeObject, markup, nesting) {
- // split partially contained text nodes at the start and end of the range
- if (rangeObject.startContainer.nodeType === 3 && rangeObject.startOffset > 0
- && rangeObject.startOffset < rangeObject.startContainer.data.length) {
- this.split(rangeObject, jQuery(rangeObject.startContainer).parent(),
- false);
- }
- if (rangeObject.endContainer.nodeType === 3 && rangeObject.endOffset > 0
- && rangeObject.endOffset < rangeObject.endContainer.data.length) {
- this.split(rangeObject, jQuery(rangeObject.endContainer).parent(),
- true);
- }
+ /**
+ * Apply the given markup additively to the given range. The given rangeObject will be modified if necessary
+ * @param {GENTICS.Utils.RangeObject} rangeObject range to which the markup shall be added
+ * @param {jQuery} markup markup to be applied as jQuery object
+ * @param {boolean} allownesting true when nesting of the added markup is allowed, false if not (default: false)
+ * @method
+ */
+ addMarkup: function (rangeObject, markup, nesting) {
+ // split partially contained text nodes at the start and end of the range
+ if (rangeObject.startContainer.nodeType === 3
+ && rangeObject.startOffset > 0
+ && rangeObject.startOffset < rangeObject.startContainer.data.length) {
+ this.split(rangeObject, jQuery(rangeObject.startContainer).parent(), false);
+ }
+ if (rangeObject.endContainer.nodeType === 3 && rangeObject.endOffset > 0 && rangeObject.endOffset < rangeObject.endContainer.data.length) {
+ this.split(rangeObject, jQuery(rangeObject.endContainer).parent(), true);
+ }
- // get the range tree
- var rangeTree = rangeObject.getRangeTree();
- this.recursiveAddMarkup(rangeTree, markup, rangeObject, nesting);
+ // get the range tree
+ var rangeTree = rangeObject.getRangeTree();
+ this.recursiveAddMarkup(rangeTree, markup, rangeObject, nesting);
- // cleanup DOM
- this.doCleanup({'merge' : true, 'removeempty' : true}, rangeObject);
- },
+ // cleanup DOM
+ this.doCleanup({
+ 'merge': true,
+ 'removeempty': true
+ }, rangeObject);
+ },
- /**
- * Recursive helper method to add the given markup to the range
- * @param rangeTree rangetree at the current level
- * @param markup markup to be applied
- * @param rangeObject range object, which eventually is updated
- * @param nesting true when nesting of the added markup is allowed, false if not
- * @hide
- */
- recursiveAddMarkup: function (rangeTree, markup, rangeObject, nesting) {
- var i, innerRange, rangeLength;
+ /**
+ * Recursive helper method to add the given markup to the range
+ * @param rangeTree rangetree at the current level
+ * @param markup markup to be applied
+ * @param rangeObject range object, which eventually is updated
+ * @param nesting true when nesting of the added markup is allowed, false if not
+ * @hide
+ */
+ recursiveAddMarkup: function (rangeTree, markup, rangeObject, nesting) {
+ var i, innerRange, rangeLength;
- // iterate through all rangetree objects of that level
- for ( i = 0, rangeLength = rangeTree.length; i < rangeLength; ++i) {
- // check whether the rangetree object is fully contained and the markup may be wrapped around the object
- if (rangeTree[i].type == 'full' && this.allowsNesting(markup.get(0), rangeTree[i].domobj)) {
- // we wrap the object, when
- // 1. nesting of markup is allowed or the node is not of the markup to be added
- // 2. the node an element node or a non-empty text node
- if ((nesting || rangeTree[i].domobj.nodeName != markup.get(0).nodeName)
- && (rangeTree[i].domobj.nodeType !== 3 || jQuery
- .trim(rangeTree[i].domobj.data).length !== 0)) {
- // wrap the object
- jQuery(rangeTree[i].domobj).wrap(markup);
+ // iterate through all rangetree objects of that level
+ for (i = 0, rangeLength = rangeTree.length; i < rangeLength; ++i) {
+ // check whether the rangetree object is fully contained and the markup may be wrapped around the object
+ if (rangeTree[i].type == 'full' && this.allowsNesting(markup.get(0), rangeTree[i].domobj)) {
+ // we wrap the object, when
+ // 1. nesting of markup is allowed or the node is not of the markup to be added
+ // 2. the node an element node or a non-empty text node
+ if ((nesting || rangeTree[i].domobj.nodeName != markup.get(0).nodeName) && (rangeTree[i].domobj.nodeType !== 3 || jQuery.trim(rangeTree[i].domobj.data).length !== 0)) {
+ // wrap the object
+ jQuery(rangeTree[i].domobj).wrap(markup);
- // TODO eventually update the range (if it changed)
+ // TODO eventually update the range (if it changed)
- // when nesting is not allowed, we remove the markup from the inner element
- if (!nesting && rangeTree[i].domobj.nodeType !== 3) {
- innerRange = new GENTICS.Utils.RangeObject();
- innerRange.startContainer = innerRange.endContainer = rangeTree[i].domobj.parentNode;
- innerRange.startOffset = 0;
- innerRange.endOffset = innerRange.endContainer.childNodes.length;
- this.removeMarkup(innerRange, markup, jQuery(rangeTree[i].domobj.parentNode));
+ // when nesting is not allowed, we remove the markup from the inner element
+ if (!nesting && rangeTree[i].domobj.nodeType !== 3) {
+ innerRange = new GENTICS.Utils.RangeObject();
+ innerRange.startContainer = innerRange.endContainer = rangeTree[i].domobj.parentNode;
+ innerRange.startOffset = 0;
+ innerRange.endOffset = innerRange.endContainer.childNodes.length;
+ this.removeMarkup(innerRange, markup, jQuery(rangeTree[i].domobj.parentNode));
+ }
}
- }
- } else {
- // TODO check whether the object may be replaced by the given markup
- if (false) {
- // TODO replace
} else {
+ // TODO check whether the object may be replaced by the given markup
+ //if (false) {
+ // TODO replace
+ //} else {
// recurse into the children (if any), but not if nesting is not
// allowed and the object is of the markup to be added
- if ((nesting || (rangeTree[i].domobj && rangeTree[i].domobj.nodeName !== markup.get(0).nodeName))
- && rangeTree[i].children && rangeTree[i].children.length > 0) {
+ if ((nesting || (rangeTree[i].domobj && rangeTree[i].domobj.nodeName !== markup.get(0).nodeName)) && rangeTree[i].children && rangeTree[i].children.length > 0) {
this.recursiveAddMarkup(rangeTree[i].children, markup);
}
}
}
- }
- },
+ },
- /**
- * Find the highest occurrence of a node with given nodename within the parents
- * of the start. When limit objects are given, the search stops there.
- * The limiting object is of the found type, it won't be considered
- * @param {DOMObject} start start object
- * @param {String} nodeName name of the node to search for (case-insensitive)
- * @param {jQuery} limit Limiting node(s) as jQuery object (if none given, the search will stop when there are no more parents)
- * @return {DOMObject} the found DOM object or undefined
- * @method
- */
- findHighestElement: function (start, nodeName, limit) {
- nodeName = nodeName.toLowerCase();
+ /**
+ * Find the highest occurrence of a node with given nodename within the parents
+ * of the start. When limit objects are given, the search stops there.
+ * The limiting object is of the found type, it won't be considered
+ * @param {DOMObject} start start object
+ * @param {String} nodeName name of the node to search for (case-insensitive)
+ * @param {jQuery} limit Limiting node(s) as jQuery object (if none given, the search will stop when there are no more parents)
+ * @return {DOMObject} the found DOM object or undefined
+ * @method
+ */
+ findHighestElement: function (start, nodeName, limit) {
+ nodeName = nodeName.toLowerCase();
- // this will be the highest found markup object (up to a limit object)
- var highestObject,
- // blah
- testObject = start,
- // helper function to stop when we reach a limit object
- isLimit = limit ? function () {
- return limit.filter(
- function() {
- return testObject == this;
- }
- ).length;
- } : function () {
- return false;
- };
+ // this will be the highest found markup object (up to a limit object)
+ var highestObject,
+ // blah
+ testObject = start,
+ // helper function to stop when we reach a limit object
+ isLimit = limit ? function () {
+ return limit.filter(
+ function () {
+ return testObject == this;
+ }
+ ).length;
+ } : function () {
+ return false;
+ };
- // now get the highest parent that has the given markup (until we reached
- // one of the limit objects or there are no more parent nodes)
- while (!isLimit() && testObject) {
- if (testObject.nodeName.toLowerCase() === nodeName) {
- highestObject = testObject;
+ // now get the highest parent that has the given markup (until we reached
+ // one of the limit objects or there are no more parent nodes)
+ while (!isLimit() && testObject) {
+ if (testObject.nodeName.toLowerCase() === nodeName) {
+ highestObject = testObject;
+ }
+ testObject = testObject.parentNode;
}
- testObject = testObject.parentNode;
- }
- return highestObject;
- },
+ return highestObject;
+ },
- /**
- * Remove the given markup from the given range. The given rangeObject will be modified if necessary
- * TODO: add parameter deep/shallow
- * @param {GENTICS.Utils.RangeObject} rangeObject range from which the markup shall be removed
- * @param {jQuery} markup markup to be removed as jQuery object
- * @param {jQuery} limit Limiting node(s) as jQuery object
- * @method
- */
- removeMarkup: function (rangeObject, markup, limit) {
- var nodeName = markup.get(0).nodeName,
- startSplitLimit = this.findHighestElement(rangeObject.startContainer, nodeName, limit),
- endSplitLimit = this.findHighestElement(rangeObject.endContainer, nodeName, limit),
- didSplit = false,
- highestObject, root, rangeTree;
+ /**
+ * Remove the given markup from the given range. The given rangeObject will be modified if necessary
+ * TODO: add parameter deep/shallow
+ * @param {GENTICS.Utils.RangeObject} rangeObject range from which the markup shall be removed
+ * @param {jQuery} markup markup to be removed as jQuery object
+ * @param {jQuery} limit Limiting node(s) as jQuery object
+ * @method
+ */
+ removeMarkup: function (rangeObject, markup, limit) {
+ var nodeName = markup.get(0).nodeName,
+ startSplitLimit = this.findHighestElement(rangeObject.startContainer, nodeName, limit),
+ endSplitLimit = this.findHighestElement(rangeObject.endContainer, nodeName, limit),
+ didSplit = false,
+ highestObject,
+ root,
+ rangeTree;
- if (startSplitLimit && rangeObject.startOffset > 0) {
- // when the start is in the start of its container, we don't split
- this.split(rangeObject, jQuery(startSplitLimit).parent(), false);
- didSplit = true;
- }
-
- if (endSplitLimit) {
- // when the end is in the end of its container, we don't split
- if (rangeObject.endContainer.nodeType === 3 && rangeObject.endOffset < rangeObject.endContainer.data.length) {
- this.split(rangeObject, jQuery(endSplitLimit).parent(), true);
+ if (startSplitLimit && rangeObject.startOffset > 0) {
+ // when the start is in the start of its container, we don't split
+ this.split(rangeObject, jQuery(startSplitLimit).parent(), false);
didSplit = true;
}
- if (rangeObject.endContainer.nodeType === 1 && rangeObject.endOffset < rangeObject.childNodes.length) {
- this.split(rangeObject, jQuery(endSplitLimit).parent(), true);
- didSplit = true;
+
+ if (endSplitLimit) {
+ // when the end is in the end of its container, we don't split
+ if (rangeObject.endContainer.nodeType === 3 && rangeObject.endOffset < rangeObject.endContainer.data.length) {
+ this.split(rangeObject, jQuery(endSplitLimit).parent(), true);
+ didSplit = true;
+ }
+ if (rangeObject.endContainer.nodeType === 1 && rangeObject.endOffset < rangeObject.endContainer.childNodes.length) {
+ this.split(rangeObject, jQuery(endSplitLimit).parent(), true);
+ didSplit = true;
+ }
}
- }
- // when we split the DOM, we maybe need to correct the range
- if (didSplit) {
- rangeObject.correctRange();
- }
+ // when we split the DOM, we maybe need to correct the range
+ if (didSplit) {
+ rangeObject.correctRange();
+ }
- // find the highest occurrence of the markup
- highestObject = this.findHighestElement(rangeObject.getCommonAncestorContainer(), nodeName, limit);
- root = highestObject ? highestObject.parentNode : rangeObject.getCommonAncestorContainer();
+ // find the highest occurrence of the markup
+ highestObject = this.findHighestElement(rangeObject.getCommonAncestorContainer(), nodeName, limit);
+ root = highestObject ? highestObject.parentNode : rangeObject.getCommonAncestorContainer();
- if (root) {
- // construct the range tree
- rangeTree = rangeObject.getRangeTree(root);
-
- // remove the markup from the range tree
- this.recursiveRemoveMarkup(rangeTree, markup);
-
- // cleanup DOM
- this.doCleanup({'merge' : true, 'removeempty' : true}, rangeObject, root);
- }
- },
+ if (root) {
+ // construct the range tree
+ rangeTree = rangeObject.getRangeTree(root);
- /**
- * TODO: pass the range itself and eventually update it if necessary
- * Recursive helper method to remove the given markup from the range
- * @param rangeTree rangetree at the current level
- * @param markup markup to be applied
- * @hide
- */
- recursiveRemoveMarkup: function (rangeTree, markup) {
- var i, rangeLength, content;
- // iterate over the rangetree objects of this level
- for (i = 0, rangeLength = rangeTree.length; i < rangeLength; ++i) {
- // check whether the object is the markup to be removed and is fully into the range
- if (rangeTree[i].type == 'full' && rangeTree[i].domobj.nodeName == markup.get(0).nodeName) {
- // found the markup, so remove it
- content = jQuery(rangeTree[i].domobj).contents();
- if (content.length > 0) {
- // when the object has children, we unwrap them
- content.first().unwrap();
- } else {
- // obj has no children, so just remove it
- jQuery(rangeTree[i].domobj).remove();
- }
- }
+ // remove the markup from the range tree
+ this.recursiveRemoveMarkup(rangeTree, markup);
- // if the object has children, we do the recursion now
- if (rangeTree[i].children) {
- this.recursiveRemoveMarkup(rangeTree[i].children, markup);
+ // cleanup DOM
+ this.doCleanup({
+ 'merge': true,
+ 'removeempty': true
+ }, rangeObject, root);
}
- }
- },
+ },
- /**
- * Cleanup the DOM, starting with the given startobject (or the common ancestor container of the given range)
- * ATTENTION: If range is a selection you need to update the selection after doCleanup
- * Cleanup modes (given as properties in 'cleanup'):
- * <pre>
- * - merge: merges multiple successive nodes of same type, if this is allowed, starting at the children of the given node (defaults to false)
- * - removeempty: removes empty element nodes (defaults to false)
- * </pre>
- * Example for calling this method:<br/>
- * <code>GENTICS.Utils.Dom.doCleanup({merge:true,removeempty:false}, range)</code>
- * @param {object} cleanup type of cleanup to be done
- * @param {GENTICS.Utils.RangeObject} rangeObject range which is eventually updated
- * @param {DOMObject} start start object, if not given, the commonancestorcontainer is used as startobject insted
- * @return {boolean} true when the range (startContainer/startOffset/endContainer/endOffset) was modified, false if not
- * @method
- */
- doCleanup: function(cleanup, rangeObject, start) {
- var that = this, prevNode, modifiedRange, startObject, startOffset, endOffset;
+ /**
+ * TODO: pass the range itself and eventually update it if necessary
+ * Recursive helper method to remove the given markup from the range
+ * @param rangeTree rangetree at the current level
+ * @param markup markup to be applied
+ * @hide
+ */
+ recursiveRemoveMarkup: function (rangeTree, markup) {
+ var i, rangeLength, content;
+ // iterate over the rangetree objects of this level
+ for (i = 0, rangeLength = rangeTree.length; i < rangeLength; ++i) {
+ // check whether the object is the markup to be removed and is fully into the range
+ if (rangeTree[i].type == 'full' && rangeTree[i].domobj.nodeName == markup.get(0).nodeName) {
+ // found the markup, so remove it
+ content = jQuery(rangeTree[i].domobj).contents();
+ if (content.length > 0) {
+ // when the object has children, we unwrap them
+ content.first().unwrap();
+ } else {
+ // obj has no children, so just remove it
+ jQuery(rangeTree[i].domobj).remove();
+ }
+ }
- if (typeof cleanup === 'undefined') {
- cleanup = {};
- }
- if (typeof cleanup.merge === 'undefined') {
- cleanup.merge = false;
- }
- if (typeof cleanup.removeempty === 'undefined') {
- cleanup.removeempty = false;
- }
+ // if the object has children, we do the recursion now
+ if (rangeTree[i].children) {
+ this.recursiveRemoveMarkup(rangeTree[i].children, markup);
+ }
+ }
+ },
- if (typeof start === 'undefined' && rangeObject) {
- start = rangeObject.getCommonAncestorContainer();
- }
- // remember the previous node here (successive nodes of same type will be merged into this)
- prevNode = false;
- // check whether the range needed to be modified during merging
- modifiedRange = false;
- // get the start object
- startObject = jQuery(start);
- startOffset = rangeObject.startOffset;
- endOffset = rangeObject.endOffset;
+ /**
+ * Cleanup the DOM, starting with the given startobject (or the common ancestor container of the given range)
+ * ATTENTION: If range is a selection you need to update the selection after doCleanup
+ * Cleanup modes (given as properties in 'cleanup'):
+ * <pre>
+ * - merge: merges multiple successive nodes of same type, if this is allowed, starting at the children of the given node (defaults to false)
+ * - removeempty: removes empty element nodes (defaults to false)
+ * - mergeable: Custom function to predicate whether or not a given
+ * element should be mergeable. Overrides `mergeableTags'.
+ * </pre>
+ * Example for calling this method:<br/>
+ * <code>GENTICS.Utils.Dom.doCleanup({merge:true,removeempty:false}, range)</code>
+ * @param {object} cleanup type of cleanup to be done
+ * @param {GENTICS.Utils.RangeObject} rangeObject range which is eventually updated
+ * @param {DOMObject} start start object, if not given, the commonancestorcontainer is used as startobject insted
+ * @return {boolean} true when the range (startContainer/startOffset/endContainer/endOffset) was modified, false if not
+ * @method
+ */
+ doCleanup: function (cleanup, rangeObject, start) {
+ var that = this,
+ prevNode,
+ modifiedRange,
+ startObject,
+ startOffset,
+ endOffset;
- // iterate through all sub nodes
- startObject.contents().each(function(index) {
-
- // Try to read the nodeType property and return if we do not have permission
- // ie.: frame document to an external URL
- var nodeType;
- try {
- nodeType = this.nodeType;
+ if (typeof cleanup === 'undefined') {
+ cleanup = {};
}
- catch (e) {
- return;
+ if (typeof cleanup.merge === 'undefined') {
+ cleanup.merge = false;
}
+ if (typeof cleanup.removeempty === 'undefined') {
+ cleanup.removeempty = false;
+ }
- // decide further actions by node type
- switch(nodeType) {
- // found a non-text node
- case 1:
- if (prevNode && prevNode.nodeName == this.nodeName) {
- // found a successive node of same type
+ if (typeof start === 'undefined' && rangeObject) {
+ start = rangeObject.getCommonAncestorContainer();
+ }
+ // remember the previous node here (successive nodes of same type will be merged into this)
+ prevNode = false;
+ // check whether the range needed to be modified during merging
+ modifiedRange = false;
+ // get the start object
+ startObject = jQuery(start);
+ startOffset = rangeObject.startOffset;
+ endOffset = rangeObject.endOffset;
- // now we check whether the selection starts or ends in the mother node after the current node
- if (rangeObject.startContainer === startObject && startOffset > index) {
- // there will be one less object, so reduce the startOffset by one
- rangeObject.startOffset -= 1;
- // set the flag for range modification
- modifiedRange = true;
- }
- if (rangeObject.endContainer === startObject && endOffset > index) {
- // there will be one less object, so reduce the endOffset by one
- rangeObject.endOffset -= 1;
- // set the flag for range modification
- modifiedRange = true;
- }
+ // iterate through all sub nodes
+ startObject.contents().each(function () {
+ var index;
- // merge the contents of this node into the previous one
- jQuery(prevNode).append(jQuery(this).contents());
+ // Try to read the nodeType property and return if we do not have permission
+ // ie.: frame document to an external URL
+ var nodeType;
+ try {
+ nodeType = this.nodeType;
+ index = that.getIndexInParent(this);
+ } catch (e) {
+ return;
+ }
- // after merging, we eventually need to cleanup the prevNode again
- modifiedRange |= that.doCleanup(cleanup, rangeObject, prevNode);
+ // decide further actions by node type
+ switch (nodeType) {
+ // found a non-text node
+ case 1:
+ if (prevNode && prevNode.nodeName == this.nodeName) {
+ // found a successive node of same type
- // remove this node
- jQuery(this).remove();
-
- } else {
-
- // do the recursion step here
- modifiedRange |= that.doCleanup(cleanup, rangeObject, this);
-
- // eventually remove empty elements
- var removed = false;
- if (cleanup.removeempty) {
- if (GENTICS.Utils.Dom.isBlockLevelElement(this) && this.childNodes.length === 0) {
-// jQuery(this).remove();
- removed = true;
- }
- if (jQuery.inArray(this.nodeName.toLowerCase(), that.mergeableTags) >= 0
- && jQuery(this).text().length === 0 && this.childNodes.length === 0) {
-// jQuery(this).remove();
- removed = true;
- }
- }
-
- // when the current node was not removed, we eventually store it as previous (mergeable) tag
- if (!removed) {
- if (jQuery.inArray(this.nodeName.toLowerCase(), that.mergeableTags) >= 0) {
- prevNode = this;
- } else {
- prevNode = false;
- }
- } else {
- // now we check whether the selection starts or ends in the mother node of this
- if (rangeObject.startContainer === this.parentNode && startOffset > index) {
+ // now we check whether the selection starts or ends in the mother node after the current node
+ if (rangeObject.startContainer === startObject && startOffset > index) {
// there will be one less object, so reduce the startOffset by one
- rangeObject.startOffset = rangeObject.startOffset - 1;
+ rangeObject.startOffset -= 1;
// set the flag for range modification
modifiedRange = true;
}
- if (rangeObject.endContainer === this.parentNode && endOffset > index) {
+ if (rangeObject.endContainer === startObject && endOffset > index) {
// there will be one less object, so reduce the endOffset by one
- rangeObject.endOffset = rangeObject.endOffset - 1;
+ rangeObject.endOffset -= 1;
// set the flag for range modification
modifiedRange = true;
}
-
- // remove this text node
+
+ // merge the contents of this node into the previous one
+ jQuery(prevNode).append(jQuery(this).contents());
+
+ // after merging, we eventually need to cleanup the prevNode again
+ modifiedRange |= that.doCleanup(cleanup, rangeObject, prevNode);
+
+ // remove this node
jQuery(this).remove();
+ } else {
+
+ // do the recursion step here
+ modifiedRange |= that.doCleanup(cleanup, rangeObject, this);
+
+ // eventually remove empty elements
+ var removed = false;
+ if (cleanup.removeempty) {
+ if (GENTICS.Utils.Dom.isBlockLevelElement(this) && this.childNodes.length === 0) {
+ // jQuery(this).remove();
+ removed = true;
+ }
+ if (jQuery.inArray(this.nodeName.toLowerCase(), that.mergeableTags) >= 0 && jQuery(this).text().length === 0 && this.childNodes.length === 0) {
+ // jQuery(this).remove();
+ removed = true;
+ }
+ }
+
+ // when the current node was not removed, we eventually store it as previous (mergeable) tag
+ if (!removed) {
+ if (cleanup.mergeable
+ ? cleanup.mergeable(this)
+ : jQuery.inArray(this.nodeName.toLowerCase(), that.mergeableTags) >= 0) {
+ prevNode = this;
+ } else {
+ prevNode = false;
+ }
+ } else {
+ // now we check whether the selection starts or ends in the mother node of this
+ if (rangeObject.startContainer === this.parentNode && startOffset > index) {
+ // there will be one less object, so reduce the startOffset by one
+ rangeObject.startOffset = rangeObject.startOffset - 1;
+ // set the flag for range modification
+ modifiedRange = true;
+ }
+ if (rangeObject.endContainer === this.parentNode && endOffset > index) {
+ // there will be one less object, so reduce the endOffset by one
+ rangeObject.endOffset = rangeObject.endOffset - 1;
+ // set the flag for range modification
+ modifiedRange = true;
+ }
+
+ // remove this text node
+ jQuery(this).remove();
+
+ }
}
- }
- break;
- // found a text node
- case 3:
- // found a text node
- if (prevNode && prevNode.nodeType === 3 && cleanup.merge) {
- // the current text node will be merged into the last one, so
- // check whether the selection starts or ends in the current
- // text node
- if (rangeObject.startContainer === this) {
- // selection starts in the current text node
+ break;
+ // found a text node
+ case 3:
+ // found a text node
+ if (prevNode && prevNode.nodeType === 3 && cleanup.merge) {
+ // the current text node will be merged into the last one, so
+ // check whether the selection starts or ends in the current
+ // text node
+ if (rangeObject.startContainer === this) {
+ // selection starts in the current text node
- // update the start container to the last node
- rangeObject.startContainer = prevNode;
+ // update the start container to the last node
+ rangeObject.startContainer = prevNode;
- // update the start offset
- rangeObject.startOffset += prevNode.nodeValue.length;
+ // update the start offset
+ rangeObject.startOffset += prevNode.nodeValue.length;
- // set the flag for range modification
- modifiedRange = true;
-
- } else if (rangeObject.startContainer === prevNode.parentNode
- && rangeObject.startOffset === that.getIndexInParent(prevNode) + 1) {
- // selection starts right between the previous and current text nodes (which will be merged)
+ // set the flag for range modification
+ modifiedRange = true;
- // update the start container to the previous node
- rangeObject.startContainer = prevNode;
+ } else if (rangeObject.startContainer === prevNode.parentNode && rangeObject.startOffset === that.getIndexInParent(prevNode) + 1) {
+ // selection starts right between the previous and current text nodes (which will be merged)
- // set the start offset
- rangeObject.startOffset = prevNode.nodeValue.length;
+ // update the start container to the previous node
+ rangeObject.startContainer = prevNode;
- // set the flag for range modification
- modifiedRange = true;
- }
+ // set the start offset
+ rangeObject.startOffset = prevNode.nodeValue.length;
- if (rangeObject.endContainer === this) {
- // selection ends in the current text node
+ // set the flag for range modification
+ modifiedRange = true;
+ }
- // update the end container to be the last node
- rangeObject.endContainer = prevNode;
+ if (rangeObject.endContainer === this) {
+ // selection ends in the current text node
- // update the end offset
- rangeObject.endOffset += prevNode.nodeValue.length;
+ // update the end container to be the last node
+ rangeObject.endContainer = prevNode;
- // set the flag for range modification
- modifiedRange = true;
+ // update the end offset
+ rangeObject.endOffset += prevNode.nodeValue.length;
- } else if (rangeObject.endContainer === prevNode.parentNode
- && rangeObject.endOffset === that.getIndexInParent(prevNode) + 1) {
- // selection ends right between the previous and current text nodes (which will be merged)
+ // set the flag for range modification
+ modifiedRange = true;
- // update the end container to the previous node
- rangeObject.endContainer = prevNode;
+ } else if (rangeObject.endContainer === prevNode.parentNode && rangeObject.endOffset === that.getIndexInParent(prevNode) + 1) {
+ // selection ends right between the previous and current text nodes (which will be merged)
- // set the end offset
- rangeObject.endOffset = prevNode.nodeValue.length;
+ // update the end container to the previous node
+ rangeObject.endContainer = prevNode;
+ // set the end offset
+ rangeObject.endOffset = prevNode.nodeValue.length;
+
+ // set the flag for range modification
+ modifiedRange = true;
+ }
+
+ // now append the contents of the current text node into the previous
+ prevNode.data += this.data;
+
+ // remove empty text nodes
+ } else if (!(this.nodeValue === '' && cleanup.removeempty)) {
+ prevNode = this;
+ // we are finish here don't delete this node
+ break;
+ }
+
+ // now we check whether the selection starts or ends in the mother node of this
+ if (rangeObject.startContainer === this.parentNode && rangeObject.startOffset > index) {
+ // there will be one less object, so reduce the startOffset by one
+ rangeObject.startOffset = rangeObject.startOffset - 1;
// set the flag for range modification
modifiedRange = true;
}
+ if (rangeObject.endContainer === this.parentNode && rangeObject.endOffset > index) {
+ // there will be one less object, so reduce the endOffset by one
+ rangeObject.endOffset = rangeObject.endOffset - 1;
+ // set the flag for range modification
+ modifiedRange = true;
+ }
- // now append the contents of the current text node into the previous
- prevNode.data += this.data;
+ // remove this text node
+ jQuery(this).remove();
- // remove empty text nodes
- } else if ( this.nodeValue === '' && cleanup.removeempty ) {
- // do nothing here.
-
- // remember it as the last text node if not empty
- } else if ( !(this.nodeValue === '' && cleanup.removeempty) ) {
- prevNode = this;
- // we are finish here don't delete this node
+ // if this is the last text node in a sequence, we remove any zero-width spaces in the text node,
+ // unless it is the only character
+ if (prevNode && (!prevNode.nextSibling || prevNode.nextSibling.nodeType !== 3)) {
+ var pos;
+ for (pos = prevNode.data.length - 1; pos >= 0 && prevNode.data.length > 1; pos--) {
+ if (prevNode.data.charAt(pos) === '\u200b') {
+ prevNode.deleteData(pos, 1);
+ if (rangeObject.startContainer === prevNode && rangeObject.startOffset > pos) {
+ rangeObject.startOffset--;
+ modifiedRange = true;
+ }
+ if (rangeObject.endContainer === prevNode && rangeObject.endOffset > pos) {
+ rangeObject.endOffset--;
+ modifiedRange = true;
+ }
+ }
+ }
+ }
+
break;
}
+ });
- // now we check whether the selection starts or ends in the mother node of this
- if (rangeObject.startContainer === this.parentNode && rangeObject.startOffset > index) {
- // there will be one less object, so reduce the startOffset by one
- rangeObject.startOffset = rangeObject.startOffset - 1;
- // set the flag for range modification
- modifiedRange = true;
- }
- if (rangeObject.endContainer === this.parentNode && rangeObject.endOffset > index) {
- // there will be one less object, so reduce the endOffset by one
- rangeObject.endOffset = rangeObject.endOffset - 1;
- // set the flag for range modification
- modifiedRange = true;
- }
+ // eventually remove the startnode itself
+ // if (cleanup.removeempty
+ // && GENTICS.Utils.Dom.isBlockLevelElement(start)
+ // && (!start.childNodes || start.childNodes.length === 0)) {
+ // if (rangeObject.startContainer == start) {
+ // rangeObject.startContainer = start.parentNode;
+ // rangeObject.startOffset = GENTICS.Utils.Dom.getIndexInParent(start);
+ // }
+ // if (rangeObject.endContainer == start) {
+ // rangeObject.endContainer = start.parentNode;
+ // rangeObject.endOffset = GENTICS.Utils.Dom.getIndexInParent(start);
+ // }
+ // startObject.remove();
+ // modifiedRange = true;
+ // }
- // remove this text node
- jQuery(this).remove();
+ if (modifiedRange) {
+ rangeObject.clearCaches();
+ }
- break;
+ return modifiedRange;
+ },
+
+ /**
+ * Get the index of the given node within its parent node
+ * @param {DOMObject} node node to check
+ * @return {Integer} index in the parent node or false if no node given or node has no parent
+ * @method
+ */
+ getIndexInParent: function (node) {
+ if (!node) {
+ return false;
}
- });
- // eventually remove the startnode itself
-// if (cleanup.removeempty
-// && GENTICS.Utils.Dom.isBlockLevelElement(start)
-// && (!start.childNodes || start.childNodes.length === 0)) {
-// if (rangeObject.startContainer == start) {
-// rangeObject.startContainer = start.parentNode;
-// rangeObject.startOffset = GENTICS.Utils.Dom.getIndexInParent(start);
-// }
-// if (rangeObject.endContainer == start) {
-// rangeObject.endContainer = start.parentNode;
-// rangeObject.endOffset = GENTICS.Utils.Dom.getIndexInParent(start);
-// }
-// startObject.remove();
-// modifiedRange = true;
-// }
+ var index = 0,
+ check = node.previousSibling;
- if (modifiedRange) {
- rangeObject.clearCaches();
- }
+ while (check) {
+ index++;
+ check = check.previousSibling;
+ }
- return modifiedRange;
- },
+ return index;
+ },
- /**
- * Get the index of the given node within its parent node
- * @param {DOMObject} node node to check
- * @return {Integer} index in the parent node or false if no node given or node has no parent
- * @method
- */
- getIndexInParent: function (node) {
- if (!node) {
+ /**
+ * Check whether the given node is a blocklevel element
+ * @param {DOMObject} node node to check
+ * @return {boolean} true if yes, false if not (or null)
+ * @method
+ */
+ isBlockLevelElement: function (node) {
+ if (!node) {
+ return false;
+ }
+ if (node.nodeType === 1 && jQuery.inArray(node.nodeName.toLowerCase(), this.blockLevelElements) >= 0) {
+ return true;
+ }
return false;
- }
+ },
- var
- index = 0,
- check = node.previousSibling;
+ /**
+ * Check whether the given node is a linebreak element
+ * @param {DOMObject} node node to check
+ * @return {boolean} true for linebreak elements, false for everything else
+ * @method
+ */
+ isLineBreakElement: function (node) {
+ if (!node) {
+ return false;
+ }
+ return node.nodeType === 1 && node.nodeName.toLowerCase() == 'br';
+ },
- while(check) {
- index++;
- check = check.previousSibling;
- }
+ /**
+ * Check whether the given node is a list element
+ * @param {DOMObject} node node to check
+ * @return {boolean} true for list elements (li, ul, ol), false for everything else
+ * @method
+ */
+ isListElement: function (node) {
+ if (!node) {
+ return false;
+ }
+ return node.nodeType === 1 && jQuery.inArray(node.nodeName.toLowerCase(), this.listElements) >= 0;
+ },
- return index;
- },
+ /**
+ * This method checks, whether the passed dom object is a dom object, that would
+ * be split in cases of pressing enter. This currently is true for paragraphs
+ * and headings
+ * @param {DOMObject} el
+ * dom object to check
+ * @return {boolean} true for split objects, false for other
+ * @method
+ */
+ isSplitObject: function (el) {
+ return el.nodeType === 1 && blockElementNames.hasOwnProperty(el.nodeName);
+ },
- /**
- * Check whether the given node is a blocklevel element
- * @param {DOMObject} node node to check
- * @return {boolean} true if yes, false if not (or null)
- * @method
- */
- isBlockLevelElement: function (node) {
- if (!node) {
- return false;
- }
- if (node.nodeType === 1 && jQuery.inArray(node.nodeName.toLowerCase(), this.blockLevelElements) >= 0) {
- return true;
- } else {
- return false;
- }
- },
+ /**
+ * Starting with the given position (between nodes), search in the given direction to an adjacent notempty text node
+ * @param {DOMObject} parent parent node containing the position
+ * @param {Integer} index index of the position within the parent node
+ * @param {boolean} searchleft true when search direction is 'left' (default), false for 'right'
+ * @param {object} stopat define at which types of element we shall stop, may contain the following properties
+ * <pre>
+ * - blocklevel (default: true)
+ * - list (default: true)
+ * - linebreak (default: true)
+ * </pre>
+ * @return {DOMObject} the found text node or false if none found
+ * @method
+ */
+ searchAdjacentTextNode: function (parent, index, searchleft, stopat) {
+ if (!parent || parent.nodeType !== 1 || index < 0 || index > parent.childNodes.length) {
+ return false;
+ }
- /**
- * Check whether the given node is a linebreak element
- * @param {DOMObject} node node to check
- * @return {boolean} true for linebreak elements, false for everything else
- * @method
- */
- isLineBreakElement: function (node) {
- if (!node) {
- return false;
- }
- return node.nodeType === 1 && node.nodeName.toLowerCase() == 'br';
- },
+ if (typeof stopat === 'undefined') {
+ stopat = {
+ 'blocklevel': true,
+ 'list': true,
+ 'linebreak': true
+ };
+ }
- /**
- * Check whether the given node is a list element
- * @param {DOMObject} node node to check
- * @return {boolean} true for list elements (li, ul, ol), false for everything else
- * @method
- */
- isListElement: function (node) {
- if (!node) {
- return false;
- }
- return node.nodeType === 1 && jQuery.inArray(node.nodeName.toLowerCase(), this.listElements) >= 0;
- },
+ if (typeof stopat.blocklevel === 'undefined') {
+ stopat.blocklevel = true;
+ }
+ if (typeof stopat.list === 'undefined') {
+ stopat.list = true;
+ }
+ if (typeof stopat.linebreak === 'undefined') {
+ stopat.linebreak = true;
+ }
- /**
- * This method checks, whether the passed dom object is a dom object, that would
- * be split in cases of pressing enter. This currently is true for paragraphs
- * and headings
- * @param {DOMObject} el
- * dom object to check
- * @return {boolean} true for split objects, false for other
- * @method
- */
- isSplitObject: function(el) {
- if (el.nodeType === 1){
- switch(el.nodeName.toLowerCase()) {
- case 'p':
- case 'h1':
- case 'h2':
- case 'h3':
- case 'h4':
- case 'h5':
- case 'h6':
- case 'li':
- return true;
+ if (typeof searchleft === 'undefined') {
+ searchleft = true;
}
- }
- return false;
- },
- /**
- * Starting with the given position (between nodes), search in the given direction to an adjacent notempty text node
- * @param {DOMObject} parent parent node containing the position
- * @param {Integer} index index of the position within the parent node
- * @param {boolean} searchleft true when search direction is 'left' (default), false for 'right'
- * @param {object} stopat define at which types of element we shall stop, may contain the following properties
- * <pre>
- * - blocklevel (default: true)
- * - list (default: true)
- * - linebreak (default: true)
- * </pre>
- * @return {DOMObject} the found text node or false if none found
- * @method
- */
- searchAdjacentTextNode: function (parent, index, searchleft, stopat) {
- if (!parent || parent.nodeType !== 1 || index < 0 || index > parent.childNodes.length) {
- return false;
- }
+ var nextNode,
+ currentParent = parent;
- if (typeof stopat === 'undefined') {
- stopat = {'blocklevel' : true, 'list' : true, 'linebreak' : true};
- }
+ // start at the node left/right of the given position
+ if (searchleft && index > 0) {
+ nextNode = parent.childNodes[index - 1];
+ }
+ if (!searchleft && index < parent.childNodes.length) {
+ nextNode = parent.childNodes[index];
+ }
- if (typeof stopat.blocklevel === 'undefined') {
- stopat.blocklevel = true;
- }
- if (typeof stopat.list === 'undefined') {
- stopat.list = true;
- }
- if (typeof stopat.linebreak === 'undefined') {
- stopat.linebreak = true;
- }
-
- if (typeof searchleft === 'undefined') {
- searchleft = true;
- }
-
- var
- nextNode,
- currentParent = parent;
-
- // start at the node left/right of the given position
- if (searchleft && index > 0) {
- nextNode = parent.childNodes[index - 1];
- }
- if (!searchleft && index < parent.childNodes.length) {
- nextNode = parent.childNodes[index];
- }
-
- //currentParent is not a number therefore it is sufficient to directly test for it with while(currentParent)
- //otherwise there would be an error if the object is null
- while (currentParent) {
- //while (typeof currentParent !== 'undefined') {
- if (!nextNode) {
- // no next node found, check whether the parent is a blocklevel element
- if (stopat.blocklevel && this.isBlockLevelElement(currentParent)) {
- // do not leave block level elements
- return false;
- } else if (stopat.list && this.isListElement(currentParent)) {
- // do not leave list elements
- return false;
- } else {
+ //currentParent is not a number therefore it is sufficient to directly test for it with while(currentParent)
+ //otherwise there would be an error if the object is null
+ while (currentParent) {
+ //while (typeof currentParent !== 'undefined') {
+ if (!nextNode) {
+ // no next node found, check whether the parent is a blocklevel element
+ if (stopat.blocklevel && this.isBlockLevelElement(currentParent)) {
+ // do not leave block level elements
+ return false;
+ }
+ if (stopat.list && this.isListElement(currentParent)) {
+ // do not leave list elements
+ return false;
+ }
// continue with the parent
nextNode = searchleft ? currentParent.previousSibling : currentParent.nextSibling;
currentParent = currentParent.parentNode;
+ continue;
+ } else if (nextNode.nodeType === 3 && jQuery.trim(nextNode.data).length > 0) {
+ // we are lucky and found a notempty text node
+ return nextNode;
}
- } else if (nextNode.nodeType === 3 && jQuery.trim(nextNode.data).length > 0) {
- // we are lucky and found a notempty text node
- return nextNode;
- } else if (stopat.blocklevel && this.isBlockLevelElement(nextNode)) {
- // we found a blocklevel element, stop here
- return false;
- } else if (stopat.linebreak && this.isLineBreakElement(nextNode)) {
- // we found a linebreak, stop here
- return false;
- } else if (stopat.list && this.isListElement(nextNode)) {
- // we found a linebreak, stop here
- return false;
- } else if (nextNode.nodeType === 3) {
- // we found an empty text node, so step to the next
- nextNode = searchleft ? nextNode.previousSibling : nextNode.nextSibling;
- } else {
- // we found a non-blocklevel element, step into
- currentParent = nextNode;
- nextNode = searchleft ? nextNode.lastChild : nextNode.firstChild;
+ if (stopat.blocklevel && this.isBlockLevelElement(nextNode)) {
+ // we found a blocklevel element, stop here
+ return false;
+ }
+ if (stopat.linebreak && this.isLineBreakElement(nextNode)) {
+ // we found a linebreak, stop here
+ return false;
+ }
+ if (stopat.list && this.isListElement(nextNode)) {
+ // we found a linebreak, stop here
+ return false;
+ }
+ if (nextNode.nodeType === 3) {
+ // we found an empty text node, so step to the next
+ nextNode = searchleft ? nextNode.previousSibling : nextNode.nextSibling;
+ } else {
+ // we found a non-blocklevel element, step into
+ currentParent = nextNode;
+ nextNode = searchleft ? nextNode.lastChild : nextNode.firstChild;
+ }
}
- }
- },
+ },
- /**
- * Insert the given DOM Object into the start/end of the given range. The method
- * will find the appropriate place in the DOM tree for inserting the given
- * object, and will eventually split elements in between. The given range will
- * be updated if necessary. The updated range will NOT embrace the inserted
- * object, which means that the object is actually inserted before or after the
- * given range (depending on the atEnd parameter)
- *
- * @param {jQuery}
- * object object to insert into the DOM
- * @param {GENTICS.Utils.RangeObject}
- * range range where to insert the object (at start or end)
- * @param {jQuery}
- * limit limiting object(s) of the DOM modification
- * @param {boolean}
- * atEnd true when the object shall be inserted at the end, false for
- * insertion at the start (default)
- * @param {boolean}
- * true when the insertion shall be done, even if inserting the element
- * would not be allowed, false to deny inserting unallowed elements (default)
- * @return true if the object could be inserted, false if not.
- * @method
- */
- insertIntoDOM: function (object, range, limit, atEnd, force) {
- // first find the appropriate place to insert the given object
- var parentElements = range.getContainerParents(limit, atEnd),
- that = this,
- newParent,
- container, offset, splitParts, contents;
+ /**
+ * Insert the given DOM Object into the start/end of the given range. The method
+ * will find the appropriate place in the DOM tree for inserting the given
+ * object, and will eventually split elements in between. The given range will
+ * be updated if necessary. The updated range will NOT embrace the inserted
+ * object, which means that the object is actually inserted before or after the
+ * given range (depending on the atEnd parameter)
+ *
+ * @param {jQuery}
+ * object object to insert into the DOM
+ * @param {GENTICS.Utils.RangeObject}
+ * range range where to insert the object (at start or end)
+ * @param {jQuery}
+ * limit limiting object(s) of the DOM modification
+ * @param {boolean}
+ * atEnd true when the object shall be inserted at the end, false for
+ * insertion at the start (default)
+ * @param {boolean}
+ * true when the insertion shall be done, even if inserting the element
+ * would not be allowed, false to deny inserting unallowed elements (default)
+ * @return true if the object could be inserted, false if not.
+ * @method
+ */
+ insertIntoDOM: function (object, range, limit, atEnd, force) {
+ // first find the appropriate place to insert the given object
+ var parentElements = range.getContainerParents(limit, atEnd),
+ that = this,
+ newParent,
+ container,
+ offset,
+ splitParts,
+ contents;
- if (!limit) {
- limit = jQuery(document.body);
- }
+ if (!limit) {
+ limit = jQuery(document.body);
+ }
- // if no parent elements exist (up to the limit), the new parent will be the
- // limiter itself
- if (parentElements.length === 0) {
- newParent = limit.get(0);
- } else {
- jQuery.each(parentElements, function (index, parent) {
- if (that.allowsNesting(parent, object.get(0))) {
- newParent = parent;
- return false;
- }
- });
- }
+ // if no parent elements exist (up to the limit), the new parent will be the
+ // limiter itself
+ if (parentElements.length === 0) {
+ newParent = limit.get(0);
+ } else {
+ jQuery.each(parentElements, function (index, parent) {
+ if (that.allowsNesting(parent, object.get(0))) {
+ newParent = parent;
+ return false;
+ }
+ });
+ }
- if (typeof newParent === 'undefined' && limit.length > 0) {
- // found no possible new parent, so split up to the limit object
- newParent = limit.get(0);
- }
+ if (typeof newParent === 'undefined' && limit.length > 0) {
+ // found no possible new parent, so split up to the limit object
+ newParent = limit.get(0);
+ }
- // check whether it is allowed to insert the element at all
- if (!this.allowsNesting(newParent, object.get(0)) && !force) {
- return false;
- }
+ // check whether it is allowed to insert the element at all
+ if (!this.allowsNesting(newParent, object.get(0)) && !force) {
+ return false;
+ }
- if (typeof newParent !== 'undefined') {
- // we found a possible new parent, so we split the DOM up to the new parent
- splitParts = this.split(range, jQuery(newParent), atEnd);
- if (splitParts === true) {
- // DOM was not split (there was no need to split it), insert the new object anyway
- container = range.startContainer;
- offset = range.startOffset;
- if (atEnd) {
- container = range.endContainer;
- offset = range.endOffset;
- }
- if (offset === 0) {
- // insert right before the first element in the container
- contents = jQuery(container).contents();
- if (contents.length > 0) {
- contents.eq(0).before(object);
- } else {
- jQuery(container).append(object);
+ if (typeof newParent !== 'undefined') {
+ // we found a possible new parent, so we split the DOM up to the new parent
+ splitParts = this.split(range, jQuery(newParent), atEnd);
+ if (splitParts === true) {
+ // DOM was not split (there was no need to split it), insert the new object anyway
+ container = range.startContainer;
+ offset = range.startOffset;
+ if (atEnd) {
+ container = range.endContainer;
+ offset = range.endOffset;
}
- return true;
- } else {
+ if (offset === 0) {
+ // insert right before the first element in the container
+ contents = jQuery(container).contents();
+ if (contents.length > 0) {
+ contents.eq(0).before(object);
+ } else {
+ jQuery(container).append(object);
+ }
+ return true;
+ }
// insert right after the element at offset-1
- jQuery(container).contents().eq(offset-1).after(object);
+ jQuery(container).contents().eq(offset - 1).after(object);
return true;
}
- } else if (splitParts) {
- // if the DOM could be split, we insert the new object in between the split parts
- splitParts.eq(0).after(object);
- return true;
- } else {
+ if (splitParts) {
+ // if the DOM could be split, we insert the new object in between the split parts
+ splitParts.eq(0).after(object);
+ return true;
+ }
// could not split, so could not insert
return false;
}
- } else {
// found no possible new parent, so we shall not insert
return false;
- }
- },
+ },
- /**
- * Remove the given DOM object from the DOM and modify the given range to reflect the user expected range after the object was removed
- * TODO: finish this
- * @param {DOMObject} object DOM object to remove
- * @param {GENTICS.Utils.RangeObject} range range which eventually be modified
- * @param {boolean} preserveContent true if the contents of the removed DOM object shall be preserved, false if not (default: false)
- * @return true if the DOM object could be removed, false if not
- * @hide
- */
- removeFromDOM: function (object, range, preserveContent) {
- if (preserveContent) {
- // check whether the range will need modification
- var indexInParent = this.getIndexInParent(object),
- numChildren = jQuery(object).contents().length,
- parent = object.parentNode;
+ /**
+ * Remove the given DOM object from the DOM and modify the given range to reflect the user expected range after the object was removed
+ * TODO: finish this
+ * @param {DOMObject} object DOM object to remove
+ * @param {GENTICS.Utils.RangeObject} range range which eventually be modified
+ * @param {boolean} preserveContent true if the contents of the removed DOM object shall be preserved, false if not (default: false)
+ * @return true if the DOM object could be removed, false if not
+ * @hide
+ */
+ removeFromDOM: function (object, range, preserveContent) {
+ if (preserveContent) {
+ // check whether the range will need modification
+ var indexInParent = this.getIndexInParent(object),
+ numChildren = jQuery(object).contents().length,
+ parent = object.parentNode;
- if (range.startContainer == parent && range.startOffset > indexInParent) {
- range.startOffset += numChildren - 1;
- } else if (range.startContainer == object) {
- range.startContainer = parent;
- range.startOffset = indexInParent + range.startOffset;
- }
+ if (range.startContainer == parent && range.startOffset > indexInParent) {
+ range.startOffset += numChildren - 1;
+ } else if (range.startContainer == object) {
+ range.startContainer = parent;
+ range.startOffset = indexInParent + range.startOffset;
+ }
- if (range.endContainer == parent && range.endOffset > indexInParent) {
- range.endOffset += numChildren - 1;
- } else if (range.endContainer == object) {
- range.endContainer = parent;
- range.endOffset = indexInParent + range.endOffset;
- }
+ if (range.endContainer == parent && range.endOffset > indexInParent) {
+ range.endOffset += numChildren - 1;
+ } else if (range.endContainer == object) {
+ range.endContainer = parent;
+ range.endOffset = indexInParent + range.endOffset;
+ }
- // we simply unwrap the children of the object
- jQuery(object).contents().unwrap();
+ // we simply unwrap the children of the object
+ jQuery(object).contents().unwrap();
- // optionally do cleanup
- this.doCleanup({'merge' : true}, range, parent);
- } else {
- // TODO
- }
- },
+ // optionally do cleanup
+ this.doCleanup({
+ 'merge': true
+ }, range, parent);
+ }
+ },
- /**
- * Remove the content defined by the given range from the DOM. Update the given
- * range object to be a collapsed selection at the place of the previous
- * selection.
- * @param rangeObject range object
- * @return true if the range could be removed, false if not
- */
- removeRange: function (rangeObject) {
- if (!rangeObject) {
- // no range given
- return false;
- }
- if (rangeObject.isCollapsed()) {
- // the range is collapsed, nothing to delete
- return false;
- }
+ /**
+ * Remove the content defined by the given range from the DOM. Update the given
+ * range object to be a collapsed selection at the place of the previous
+ * selection.
+ * @param rangeObject range object
+ * @return true if the range could be removed, false if not
+ */
+ removeRange: function (rangeObject) {
+ if (!rangeObject) {
+ // no range given
+ return false;
+ }
+ if (rangeObject.isCollapsed()) {
+ // the range is collapsed, nothing to delete
+ return false;
+ }
- // split partially contained text nodes at the start and end of the range
- if (rangeObject.startContainer.nodeType == 3 && rangeObject.startOffset > 0
- && rangeObject.startOffset < rangeObject.startContainer.data.length) {
- this.split(rangeObject, jQuery(rangeObject.startContainer).parent(),
- false);
- }
- if (rangeObject.endContainer.nodeType == 3 && rangeObject.endOffset > 0
- && rangeObject.endOffset < rangeObject.endContainer.data.length) {
- this.split(rangeObject, jQuery(rangeObject.endContainer).parent(),
- true);
- }
+ // split partially contained text nodes at the start and end of the range
+ if (rangeObject.startContainer.nodeType == 3
+ && rangeObject.startOffset > 0
+ && rangeObject.startOffset < rangeObject.startContainer.data.length) {
+ this.split(rangeObject, jQuery(rangeObject.startContainer).parent(), false);
+ }
+ if (rangeObject.endContainer.nodeType == 3 && rangeObject.endOffset > 0 && rangeObject.endOffset < rangeObject.endContainer.data.length) {
+ this.split(rangeObject, jQuery(rangeObject.endContainer).parent(), true);
+ }
- // construct the range tree
- var rangeTree = rangeObject.getRangeTree();
+ // construct the range tree
+ var rangeTree = rangeObject.getRangeTree();
- // collapse the range
- rangeObject.endContainer = rangeObject.startContainer;
- rangeObject.endOffset = rangeObject.startOffset;
+ // collapse the range
+ rangeObject.endContainer = rangeObject.startContainer;
+ rangeObject.endOffset = rangeObject.startOffset;
- // remove the markup from the range tree
- this.recursiveRemoveRange(rangeTree, rangeObject);
+ // remove the markup from the range tree
+ this.recursiveRemoveRange(rangeTree, rangeObject);
- // do some cleanup
- this.doCleanup({'merge' : true}, rangeObject);
-// this.doCleanup({'merge' : true, 'removeempty' : true}, rangeObject);
+ // do some cleanup
+ this.doCleanup({
+ 'merge': true
+ }, rangeObject);
+ // this.doCleanup({'merge' : true, 'removeempty' : true}, rangeObject);
- // clear the caches of the range object
- rangeObject.clearCaches();
- },
+ // clear the caches of the range object
+ rangeObject.clearCaches();
+ },
- recursiveRemoveRange: function (rangeTree, rangeObject) {
- // iterate over the rangetree objects of this level
- for (var i = 0; i < rangeTree.length; ++i) {
- // check for nodes fully in the range
- if (rangeTree[i].type == 'full') {
- // if the domobj is the startcontainer, or the startcontainer is inside the domobj, we need to update the rangeObject
- if (jQuery(rangeObject.startContainer).parents().andSelf().filter(rangeTree[i].domobj).length > 0) {
- rangeObject.startContainer = rangeObject.endContainer = rangeTree[i].domobj.parentNode;
- rangeObject.startOffset = rangeObject.endOffset = this.getIndexInParent(rangeTree[i].domobj);
- }
+ recursiveRemoveRange: function (rangeTree, rangeObject) {
+ // iterate over the rangetree objects of this level
+ var i;
+ for (i = 0; i < rangeTree.length; ++i) {
+ // check for nodes fully in the range
+ if (rangeTree[i].type == 'full') {
+ // if the domobj is the startcontainer, or the startcontainer is inside the domobj, we need to update the rangeObject
+ if (jQuery(rangeObject.startContainer).parents().andSelf().filter(rangeTree[i].domobj).length > 0) {
+ rangeObject.startContainer = rangeObject.endContainer = rangeTree[i].domobj.parentNode;
+ rangeObject.startOffset = rangeObject.endOffset = this.getIndexInParent(rangeTree[i].domobj);
+ }
- // remove the object from the DOM
- jQuery(rangeTree[i].domobj).remove();
- } else if (rangeTree[i].type == 'partial' && rangeTree[i].children) {
- // node partially selected and has children, so do recursion
- this.recursiveRemoveRange(rangeTree[i].children, rangeObject);
+ // remove the object from the DOM
+ jQuery(rangeTree[i].domobj).remove();
+ } else if (rangeTree[i].type == 'partial' && rangeTree[i].children) {
+ // node partially selected and has children, so do recursion
+ this.recursiveRemoveRange(rangeTree[i].children, rangeObject);
+ }
}
- }
- },
+ },
- /**
- * Extend the given range to have start and end at the nearest word boundaries to the left (start) and right (end)
- * @param {GENTICS.Utils.RangeObject} range range to be extended
- * @param {boolean} fromBoundaries true if extending will also be done, if one or both ends of the range already are at a word boundary, false if not, default: false
- * @method
- */
- extendToWord: function (range, fromBoundaries) {
- // search the word boundaries to the left and right
- var leftBoundary = this.searchWordBoundary(range.startContainer, range.startOffset, true),
- rightBoundary = this.searchWordBoundary(range.endContainer, range.endOffset, false);
+ /**
+ * Extend the given range to have start and end at the nearest word boundaries to the left (start) and right (end)
+ * @param {GENTICS.Utils.RangeObject} range range to be extended
+ * @param {boolean} fromBoundaries true if extending will also be done, if one or both ends of the range already are at a word boundary, false if not, default: false
+ * @method
+ */
+ extendToWord: function (range, fromBoundaries) {
+ // search the word boundaries to the left and right
+ var leftBoundary = this.searchWordBoundary(range.startContainer, range.startOffset, true),
+ rightBoundary = this.searchWordBoundary(range.endContainer, range.endOffset, false);
- // check whether we must not extend the range from word boundaries
- if (!fromBoundaries) {
- // we only extend the range if both ends would be different
- if (range.startContainer == leftBoundary.container && range.startOffset == leftBoundary.offset) {
- return;
+ // check whether we must not extend the range from word boundaries
+ if (!fromBoundaries) {
+ // we only extend the range if both ends would be different
+ if (range.startContainer == leftBoundary.container && range.startOffset == leftBoundary.offset) {
+ return;
+ }
+ if (range.endContainer == rightBoundary.container && range.endOffset == rightBoundary.offset) {
+ return;
+ }
}
- if (range.endContainer == rightBoundary.container && range.endOffset == rightBoundary.offset) {
- return;
- }
- }
- // set the new boundaries
- range.startContainer = leftBoundary.container;
- range.startOffset = leftBoundary.offset;
- range.endContainer = rightBoundary.container;
- range.endOffset = rightBoundary.offset;
+ // set the new boundaries
+ range.startContainer = leftBoundary.container;
+ range.startOffset = leftBoundary.offset;
+ range.endContainer = rightBoundary.container;
+ range.endOffset = rightBoundary.offset;
- // correct the range
- range.correctRange();
+ // correct the range
+ range.correctRange();
- // clear caches
- range.clearCaches();
- },
+ // clear caches
+ range.clearCaches();
+ },
- /**
- * Helper method to check whether the given DOM object is a word boundary.
- * @param {DOMObject} object DOM object in question
- * @return {boolean} true when the DOM object is a word boundary, false if not
- * @hide
- */
- isWordBoundaryElement: function (object) {
- if (!object || !object.nodeName) {
- return false;
- }
- return jQuery.inArray(object.nodeName.toLowerCase(), this.nonWordBoundaryTags) == -1;
- },
+ /**
+ * Helper method to check whether the given DOM object is a word boundary.
+ * @param {DOMObject} object DOM object in question
+ * @return {boolean} true when the DOM object is a word boundary, false if not
+ * @hide
+ */
+ isWordBoundaryElement: function (object) {
+ if (!object || !object.nodeName) {
+ return false;
+ }
+ return jQuery.inArray(object.nodeName.toLowerCase(), this.nonWordBoundaryTags) == -1;
+ },
- /**
- * Search for the next word boundary, starting at the given position
- * @param {DOMObject} container container of the start position
- * @param {Integer} offset offset of the start position
- * @param {boolean} searchleft true for searching to the left, false for searching to the right (default: true)
- * @return {object} object with properties 'container' and 'offset' marking the found word boundary
- * @method
- */
- searchWordBoundary: function (container, offset, searchleft) {
- if (typeof searchleft === 'undefined') {
- searchleft = true;
- }
- var boundaryFound = false, wordBoundaryPos, tempWordBoundaryPos, textNode;
- while (!boundaryFound) {
- // check the node type
- if (container.nodeType === 3) {
- // we are currently in a text node
+ /**
+ * Search for the next word boundary, starting at the given position
+ * @param {DOMObject} container container of the start position
+ * @param {Integer} offset offset of the start position
+ * @param {boolean} searchleft true for searching to the left, false for searching to the right (default: true)
+ * @return {object} object with properties 'container' and 'offset' marking the found word boundary
+ * @method
+ */
+ searchWordBoundary: function (container, offset, searchleft) {
+ if (typeof searchleft === 'undefined') {
+ searchleft = true;
+ }
+ var boundaryFound = false,
+ wordBoundaryPos,
+ tempWordBoundaryPos,
+ textNode;
+ while (!boundaryFound) {
+ // check the node type
+ if (container.nodeType === 3) {
+ // we are currently in a text node
- // find the nearest word boundary character
- if (!searchleft) {
- // search right
- wordBoundaryPos = container.data.substring(offset).search(this.nonWordRegex);
- if (wordBoundaryPos != -1) {
- // found a word boundary
- offset = offset + wordBoundaryPos;
- boundaryFound = true;
- } else {
- // found no word boundary, so we set the position after the container
- offset = this.getIndexInParent(container) + 1;
- container = container.parentNode;
- }
- } else {
- // search left
- wordBoundaryPos = container.data.substring(0, offset).search(this.nonWordRegex);
- tempWordBoundaryPos = wordBoundaryPos;
- while (tempWordBoundaryPos != -1) {
- wordBoundaryPos = tempWordBoundaryPos;
- tempWordBoundaryPos = container.data.substring(
- wordBoundaryPos + 1, offset).search(this.nonWordRegex);
- if (tempWordBoundaryPos != -1) {
- tempWordBoundaryPos = tempWordBoundaryPos + wordBoundaryPos + 1;
- }
- }
-
- if (wordBoundaryPos != -1) {
- // found a word boundary
- offset = wordBoundaryPos + 1;
- boundaryFound = true;
- } else {
- // found no word boundary, so we set the position before the container
- offset = this.getIndexInParent(container);
- container = container.parentNode;
- }
- }
- } else if (container.nodeType === 1) {
- // we are currently in an element node (between nodes)
-
- if (!searchleft) {
- // check whether there is an element to the right
- if (offset < container.childNodes.length) {
- // there is an element to the right, check whether it is a word boundary element
- if (this.isWordBoundaryElement(container.childNodes[offset])) {
- // we are done
+ // find the nearest word boundary character
+ if (!searchleft) {
+ // search right
+ wordBoundaryPos = container.data.substring(offset).search(this.nonWordRegex);
+ if (wordBoundaryPos != -1) {
+ // found a word boundary
+ offset = offset + wordBoundaryPos;
boundaryFound = true;
} else {
- // element to the right is no word boundary, so enter it
- container = container.childNodes[offset];
- offset = 0;
+ // found no word boundary, so we set the position after the container
+ offset = this.getIndexInParent(container) + 1;
+ container = container.parentNode;
}
} else {
- // no element to the right, check whether the element itself is a boundary element
- if (this.isWordBoundaryElement(container)) {
- // we are done
+ // search left
+ wordBoundaryPos = container.data.substring(0, offset).search(this.nonWordRegex);
+ tempWordBoundaryPos = wordBoundaryPos;
+ while (tempWordBoundaryPos != -1) {
+ wordBoundaryPos = tempWordBoundaryPos;
+ tempWordBoundaryPos = container.data.substring(wordBoundaryPos + 1, offset).search(this.nonWordRegex);
+ if (tempWordBoundaryPos != -1) {
+ tempWordBoundaryPos = tempWordBoundaryPos + wordBoundaryPos + 1;
+ }
+ }
+
+ if (wordBoundaryPos != -1) {
+ // found a word boundary
+ offset = wordBoundaryPos + 1;
boundaryFound = true;
} else {
- // element itself is no boundary element, so go to parent
- offset = this.getIndexInParent(container) + 1;
+ // found no word boundary, so we set the position before the container
+ offset = this.getIndexInParent(container);
container = container.parentNode;
}
}
- } else {
- // check whether there is an element to the left
- if (offset > 0) {
- // there is an element to the left, check whether it is a word boundary element
- if (this.isWordBoundaryElement(container.childNodes[offset - 1])) {
- // we are done
- boundaryFound = true;
+ } else if (container.nodeType === 1) {
+ // we are currently in an element node (between nodes)
+
+ if (!searchleft) {
+ // check whether there is an element to the right
+ if (offset < container.childNodes.length) {
+ // there is an element to the right, check whether it is a word boundary element
+ if (this.isWordBoundaryElement(container.childNodes[offset])) {
+ // we are done
+ boundaryFound = true;
+ } else {
+ // element to the right is no word boundary, so enter it
+ container = container.childNodes[offset];
+ offset = 0;
+ }
} else {
- // element to the left is no word boundary, so enter it
- container = container.childNodes[offset - 1];
- offset = container.nodeType === 3 ? container.data.length : container.childNodes.length;
+ // no element to the right, check whether the element itself is a boundary element
+ if (this.isWordBoundaryElement(container)) {
+ // we are done
+ boundaryFound = true;
+ } else {
+ // element itself is no boundary element, so go to parent
+ offset = this.getIndexInParent(container) + 1;
+ container = container.parentNode;
+ }
}
} else {
- // no element to the left, check whether the element itself is a boundary element
- if (this.isWordBoundaryElement(container)) {
- // we are done
- boundaryFound = true;
+ // check whether there is an element to the left
+ if (offset > 0) {
+ // there is an element to the left, check whether it is a word boundary element
+ if (this.isWordBoundaryElement(container.childNodes[offset - 1])) {
+ // we are done
+ boundaryFound = true;
+ } else {
+ // element to the left is no word boundary, so enter it
+ container = container.childNodes[offset - 1];
+ offset = container.nodeType === 3 ? container.data.length : container.childNodes.length;
+ }
} else {
- // element itself is no boundary element, so go to parent
- offset = this.getIndexInParent(container);
- container = container.parentNode;
+ // no element to the left, check whether the element itself is a boundary element
+ if (this.isWordBoundaryElement(container)) {
+ // we are done
+ boundaryFound = true;
+ } else {
+ // element itself is no boundary element, so go to parent
+ offset = this.getIndexInParent(container);
+ container = container.parentNode;
+ }
}
}
}
}
- }
- if (container.nodeType !== 3) {
- textNode = this.searchAdjacentTextNode(container, offset, !searchleft);
- if (textNode) {
- container = textNode;
- offset = searchleft ? 0 : container.data.length;
+ if (container.nodeType !== 3) {
+ textNode = this.searchAdjacentTextNode(container, offset, !searchleft);
+ if (textNode) {
+ container = textNode;
+ offset = searchleft ? 0 : container.data.length;
+ }
}
- }
- return {'container' : container, 'offset' : offset};
- },
+ return {
+ 'container': container,
+ 'offset': offset
+ };
+ },
- /**
- * Check whether the given dom object is empty
- * @param {DOMObject} domObject object to check
- * @return {boolean} true when the object is empty, false if not
- * @method
- */
- isEmpty: function (domObject) {
- // a non dom object is considered empty
- if (!domObject) {
+ /**
+ * Check whether the given dom object is empty
+ * @param {DOMObject} domObject object to check
+ * @return {boolean} true when the object is empty, false if not
+ * @method
+ */
+ isEmpty: function (domObject) {
+ // a non dom object is considered empty
+ if (!domObject) {
+ return true;
+ }
+
+ // some tags are considered to be non-empty
+ if (jQuery.inArray(domObject.nodeName.toLowerCase(), this.nonEmptyTags) != -1) {
+ return false;
+ }
+
+ // text nodes are not empty, if they contain non-whitespace characters
+ if (domObject.nodeType === 3) {
+ return domObject.data.search(/\S/) == -1;
+ }
+
+ // all other nodes are not empty if they contain at least one child which is not empty
+ var i, childNodes;
+ for (i = 0, childNodes = domObject.childNodes.length; i < childNodes; ++i) {
+ if (!this.isEmpty(domObject.childNodes[i])) {
+ return false;
+ }
+ }
+
+ // found no contents, so the element is empty
return true;
- }
+ },
- // some tags are considered to be non-empty
- if (jQuery.inArray(domObject.nodeName.toLowerCase(), this.nonEmptyTags) != -1) {
- return false;
- }
+ /**
+ * Set the cursor (collapsed selection) right after the given DOM object
+ * @param domObject DOM object
+ * @method
+ */
+ setCursorAfter: function (domObject) {
+ var newRange = new GENTICS.Utils.RangeObject(),
+ index = this.getIndexInParent(domObject),
+ targetNode,
+ offset;
- // text nodes are not empty, if they contain non-whitespace characters
- if (domObject.nodeType === 3) {
- return domObject.data.search(/\S/) == -1;
- }
+ // selection cannot be set between to TEXT_NODEs
+ // if domOject is a Text node set selection at last position in that node
+ if (domObject.nodeType == 3) {
+ targetNode = domObject;
+ offset = targetNode.nodeValue.length;
- // all other nodes are not empty if they contain at least one child which is not empty
- for (var i = 0, childNodes = domObject.childNodes.length; i < childNodes; ++i) {
- if (!this.isEmpty(domObject.childNodes[i])) {
- return false;
+ // if domOject is a Text node set selection at last position in that node
+ } else if (domObject.nextSibling && domObject.nextSibling.nodeType == 3) {
+ targetNode = domObject.nextSibling;
+ offset = 0;
+ } else {
+ targetNode = domObject.parentNode;
+ offset = this.getIndexInParent(domObject) + 1;
}
- }
- // found no contents, so the element is empty
- return true;
- },
+ newRange.startContainer = newRange.endContainer = targetNode;
+ newRange.startOffset = newRange.endOffset = offset;
- /**
- * Set the cursor (collapsed selection) right after the given DOM object
- * @param domObject DOM object
- * @method
- */
- setCursorAfter: function (domObject) {
- var
- newRange = new GENTICS.Utils.RangeObject(),
- index = this.getIndexInParent(domObject),
- targetNode,
- offset;
-
- // selection cannot be set between to TEXT_NODEs
- // if domOject is a Text node set selection at last position in that node
- if ( domObject.nodeType == 3) {
- targetNode = domObject;
- offset = targetNode.nodeValue.length;
+ // select the range
+ newRange.select();
- // if domOject is a Text node set selection at last position in that node
- } else if ( domObject.nextSibling && domObject.nextSibling.nodeType == 3) {
- targetNode = domObject.nextSibling;
- offset = 0;
- } else {
- targetNode = domObject.parentNode;
- offset = this.getIndexInParent(domObject) + 1;
- }
-
- newRange.startContainer = newRange.endContainer = targetNode;
- newRange.startOffset = newRange.endOffset = offset;
+ return newRange;
+ },
- // select the range
- newRange.select();
-
- return newRange;
- },
-
- /**
- * Select a DOM node
- * will create a new range which spans the provided dom node and selects it afterwards
- * @param domObject DOM object
- * @method
- */
- selectDomNode: function (domObject) {
- var newRange = new GENTICS.Utils.RangeObject();
- newRange.startContainer = newRange.endContainer = domObject.parentNode;
- newRange.startOffset = this.getIndexInParent(domObject);
- newRange.endOffset = newRange.startOffset + 1;
- newRange.select();
- },
+ /**
+ * Select a DOM node
+ * will create a new range which spans the provided dom node and selects it afterwards
+ * @param domObject DOM object
+ * @method
+ */
+ selectDomNode: function (domObject) {
+ var newRange = new GENTICS.Utils.RangeObject();
+ newRange.startContainer = newRange.endContainer = domObject.parentNode;
+ newRange.startOffset = this.getIndexInParent(domObject);
+ newRange.endOffset = newRange.startOffset + 1;
+ newRange.select();
+ },
- /**
- * Set the cursor (collapsed selection) at the start into the given DOM object
- * @param domObject DOM object
- * @method
- */
- setCursorInto: function (domObject) {
- // set a new range into the given dom object
- var newRange = new GENTICS.Utils.RangeObject();
- newRange.startContainer = newRange.endContainer = domObject;
- newRange.startOffset = newRange.endOffset = 0;
+ /**
+ * Set the cursor (collapsed selection) at the start into the given DOM object
+ * @param domObject DOM object
+ * @method
+ */
+ setCursorInto: function (domObject) {
+ // set a new range into the given dom object
+ var newRange = new GENTICS.Utils.RangeObject();
+ newRange.startContainer = newRange.endContainer = domObject;
+ newRange.startOffset = newRange.endOffset = 0;
- // select the range
- newRange.select();
- },
-
+ // select the range
+ newRange.select();
+ },
- /**
- * "An editing host is a node that is either an Element with a contenteditable
- * attribute set to the true state, or the Element child of a Document whose
- * designMode is enabled."
- * @param domObject DOM object
- * @method
- */
- isEditingHost: function (node) {
- return node
- && node.nodeType == 1 //ELEMENT_NODE
- && (node.contentEditable == "true"
- || (node.parentNode
- && node.parentNode.nodeType == 9 //DOCUEMENT_NODE
- && node.parentNode.designMode == "on"));
- },
- /**
- * "Something is editable if it is a node which is not an editing host, does
- * not have a contenteditable attribute set to the false state, and whose
- * parent is an editing host or editable."
- * @param domObject DOM object
- * @method
- */
- isEditable: function (node) {
- // This is slightly a lie, because we're excluding non-HTML elements with
- // contentEditable attributes.
- return node
- && !this.isEditingHost(node)
- && (node.nodeType != 1 || node.contentEditable != "false") // ELEMENT_NODE
- && (this.isEditingHost(node.parentNode) || this.isEditable(node.parentNode));
- },
+ /**
+ * "An editing host is a node that is either an Element with a contenteditable
+ * attribute set to the true state, or the Element child of a Document whose
+ * designMode is enabled."
+ * @param domObject DOM object
+ * @method
+ */
+ isEditingHost: function (node) {
+ return node
+ && node.nodeType == 1 //ELEMENT_NODE
+ && (node.contentEditable == "true" || (node.parentNode && node.parentNode.nodeType == 9 //DOCUEMENT_NODE
+ && node.parentNode.designMode == "on"));
+ },
- /**
- * "The editing host of node is null if node is neither editable nor an editing
- * host; node itself, if node is an editing host; or the nearest ancestor of
- * node that is an editing host, if node is editable."
- * @param domObject DOM object
- * @method
- */
- getEditingHostOf: function(node) {
- if (this.isEditingHost(node)) {
- return node;
- } else if (this.isEditable(node)) {
- var ancestor = node.parentNode;
- while (!this.isEditingHost(ancestor)) {
- ancestor = ancestor.parentNode;
+ /**
+ * "Something is editable if it is a node which is not an editing host, does
+ * not have a contenteditable attribute set to the false state, and whose
+ * parent is an editing host or editable."
+ * @param domObject DOM object
+ * @method
+ */
+ isEditable: function (node) {
+ // This is slightly a lie, because we're excluding non-HTML elements with
+ // contentEditable attributes.
+ return node
+ && !this.isEditingHost(node)
+ && (node.nodeType != 1 || node.contentEditable != "false") // ELEMENT_NODE
+ && (this.isEditingHost(node.parentNode) || this.isEditable(node.parentNode));
+ },
+
+ /**
+ * "The editing host of node is null if node is neither editable nor an editing
+ * host; node itself, if node is an editing host; or the nearest ancestor of
+ * node that is an editing host, if node is editable."
+ * @param domObject DOM object
+ * @method
+ */
+ getEditingHostOf: function (node) {
+ if (this.isEditingHost(node)) {
+ return node;
}
- return ancestor;
- } else {
+ if (this.isEditable(node)) {
+ var ancestor = node.parentNode;
+ while (!this.isEditingHost(ancestor)) {
+ ancestor = ancestor.parentNode;
+ }
+ return ancestor;
+ }
return null;
- }
- },
+ },
- /**
- *
- * "Two nodes are in the same editing host if the editing host of the first is
- * non-null and the same as the editing host of the second."
- * @param node1 DOM object
- * @param node2 DOM object
- * @method
- */
- inSameEditingHost: function (node1, node2) {
- return this.getEditingHostOf(node1)
- && this.getEditingHostOf(node1) == this.getEditingHostOf(node2);
- },
+ /**
+ *
+ * "Two nodes are in the same editing host if the editing host of the first is
+ * non-null and the same as the editing host of the second."
+ * @param node1 DOM object
+ * @param node2 DOM object
+ * @method
+ */
+ inSameEditingHost: function (node1, node2) {
+ return this.getEditingHostOf(node1) && this.getEditingHostOf(node1) == this.getEditingHostOf(node2);
+ },
- // "A block node is either an Element whose "display" property does not have
- // resolved value "inline" or "inline-block" or "inline-table" or "none", or a
- // Document, or a DocumentFragment."
- isBlockNode: function (node) {
- return node
- && ((node.nodeType == $_.Node.ELEMENT_NODE && $_( ["inline", "inline-block", "inline-table", "none"] ).indexOf($_.getComputedStyle(node).display) == -1)
- || node.nodeType == $_.Node.DOCUMENT_NODE
- || node.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE);
- },
+ // "A block node is either an Element whose "display" property does not have
+ // resolved value "inline" or "inline-block" or "inline-table" or "none", or a
+ // Document, or a DocumentFragment."
+ isBlockNode: function (node) {
+ return node && ((node.nodeType == $_.Node.ELEMENT_NODE && $_(["inline", "inline-block", "inline-table", "none"]).indexOf($_.getComputedStyle(node).display) == -1) || node.nodeType == $_.Node.DOCUMENT_NODE || node.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE);
+ },
- /**
- * Get the first visible child of the given node.
- * @param node node
- * @param includeNode when set to true, the node itself may be returned, otherwise only children are allowed
- * @return first visible child or null if none found
- */
- getFirstVisibleChild: function (node, includeNode) {
- // no node -> no child
- if (!node) {
- return null;
- }
+ /**
+ * Get the first visible child of the given node.
+ * @param node node
+ * @param includeNode when set to true, the node itself may be returned, otherwise only children are allowed
+ * @return first visible child or null if none found
+ */
+ getFirstVisibleChild: function (node, includeNode) {
+ // no node -> no child
+ if (!node) {
+ return null;
+ }
- // check whether the node itself is visible
- if ((node.nodeType == $_.Node.TEXT_NODE && this.isEmpty(node))
- || (node.nodeType == $_.Node.ELEMENT_NODE && node.offsetHeight == 0 && jQuery.inArray(node.nodeName.toLowerCase(), this.nonEmptyTags) === -1)) {
- return null;
- }
+ // check whether the node itself is visible
+ if ((node.nodeType == $_.Node.TEXT_NODE && this.isEmpty(node)) || (node.nodeType == $_.Node.ELEMENT_NODE && node.offsetHeight == 0 && jQuery.inArray(node.nodeName.toLowerCase(), this.nonEmptyTags) === -1)) {
+ return null;
+ }
- // if the node is a text node, or does not have children, or is not editable, it is the first visible child
- if (node.nodeType == $_.Node.TEXT_NODE
- || (node.nodeType == $_.Node.ELEMENT_NODE && node.childNodes.length == 0)
- || !jQuery(node).contentEditable()) {
- return includeNode ? node : null;
- }
+ // if the node is a text node, or does not have children, or is not editable, it is the first visible child
+ if (node.nodeType == $_.Node.TEXT_NODE || (node.nodeType == $_.Node.ELEMENT_NODE && node.childNodes.length == 0) || !jQuery(node).contentEditable()) {
+ return includeNode ? node : null;
+ }
- // otherwise traverse through the children
- for (var i = 0; i < node.childNodes.length; ++i) {
- var visibleChild = this.getFirstVisibleChild(node.childNodes[i], true);
- if (visibleChild != null) {
- return visibleChild;
+ // otherwise traverse through the children
+ var i;
+ for (i = 0; i < node.childNodes.length; ++i) {
+ var visibleChild = this.getFirstVisibleChild(node.childNodes[i], true);
+ if (visibleChild != null) {
+ return visibleChild;
+ }
}
- }
- return null;
- },
-
- /**
- * Get the last visible child of the given node.
- * @param node node
- * @param includeNode when set to true, the node itself may be returned, otherwise only children are allowed
- * @return last visible child or null if none found
- */
- getLastVisibleChild: function (node, includeNode) {
- // no node -> no child
- if (!node) {
return null;
- }
+ },
- // check whether the node itself is visible
- if ((node.nodeType == $_.Node.TEXT_NODE && this.isEmpty(node))
- || (node.nodeType == $_.Node.ELEMENT_NODE && node.offsetHeight == 0 && jQuery.inArray(node.nodeName.toLowerCase(), this.nonEmptyTags) === -1)) {
- return null;
- }
+ /**
+ * Get the last visible child of the given node.
+ * @param node node
+ * @param includeNode when set to true, the node itself may be returned, otherwise only children are allowed
+ * @return last visible child or null if none found
+ */
+ getLastVisibleChild: function (node, includeNode) {
+ // no node -> no child
+ if (!node) {
+ return null;
+ }
- // if the node is a text node, or does not have children, or is not editable, it is the first visible child
- if (node.nodeType == $_.Node.TEXT_NODE
- || (node.nodeType == $_.Node.ELEMENT_NODE && node.childNodes.length == 0)
- || !jQuery(node).contentEditable()) {
- return includeNode ? node : null;
- }
+ // check whether the node itself is visible
+ if ((node.nodeType == $_.Node.TEXT_NODE && this.isEmpty(node)) || (node.nodeType == $_.Node.ELEMENT_NODE && node.offsetHeight == 0 && jQuery.inArray(node.nodeName.toLowerCase(), this.nonEmptyTags) === -1)) {
+ return null;
+ }
- // otherwise traverse through the children
- for (var i = node.childNodes.length - 1; i >= 0; --i) {
- var visibleChild = this.getLastVisibleChild(node.childNodes[i], true);
- if (visibleChild != null) {
- return visibleChild;
+ // if the node is a text node, or does not have children, or is not editable, it is the first visible child
+ if (node.nodeType == $_.Node.TEXT_NODE || (node.nodeType == $_.Node.ELEMENT_NODE && node.childNodes.length == 0) || !jQuery(node).contentEditable()) {
+ return includeNode ? node : null;
}
- }
- return null;
- }
-});
+ // otherwise traverse through the children
+ var i;
+ for (i = node.childNodes.length - 1; i >= 0; --i) {
+ var visibleChild = this.getLastVisibleChild(node.childNodes[i], true);
+ if (visibleChild != null) {
+ return visibleChild;
+ }
+ }
+ return null;
+ }
+ });
-/**
- * Create the singleton object
- * @hide
- */
-GENTICS.Utils.Dom = new Dom();
-return GENTICS.Utils.Dom;
+ /**
+ * Create the singleton object
+ * @hide
+ */
+ GENTICS.Utils.Dom = new Dom();
+
+ return GENTICS.Utils.Dom;
});