]> git.mxchange.org Git - friendica.git/blobdiff - library/tinymce/jscripts/tiny_mce/plugins/noneditable/editor_plugin_src.js
update tinymce to 3.5b2 to fix issues with FF 11 and pasting into code blocks
[friendica.git] / library / tinymce / jscripts / tiny_mce / plugins / noneditable / editor_plugin_src.js
old mode 100755 (executable)
new mode 100644 (file)
index 656c971..c87d241
  */\r
 \r
 (function() {\r
-       var Event = tinymce.dom.Event;\r
+       var TreeWalker = tinymce.dom.TreeWalker;\r
+       var externalName = 'contenteditable', internalName = 'data-mce-' + externalName;\r
+       var VK = tinymce.VK;\r
+\r
+       function handleContentEditableSelection(ed) {\r
+               var dom = ed.dom, selection = ed.selection, invisibleChar, caretContainerId = 'mce_noneditablecaret';\r
+\r
+               // Setup invisible character use zero width space on Gecko since it doesn't change the height of the container\r
+               invisibleChar = tinymce.isGecko ? '\u200B' : '\uFEFF';\r
+\r
+               // Returns the content editable state of a node "true/false" or null\r
+               function getContentEditable(node) {\r
+                       var contentEditable;\r
+\r
+                       // Ignore non elements\r
+                       if (node.nodeType === 1) {\r
+                               // Check for fake content editable\r
+                               contentEditable = node.getAttribute(internalName);\r
+                               if (contentEditable && contentEditable !== "inherit") {\r
+                                       return contentEditable;\r
+                               }\r
+\r
+                               // Check for real content editable\r
+                               contentEditable = node.contentEditable;\r
+                               if (contentEditable !== "inherit") {\r
+                                       return contentEditable;\r
+                               }\r
+                       }\r
+\r
+                       return null;\r
+               };\r
+\r
+               // Returns the noneditable parent or null if there is a editable before it or if it wasn't found\r
+               function getNonEditableParent(node) {\r
+                       var state;\r
+\r
+                       while (node) {\r
+                               state = getContentEditable(node);\r
+                               if (state) {\r
+                                       return state  === "false" ? node : null;\r
+                               }\r
+\r
+                               node = node.parentNode;\r
+                       }\r
+               };\r
+\r
+               // Get caret container parent for the specified node\r
+               function getParentCaretContainer(node) {\r
+                       while (node) {\r
+                               if (node.id === caretContainerId) {\r
+                                       return node;\r
+                               }\r
+\r
+                               node = node.parentNode;\r
+                       }\r
+               };\r
+\r
+               // Finds the first text node in the specified node\r
+               function findFirstTextNode(node) {\r
+                       var walker;\r
+\r
+                       if (node) {\r
+                               walker = new TreeWalker(node, node);\r
+\r
+                               for (node = walker.current(); node; node = walker.next()) {\r
+                                       if (node.nodeType === 3) {\r
+                                               return node;\r
+                                       }\r
+                               }\r
+                       }\r
+               };\r
+\r
+               // Insert caret container before/after target or expand selection to include block\r
+               function insertCaretContainerOrExpandToBlock(target, before) {\r
+                       var caretContainer, rng;\r
+\r
+                       // Select block\r
+                       if (getContentEditable(target) === "false") {\r
+                               if (dom.isBlock(target)) {\r
+                                       selection.select(target);\r
+                                       return;\r
+                               }\r
+                       }\r
+\r
+                       rng = dom.createRng();\r
+\r
+                       if (getContentEditable(target) === "true") {\r
+                               if (!target.firstChild) {\r
+                                       target.appendChild(ed.getDoc().createTextNode('\u00a0'));\r
+                               }\r
+\r
+                               target = target.firstChild;\r
+                               before = true;\r
+                       }\r
+\r
+                       //caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style:'border: 1px solid red'}, invisibleChar);\r
+                       caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true}, invisibleChar);\r
+\r
+                       if (before) {\r
+                               target.parentNode.insertBefore(caretContainer, target);\r
+                       } else {\r
+                               dom.insertAfter(caretContainer, target);\r
+                       }\r
+\r
+                       rng.setStart(caretContainer.firstChild, 1);\r
+                       rng.collapse(true);\r
+                       selection.setRng(rng);\r
+\r
+                       return caretContainer;\r
+               };\r
+\r
+               // Removes any caret container except the one we might be in\r
+               function removeCaretContainer(caretContainer) {\r
+                       var child, currentCaretContainer, lastContainer;\r
+\r
+                       if (caretContainer) {\r
+                                       rng = selection.getRng(true);\r
+                                       rng.setStartBefore(caretContainer);\r
+                                       rng.setEndBefore(caretContainer);\r
+\r
+                                       child = findFirstTextNode(caretContainer);\r
+                                       if (child && child.nodeValue.charAt(0) == invisibleChar) {\r
+                                               child = child.deleteData(0, 1);\r
+                                       }\r
+\r
+                                       dom.remove(caretContainer, true);\r
+\r
+                                       selection.setRng(rng);\r
+                       } else {\r
+                               currentCaretContainer = getParentCaretContainer(selection.getStart());\r
+                               while ((caretContainer = dom.get(caretContainerId)) && caretContainer !== lastContainer) {\r
+                                       if (currentCaretContainer !== caretContainer) {\r
+                                               child = findFirstTextNode(caretContainer);\r
+                                               if (child && child.nodeValue.charAt(0) == invisibleChar) {\r
+                                                       child = child.deleteData(0, 1);\r
+                                               }\r
+\r
+                                               dom.remove(caretContainer, true);\r
+                                       }\r
+\r
+                                       lastContainer = caretContainer;\r
+                               }\r
+                       }\r
+               };\r
+\r
+               // Modifies the selection to include contentEditable false elements or insert caret containers\r
+               function moveSelection() {\r
+                       var nonEditableStart, nonEditableEnd, isCollapsed, rng, element;\r
+\r
+                       // Checks if there is any contents to the left/right side of caret returns the noneditable element or any editable element if it finds one inside\r
+                       function hasSideContent(element, left) {\r
+                               var container, offset, walker, node, len;\r
+\r
+                               container = rng.startContainer;\r
+                               offset = rng.startOffset;\r
+\r
+                               // If endpoint is in middle of text node then expand to beginning/end of element\r
+                               if (container.nodeType == 3) {\r
+                                       len = container.nodeValue.length;\r
+                                       if ((offset > 0 && offset < len) || (left ? offset == len : offset == 0)) {\r
+                                               return;\r
+                                       }\r
+                               } else {\r
+                                       // Can we resolve the node by index\r
+                                       if (offset < container.childNodes.length) {\r
+                                               // Browser represents caret position as the offset at the start of an element. When moving right\r
+                                               // this is the element we are moving into so we consider our container to be child node at offset-1\r
+                                               var pos = !left && offset > 0 ? offset-1 : offset;\r
+                                               container = container.childNodes[pos];\r
+                                               if (container.hasChildNodes()) {\r
+                                                       container = container.firstChild;\r
+                                               }\r
+                                       } else {\r
+                                               // If not then the caret is at the last position in it's container and the caret container should be inserted after the noneditable element\r
+                                               return !left ? element : null;\r
+                                       }\r
+                               }\r
+\r
+                               // Walk left/right to look for contents\r
+                               walker = new TreeWalker(container, element);\r
+                               while (node = walker[left ? 'prev' : 'next']()) {\r
+                                       if (node.nodeType === 3 && node.nodeValue.length > 0) {\r
+                                               return;\r
+                                       } else if (getContentEditable(node) === "true") {\r
+                                               // Found contentEditable=true element return this one to we can move the caret inside it\r
+                                               return node;\r
+                                       }\r
+                               }\r
+\r
+                               return element;\r
+                       };\r
+\r
+                       // Remove any existing caret containers\r
+                       removeCaretContainer();\r
+\r
+                       // Get noneditable start/end elements\r
+                       isCollapsed = selection.isCollapsed();\r
+                       nonEditableStart = getNonEditableParent(selection.getStart());\r
+                       nonEditableEnd = getNonEditableParent(selection.getEnd());\r
+\r
+                       // Is any fo the range endpoints noneditable\r
+                       if (nonEditableStart || nonEditableEnd) {\r
+                               rng = selection.getRng(true);\r
+\r
+                               // If it's a caret selection then look left/right to see if we need to move the caret out side or expand\r
+                               if (isCollapsed) {\r
+                                       nonEditableStart = nonEditableStart || nonEditableEnd;\r
+                                       var start = selection.getStart();\r
+                                       if (element = hasSideContent(nonEditableStart, true)) {\r
+                                               // We have no contents to the left of the caret then insert a caret container before the noneditable element\r
+                                               insertCaretContainerOrExpandToBlock(element, true);\r
+                                       } else if (element = hasSideContent(nonEditableStart, false)) {\r
+                                               // We have no contents to the right of the caret then insert a caret container after the noneditable element\r
+                                               insertCaretContainerOrExpandToBlock(element, false);\r
+                                       } else {\r
+                                               // We are in the middle of a noneditable so expand to select it\r
+                                               selection.select(nonEditableStart);\r
+                                       }\r
+                               } else {\r
+                                       rng = selection.getRng(true);\r
+\r
+                                       // Expand selection to include start non editable element\r
+                                       if (nonEditableStart) {\r
+                                               rng.setStartBefore(nonEditableStart);\r
+                                       }\r
+\r
+                                       // Expand selection to include end non editable element\r
+                                       if (nonEditableEnd) {\r
+                                               rng.setEndAfter(nonEditableEnd);\r
+                                       }\r
+\r
+                                       selection.setRng(rng);\r
+                               }\r
+                       }\r
+               };\r
+\r
+               function handleKey(ed, e) {\r
+                       var keyCode = e.keyCode, nonEditableParent, caretContainer, startElement, endElement;\r
+\r
+                       function getNonEmptyTextNodeSibling(node, prev) {\r
+                               while (node = node[prev ? 'previousSibling' : 'nextSibling']) {\r
+                                       if (node.nodeType !== 3 || node.nodeValue.length > 0) {\r
+                                               return node;\r
+                                       }\r
+                               }\r
+                       };\r
+\r
+                       function positionCaretOnElement(element, start) {\r
+                               selection.select(element);\r
+                               selection.collapse(start);\r
+                       }\r
+\r
+                       startElement = selection.getStart()\r
+                       endElement = selection.getEnd();\r
+\r
+                       // Disable all key presses in contentEditable=false except delete or backspace\r
+                       nonEditableParent = getNonEditableParent(startElement) || getNonEditableParent(endElement);\r
+                       if (nonEditableParent && (keyCode < 112 || keyCode > 124) && keyCode != VK.DELETE && keyCode != VK.BACKSPACE) {\r
+                               e.preventDefault();\r
+\r
+                               // Arrow left/right select the element and collapse left/right\r
+                               if (keyCode == VK.LEFT || keyCode == VK.RIGHT) {\r
+                                       var left = keyCode == VK.LEFT;\r
+                                       // If a block element find previous or next element to position the caret\r
+                                       if (ed.dom.isBlock(nonEditableParent)) {\r
+                                               var targetElement = left ? nonEditableParent.previousSibling : nonEditableParent.nextSibling;\r
+                                               var walker = new TreeWalker(targetElement, targetElement);\r
+                                               var caretElement = left ? walker.prev() : walker.next();\r
+                                               positionCaretOnElement(caretElement, !left);\r
+                                       } else {\r
+                                               positionCaretOnElement(nonEditableParent, left);\r
+                                       }\r
+                               }\r
+                       } else {\r
+                               // Is arrow left/right, backspace or delete\r
+                               if (keyCode == VK.LEFT || keyCode == VK.RIGHT || keyCode == VK.BACKSPACE || keyCode == VK.DELETE) {\r
+                                       caretContainer = getParentCaretContainer(startElement);\r
+                                       if (caretContainer) {\r
+                                               // Arrow left or backspace\r
+                                               if (keyCode == VK.LEFT || keyCode == VK.BACKSPACE) {\r
+                                                       nonEditableParent = getNonEmptyTextNodeSibling(caretContainer, true);\r
+\r
+                                                       if (nonEditableParent && getContentEditable(nonEditableParent) === "false") {\r
+                                                               e.preventDefault();\r
+\r
+                                                               if (keyCode == VK.LEFT) {\r
+                                                                       positionCaretOnElement(nonEditableParent, true);\r
+                                                               } else {\r
+                                                                       dom.remove(nonEditableParent);\r
+                                                               }\r
+                                                       } else {\r
+                                                               removeCaretContainer(caretContainer);\r
+                                                       }\r
+                                               }\r
+\r
+                                               // Arrow right or delete\r
+                                               if (keyCode == VK.RIGHT || keyCode == VK.DELETE) {\r
+                                                       nonEditableParent = getNonEmptyTextNodeSibling(caretContainer);\r
+\r
+                                                       if (nonEditableParent && getContentEditable(nonEditableParent) === "false") {\r
+                                                               e.preventDefault();\r
+\r
+                                                               if (keyCode == VK.RIGHT) {\r
+                                                                       positionCaretOnElement(nonEditableParent, false);\r
+                                                               } else {\r
+                                                                       dom.remove(nonEditableParent);\r
+                                                               }\r
+                                                       } else {\r
+                                                               removeCaretContainer(caretContainer);\r
+                                                       }\r
+                                               }\r
+                                       }\r
+                               }\r
+                       }\r
+               };\r
+\r
+               ed.onMouseDown.addToTop(function(ed, e){\r
+                       // prevent collapsing selection to caret when clicking in a non-editable section\r
+                       var node = ed.selection.getNode();\r
+                       if (getContentEditable(node) === "false" && node == e.target) {\r
+                               e.preventDefault();\r
+                       }\r
+               });\r
+               ed.onMouseUp.addToTop(moveSelection);\r
+               ed.onKeyDown.addToTop(handleKey);\r
+               ed.onKeyUp.addToTop(moveSelection);\r
+       };\r
 \r
        tinymce.create('tinymce.plugins.NonEditablePlugin', {\r
                init : function(ed, url) {\r
-                       var t = this, editClass, nonEditClass;\r
+                       var editClass, nonEditClass, nonEditableRegExps;\r
+\r
+                       editClass = " " + tinymce.trim(ed.getParam("noneditable_editable_class", "mceEditable")) + " ";\r
+                       nonEditClass = " " + tinymce.trim(ed.getParam("noneditable_noneditable_class", "mceNonEditable")) + " ";\r
+\r
+                       // Setup noneditable regexps array\r
+                       nonEditableRegExps = ed.getParam("noneditable_regexp");\r
+                       if (nonEditableRegExps && !nonEditableRegExps.length) {\r
+                               nonEditableRegExps = [nonEditableRegExps];\r
+                       }\r
+\r
+                       ed.onPreInit.add(function() {\r
+                               handleContentEditableSelection(ed);\r
+\r
+                               if (nonEditableRegExps) {\r
+                                       ed.onBeforeSetContent.add(function(ed, args) {\r
+                                               var i = nonEditableRegExps.length, content = args.content, cls = tinymce.trim(nonEditClass);\r
+\r
+                                               // Don't replace the variables when raw is used for example on undo/redo\r
+                                               if (args.format == "raw") {\r
+                                                       return;\r
+                                               }\r
 \r
-                       t.editor = ed;\r
-                       editClass = ed.getParam("noneditable_editable_class", "mceEditable");\r
-                       nonEditClass = ed.getParam("noneditable_noneditable_class", "mceNonEditable");\r
+                                               while (i--) {\r
+                                                       content = content.replace(nonEditableRegExps[i], function() {\r
+                                                               var args = arguments;\r
 \r
-                       ed.onNodeChange.addToTop(function(ed, cm, n) {\r
-                               var sc, ec;\r
+                                                               return '<span class="' + cls + '" data-mce-content="' + ed.dom.encode(args[0]) + '">' + ed.dom.encode(typeof(args[1]) === "string" ? args[1] : args[0]) + '</span>';\r
+                                                       });\r
+                                               }\r
 \r
-                               // Block if start or end is inside a non editable element\r
-                               sc = ed.dom.getParent(ed.selection.getStart(), function(n) {\r
-                                       return ed.dom.hasClass(n, nonEditClass);\r
+                                               args.content = content;\r
+                                       });\r
+                               }\r
+                               \r
+                               // Apply contentEditable true/false on elements with the noneditable/editable classes\r
+                               ed.parser.addAttributeFilter('class', function(nodes) {\r
+                                       var i = nodes.length, className, node;\r
+\r
+                                       while (i--) {\r
+                                               node = nodes[i];\r
+                                               className = " " + node.attr("class") + " ";\r
+\r
+                                               if (className.indexOf(editClass) !== -1) {\r
+                                                       node.attr(internalName, "true");\r
+                                               } else if (className.indexOf(nonEditClass) !== -1) {\r
+                                                       node.attr(internalName, "false");\r
+                                               }\r
+                                       }\r
                                });\r
 \r
-                               ec = ed.dom.getParent(ed.selection.getEnd(), function(n) {\r
-                                       return ed.dom.hasClass(n, nonEditClass);\r
+                               // Remove internal name\r
+                               ed.serializer.addAttributeFilter(internalName, function(nodes, name) {\r
+                                       var i = nodes.length, node;\r
+\r
+                                       while (i--) {\r
+                                               node = nodes[i];\r
+\r
+                                               if (nonEditableRegExps && node.attr('data-mce-content')) {\r
+                                                       node.name = "#text";\r
+                                                       node.type = 3;\r
+                                                       node.raw = true;\r
+                                                       node.value = node.attr('data-mce-content');\r
+                                               } else {\r
+                                                       node.attr(externalName, null);\r
+                                                       node.attr(internalName, null);\r
+                                               }\r
+                                       }\r
                                });\r
 \r
-                               // Block or unblock\r
-                               if (sc || ec) {\r
-                                       t._setDisabled(1);\r
-                                       return false;\r
-                               } else\r
-                                       t._setDisabled(0);\r
+                               // Convert external name into internal name\r
+                               ed.parser.addAttributeFilter(externalName, function(nodes, name) {\r
+                                       var i = nodes.length, node;\r
+\r
+                                       while (i--) {\r
+                                               node = nodes[i];\r
+                                               node.attr(internalName, node.attr(externalName));\r
+                                               node.attr(externalName, null);\r
+                                       }\r
+                               });\r
                        });\r
                },\r
 \r
                                infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/noneditable',\r
                                version : tinymce.majorVersion + "." + tinymce.minorVersion\r
                        };\r
-               },\r
-\r
-               _block : function(ed, e) {\r
-                       var k = e.keyCode;\r
-\r
-                       // Don't block arrow keys, pg up/down, and F1-F12\r
-                       if ((k > 32 && k < 41) || (k > 111 && k < 124))\r
-                               return;\r
-\r
-                       return Event.cancel(e);\r
-               },\r
-\r
-               _setDisabled : function(s) {\r
-                       var t = this, ed = t.editor;\r
-\r
-                       tinymce.each(ed.controlManager.controls, function(c) {\r
-                               c.setDisabled(s);\r
-                       });\r
-\r
-                       if (s !== t.disabled) {\r
-                               if (s) {\r
-                                       ed.onKeyDown.addToTop(t._block);\r
-                                       ed.onKeyPress.addToTop(t._block);\r
-                                       ed.onKeyUp.addToTop(t._block);\r
-                                       ed.onPaste.addToTop(t._block);\r
-                               } else {\r
-                                       ed.onKeyDown.remove(t._block);\r
-                                       ed.onKeyPress.remove(t._block);\r
-                                       ed.onKeyUp.remove(t._block);\r
-                                       ed.onPaste.remove(t._block);\r
-                               }\r
-\r
-                               t.disabled = s;\r
-                       }\r
                }\r
        });\r
 \r