src/pyams_skin/resources/js/ext/tinymce/dev/classes/dom/BookmarkManager.js
changeset 557 bca7a7e058a3
equal deleted inserted replaced
-1:000000000000 557:bca7a7e058a3
       
     1 /**
       
     2  * BookmarkManager.js
       
     3  *
       
     4  * Copyright, Moxiecode Systems AB
       
     5  * Released under LGPL License.
       
     6  *
       
     7  * License: http://www.tinymce.com/license
       
     8  * Contributing: http://www.tinymce.com/contributing
       
     9  */
       
    10 
       
    11 /**
       
    12  * This class handles selection bookmarks.
       
    13  *
       
    14  * @class tinymce.dom.BookmarkManager
       
    15  */
       
    16 define("tinymce/dom/BookmarkManager", [
       
    17 	"tinymce/Env",
       
    18 	"tinymce/util/Tools"
       
    19 ], function(Env, Tools) {
       
    20 	/**
       
    21 	 * Constructs a new BookmarkManager instance for a specific selection instance.
       
    22 	 *
       
    23 	 * @constructor
       
    24 	 * @method BookmarkManager
       
    25 	 * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
       
    26 	 */
       
    27 	function BookmarkManager(selection) {
       
    28 		var dom = selection.dom;
       
    29 
       
    30 		/**
       
    31 		 * Returns a bookmark location for the current selection. This bookmark object
       
    32 		 * can then be used to restore the selection after some content modification to the document.
       
    33 		 *
       
    34 		 * @method getBookmark
       
    35 		 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
       
    36 		 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
       
    37 		 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
       
    38 		 * @example
       
    39 		 * // Stores a bookmark of the current selection
       
    40 		 * var bm = tinymce.activeEditor.selection.getBookmark();
       
    41 		 *
       
    42 		 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
       
    43 		 *
       
    44 		 * // Restore the selection bookmark
       
    45 		 * tinymce.activeEditor.selection.moveToBookmark(bm);
       
    46 		 */
       
    47 		this.getBookmark = function(type, normalized) {
       
    48 			var rng, rng2, id, collapsed, name, element, chr = '', styles;
       
    49 
       
    50 			function findIndex(name, element) {
       
    51 				var index = 0;
       
    52 
       
    53 				Tools.each(dom.select(name), function(node, i) {
       
    54 					if (node == element) {
       
    55 						index = i;
       
    56 					}
       
    57 				});
       
    58 
       
    59 				return index;
       
    60 			}
       
    61 
       
    62 			function normalizeTableCellSelection(rng) {
       
    63 				function moveEndPoint(start) {
       
    64 					var container, offset, childNodes, prefix = start ? 'start' : 'end';
       
    65 
       
    66 					container = rng[prefix + 'Container'];
       
    67 					offset = rng[prefix + 'Offset'];
       
    68 
       
    69 					if (container.nodeType == 1 && container.nodeName == "TR") {
       
    70 						childNodes = container.childNodes;
       
    71 						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
       
    72 						if (container) {
       
    73 							offset = start ? 0 : container.childNodes.length;
       
    74 							rng['set' + (start ? 'Start' : 'End')](container, offset);
       
    75 						}
       
    76 					}
       
    77 				}
       
    78 
       
    79 				moveEndPoint(true);
       
    80 				moveEndPoint();
       
    81 
       
    82 				return rng;
       
    83 			}
       
    84 
       
    85 			function getLocation() {
       
    86 				var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {};
       
    87 
       
    88 				function getPoint(rng, start) {
       
    89 					var container = rng[start ? 'startContainer' : 'endContainer'],
       
    90 						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
       
    91 
       
    92 					if (container.nodeType == 3) {
       
    93 						if (normalized) {
       
    94 							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
       
    95 								offset += node.nodeValue.length;
       
    96 							}
       
    97 						}
       
    98 
       
    99 						point.push(offset);
       
   100 					} else {
       
   101 						childNodes = container.childNodes;
       
   102 
       
   103 						if (offset >= childNodes.length && childNodes.length) {
       
   104 							after = 1;
       
   105 							offset = Math.max(0, childNodes.length - 1);
       
   106 						}
       
   107 
       
   108 						point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
       
   109 					}
       
   110 
       
   111 					for (; container && container != root; container = container.parentNode) {
       
   112 						point.push(dom.nodeIndex(container, normalized));
       
   113 					}
       
   114 
       
   115 					return point;
       
   116 				}
       
   117 
       
   118 				bookmark.start = getPoint(rng, true);
       
   119 
       
   120 				if (!selection.isCollapsed()) {
       
   121 					bookmark.end = getPoint(rng);
       
   122 				}
       
   123 
       
   124 				return bookmark;
       
   125 			}
       
   126 
       
   127 			if (type == 2) {
       
   128 				element = selection.getNode();
       
   129 				name = element ? element.nodeName : null;
       
   130 
       
   131 				if (name == 'IMG') {
       
   132 					return {name: name, index: findIndex(name, element)};
       
   133 				}
       
   134 
       
   135 				if (selection.tridentSel) {
       
   136 					return selection.tridentSel.getBookmark(type);
       
   137 				}
       
   138 
       
   139 				return getLocation();
       
   140 			}
       
   141 
       
   142 			// Handle simple range
       
   143 			if (type) {
       
   144 				return {rng: selection.getRng()};
       
   145 			}
       
   146 
       
   147 			rng = selection.getRng();
       
   148 			id = dom.uniqueId();
       
   149 			collapsed = selection.isCollapsed();
       
   150 			styles = 'overflow:hidden;line-height:0px';
       
   151 
       
   152 			// Explorer method
       
   153 			if (rng.duplicate || rng.item) {
       
   154 				// Text selection
       
   155 				if (!rng.item) {
       
   156 					rng2 = rng.duplicate();
       
   157 
       
   158 					try {
       
   159 						// Insert start marker
       
   160 						rng.collapse();
       
   161 						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
       
   162 
       
   163 						// Insert end marker
       
   164 						if (!collapsed) {
       
   165 							rng2.collapse(false);
       
   166 
       
   167 							// Detect the empty space after block elements in IE and move the
       
   168 							// end back one character <p></p>] becomes <p>]</p>
       
   169 							rng.moveToElementText(rng2.parentElement());
       
   170 							if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
       
   171 								rng2.move('character', -1);
       
   172 							}
       
   173 
       
   174 							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
       
   175 						}
       
   176 					} catch (ex) {
       
   177 						// IE might throw unspecified error so lets ignore it
       
   178 						return null;
       
   179 					}
       
   180 				} else {
       
   181 					// Control selection
       
   182 					element = rng.item(0);
       
   183 					name = element.nodeName;
       
   184 
       
   185 					return {name: name, index: findIndex(name, element)};
       
   186 				}
       
   187 			} else {
       
   188 				element = selection.getNode();
       
   189 				name = element.nodeName;
       
   190 				if (name == 'IMG') {
       
   191 					return {name: name, index: findIndex(name, element)};
       
   192 				}
       
   193 
       
   194 				// W3C method
       
   195 				rng2 = normalizeTableCellSelection(rng.cloneRange());
       
   196 
       
   197 				// Insert end marker
       
   198 				if (!collapsed) {
       
   199 					rng2.collapse(false);
       
   200 					rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
       
   201 				}
       
   202 
       
   203 				rng = normalizeTableCellSelection(rng);
       
   204 				rng.collapse(true);
       
   205 				rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
       
   206 			}
       
   207 
       
   208 			selection.moveToBookmark({id: id, keep: 1});
       
   209 
       
   210 			return {id: id};
       
   211 		};
       
   212 
       
   213 		/**
       
   214 		 * Restores the selection to the specified bookmark.
       
   215 		 *
       
   216 		 * @method moveToBookmark
       
   217 		 * @param {Object} bookmark Bookmark to restore selection from.
       
   218 		 * @return {Boolean} true/false if it was successful or not.
       
   219 		 * @example
       
   220 		 * // Stores a bookmark of the current selection
       
   221 		 * var bm = tinymce.activeEditor.selection.getBookmark();
       
   222 		 *
       
   223 		 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
       
   224 		 *
       
   225 		 * // Restore the selection bookmark
       
   226 		 * tinymce.activeEditor.selection.moveToBookmark(bm);
       
   227 		 */
       
   228 		this.moveToBookmark = function(bookmark) {
       
   229 			var rng, root, startContainer, endContainer, startOffset, endOffset;
       
   230 
       
   231 			function setEndPoint(start) {
       
   232 				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
       
   233 
       
   234 				if (point) {
       
   235 					offset = point[0];
       
   236 
       
   237 					// Find container node
       
   238 					for (node = root, i = point.length - 1; i >= 1; i--) {
       
   239 						children = node.childNodes;
       
   240 
       
   241 						if (point[i] > children.length - 1) {
       
   242 							return;
       
   243 						}
       
   244 
       
   245 						node = children[point[i]];
       
   246 					}
       
   247 
       
   248 					// Move text offset to best suitable location
       
   249 					if (node.nodeType === 3) {
       
   250 						offset = Math.min(point[0], node.nodeValue.length);
       
   251 					}
       
   252 
       
   253 					// Move element offset to best suitable location
       
   254 					if (node.nodeType === 1) {
       
   255 						offset = Math.min(point[0], node.childNodes.length);
       
   256 					}
       
   257 
       
   258 					// Set offset within container node
       
   259 					if (start) {
       
   260 						rng.setStart(node, offset);
       
   261 					} else {
       
   262 						rng.setEnd(node, offset);
       
   263 					}
       
   264 				}
       
   265 
       
   266 				return true;
       
   267 			}
       
   268 
       
   269 			function restoreEndPoint(suffix) {
       
   270 				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
       
   271 
       
   272 				if (marker) {
       
   273 					node = marker.parentNode;
       
   274 
       
   275 					if (suffix == 'start') {
       
   276 						if (!keep) {
       
   277 							idx = dom.nodeIndex(marker);
       
   278 						} else {
       
   279 							node = marker.firstChild;
       
   280 							idx = 1;
       
   281 						}
       
   282 
       
   283 						startContainer = endContainer = node;
       
   284 						startOffset = endOffset = idx;
       
   285 					} else {
       
   286 						if (!keep) {
       
   287 							idx = dom.nodeIndex(marker);
       
   288 						} else {
       
   289 							node = marker.firstChild;
       
   290 							idx = 1;
       
   291 						}
       
   292 
       
   293 						endContainer = node;
       
   294 						endOffset = idx;
       
   295 					}
       
   296 
       
   297 					if (!keep) {
       
   298 						prev = marker.previousSibling;
       
   299 						next = marker.nextSibling;
       
   300 
       
   301 						// Remove all marker text nodes
       
   302 						Tools.each(Tools.grep(marker.childNodes), function(node) {
       
   303 							if (node.nodeType == 3) {
       
   304 								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
       
   305 							}
       
   306 						});
       
   307 
       
   308 						// Remove marker but keep children if for example contents where inserted into the marker
       
   309 						// Also remove duplicated instances of the marker for example by a
       
   310 						// split operation or by WebKit auto split on paste feature
       
   311 						while ((marker = dom.get(bookmark.id + '_' + suffix))) {
       
   312 							dom.remove(marker, 1);
       
   313 						}
       
   314 
       
   315 						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
       
   316 						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market
       
   317 						// isn't worth the effort. Sorry, Opera but it's just a fact
       
   318 						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
       
   319 							idx = prev.nodeValue.length;
       
   320 							prev.appendData(next.nodeValue);
       
   321 							dom.remove(next);
       
   322 
       
   323 							if (suffix == 'start') {
       
   324 								startContainer = endContainer = prev;
       
   325 								startOffset = endOffset = idx;
       
   326 							} else {
       
   327 								endContainer = prev;
       
   328 								endOffset = idx;
       
   329 							}
       
   330 						}
       
   331 					}
       
   332 				}
       
   333 			}
       
   334 
       
   335 			function addBogus(node) {
       
   336 				// Adds a bogus BR element for empty block elements
       
   337 				if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
       
   338 					node.innerHTML = '<br data-mce-bogus="1" />';
       
   339 				}
       
   340 
       
   341 				return node;
       
   342 			}
       
   343 
       
   344 			if (bookmark) {
       
   345 				if (bookmark.start) {
       
   346 					rng = dom.createRng();
       
   347 					root = dom.getRoot();
       
   348 
       
   349 					if (selection.tridentSel) {
       
   350 						return selection.tridentSel.moveToBookmark(bookmark);
       
   351 					}
       
   352 
       
   353 					if (setEndPoint(true) && setEndPoint()) {
       
   354 						selection.setRng(rng);
       
   355 					}
       
   356 				} else if (bookmark.id) {
       
   357 					// Restore start/end points
       
   358 					restoreEndPoint('start');
       
   359 					restoreEndPoint('end');
       
   360 
       
   361 					if (startContainer) {
       
   362 						rng = dom.createRng();
       
   363 						rng.setStart(addBogus(startContainer), startOffset);
       
   364 						rng.setEnd(addBogus(endContainer), endOffset);
       
   365 						selection.setRng(rng);
       
   366 					}
       
   367 				} else if (bookmark.name) {
       
   368 					selection.select(dom.select(bookmark.name)[bookmark.index]);
       
   369 				} else if (bookmark.rng) {
       
   370 					selection.setRng(bookmark.rng);
       
   371 				}
       
   372 			}
       
   373 		};
       
   374 	}
       
   375 
       
   376 	/**
       
   377 	 * Returns true/false if the specified node is a bookmark node or not.
       
   378 	 *
       
   379 	 * @static
       
   380 	 * @method isBookmarkNode
       
   381 	 * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
       
   382 	 * @return {Boolean} true/false if the node is a bookmark node or not.
       
   383 	 */
       
   384 	BookmarkManager.isBookmarkNode = function(node) {
       
   385 		return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
       
   386 	};
       
   387 
       
   388 	return BookmarkManager;
       
   389 });