+++ /dev/null
-/**\r
- * editor_plugin_src.js\r
- *\r
- * Copyright 2009, Moxiecode Systems AB\r
- * Released under LGPL License.\r
- *\r
- * License: http://tinymce.moxiecode.com/license\r
- * Contributing: http://tinymce.moxiecode.com/contributing\r
- */\r
-\r
-(function() {\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', invisibleChar = '\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
- function canDelete(backspace) {\r
- var rng, container, offset, nonEditableParent;\r
-\r
- function removeNodeIfNotParent(node) {\r
- var parent = container;\r
-\r
- while (parent) {\r
- if (parent === node) {\r
- return;\r
- }\r
-\r
- parent = parent.parentNode;\r
- }\r
-\r
- dom.remove(node);\r
- moveSelection();\r
- }\r
-\r
- function isNextPrevTreeNodeNonEditable() {\r
- var node, walker, nonEmptyElements = ed.schema.getNonEmptyElements();\r
-\r
- walker = new tinymce.dom.TreeWalker(container, ed.getBody());\r
- while (node = (backspace ? walker.prev() : walker.next())) {\r
- // Found IMG/INPUT etc\r
- if (nonEmptyElements[node.nodeName.toLowerCase()]) {\r
- break;\r
- }\r
-\r
- // Found text node with contents\r
- if (node.nodeType === 3 && tinymce.trim(node.nodeValue).length > 0) {\r
- break;\r
- }\r
-\r
- // Found non editable node\r
- if (getContentEditable(node) === "false") {\r
- removeNodeIfNotParent(node);\r
- return true;\r
- }\r
- }\r
-\r
- // Check if the content node is within a non editable parent\r
- if (getNonEditableParent(node)) {\r
- return true;\r
- }\r
-\r
- return false;\r
- }\r
-\r
- if (selection.isCollapsed()) {\r
- rng = selection.getRng(true);\r
- container = rng.startContainer;\r
- offset = rng.startOffset;\r
- container = getParentCaretContainer(container) || container;\r
-\r
- // Is in noneditable parent\r
- if (nonEditableParent = getNonEditableParent(container)) {\r
- removeNodeIfNotParent(nonEditableParent);\r
- return false;\r
- }\r
-\r
- // Check if the caret is in the middle of a text node\r
- if (container.nodeType == 3 && (backspace ? offset > 0 : offset < container.nodeValue.length)) {\r
- return true;\r
- }\r
-\r
- // Resolve container index\r
- if (container.nodeType == 1) {\r
- container = container.childNodes[offset] || container;\r
- }\r
-\r
- // Check if previous or next tree node is non editable then block the event\r
- if (isNextPrevTreeNodeNonEditable()) {\r
- return false;\r
- }\r
- }\r
-\r
- return true;\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
- // Is Ctrl+c, Ctrl+v or Ctrl+x then use default browser behavior\r
- if ((tinymce.isMac ? e.metaKey : e.ctrlKey) && (keyCode == 67 || keyCode == 88 || keyCode == 86)) {\r
- return;\r
- }\r
-\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
- return;\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
- return;\r
- }\r
- } else {\r
- removeCaretContainer(caretContainer);\r
- }\r
- }\r
- }\r
-\r
- if ((keyCode == VK.BACKSPACE || keyCode == VK.DELETE) && !canDelete(keyCode == VK.BACKSPACE)) {\r
- e.preventDefault();\r
- return false;\r
- }\r
- }\r
- }\r
- };\r
-\r
- ed.onMouseDown.addToTop(function(ed, e) {\r
- var node = ed.selection.getNode();\r
-\r
- if (getContentEditable(node) === "false" && node == e.target) {\r
- // Expand selection on mouse down we can't block the default event since it's used for drag/drop\r
- moveSelection();\r
- }\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 editClass, nonEditClass, nonEditableRegExps;\r
-\r
- // Converts configured regexps to noneditable span items\r
- function convertRegExpsToNonEditable(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
- while (i--) {\r
- content = content.replace(nonEditableRegExps[i], function(match) {\r
- var args = arguments, index = args[args.length - 2];\r
-\r
- // Is value inside an attribute then don't replace\r
- if (index > 0 && content.charAt(index - 1) == '"') {\r
- return match;\r
- }\r
-\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
- args.content = content;\r
- };\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.selection.onBeforeSetContent.add(convertRegExpsToNonEditable);\r
- ed.onBeforeSetContent.add(convertRegExpsToNonEditable);\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
- // 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
- // 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
- getInfo : function() {\r
- return {\r
- longname : 'Non editable elements',\r
- author : 'Moxiecode Systems AB',\r
- authorurl : 'http://tinymce.moxiecode.com',\r
- infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/noneditable',\r
- version : tinymce.majorVersion + "." + tinymce.minorVersion\r
- };\r
- }\r
- });\r
-\r
- // Register plugin\r
- tinymce.PluginManager.add('noneditable', tinymce.plugins.NonEditablePlugin);\r
-})();
\ No newline at end of file