]> git.mxchange.org Git - friendica.git/blob - 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
1 /**\r
2  * editor_plugin_src.js\r
3  *\r
4  * Copyright 2009, Moxiecode Systems AB\r
5  * Released under LGPL License.\r
6  *\r
7  * License: http://tinymce.moxiecode.com/license\r
8  * Contributing: http://tinymce.moxiecode.com/contributing\r
9  */\r
10 \r
11 (function() {\r
12         var TreeWalker = tinymce.dom.TreeWalker;\r
13         var externalName = 'contenteditable', internalName = 'data-mce-' + externalName;\r
14         var VK = tinymce.VK;\r
15 \r
16         function handleContentEditableSelection(ed) {\r
17                 var dom = ed.dom, selection = ed.selection, invisibleChar, caretContainerId = 'mce_noneditablecaret';\r
18 \r
19                 // Setup invisible character use zero width space on Gecko since it doesn't change the height of the container\r
20                 invisibleChar = tinymce.isGecko ? '\u200B' : '\uFEFF';\r
21 \r
22                 // Returns the content editable state of a node "true/false" or null\r
23                 function getContentEditable(node) {\r
24                         var contentEditable;\r
25 \r
26                         // Ignore non elements\r
27                         if (node.nodeType === 1) {\r
28                                 // Check for fake content editable\r
29                                 contentEditable = node.getAttribute(internalName);\r
30                                 if (contentEditable && contentEditable !== "inherit") {\r
31                                         return contentEditable;\r
32                                 }\r
33 \r
34                                 // Check for real content editable\r
35                                 contentEditable = node.contentEditable;\r
36                                 if (contentEditable !== "inherit") {\r
37                                         return contentEditable;\r
38                                 }\r
39                         }\r
40 \r
41                         return null;\r
42                 };\r
43 \r
44                 // Returns the noneditable parent or null if there is a editable before it or if it wasn't found\r
45                 function getNonEditableParent(node) {\r
46                         var state;\r
47 \r
48                         while (node) {\r
49                                 state = getContentEditable(node);\r
50                                 if (state) {\r
51                                         return state  === "false" ? node : null;\r
52                                 }\r
53 \r
54                                 node = node.parentNode;\r
55                         }\r
56                 };\r
57 \r
58                 // Get caret container parent for the specified node\r
59                 function getParentCaretContainer(node) {\r
60                         while (node) {\r
61                                 if (node.id === caretContainerId) {\r
62                                         return node;\r
63                                 }\r
64 \r
65                                 node = node.parentNode;\r
66                         }\r
67                 };\r
68 \r
69                 // Finds the first text node in the specified node\r
70                 function findFirstTextNode(node) {\r
71                         var walker;\r
72 \r
73                         if (node) {\r
74                                 walker = new TreeWalker(node, node);\r
75 \r
76                                 for (node = walker.current(); node; node = walker.next()) {\r
77                                         if (node.nodeType === 3) {\r
78                                                 return node;\r
79                                         }\r
80                                 }\r
81                         }\r
82                 };\r
83 \r
84                 // Insert caret container before/after target or expand selection to include block\r
85                 function insertCaretContainerOrExpandToBlock(target, before) {\r
86                         var caretContainer, rng;\r
87 \r
88                         // Select block\r
89                         if (getContentEditable(target) === "false") {\r
90                                 if (dom.isBlock(target)) {\r
91                                         selection.select(target);\r
92                                         return;\r
93                                 }\r
94                         }\r
95 \r
96                         rng = dom.createRng();\r
97 \r
98                         if (getContentEditable(target) === "true") {\r
99                                 if (!target.firstChild) {\r
100                                         target.appendChild(ed.getDoc().createTextNode('\u00a0'));\r
101                                 }\r
102 \r
103                                 target = target.firstChild;\r
104                                 before = true;\r
105                         }\r
106 \r
107                         //caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style:'border: 1px solid red'}, invisibleChar);\r
108                         caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true}, invisibleChar);\r
109 \r
110                         if (before) {\r
111                                 target.parentNode.insertBefore(caretContainer, target);\r
112                         } else {\r
113                                 dom.insertAfter(caretContainer, target);\r
114                         }\r
115 \r
116                         rng.setStart(caretContainer.firstChild, 1);\r
117                         rng.collapse(true);\r
118                         selection.setRng(rng);\r
119 \r
120                         return caretContainer;\r
121                 };\r
122 \r
123                 // Removes any caret container except the one we might be in\r
124                 function removeCaretContainer(caretContainer) {\r
125                         var child, currentCaretContainer, lastContainer;\r
126 \r
127                         if (caretContainer) {\r
128                                         rng = selection.getRng(true);\r
129                                         rng.setStartBefore(caretContainer);\r
130                                         rng.setEndBefore(caretContainer);\r
131 \r
132                                         child = findFirstTextNode(caretContainer);\r
133                                         if (child && child.nodeValue.charAt(0) == invisibleChar) {\r
134                                                 child = child.deleteData(0, 1);\r
135                                         }\r
136 \r
137                                         dom.remove(caretContainer, true);\r
138 \r
139                                         selection.setRng(rng);\r
140                         } else {\r
141                                 currentCaretContainer = getParentCaretContainer(selection.getStart());\r
142                                 while ((caretContainer = dom.get(caretContainerId)) && caretContainer !== lastContainer) {\r
143                                         if (currentCaretContainer !== caretContainer) {\r
144                                                 child = findFirstTextNode(caretContainer);\r
145                                                 if (child && child.nodeValue.charAt(0) == invisibleChar) {\r
146                                                         child = child.deleteData(0, 1);\r
147                                                 }\r
148 \r
149                                                 dom.remove(caretContainer, true);\r
150                                         }\r
151 \r
152                                         lastContainer = caretContainer;\r
153                                 }\r
154                         }\r
155                 };\r
156 \r
157                 // Modifies the selection to include contentEditable false elements or insert caret containers\r
158                 function moveSelection() {\r
159                         var nonEditableStart, nonEditableEnd, isCollapsed, rng, element;\r
160 \r
161                         // 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
162                         function hasSideContent(element, left) {\r
163                                 var container, offset, walker, node, len;\r
164 \r
165                                 container = rng.startContainer;\r
166                                 offset = rng.startOffset;\r
167 \r
168                                 // If endpoint is in middle of text node then expand to beginning/end of element\r
169                                 if (container.nodeType == 3) {\r
170                                         len = container.nodeValue.length;\r
171                                         if ((offset > 0 && offset < len) || (left ? offset == len : offset == 0)) {\r
172                                                 return;\r
173                                         }\r
174                                 } else {\r
175                                         // Can we resolve the node by index\r
176                                         if (offset < container.childNodes.length) {\r
177                                                 // Browser represents caret position as the offset at the start of an element. When moving right\r
178                                                 // this is the element we are moving into so we consider our container to be child node at offset-1\r
179                                                 var pos = !left && offset > 0 ? offset-1 : offset;\r
180                                                 container = container.childNodes[pos];\r
181                                                 if (container.hasChildNodes()) {\r
182                                                         container = container.firstChild;\r
183                                                 }\r
184                                         } else {\r
185                                                 // 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
186                                                 return !left ? element : null;\r
187                                         }\r
188                                 }\r
189 \r
190                                 // Walk left/right to look for contents\r
191                                 walker = new TreeWalker(container, element);\r
192                                 while (node = walker[left ? 'prev' : 'next']()) {\r
193                                         if (node.nodeType === 3 && node.nodeValue.length > 0) {\r
194                                                 return;\r
195                                         } else if (getContentEditable(node) === "true") {\r
196                                                 // Found contentEditable=true element return this one to we can move the caret inside it\r
197                                                 return node;\r
198                                         }\r
199                                 }\r
200 \r
201                                 return element;\r
202                         };\r
203 \r
204                         // Remove any existing caret containers\r
205                         removeCaretContainer();\r
206 \r
207                         // Get noneditable start/end elements\r
208                         isCollapsed = selection.isCollapsed();\r
209                         nonEditableStart = getNonEditableParent(selection.getStart());\r
210                         nonEditableEnd = getNonEditableParent(selection.getEnd());\r
211 \r
212                         // Is any fo the range endpoints noneditable\r
213                         if (nonEditableStart || nonEditableEnd) {\r
214                                 rng = selection.getRng(true);\r
215 \r
216                                 // If it's a caret selection then look left/right to see if we need to move the caret out side or expand\r
217                                 if (isCollapsed) {\r
218                                         nonEditableStart = nonEditableStart || nonEditableEnd;\r
219                                         var start = selection.getStart();\r
220                                         if (element = hasSideContent(nonEditableStart, true)) {\r
221                                                 // We have no contents to the left of the caret then insert a caret container before the noneditable element\r
222                                                 insertCaretContainerOrExpandToBlock(element, true);\r
223                                         } else if (element = hasSideContent(nonEditableStart, false)) {\r
224                                                 // We have no contents to the right of the caret then insert a caret container after the noneditable element\r
225                                                 insertCaretContainerOrExpandToBlock(element, false);\r
226                                         } else {\r
227                                                 // We are in the middle of a noneditable so expand to select it\r
228                                                 selection.select(nonEditableStart);\r
229                                         }\r
230                                 } else {\r
231                                         rng = selection.getRng(true);\r
232 \r
233                                         // Expand selection to include start non editable element\r
234                                         if (nonEditableStart) {\r
235                                                 rng.setStartBefore(nonEditableStart);\r
236                                         }\r
237 \r
238                                         // Expand selection to include end non editable element\r
239                                         if (nonEditableEnd) {\r
240                                                 rng.setEndAfter(nonEditableEnd);\r
241                                         }\r
242 \r
243                                         selection.setRng(rng);\r
244                                 }\r
245                         }\r
246                 };\r
247 \r
248                 function handleKey(ed, e) {\r
249                         var keyCode = e.keyCode, nonEditableParent, caretContainer, startElement, endElement;\r
250 \r
251                         function getNonEmptyTextNodeSibling(node, prev) {\r
252                                 while (node = node[prev ? 'previousSibling' : 'nextSibling']) {\r
253                                         if (node.nodeType !== 3 || node.nodeValue.length > 0) {\r
254                                                 return node;\r
255                                         }\r
256                                 }\r
257                         };\r
258 \r
259                         function positionCaretOnElement(element, start) {\r
260                                 selection.select(element);\r
261                                 selection.collapse(start);\r
262                         }\r
263 \r
264                         startElement = selection.getStart()\r
265                         endElement = selection.getEnd();\r
266 \r
267                         // Disable all key presses in contentEditable=false except delete or backspace\r
268                         nonEditableParent = getNonEditableParent(startElement) || getNonEditableParent(endElement);\r
269                         if (nonEditableParent && (keyCode < 112 || keyCode > 124) && keyCode != VK.DELETE && keyCode != VK.BACKSPACE) {\r
270                                 e.preventDefault();\r
271 \r
272                                 // Arrow left/right select the element and collapse left/right\r
273                                 if (keyCode == VK.LEFT || keyCode == VK.RIGHT) {\r
274                                         var left = keyCode == VK.LEFT;\r
275                                         // If a block element find previous or next element to position the caret\r
276                                         if (ed.dom.isBlock(nonEditableParent)) {\r
277                                                 var targetElement = left ? nonEditableParent.previousSibling : nonEditableParent.nextSibling;\r
278                                                 var walker = new TreeWalker(targetElement, targetElement);\r
279                                                 var caretElement = left ? walker.prev() : walker.next();\r
280                                                 positionCaretOnElement(caretElement, !left);\r
281                                         } else {\r
282                                                 positionCaretOnElement(nonEditableParent, left);\r
283                                         }\r
284                                 }\r
285                         } else {\r
286                                 // Is arrow left/right, backspace or delete\r
287                                 if (keyCode == VK.LEFT || keyCode == VK.RIGHT || keyCode == VK.BACKSPACE || keyCode == VK.DELETE) {\r
288                                         caretContainer = getParentCaretContainer(startElement);\r
289                                         if (caretContainer) {\r
290                                                 // Arrow left or backspace\r
291                                                 if (keyCode == VK.LEFT || keyCode == VK.BACKSPACE) {\r
292                                                         nonEditableParent = getNonEmptyTextNodeSibling(caretContainer, true);\r
293 \r
294                                                         if (nonEditableParent && getContentEditable(nonEditableParent) === "false") {\r
295                                                                 e.preventDefault();\r
296 \r
297                                                                 if (keyCode == VK.LEFT) {\r
298                                                                         positionCaretOnElement(nonEditableParent, true);\r
299                                                                 } else {\r
300                                                                         dom.remove(nonEditableParent);\r
301                                                                 }\r
302                                                         } else {\r
303                                                                 removeCaretContainer(caretContainer);\r
304                                                         }\r
305                                                 }\r
306 \r
307                                                 // Arrow right or delete\r
308                                                 if (keyCode == VK.RIGHT || keyCode == VK.DELETE) {\r
309                                                         nonEditableParent = getNonEmptyTextNodeSibling(caretContainer);\r
310 \r
311                                                         if (nonEditableParent && getContentEditable(nonEditableParent) === "false") {\r
312                                                                 e.preventDefault();\r
313 \r
314                                                                 if (keyCode == VK.RIGHT) {\r
315                                                                         positionCaretOnElement(nonEditableParent, false);\r
316                                                                 } else {\r
317                                                                         dom.remove(nonEditableParent);\r
318                                                                 }\r
319                                                         } else {\r
320                                                                 removeCaretContainer(caretContainer);\r
321                                                         }\r
322                                                 }\r
323                                         }\r
324                                 }\r
325                         }\r
326                 };\r
327 \r
328                 ed.onMouseDown.addToTop(function(ed, e){\r
329                         // prevent collapsing selection to caret when clicking in a non-editable section\r
330                         var node = ed.selection.getNode();\r
331                         if (getContentEditable(node) === "false" && node == e.target) {\r
332                                 e.preventDefault();\r
333                         }\r
334                 });\r
335                 ed.onMouseUp.addToTop(moveSelection);\r
336                 ed.onKeyDown.addToTop(handleKey);\r
337                 ed.onKeyUp.addToTop(moveSelection);\r
338         };\r
339 \r
340         tinymce.create('tinymce.plugins.NonEditablePlugin', {\r
341                 init : function(ed, url) {\r
342                         var editClass, nonEditClass, nonEditableRegExps;\r
343 \r
344                         editClass = " " + tinymce.trim(ed.getParam("noneditable_editable_class", "mceEditable")) + " ";\r
345                         nonEditClass = " " + tinymce.trim(ed.getParam("noneditable_noneditable_class", "mceNonEditable")) + " ";\r
346 \r
347                         // Setup noneditable regexps array\r
348                         nonEditableRegExps = ed.getParam("noneditable_regexp");\r
349                         if (nonEditableRegExps && !nonEditableRegExps.length) {\r
350                                 nonEditableRegExps = [nonEditableRegExps];\r
351                         }\r
352 \r
353                         ed.onPreInit.add(function() {\r
354                                 handleContentEditableSelection(ed);\r
355 \r
356                                 if (nonEditableRegExps) {\r
357                                         ed.onBeforeSetContent.add(function(ed, args) {\r
358                                                 var i = nonEditableRegExps.length, content = args.content, cls = tinymce.trim(nonEditClass);\r
359 \r
360                                                 // Don't replace the variables when raw is used for example on undo/redo\r
361                                                 if (args.format == "raw") {\r
362                                                         return;\r
363                                                 }\r
364 \r
365                                                 while (i--) {\r
366                                                         content = content.replace(nonEditableRegExps[i], function() {\r
367                                                                 var args = arguments;\r
368 \r
369                                                                 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
370                                                         });\r
371                                                 }\r
372 \r
373                                                 args.content = content;\r
374                                         });\r
375                                 }\r
376                                 \r
377                                 // Apply contentEditable true/false on elements with the noneditable/editable classes\r
378                                 ed.parser.addAttributeFilter('class', function(nodes) {\r
379                                         var i = nodes.length, className, node;\r
380 \r
381                                         while (i--) {\r
382                                                 node = nodes[i];\r
383                                                 className = " " + node.attr("class") + " ";\r
384 \r
385                                                 if (className.indexOf(editClass) !== -1) {\r
386                                                         node.attr(internalName, "true");\r
387                                                 } else if (className.indexOf(nonEditClass) !== -1) {\r
388                                                         node.attr(internalName, "false");\r
389                                                 }\r
390                                         }\r
391                                 });\r
392 \r
393                                 // Remove internal name\r
394                                 ed.serializer.addAttributeFilter(internalName, function(nodes, name) {\r
395                                         var i = nodes.length, node;\r
396 \r
397                                         while (i--) {\r
398                                                 node = nodes[i];\r
399 \r
400                                                 if (nonEditableRegExps && node.attr('data-mce-content')) {\r
401                                                         node.name = "#text";\r
402                                                         node.type = 3;\r
403                                                         node.raw = true;\r
404                                                         node.value = node.attr('data-mce-content');\r
405                                                 } else {\r
406                                                         node.attr(externalName, null);\r
407                                                         node.attr(internalName, null);\r
408                                                 }\r
409                                         }\r
410                                 });\r
411 \r
412                                 // Convert external name into internal name\r
413                                 ed.parser.addAttributeFilter(externalName, function(nodes, name) {\r
414                                         var i = nodes.length, node;\r
415 \r
416                                         while (i--) {\r
417                                                 node = nodes[i];\r
418                                                 node.attr(internalName, node.attr(externalName));\r
419                                                 node.attr(externalName, null);\r
420                                         }\r
421                                 });\r
422                         });\r
423                 },\r
424 \r
425                 getInfo : function() {\r
426                         return {\r
427                                 longname : 'Non editable elements',\r
428                                 author : 'Moxiecode Systems AB',\r
429                                 authorurl : 'http://tinymce.moxiecode.com',\r
430                                 infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/noneditable',\r
431                                 version : tinymce.majorVersion + "." + tinymce.minorVersion\r
432                         };\r
433                 }\r
434         });\r
435 \r
436         // Register plugin\r
437         tinymce.PluginManager.add('noneditable', tinymce.plugins.NonEditablePlugin);\r
438 })();