src/pyams_skin/resources/js/ext/tinymce/dev/plugins/noneditable/plugin.js
changeset 69 a361355b55c7
equal deleted inserted replaced
68:fd8fb93e1b6a 69:a361355b55c7
       
     1 /**
       
     2  * plugin.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 /*jshint loopfunc:true */
       
    12 /*eslint no-loop-func:0 */
       
    13 /*global tinymce:true */
       
    14 
       
    15 tinymce.PluginManager.add('noneditable', function(editor) {
       
    16 	var TreeWalker = tinymce.dom.TreeWalker;
       
    17 	var externalName = 'contenteditable', internalName = 'data-mce-' + externalName;
       
    18 	var VK = tinymce.util.VK;
       
    19 
       
    20 	// Returns the content editable state of a node "true/false" or null
       
    21 	function getContentEditable(node) {
       
    22 		var contentEditable;
       
    23 
       
    24 		// Ignore non elements
       
    25 		if (node.nodeType === 1) {
       
    26 			// Check for fake content editable
       
    27 			contentEditable = node.getAttribute(internalName);
       
    28 			if (contentEditable && contentEditable !== "inherit") {
       
    29 				return contentEditable;
       
    30 			}
       
    31 
       
    32 			// Check for real content editable
       
    33 			contentEditable = node.contentEditable;
       
    34 			if (contentEditable !== "inherit") {
       
    35 				return contentEditable;
       
    36 			}
       
    37 		}
       
    38 
       
    39 		return null;
       
    40 	}
       
    41 
       
    42 	// Returns the noneditable parent or null if there is a editable before it or if it wasn't found
       
    43 	function getNonEditableParent(node) {
       
    44 		var state;
       
    45 
       
    46 		while (node) {
       
    47 			state = getContentEditable(node);
       
    48 			if (state) {
       
    49 				return state === "false" ? node : null;
       
    50 			}
       
    51 
       
    52 			node = node.parentNode;
       
    53 		}
       
    54 	}
       
    55 
       
    56 	function handleContentEditableSelection() {
       
    57 		var dom = editor.dom, selection = editor.selection, caretContainerId = 'mce_noneditablecaret', invisibleChar = '\uFEFF';
       
    58 
       
    59 		// Get caret container parent for the specified node
       
    60 		function getParentCaretContainer(node) {
       
    61 			while (node) {
       
    62 				if (node.id === caretContainerId) {
       
    63 					return node;
       
    64 				}
       
    65 
       
    66 				node = node.parentNode;
       
    67 			}
       
    68 		}
       
    69 
       
    70 		// Finds the first text node in the specified node
       
    71 		function findFirstTextNode(node) {
       
    72 			var walker;
       
    73 
       
    74 			if (node) {
       
    75 				walker = new TreeWalker(node, node);
       
    76 
       
    77 				for (node = walker.current(); node; node = walker.next()) {
       
    78 					if (node.nodeType === 3) {
       
    79 						return node;
       
    80 					}
       
    81 				}
       
    82 			}
       
    83 		}
       
    84 
       
    85 		// Insert caret container before/after target or expand selection to include block
       
    86 		function insertCaretContainerOrExpandToBlock(target, before) {
       
    87 			var caretContainer, rng;
       
    88 
       
    89 			// Select block
       
    90 			if (getContentEditable(target) === "false") {
       
    91 				if (dom.isBlock(target)) {
       
    92 					selection.select(target);
       
    93 					return;
       
    94 				}
       
    95 			}
       
    96 
       
    97 			rng = dom.createRng();
       
    98 
       
    99 			if (getContentEditable(target) === "true") {
       
   100 				if (!target.firstChild) {
       
   101 					target.appendChild(editor.getDoc().createTextNode('\u00a0'));
       
   102 				}
       
   103 
       
   104 				target = target.firstChild;
       
   105 				before = true;
       
   106 			}
       
   107 
       
   108 			/*
       
   109 			caretContainer = dom.create('span', {
       
   110 				id: caretContainerId,
       
   111 				'data-mce-bogus': true,
       
   112 				style:'border: 1px solid red'
       
   113 			}, invisibleChar);
       
   114 			*/
       
   115 
       
   116 			caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true}, invisibleChar);
       
   117 
       
   118 			if (before) {
       
   119 				target.parentNode.insertBefore(caretContainer, target);
       
   120 			} else {
       
   121 				dom.insertAfter(caretContainer, target);
       
   122 			}
       
   123 
       
   124 			rng.setStart(caretContainer.firstChild, 1);
       
   125 			rng.collapse(true);
       
   126 			selection.setRng(rng);
       
   127 
       
   128 			return caretContainer;
       
   129 		}
       
   130 
       
   131 		// Removes any caret container
       
   132 		function removeCaretContainer(caretContainer) {
       
   133 			var rng, child, lastContainer;
       
   134 
       
   135 			if (caretContainer) {
       
   136 				rng = selection.getRng(true);
       
   137 				rng.setStartBefore(caretContainer);
       
   138 				rng.setEndBefore(caretContainer);
       
   139 
       
   140 				child = findFirstTextNode(caretContainer);
       
   141 				if (child && child.nodeValue.charAt(0) == invisibleChar) {
       
   142 					child = child.deleteData(0, 1);
       
   143 				}
       
   144 
       
   145 				dom.remove(caretContainer, true);
       
   146 
       
   147 				selection.setRng(rng);
       
   148 			} else {
       
   149 				while ((caretContainer = dom.get(caretContainerId)) && caretContainer !== lastContainer) {
       
   150 					child = findFirstTextNode(caretContainer);
       
   151 					if (child && child.nodeValue.charAt(0) == invisibleChar) {
       
   152 						child = child.deleteData(0, 1);
       
   153 					}
       
   154 
       
   155 					dom.remove(caretContainer, true);
       
   156 
       
   157 					lastContainer = caretContainer;
       
   158 				}
       
   159 			}
       
   160 		}
       
   161 
       
   162 		// Modifies the selection to include contentEditable false elements or insert caret containers
       
   163 		function moveSelection() {
       
   164 			var nonEditableStart, nonEditableEnd, isCollapsed, rng, element;
       
   165 
       
   166 			// Checks if there is any contents to the left/right side of caret returns the noneditable element or
       
   167 			// any editable element if it finds one inside
       
   168 			function hasSideContent(element, left) {
       
   169 				var container, offset, walker, node, len;
       
   170 
       
   171 				container = rng.startContainer;
       
   172 				offset = rng.startOffset;
       
   173 
       
   174 				// If endpoint is in middle of text node then expand to beginning/end of element
       
   175 				if (container.nodeType == 3) {
       
   176 					len = container.nodeValue.length;
       
   177 					if ((offset > 0 && offset < len) || (left ? offset == len : offset === 0)) {
       
   178 						return;
       
   179 					}
       
   180 				} else {
       
   181 					// Can we resolve the node by index
       
   182 					if (offset < container.childNodes.length) {
       
   183 						// Browser represents caret position as the offset at the start of an element. When moving right
       
   184 						// this is the element we are moving into so we consider our container to be child node at offset-1
       
   185 						var pos = !left && offset > 0 ? offset - 1 : offset;
       
   186 						container = container.childNodes[pos];
       
   187 						if (container.hasChildNodes()) {
       
   188 							container = container.firstChild;
       
   189 						}
       
   190 					} else {
       
   191 						// If not then the caret is at the last position in it's container and the caret container
       
   192 						// should be inserted after the noneditable element
       
   193 						return !left ? element : null;
       
   194 					}
       
   195 				}
       
   196 
       
   197 				// Walk left/right to look for contents
       
   198 				walker = new TreeWalker(container, element);
       
   199 				while ((node = walker[left ? 'prev' : 'next']())) {
       
   200 					if (node.nodeType === 3 && node.nodeValue.length > 0) {
       
   201 						return;
       
   202 					} else if (getContentEditable(node) === "true") {
       
   203 						// Found contentEditable=true element return this one to we can move the caret inside it
       
   204 						return node;
       
   205 					}
       
   206 				}
       
   207 
       
   208 				return element;
       
   209 			}
       
   210 
       
   211 			// Remove any existing caret containers
       
   212 			removeCaretContainer();
       
   213 
       
   214 			// Get noneditable start/end elements
       
   215 			isCollapsed = selection.isCollapsed();
       
   216 			nonEditableStart = getNonEditableParent(selection.getStart());
       
   217 			nonEditableEnd = getNonEditableParent(selection.getEnd());
       
   218 
       
   219 			// Is any fo the range endpoints noneditable
       
   220 			if (nonEditableStart || nonEditableEnd) {
       
   221 				rng = selection.getRng(true);
       
   222 
       
   223 				// If it's a caret selection then look left/right to see if we need to move the caret out side or expand
       
   224 				if (isCollapsed) {
       
   225 					nonEditableStart = nonEditableStart || nonEditableEnd;
       
   226 
       
   227 					if ((element = hasSideContent(nonEditableStart, true))) {
       
   228 						// We have no contents to the left of the caret then insert a caret container before the noneditable element
       
   229 						insertCaretContainerOrExpandToBlock(element, true);
       
   230 					} else if ((element = hasSideContent(nonEditableStart, false))) {
       
   231 						// We have no contents to the right of the caret then insert a caret container after the noneditable element
       
   232 						insertCaretContainerOrExpandToBlock(element, false);
       
   233 					} else {
       
   234 						// We are in the middle of a noneditable so expand to select it
       
   235 						selection.select(nonEditableStart);
       
   236 					}
       
   237 				} else {
       
   238 					rng = selection.getRng(true);
       
   239 
       
   240 					// Expand selection to include start non editable element
       
   241 					if (nonEditableStart) {
       
   242 						rng.setStartBefore(nonEditableStart);
       
   243 					}
       
   244 
       
   245 					// Expand selection to include end non editable element
       
   246 					if (nonEditableEnd) {
       
   247 						rng.setEndAfter(nonEditableEnd);
       
   248 					}
       
   249 
       
   250 					selection.setRng(rng);
       
   251 				}
       
   252 			}
       
   253 		}
       
   254 
       
   255 		function handleKey(e) {
       
   256 			var keyCode = e.keyCode, nonEditableParent, caretContainer, startElement, endElement;
       
   257 
       
   258 			function getNonEmptyTextNodeSibling(node, prev) {
       
   259 				while ((node = node[prev ? 'previousSibling' : 'nextSibling'])) {
       
   260 					if (node.nodeType !== 3 || node.nodeValue.length > 0) {
       
   261 						return node;
       
   262 					}
       
   263 				}
       
   264 			}
       
   265 
       
   266 			function positionCaretOnElement(element, start) {
       
   267 				selection.select(element);
       
   268 				selection.collapse(start);
       
   269 			}
       
   270 
       
   271 			function canDelete(backspace) {
       
   272 				var rng, container, offset, nonEditableParent;
       
   273 
       
   274 				function removeNodeIfNotParent(node) {
       
   275 					var parent = container;
       
   276 
       
   277 					while (parent) {
       
   278 						if (parent === node) {
       
   279 							return;
       
   280 						}
       
   281 
       
   282 						parent = parent.parentNode;
       
   283 					}
       
   284 
       
   285 					dom.remove(node);
       
   286 					moveSelection();
       
   287 				}
       
   288 
       
   289 				function isNextPrevTreeNodeNonEditable() {
       
   290 					var node, walker, nonEmptyElements = editor.schema.getNonEmptyElements();
       
   291 
       
   292 					walker = new tinymce.dom.TreeWalker(container, editor.getBody());
       
   293 					while ((node = (backspace ? walker.prev() : walker.next()))) {
       
   294 						// Found IMG/INPUT etc
       
   295 						if (nonEmptyElements[node.nodeName.toLowerCase()]) {
       
   296 							break;
       
   297 						}
       
   298 
       
   299 						// Found text node with contents
       
   300 						if (node.nodeType === 3 && tinymce.trim(node.nodeValue).length > 0) {
       
   301 							break;
       
   302 						}
       
   303 
       
   304 						// Found non editable node
       
   305 						if (getContentEditable(node) === "false") {
       
   306 							removeNodeIfNotParent(node);
       
   307 							return true;
       
   308 						}
       
   309 					}
       
   310 
       
   311 					// Check if the content node is within a non editable parent
       
   312 					if (getNonEditableParent(node)) {
       
   313 						return true;
       
   314 					}
       
   315 
       
   316 					return false;
       
   317 				}
       
   318 
       
   319 				if (selection.isCollapsed()) {
       
   320 					rng = selection.getRng(true);
       
   321 					container = rng.startContainer;
       
   322 					offset = rng.startOffset;
       
   323 					container = getParentCaretContainer(container) || container;
       
   324 
       
   325 					// Is in noneditable parent
       
   326 					if ((nonEditableParent = getNonEditableParent(container))) {
       
   327 						removeNodeIfNotParent(nonEditableParent);
       
   328 						return false;
       
   329 					}
       
   330 
       
   331 					// Check if the caret is in the middle of a text node
       
   332 					if (container.nodeType == 3 && (backspace ? offset > 0 : offset < container.nodeValue.length)) {
       
   333 						return true;
       
   334 					}
       
   335 
       
   336 					// Resolve container index
       
   337 					if (container.nodeType == 1) {
       
   338 						container = container.childNodes[offset] || container;
       
   339 					}
       
   340 
       
   341 					// Check if previous or next tree node is non editable then block the event
       
   342 					if (isNextPrevTreeNodeNonEditable()) {
       
   343 						return false;
       
   344 					}
       
   345 				}
       
   346 
       
   347 				return true;
       
   348 			}
       
   349 
       
   350 			moveSelection();
       
   351 
       
   352 			startElement = selection.getStart();
       
   353 			endElement = selection.getEnd();
       
   354 
       
   355 			// Disable all key presses in contentEditable=false except delete or backspace
       
   356 			nonEditableParent = getNonEditableParent(startElement) || getNonEditableParent(endElement);
       
   357 			var currentNode = editor.selection.getNode();
       
   358 
       
   359 			var isDirectionKey = keyCode == VK.LEFT || keyCode == VK.RIGHT || keyCode == VK.UP || keyCode == VK.DOWN;
       
   360 			var left = keyCode == VK.LEFT || keyCode == VK.UP;
       
   361 
       
   362 			if (nonEditableParent && (keyCode < 112 || keyCode > 124) && keyCode != VK.DELETE && keyCode != VK.BACKSPACE) {
       
   363 
       
   364 				// Is Ctrl+c, Ctrl+v or Ctrl+x then use default browser behavior
       
   365 				if ((tinymce.isMac ? e.metaKey : e.ctrlKey) && (keyCode == 67 || keyCode == 88 || keyCode == 86)) {
       
   366 					return;
       
   367 				}
       
   368 
       
   369 				e.preventDefault();
       
   370 
       
   371 				// Arrow left/right select the element and collapse left/right
       
   372 				if (isDirectionKey) {
       
   373 
       
   374 					// If a block element find previous or next element to position the caret
       
   375 					if (editor.dom.isBlock(nonEditableParent)) {
       
   376 						var targetElement = left ? nonEditableParent.previousSibling : nonEditableParent.nextSibling;
       
   377 
       
   378 						// Handling for edge-cases:
       
   379 						// 	- two nonEditables in a row -> no way to get between them
       
   380 						// 	- nonEditable as the first/last element -> no way to get before/behind it
       
   381 						if (!targetElement || targetElement && getContentEditable(targetElement) === 'false') {
       
   382 							var p = dom.create('p', null, '&nbsp;');
       
   383 							p.className = 'mceTmpParagraph';
       
   384 
       
   385 							var insertElement = left ? nonEditableParent : targetElement;
       
   386 
       
   387 							if (insertElement && insertElement.parentNode) {
       
   388 								insertElement.parentNode.insertBefore(p, insertElement);
       
   389 							} else if (!targetElement && !left) {
       
   390 								nonEditableParent.parentNode.appendChild(p);
       
   391 							}
       
   392 
       
   393 							targetElement = p;
       
   394 						}
       
   395 
       
   396 						var walker = new TreeWalker(targetElement, targetElement);
       
   397 						var caretElement = left ? walker.prev() : walker.next();
       
   398 
       
   399 						positionCaretOnElement(caretElement, !left);
       
   400 					} else {
       
   401 						positionCaretOnElement(nonEditableParent, left);
       
   402 					}
       
   403 				}
       
   404 			} else {
       
   405 				// Is arrow left/right, backspace or delete
       
   406 				if (isDirectionKey || keyCode == VK.BACKSPACE || keyCode == VK.DELETE) {
       
   407 					caretContainer = getParentCaretContainer(startElement);
       
   408 
       
   409 					if (caretContainer) {
       
   410 						// Arrow left or backspace
       
   411 						if (keyCode == VK.LEFT || keyCode == VK.BACKSPACE) {
       
   412 							nonEditableParent = getNonEmptyTextNodeSibling(caretContainer, true);
       
   413 
       
   414 							if (nonEditableParent && getContentEditable(nonEditableParent) === "false") {
       
   415 								e.preventDefault();
       
   416 
       
   417 								if (keyCode == VK.LEFT) {
       
   418 									positionCaretOnElement(nonEditableParent, true);
       
   419 								} else {
       
   420 									dom.remove(nonEditableParent);
       
   421 									return;
       
   422 								}
       
   423 							} else {
       
   424 								removeCaretContainer(caretContainer);
       
   425 							}
       
   426 						}
       
   427 
       
   428 						// Arrow right or delete
       
   429 						if (keyCode == VK.RIGHT || keyCode == VK.DELETE) {
       
   430 							nonEditableParent = getNonEmptyTextNodeSibling(caretContainer, true);
       
   431 
       
   432 							if (nonEditableParent && getContentEditable(nonEditableParent) === "false") {
       
   433 								e.preventDefault();
       
   434 
       
   435 								if (keyCode == VK.RIGHT) {
       
   436 									positionCaretOnElement(nonEditableParent, false);
       
   437 								} else {
       
   438 									dom.remove(nonEditableParent);
       
   439 									return;
       
   440 								}
       
   441 							} else {
       
   442 								removeCaretContainer(caretContainer);
       
   443 							}
       
   444 						}
       
   445 					} else {
       
   446 
       
   447 						if (isDirectionKey) {
       
   448 							// Removal of separator paragraphs between two nonEditables
       
   449 							// and before/after a nonEditable as the first/last element
       
   450 							if (currentNode && currentNode.className.indexOf('mceTmpParagraph') !== -1 &&
       
   451 									currentNode[left ? 'previousSibling' : 'nextSibling']) {
       
   452 								var jumpTarget = currentNode[left ? 'previousSibling' : 'nextSibling'];
       
   453 
       
   454 								// current node is still empty and a separator -> remove it
       
   455 								// else: remove the separator class, as it now includes content
       
   456 								if (currentNode.innerHTML === '&nbsp;' || currentNode.innerHTML === '' || currentNode.innerHTML === ' ') {
       
   457 									dom.remove(currentNode);
       
   458 								} else {
       
   459 									currentNode.className = currentNode.className.replace('mceTmpParagraph', '');
       
   460 								}
       
   461 
       
   462 								positionCaretOnElement(jumpTarget, !left);
       
   463 							}
       
   464 						}
       
   465 
       
   466 						var rng = selection.getRng(true);
       
   467 						var container = rng.endContainer;
       
   468 
       
   469 						// FIX: If end of node is selected, check wether next sibling is nonEditable to correctly remove it
       
   470 						// 			(else would break for more complex nonEditables, their content would get moved to the current node)
       
   471 						if (dom.isBlock(container) && dom.isBlock(container.nextSibling) && rng.endOffset == 1 && keyCode == VK.DELETE) {
       
   472 							nonEditableParent = getNonEditableParent(container.nextSibling);
       
   473 						}
       
   474 
       
   475 						// correctly remove block-level nonEditable domNode on delete/backspace
       
   476 						if (nonEditableParent && (keyCode == VK.DELETE || keyCode == VK.BACKSPACE) && dom.isBlock(nonEditableParent)) {
       
   477 							e.preventDefault();
       
   478 							dom.remove(nonEditableParent);
       
   479 							return;
       
   480 						}
       
   481 					}
       
   482 
       
   483 					if ((keyCode == VK.BACKSPACE || keyCode == VK.DELETE) && !canDelete(keyCode == VK.BACKSPACE)) {
       
   484 						e.preventDefault();
       
   485 						return false;
       
   486 					}
       
   487 				}
       
   488 			}
       
   489 		}
       
   490 
       
   491 		editor.on('mousedown', function(e) {
       
   492 			var node = editor.selection.getNode();
       
   493 
       
   494 			// Also remove separator lines when clicking on another node
       
   495 			if (node && node.className.indexOf('mceTmpParagraph') !== -1 && node !== e.target) {
       
   496 				// current node is still empty and a separator -> remove it
       
   497 				// else: remove the separator class, as it now includes content
       
   498 				if (node.innerHTML === '&nbsp;' || node.innerHTML === '' || node.innerHTML === ' ') {
       
   499 					dom.remove(node);
       
   500 				} else {
       
   501 					node.className = node.className.replace('mceTmpParagraph', '');
       
   502 				}
       
   503 			}
       
   504 
       
   505 			if (getContentEditable(node) === "false" && node == e.target) {
       
   506 				// Expand selection on mouse down we can't block the default event since it's used for drag/drop
       
   507 				moveSelection();
       
   508 			}
       
   509 		});
       
   510 
       
   511 		editor.on('mouseup', moveSelection);
       
   512 
       
   513 		editor.on('keydown', handleKey);
       
   514 	}
       
   515 
       
   516 	var editClass, nonEditClass, nonEditableRegExps;
       
   517 
       
   518 	// Converts configured regexps to noneditable span items
       
   519 	function convertRegExpsToNonEditable(e) {
       
   520 		var i = nonEditableRegExps.length, content = e.content, cls = tinymce.trim(nonEditClass);
       
   521 
       
   522 		// Don't replace the variables when raw is used for example on undo/redo
       
   523 		if (e.format == "raw") {
       
   524 			return;
       
   525 		}
       
   526 
       
   527 		while (i--) {
       
   528 			content = content.replace(nonEditableRegExps[i], function(match) {
       
   529 				var args = arguments, index = args[args.length - 2];
       
   530 
       
   531 				// Is value inside an attribute then don't replace
       
   532 				if (index > 0 && content.charAt(index - 1) == '"') {
       
   533 					return match;
       
   534 				}
       
   535 
       
   536 				return (
       
   537 					'<span class="' + cls + '" data-mce-content="' + editor.dom.encode(args[0]) + '">' +
       
   538 					editor.dom.encode(typeof args[1] === "string" ? args[1] : args[0]) + '</span>'
       
   539 				);
       
   540 			});
       
   541 		}
       
   542 
       
   543 		e.content = content;
       
   544 	}
       
   545 
       
   546 	editClass = " " + tinymce.trim(editor.getParam("noneditable_editable_class", "mceEditable")) + " ";
       
   547 	nonEditClass = " " + tinymce.trim(editor.getParam("noneditable_noneditable_class", "mceNonEditable")) + " ";
       
   548 
       
   549 	// Setup noneditable regexps array
       
   550 	nonEditableRegExps = editor.getParam("noneditable_regexp");
       
   551 	if (nonEditableRegExps && !nonEditableRegExps.length) {
       
   552 		nonEditableRegExps = [nonEditableRegExps];
       
   553 	}
       
   554 
       
   555 	editor.on('PreInit', function() {
       
   556 		handleContentEditableSelection();
       
   557 
       
   558 		if (nonEditableRegExps) {
       
   559 			editor.on('BeforeSetContent', convertRegExpsToNonEditable);
       
   560 		}
       
   561 
       
   562 		// Apply contentEditable true/false on elements with the noneditable/editable classes
       
   563 		editor.parser.addAttributeFilter('class', function(nodes) {
       
   564 			var i = nodes.length, className, node;
       
   565 
       
   566 			while (i--) {
       
   567 				node = nodes[i];
       
   568 				className = " " + node.attr("class") + " ";
       
   569 
       
   570 				if (className.indexOf(editClass) !== -1) {
       
   571 					node.attr(internalName, "true");
       
   572 				} else if (className.indexOf(nonEditClass) !== -1) {
       
   573 					node.attr(internalName, "false");
       
   574 				}
       
   575 			}
       
   576 		});
       
   577 
       
   578 		// Remove internal name
       
   579 		editor.serializer.addAttributeFilter(internalName, function(nodes) {
       
   580 			var i = nodes.length, node;
       
   581 
       
   582 			while (i--) {
       
   583 				node = nodes[i];
       
   584 
       
   585 				if (nonEditableRegExps && node.attr('data-mce-content')) {
       
   586 					node.name = "#text";
       
   587 					node.type = 3;
       
   588 					node.raw = true;
       
   589 					node.value = node.attr('data-mce-content');
       
   590 				} else {
       
   591 					node.attr(externalName, null);
       
   592 					node.attr(internalName, null);
       
   593 				}
       
   594 			}
       
   595 		});
       
   596 
       
   597 		// Convert external name into internal name
       
   598 		editor.parser.addAttributeFilter(externalName, function(nodes) {
       
   599 			var i = nodes.length, node;
       
   600 
       
   601 			while (i--) {
       
   602 				node = nodes[i];
       
   603 				node.attr(internalName, node.attr(externalName));
       
   604 				node.attr(externalName, null);
       
   605 			}
       
   606 		});
       
   607 	});
       
   608 
       
   609 	editor.on('drop', function(e) {
       
   610 		if (getNonEditableParent(e.target)) {
       
   611 			e.preventDefault();
       
   612 		}
       
   613 	});
       
   614 });