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
positionCaretOnElement(nonEditableParent, true);\r
} else {\r
dom.remove(nonEditableParent);\r
+ return;\r
}\r
} else {\r
removeCaretContainer(caretContainer);\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
- // prevent collapsing selection to caret when clicking in a non-editable section\r
+ ed.onMouseDown.addToTop(function(ed, e) {\r
var node = ed.selection.getNode();\r
+\r
if (getContentEditable(node) === "false" && node == e.target) {\r
- e.preventDefault();\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
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
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
- while (i--) {\r
- content = content.replace(nonEditableRegExps[i], function() {\r
- var args = arguments;\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
+ ed.selection.onBeforeSetContent.add(convertRegExpsToNonEditable);\r
+ ed.onBeforeSetContent.add(convertRegExpsToNonEditable);\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