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