]> git.mxchange.org Git - friendica.git/blob - library/tinymce/jscripts/tiny_mce/plugins/lists/editor_plugin_src.js
update tinymce to 3.5.0.1
[friendica.git] / library / tinymce / jscripts / tiny_mce / plugins / lists / editor_plugin_src.js
1 /**
2  * editor_plugin_src.js
3  *
4  * Copyright 2011, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
9  */
10
11 (function() {
12         var each = tinymce.each, Event = tinymce.dom.Event, bookmark;
13
14         // Skips text nodes that only contain whitespace since they aren't semantically important.
15         function skipWhitespaceNodes(e, next) {
16                 while (e && (e.nodeType === 8 || (e.nodeType === 3 && /^[ \t\n\r]*$/.test(e.nodeValue)))) {
17                         e = next(e);
18                 }
19                 return e;
20         }
21
22         function skipWhitespaceNodesBackwards(e) {
23                 return skipWhitespaceNodes(e, function(e) {
24                         return e.previousSibling;
25                 });
26         }
27
28         function skipWhitespaceNodesForwards(e) {
29                 return skipWhitespaceNodes(e, function(e) {
30                         return e.nextSibling;
31                 });
32         }
33
34         function hasParentInList(ed, e, list) {
35                 return ed.dom.getParent(e, function(p) {
36                         return tinymce.inArray(list, p) !== -1;
37                 });
38         }
39
40         function isList(e) {
41                 return e && (e.tagName === 'OL' || e.tagName === 'UL');
42         }
43
44         function splitNestedLists(element, dom) {
45                 var tmp, nested, wrapItem;
46                 tmp = skipWhitespaceNodesBackwards(element.lastChild);
47                 while (isList(tmp)) {
48                         nested = tmp;
49                         tmp = skipWhitespaceNodesBackwards(nested.previousSibling);
50                 }
51                 if (nested) {
52                         wrapItem = dom.create('li', { style: 'list-style-type: none;'});
53                         dom.split(element, nested);
54                         dom.insertAfter(wrapItem, nested);
55                         wrapItem.appendChild(nested);
56                         wrapItem.appendChild(nested);
57                         element = wrapItem.previousSibling;
58                 }
59                 return element;
60         }
61
62         function attemptMergeWithAdjacent(e, allowDifferentListStyles, mergeParagraphs) {
63                 e = attemptMergeWithPrevious(e, allowDifferentListStyles, mergeParagraphs);
64                 return attemptMergeWithNext(e, allowDifferentListStyles, mergeParagraphs);
65         }
66
67         function attemptMergeWithPrevious(e, allowDifferentListStyles, mergeParagraphs) {
68                 var prev = skipWhitespaceNodesBackwards(e.previousSibling);
69                 if (prev) {
70                         return attemptMerge(prev, e, allowDifferentListStyles ? prev : false, mergeParagraphs);
71                 } else {
72                         return e;
73                 }
74         }
75
76         function attemptMergeWithNext(e, allowDifferentListStyles, mergeParagraphs) {
77                 var next = skipWhitespaceNodesForwards(e.nextSibling);
78                 if (next) {
79                         return attemptMerge(e, next, allowDifferentListStyles ? next : false, mergeParagraphs);
80                 } else {
81                         return e;
82                 }
83         }
84
85         function attemptMerge(e1, e2, differentStylesMasterElement, mergeParagraphs) {
86                 if (canMerge(e1, e2, !!differentStylesMasterElement, mergeParagraphs)) {
87                         return merge(e1, e2, differentStylesMasterElement);
88                 } else if (e1 && e1.tagName === 'LI' && isList(e2)) {
89                         // Fix invalidly nested lists.
90                         e1.appendChild(e2);
91                 }
92                 return e2;
93         }
94
95         function canMerge(e1, e2, allowDifferentListStyles, mergeParagraphs) {
96                 if (!e1 || !e2) {
97                         return false;
98                 } else if (e1.tagName === 'LI' && e2.tagName === 'LI') {
99                         return e2.style.listStyleType === 'none' || containsOnlyAList(e2);
100                 } else if (isList(e1)) {
101                         return (e1.tagName === e2.tagName && (allowDifferentListStyles || e1.style.listStyleType === e2.style.listStyleType)) || isListForIndent(e2);
102                 } else return mergeParagraphs && e1.tagName === 'P' && e2.tagName === 'P';
103         }
104
105         function isListForIndent(e) {
106                 var firstLI = skipWhitespaceNodesForwards(e.firstChild), lastLI = skipWhitespaceNodesBackwards(e.lastChild);
107                 return firstLI && lastLI && isList(e) && firstLI === lastLI && (isList(firstLI) || firstLI.style.listStyleType === 'none' || containsOnlyAList(firstLI));
108         }
109
110         function containsOnlyAList(e) {
111                 var firstChild = skipWhitespaceNodesForwards(e.firstChild), lastChild = skipWhitespaceNodesBackwards(e.lastChild);
112                 return firstChild && lastChild && firstChild === lastChild && isList(firstChild);
113         }
114
115         function merge(e1, e2, masterElement) {
116                 var lastOriginal = skipWhitespaceNodesBackwards(e1.lastChild), firstNew = skipWhitespaceNodesForwards(e2.firstChild);
117                 if (e1.tagName === 'P') {
118                         e1.appendChild(e1.ownerDocument.createElement('br'));
119                 }
120                 while (e2.firstChild) {
121                         e1.appendChild(e2.firstChild);
122                 }
123                 if (masterElement) {
124                         e1.style.listStyleType = masterElement.style.listStyleType;
125                 }
126                 e2.parentNode.removeChild(e2);
127                 attemptMerge(lastOriginal, firstNew, false);
128                 return e1;
129         }
130
131         function findItemToOperateOn(e, dom) {
132                 var item;
133                 if (!dom.is(e, 'li,ol,ul')) {
134                         item = dom.getParent(e, 'li');
135                         if (item) {
136                                 e = item;
137                         }
138                 }
139                 return e;
140         }
141
142         tinymce.create('tinymce.plugins.Lists', {
143                 init: function(ed) {
144                         var LIST_TABBING = 'TABBING';
145                         var LIST_EMPTY_ITEM = 'EMPTY';
146                         var LIST_ESCAPE = 'ESCAPE';
147                         var LIST_PARAGRAPH = 'PARAGRAPH';
148                         var LIST_UNKNOWN = 'UNKNOWN';
149                         var state = LIST_UNKNOWN;
150
151                         function isTabInList(e) {
152                                 // Don't indent on Ctrl+Tab or Alt+Tab
153                                 return e.keyCode === tinymce.VK.TAB && !(e.altKey || e.ctrlKey) &&
154                                         (ed.queryCommandState('InsertUnorderedList') || ed.queryCommandState('InsertOrderedList'));
155                         }
156
157                         function isOnLastListItem() {
158                                 var li = getLi();
159                                 var grandParent = li.parentNode.parentNode;
160                                 var isLastItem = li.parentNode.lastChild === li;
161                                 return isLastItem && !isNestedList(grandParent) && isEmptyListItem(li);
162                         }
163
164                         function isNestedList(grandParent) {
165                                 if (isList(grandParent)) {
166                                         return grandParent.parentNode && grandParent.parentNode.tagName === 'LI';
167                                 } else {
168                                         return  grandParent.tagName === 'LI';
169                                 }
170                         }
171
172                         function isInEmptyListItem() {
173                                 return ed.selection.isCollapsed() && isEmptyListItem(getLi());
174                         }
175
176                         function getLi() {
177                                 var n = ed.selection.getStart();
178                                 // Get start will return BR if the LI only contains a BR or an empty element as we use these to fix caret position
179                                 return ((n.tagName == 'BR' || n.tagName == '') && n.parentNode.tagName == 'LI') ? n.parentNode : n;
180                         }
181
182                         function isEmptyListItem(li) {
183                                 var numChildren = li.childNodes.length;
184                                 if (li.tagName === 'LI') {
185                                         return numChildren == 0 ? true : numChildren == 1 && (li.firstChild.tagName == '' || li.firstChild.tagName == 'BR' || isEmptyIE9Li(li));
186                                 }
187                                 return false;
188                         }
189
190                         function isEmptyIE9Li(li) {
191                                 // only consider this to be last item if there is no list item content or that content is nbsp or space since IE9 creates these
192                                 var lis = tinymce.grep(li.parentNode.childNodes, function(n) {return n.tagName == 'LI'});
193                                 var isLastLi = li == lis[lis.length - 1];
194                                 var child = li.firstChild;
195                                 return tinymce.isIE9 && isLastLi && (child.nodeValue == String.fromCharCode(160) || child.nodeValue == String.fromCharCode(32));
196                         }
197
198                         function isEnter(e) {
199                                 return e.keyCode === tinymce.VK.ENTER;
200                         }
201
202                         function isEnterWithoutShift(e) {
203                                 return isEnter(e) && !e.shiftKey;
204                         }
205
206                         function getListKeyState(e) {
207                                 if (isTabInList(e)) {
208                                         return LIST_TABBING;
209                                 } else if (isEnterWithoutShift(e) && isOnLastListItem()) {
210                                         // Returns LIST_UNKNOWN since breaking out of lists is handled by the EnterKey.js logic now
211                                         //return LIST_ESCAPE;
212                                         return LIST_UNKNOWN;
213                                 } else if (isEnterWithoutShift(e) && isInEmptyListItem()) {
214                                         return LIST_EMPTY_ITEM;
215                                 } else {
216                                         return LIST_UNKNOWN;
217                                 }
218                         }
219
220                         function cancelDefaultEvents(ed, e) {
221                                 // list escape is done manually using outdent as it does not create paragraphs correctly in td's
222                                 if (state == LIST_TABBING || state == LIST_EMPTY_ITEM || tinymce.isGecko && state == LIST_ESCAPE) {
223                                         Event.cancel(e);
224                                 }
225                         }
226
227                         function isCursorAtEndOfContainer() {
228                                 var range = ed.selection.getRng(true);
229                                 var startContainer = range.startContainer;
230                                 if (startContainer.nodeType == 3) {
231                                         var value = startContainer.nodeValue;
232                                         if (tinymce.isIE9 && value.length > 1 && value.charCodeAt(value.length-1) == 32) {
233                                                 // IE9 places a space on the end of the text in some cases so ignore last char
234                                                 return (range.endOffset == value.length-1);
235                                         } else {
236                                                 return (range.endOffset == value.length);
237                                         }
238                                 } else if (startContainer.nodeType == 1) {
239                                         return range.endOffset == startContainer.childNodes.length;
240                                 }
241                                 return false;
242                         }
243
244                         /*
245                                 If we are at the end of a list item surrounded with an element, pressing enter should create a
246                                 new list item instead without splitting the element e.g. don't want to create new P or H1 tag
247                           */
248                         function isEndOfListItem() {
249                                 var node = ed.selection.getNode();
250                                 var validElements = 'h1,h2,h3,h4,h5,h6,p,div';
251                                 var isLastParagraphOfLi = ed.dom.is(node, validElements) && node.parentNode.tagName === 'LI' && node.parentNode.lastChild === node;
252                                 return ed.selection.isCollapsed() && isLastParagraphOfLi && isCursorAtEndOfContainer();
253                         }
254
255                         // Creates a new list item after the current selection's list item parent
256                         function createNewLi(ed, e) {
257                                 if (isEnterWithoutShift(e) && isEndOfListItem()) {
258                                         var node = ed.selection.getNode();
259                                         var li = ed.dom.create("li");
260                                         var parentLi = ed.dom.getParent(node, 'li');
261                                         ed.dom.insertAfter(li, parentLi);
262
263                                         // Move caret to new list element.
264                                         if (tinymce.isIE6 || tinymce.isIE7 || tinyMCE.isIE8) {
265                                                 // Removed this line since it would create an odd <&nbsp;> tag and placing the caret inside an empty LI is handled and should be handled by the selection logic
266                                                 //li.appendChild(ed.dom.create("&nbsp;")); // IE needs an element within the bullet point
267                                                 ed.selection.setCursorLocation(li, 1);
268                                         } else {
269                                                 ed.selection.setCursorLocation(li, 0);
270                                         }
271                                         e.preventDefault();
272                                 }
273                         }
274
275                         function imageJoiningListItem(ed, e) {
276                                 var prevSibling;
277
278                                 if (!tinymce.isGecko)
279                                         return;
280
281                                 var n = ed.selection.getStart();
282                                 if (e.keyCode != tinymce.VK.BACKSPACE || n.tagName !== 'IMG')
283                                         return;
284
285                                 function lastLI(node) {
286                                         var child = node.firstChild;
287                                         var li = null;
288                                         do {
289                                                 if (!child)
290                                                         break;
291
292                                                 if (child.tagName === 'LI')
293                                                         li = child;
294                                         } while (child = child.nextSibling);
295
296                                         return li;
297                                 }
298
299                                 function addChildren(parentNode, destination) {
300                                         while (parentNode.childNodes.length > 0)
301                                                 destination.appendChild(parentNode.childNodes[0]);
302                                 }
303
304                                 // Check if there is a previous sibling
305                                 prevSibling = n.parentNode.previousSibling;
306                                 if (!prevSibling)
307                                         return;
308
309                                 var ul;
310                                 if (prevSibling.tagName === 'UL' || prevSibling.tagName === 'OL')
311                                         ul = prevSibling;
312                                 else if (prevSibling.previousSibling && (prevSibling.previousSibling.tagName === 'UL' || prevSibling.previousSibling.tagName === 'OL'))
313                                         ul = prevSibling.previousSibling;
314                                 else
315                                         return;
316
317                                 var li = lastLI(ul);
318
319                                 // move the caret to the end of the list item
320                                 var rng = ed.dom.createRng();
321                                 rng.setStart(li, 1);
322                                 rng.setEnd(li, 1);
323                                 ed.selection.setRng(rng);
324                                 ed.selection.collapse(true);
325
326                                 // save a bookmark at the end of the list item
327                                 var bookmark = ed.selection.getBookmark();
328
329                                 // copy the image an its text to the list item
330                                 var clone = n.parentNode.cloneNode(true);
331                                 if (clone.tagName === 'P' || clone.tagName === 'DIV')
332                                         addChildren(clone, li);
333                                 else
334                                         li.appendChild(clone);
335
336                                 // remove the old copy of the image
337                                 n.parentNode.parentNode.removeChild(n.parentNode);
338
339                                 // move the caret where we saved the bookmark
340                                 ed.selection.moveToBookmark(bookmark);
341                         }
342
343                         // fix the cursor position to ensure it is correct in IE
344                         function setCursorPositionToOriginalLi(li) {
345                                 var list = ed.dom.getParent(li, 'ol,ul');
346                                 if (list != null) {
347                                         var lastLi = list.lastChild;
348                                         // Removed this line since IE9 would report an DOM character error and placing the caret inside an empty LI is handled and should be handled by the selection logic
349                                         //lastLi.appendChild(ed.getDoc().createElement(''));
350                                         ed.selection.setCursorLocation(lastLi, 0);
351                                 }
352                         }
353
354                         this.ed = ed;
355                         ed.addCommand('Indent', this.indent, this);
356                         ed.addCommand('Outdent', this.outdent, this);
357                         ed.addCommand('InsertUnorderedList', function() {
358                                 this.applyList('UL', 'OL');
359                         }, this);
360                         ed.addCommand('InsertOrderedList', function() {
361                                 this.applyList('OL', 'UL');
362                         }, this);
363
364                         ed.onInit.add(function() {
365                                 ed.editorCommands.addCommands({
366                                         'outdent': function() {
367                                                 var sel = ed.selection, dom = ed.dom;
368
369                                                 function hasStyleIndent(n) {
370                                                         n = dom.getParent(n, dom.isBlock);
371                                                         return n && (parseInt(ed.dom.getStyle(n, 'margin-left') || 0, 10) + parseInt(ed.dom.getStyle(n, 'padding-left') || 0, 10)) > 0;
372                                                 }
373
374                                                 return hasStyleIndent(sel.getStart()) || hasStyleIndent(sel.getEnd()) || ed.queryCommandState('InsertOrderedList') || ed.queryCommandState('InsertUnorderedList');
375                                         }
376                                 }, 'state');
377                         });
378
379                         ed.onKeyUp.add(function(ed, e) {
380                                 if (state == LIST_TABBING) {
381                                         ed.execCommand(e.shiftKey ? 'Outdent' : 'Indent', true, null);
382                                         state = LIST_UNKNOWN;
383                                         return Event.cancel(e);
384                                 } else if (state == LIST_EMPTY_ITEM) {
385                                         var li = getLi();
386                                         var shouldOutdent =  ed.settings.list_outdent_on_enter === true || e.shiftKey;
387                                         ed.execCommand(shouldOutdent ? 'Outdent' : 'Indent', true, null);
388                                         if (tinymce.isIE) {
389                                                 setCursorPositionToOriginalLi(li);
390                                         }
391
392                                         return Event.cancel(e);
393                                 } else if (state == LIST_ESCAPE) {
394                                         if (tinymce.isIE6 || tinymce.isIE7 || tinymce.isIE8) {
395                                                 // append a zero sized nbsp so that caret is positioned correctly in IE after escaping and applying formatting.
396                                                 // if there is no text then applying formatting for e.g a H1 to the P tag immediately following list after
397                                                 // escaping from it will cause the caret to be positioned on the last li instead of staying the in P tag.
398                                                 var n = ed.getDoc().createTextNode('\uFEFF');
399                                                 ed.selection.getNode().appendChild(n);
400                                         } else if (tinymce.isIE9 || tinymce.isGecko) {
401                                                 // IE9 does not escape the list so we use outdent to do this and cancel the default behaviour
402                                                 // Gecko does not create a paragraph outdenting inside a TD so default behaviour is cancelled and we outdent ourselves
403                                                 ed.execCommand('Outdent');
404                                                 return Event.cancel(e);
405                                         }
406                                 }
407                         });
408
409                         function fixListItem(parent, reference) {
410                                 // a zero-sized non-breaking space is placed in the empty list item so that the nested list is
411                                 // displayed on the below line instead of next to it
412                                 var n = ed.getDoc().createTextNode('\uFEFF');
413                                 parent.insertBefore(n, reference);
414                                 ed.selection.setCursorLocation(n, 0);
415                                 // repaint to remove rendering artifact. only visible when creating new list
416                                 ed.execCommand('mceRepaint');
417                         }
418
419                         function fixIndentedListItemForGecko(ed, e) {
420                                 if (isEnter(e)) {
421                                         var li = getLi();
422                                         if (li) {
423                                                 var parent = li.parentNode;
424                                                 var grandParent = parent && parent.parentNode;
425                                                 if (grandParent && grandParent.nodeName == 'LI' && grandParent.firstChild == parent && li == parent.firstChild) {
426                                                         fixListItem(grandParent, parent);
427                                                 }
428                                         }
429                                 }
430                         }
431
432                         function fixIndentedListItemForIE8(ed, e) {
433                                 if (isEnter(e)) {
434                                         var li = getLi();
435                                         if (ed.dom.select('ul li', li).length === 1) {
436                                                 var list = li.firstChild;
437                                                 fixListItem(li, list);
438                                         }
439                                 }
440                         }
441
442                         function fixDeletingFirstCharOfList(ed, e) {
443                                 function listElements(list, li) {
444                                         var elements = [];
445                                         var walker = new tinymce.dom.TreeWalker(li, list);
446                                         for (var node = walker.current(); node; node = walker.next()) {
447                                                 if (ed.dom.is(node, 'ol,ul,li')) {
448                                                         elements.push(node);
449                                                 }
450                                         }
451                                         return elements;
452                                 }
453
454                                 if (e.keyCode == tinymce.VK.BACKSPACE) {
455                                         var li = getLi();
456                                         if (li) {
457                                                 var list = ed.dom.getParent(li, 'ol,ul');
458                                                 if (list && list.firstChild === li) {
459                                                         var elements = listElements(list, li);
460                                                         ed.execCommand("Outdent", false, elements);
461                                                         ed.undoManager.add();
462                                                         return Event.cancel(e);
463                                                 }
464                                         }
465                                 }
466                         }
467
468                         function fixDeletingEmptyLiInWebkit(ed, e) {
469                                 var li = getLi();
470                                 if (e.keyCode === tinymce.VK.BACKSPACE && ed.dom.is(li, 'li') && li.parentNode.firstChild!==li) {
471                                         if (ed.dom.select('ul,ol', li).length === 1) {
472                                                 var prevLi = li.previousSibling;
473                                                 ed.dom.remove(ed.dom.select('br', li));
474                                                 ed.dom.remove(li, true);
475                                                 var textNodes = tinymce.grep(prevLi.childNodes, function(n){ return n.nodeType === 3 });
476                                                 if (textNodes.length === 1) {
477                                                         var textNode = textNodes[0]
478                                                         ed.selection.setCursorLocation(textNode, textNode.length);
479                                                 }
480                                                 ed.undoManager.add();
481                                                 return Event.cancel(e);
482                                         }
483                                 }
484                         }
485
486                         ed.onKeyDown.add(function(_, e) { state = getListKeyState(e); });
487                         ed.onKeyDown.add(cancelDefaultEvents);
488                         ed.onKeyDown.add(imageJoiningListItem);
489                         ed.onKeyDown.add(createNewLi);
490
491                         if (tinymce.isGecko) {
492                                 ed.onKeyUp.add(fixIndentedListItemForGecko);
493                         }
494                         if (tinymce.isIE8) {
495                                 ed.onKeyUp.add(fixIndentedListItemForIE8);
496                         }
497                         if (tinymce.isGecko || tinymce.isWebKit) {
498                                 ed.onKeyDown.add(fixDeletingFirstCharOfList);
499                         }
500                         if (tinymce.isWebKit) {
501                                 ed.onKeyDown.add(fixDeletingEmptyLiInWebkit);
502                         }
503                 },
504
505                 applyList: function(targetListType, oppositeListType) {
506                         var t = this, ed = t.ed, dom = ed.dom, applied = [], hasSameType = false, hasOppositeType = false, hasNonList = false, actions,
507                                         selectedBlocks = ed.selection.getSelectedBlocks();
508
509                         function cleanupBr(e) {
510                                 if (e && e.tagName === 'BR') {
511                                         dom.remove(e);
512                                 }
513                         }
514
515                         function makeList(element) {
516                                 var list = dom.create(targetListType), li;
517
518                                 function adjustIndentForNewList(element) {
519                                         // If there's a margin-left, outdent one level to account for the extra list margin.
520                                         if (element.style.marginLeft || element.style.paddingLeft) {
521                                                 t.adjustPaddingFunction(false)(element);
522                                         }
523                                 }
524
525                                 if (element.tagName === 'LI') {
526                                         // No change required.
527                                 } else if (element.tagName === 'P' || element.tagName === 'DIV' || element.tagName === 'BODY') {
528                                         processBrs(element, function(startSection, br) {
529                                                 doWrapList(startSection, br, element.tagName === 'BODY' ? null : startSection.parentNode);
530                                                 li = startSection.parentNode;
531                                                 adjustIndentForNewList(li);
532                                                 cleanupBr(br);
533                                         });
534                                         if (li) {
535                                                 if (li.tagName === 'LI' && (element.tagName === 'P' || selectedBlocks.length > 1)) {
536                                                         dom.split(li.parentNode.parentNode, li.parentNode);
537                                                 }
538                                                 attemptMergeWithAdjacent(li.parentNode, true);
539                                         }
540                                         return;
541                                 } else {
542                                         // Put the list around the element.
543                                         li = dom.create('li');
544                                         dom.insertAfter(li, element);
545                                         li.appendChild(element);
546                                         adjustIndentForNewList(element);
547                                         element = li;
548                                 }
549                                 dom.insertAfter(list, element);
550                                 list.appendChild(element);
551                                 attemptMergeWithAdjacent(list, true);
552                                 applied.push(element);
553                         }
554
555                         function doWrapList(start, end, template) {
556                                 var li, n = start, tmp;
557                                 while (!dom.isBlock(start.parentNode) && start.parentNode !== dom.getRoot()) {
558                                         start = dom.split(start.parentNode, start.previousSibling);
559                                         start = start.nextSibling;
560                                         n = start;
561                                 }
562                                 if (template) {
563                                         li = template.cloneNode(true);
564                                         start.parentNode.insertBefore(li, start);
565                                         while (li.firstChild) dom.remove(li.firstChild);
566                                         li = dom.rename(li, 'li');
567                                 } else {
568                                         li = dom.create('li');
569                                         start.parentNode.insertBefore(li, start);
570                                 }
571                                 while (n && n != end) {
572                                         tmp = n.nextSibling;
573                                         li.appendChild(n);
574                                         n = tmp;
575                                 }
576                                 if (li.childNodes.length === 0) {
577                                         li.innerHTML = '<br _mce_bogus="1" />';
578                                 }
579                                 makeList(li);
580                         }
581
582                         function processBrs(element, callback) {
583                                 var startSection, previousBR, END_TO_START = 3, START_TO_END = 1,
584                                                 breakElements = 'br,ul,ol,p,div,h1,h2,h3,h4,h5,h6,table,blockquote,address,pre,form,center,dl';
585
586                                 function isAnyPartSelected(start, end) {
587                                         var r = dom.createRng(), sel;
588                                         bookmark.keep = true;
589                                         ed.selection.moveToBookmark(bookmark);
590                                         bookmark.keep = false;
591                                         sel = ed.selection.getRng(true);
592                                         if (!end) {
593                                                 end = start.parentNode.lastChild;
594                                         }
595                                         r.setStartBefore(start);
596                                         r.setEndAfter(end);
597                                         return !(r.compareBoundaryPoints(END_TO_START, sel) > 0 || r.compareBoundaryPoints(START_TO_END, sel) <= 0);
598                                 }
599
600                                 function nextLeaf(br) {
601                                         if (br.nextSibling)
602                                                 return br.nextSibling;
603                                         if (!dom.isBlock(br.parentNode) && br.parentNode !== dom.getRoot())
604                                                 return nextLeaf(br.parentNode);
605                                 }
606
607                                 // Split on BRs within the range and process those.
608                                 startSection = element.firstChild;
609                                 // First mark the BRs that have any part of the previous section selected.
610                                 var trailingContentSelected = false;
611                                 each(dom.select(breakElements, element), function(br) {
612                                         if (br.hasAttribute && br.hasAttribute('_mce_bogus')) {
613                                                 return true; // Skip the bogus Brs that are put in to appease Firefox and Safari.
614                                         }
615                                         if (isAnyPartSelected(startSection, br)) {
616                                                 dom.addClass(br, '_mce_tagged_br');
617                                                 startSection = nextLeaf(br);
618                                         }
619                                 });
620                                 trailingContentSelected = (startSection && isAnyPartSelected(startSection, undefined));
621                                 startSection = element.firstChild;
622                                 each(dom.select(breakElements, element), function(br) {
623                                         // Got a section from start to br.
624                                         var tmp = nextLeaf(br);
625                                         if (br.hasAttribute && br.hasAttribute('_mce_bogus')) {
626                                                 return true; // Skip the bogus Brs that are put in to appease Firefox and Safari.
627                                         }
628                                         if (dom.hasClass(br, '_mce_tagged_br')) {
629                                                 callback(startSection, br, previousBR);
630                                                 previousBR = null;
631                                         } else {
632                                                 previousBR = br;
633                                         }
634                                         startSection = tmp;
635                                 });
636                                 if (trailingContentSelected) {
637                                         callback(startSection, undefined, previousBR);
638                                 }
639                         }
640
641                         function wrapList(element) {
642                                 processBrs(element, function(startSection, br, previousBR) {
643                                         // Need to indent this part
644                                         doWrapList(startSection, br);
645                                         cleanupBr(br);
646                                         cleanupBr(previousBR);
647                                 });
648                         }
649
650                         function changeList(element) {
651                                 if (tinymce.inArray(applied, element) !== -1) {
652                                         return;
653                                 }
654                                 if (element.parentNode.tagName === oppositeListType) {
655                                         dom.split(element.parentNode, element);
656                                         makeList(element);
657                                         attemptMergeWithNext(element.parentNode, false);
658                                 }
659                                 applied.push(element);
660                         }
661
662                         function convertListItemToParagraph(element) {
663                                 var child, nextChild, mergedElement, splitLast;
664                                 if (tinymce.inArray(applied, element) !== -1) {
665                                         return;
666                                 }
667                                 element = splitNestedLists(element, dom);
668                                 while (dom.is(element.parentNode, 'ol,ul,li')) {
669                                         dom.split(element.parentNode, element);
670                                 }
671                                 // Push the original element we have from the selection, not the renamed one.
672                                 applied.push(element);
673                                 element = dom.rename(element, 'p');
674                                 mergedElement = attemptMergeWithAdjacent(element, false, ed.settings.force_br_newlines);
675                                 if (mergedElement === element) {
676                                         // Now split out any block elements that can't be contained within a P.
677                                         // Manually iterate to ensure we handle modifications correctly (doesn't work with tinymce.each)
678                                         child = element.firstChild;
679                                         while (child) {
680                                                 if (dom.isBlock(child)) {
681                                                         child = dom.split(child.parentNode, child);
682                                                         splitLast = true;
683                                                         nextChild = child.nextSibling && child.nextSibling.firstChild;
684                                                 } else {
685                                                         nextChild = child.nextSibling;
686                                                         if (splitLast && child.tagName === 'BR') {
687                                                                 dom.remove(child);
688                                                         }
689                                                         splitLast = false;
690                                                 }
691                                                 child = nextChild;
692                                         }
693                                 }
694                         }
695
696                         each(selectedBlocks, function(e) {
697                                 e = findItemToOperateOn(e, dom);
698                                 if (e.tagName === oppositeListType || (e.tagName === 'LI' && e.parentNode.tagName === oppositeListType)) {
699                                         hasOppositeType = true;
700                                 } else if (e.tagName === targetListType || (e.tagName === 'LI' && e.parentNode.tagName === targetListType)) {
701                                         hasSameType = true;
702                                 } else {
703                                         hasNonList = true;
704                                 }
705                         });
706
707                         if (hasNonList &&!hasSameType || hasOppositeType || selectedBlocks.length === 0) {
708                                 actions = {
709                                         'LI': changeList,
710                                         'H1': makeList,
711                                         'H2': makeList,
712                                         'H3': makeList,
713                                         'H4': makeList,
714                                         'H5': makeList,
715                                         'H6': makeList,
716                                         'P': makeList,
717                                         'BODY': makeList,
718                                         'DIV': selectedBlocks.length > 1 ? makeList : wrapList,
719                                         defaultAction: wrapList,
720                                         elements: this.selectedBlocks()
721                                 };
722                         } else {
723                                 actions = {
724                                         defaultAction: convertListItemToParagraph,
725                                         elements: this.selectedBlocks()
726                                 };
727                         }
728                         this.process(actions);
729                 },
730
731                 indent: function() {
732                         var ed = this.ed, dom = ed.dom, indented = [];
733
734                         function createWrapItem(element) {
735                                 var wrapItem = dom.create('li', { style: 'list-style-type: none;'});
736                                 dom.insertAfter(wrapItem, element);
737                                 return wrapItem;
738                         }
739
740                         function createWrapList(element) {
741                                 var wrapItem = createWrapItem(element),
742                                                 list = dom.getParent(element, 'ol,ul'),
743                                                 listType = list.tagName,
744                                                 listStyle = dom.getStyle(list, 'list-style-type'),
745                                                 attrs = {},
746                                                 wrapList;
747                                 if (listStyle !== '') {
748                                         attrs.style = 'list-style-type: ' + listStyle + ';';
749                                 }
750                                 wrapList = dom.create(listType, attrs);
751                                 wrapItem.appendChild(wrapList);
752                                 return wrapList;
753                         }
754
755                         function indentLI(element) {
756                                 if (!hasParentInList(ed, element, indented)) {
757                                         element = splitNestedLists(element, dom);
758                                         var wrapList = createWrapList(element);
759                                         wrapList.appendChild(element);
760                                         attemptMergeWithAdjacent(wrapList.parentNode, false);
761                                         attemptMergeWithAdjacent(wrapList, false);
762                                         indented.push(element);
763                                 }
764                         }
765
766                         this.process({
767                                 'LI': indentLI,
768                                 defaultAction: this.adjustPaddingFunction(true),
769                                 elements: this.selectedBlocks()
770                         });
771
772                 },
773
774                 outdent: function(ui, elements) {
775                         var t = this, ed = t.ed, dom = ed.dom, outdented = [];
776
777                         function outdentLI(element) {
778                                 var listElement, targetParent, align;
779                                 if (!hasParentInList(ed, element, outdented)) {
780                                         if (dom.getStyle(element, 'margin-left') !== '' || dom.getStyle(element, 'padding-left') !== '') {
781                                                 return t.adjustPaddingFunction(false)(element);
782                                         }
783                                         align = dom.getStyle(element, 'text-align', true);
784                                         if (align === 'center' || align === 'right') {
785                                                 dom.setStyle(element, 'text-align', 'left');
786                                                 return;
787                                         }
788                                         element = splitNestedLists(element, dom);
789                                         listElement = element.parentNode;
790                                         targetParent = element.parentNode.parentNode;
791                                         if (targetParent.tagName === 'P') {
792                                                 dom.split(targetParent, element.parentNode);
793                                         } else {
794                                                 dom.split(listElement, element);
795                                                 if (targetParent.tagName === 'LI') {
796                                                         // Nested list, need to split the LI and go back out to the OL/UL element.
797                                                         dom.split(targetParent, element);
798                                                 } else if (!dom.is(targetParent, 'ol,ul')) {
799                                                         dom.rename(element, 'p');
800                                                 }
801                                         }
802                                         outdented.push(element);
803                                 }
804                         }
805
806                         var listElements = elements && tinymce.is(elements, 'array') ? elements : this.selectedBlocks();
807                         this.process({
808                                 'LI': outdentLI,
809                                 defaultAction: this.adjustPaddingFunction(false),
810                                 elements: listElements
811                         });
812
813                         each(outdented, attemptMergeWithAdjacent);
814                 },
815
816                 process: function(actions) {
817                         var t = this, sel = t.ed.selection, dom = t.ed.dom, selectedBlocks, r;
818
819                         function isEmptyElement(element) {
820                                 var excludeBrsAndBookmarks = tinymce.grep(element.childNodes, function(n) {
821                                         return !(n.nodeName === 'BR' || n.nodeName === 'SPAN' && dom.getAttrib(n, 'data-mce-type') == 'bookmark'
822                                                         || n.nodeType == 3 && (n.nodeValue == String.fromCharCode(160) || n.nodeValue == ''));
823                                 });
824                                 return excludeBrsAndBookmarks.length === 0;
825                         }
826
827                         function processElement(element) {
828                                 dom.removeClass(element, '_mce_act_on');
829                                 if (!element || element.nodeType !== 1 || selectedBlocks.length > 1 && isEmptyElement(element)) {
830                                         return;
831                                 }
832                                 element = findItemToOperateOn(element, dom);
833                                 var action = actions[element.tagName];
834                                 if (!action) {
835                                         action = actions.defaultAction;
836                                 }
837                                 action(element);
838                         }
839
840                         function recurse(element) {
841                                 t.splitSafeEach(element.childNodes, processElement);
842                         }
843
844                         function brAtEdgeOfSelection(container, offset) {
845                                 return offset >= 0 && container.hasChildNodes() && offset < container.childNodes.length &&
846                                                 container.childNodes[offset].tagName === 'BR';
847                         }
848
849                         function isInTable() {
850                                 var n = sel.getNode();
851                                 var p = dom.getParent(n, 'td');
852                                 return p !== null;
853                         }
854
855                         selectedBlocks = actions.elements;
856
857                         r = sel.getRng(true);
858                         if (!r.collapsed) {
859                                 if (brAtEdgeOfSelection(r.endContainer, r.endOffset - 1)) {
860                                         r.setEnd(r.endContainer, r.endOffset - 1);
861                                         sel.setRng(r);
862                                 }
863                                 if (brAtEdgeOfSelection(r.startContainer, r.startOffset)) {
864                                         r.setStart(r.startContainer, r.startOffset + 1);
865                                         sel.setRng(r);
866                                 }
867                         }
868
869
870                         if (tinymce.isIE8) {
871                                 // append a zero sized nbsp so that caret is restored correctly using bookmark
872                                 var s = t.ed.selection.getNode();
873                                 if (s.tagName === 'LI' && !(s.parentNode.lastChild === s)) {
874                                         var i = t.ed.getDoc().createTextNode('\uFEFF');
875                                         s.appendChild(i);
876                                 }
877                         }
878
879                         bookmark = sel.getBookmark();
880                         actions.OL = actions.UL = recurse;
881                         t.splitSafeEach(selectedBlocks, processElement);
882                         sel.moveToBookmark(bookmark);
883                         bookmark = null;
884
885                         // we avoid doing repaint in a table as this will move the caret out of the table in Firefox 3.6
886                         if (!isInTable()) {
887                                 // Avoids table or image handles being left behind in Firefox.
888                                 t.ed.execCommand('mceRepaint');
889                         }
890                 },
891
892                 splitSafeEach: function(elements, f) {
893                         if (tinymce.isGecko && (/Firefox\/[12]\.[0-9]/.test(navigator.userAgent) ||
894                                         /Firefox\/3\.[0-4]/.test(navigator.userAgent))) {
895                                 this.classBasedEach(elements, f);
896                         } else {
897                                 each(elements, f);
898                         }
899                 },
900
901                 classBasedEach: function(elements, f) {
902                         var dom = this.ed.dom, nodes, element;
903                         // Mark nodes
904                         each(elements, function(element) {
905                                 dom.addClass(element, '_mce_act_on');
906                         });
907                         nodes = dom.select('._mce_act_on');
908                         while (nodes.length > 0) {
909                                 element = nodes.shift();
910                                 dom.removeClass(element, '_mce_act_on');
911                                 f(element);
912                                 nodes = dom.select('._mce_act_on');
913                         }
914                 },
915
916                 adjustPaddingFunction: function(isIndent) {
917                         var indentAmount, indentUnits, ed = this.ed;
918                         indentAmount = ed.settings.indentation;
919                         indentUnits = /[a-z%]+/i.exec(indentAmount);
920                         indentAmount = parseInt(indentAmount, 10);
921                         return function(element) {
922                                 var currentIndent, newIndentAmount;
923                                 currentIndent = parseInt(ed.dom.getStyle(element, 'margin-left') || 0, 10) + parseInt(ed.dom.getStyle(element, 'padding-left') || 0, 10);
924                                 if (isIndent) {
925                                         newIndentAmount = currentIndent + indentAmount;
926                                 } else {
927                                         newIndentAmount = currentIndent - indentAmount;
928                                 }
929                                 ed.dom.setStyle(element, 'padding-left', '');
930                                 ed.dom.setStyle(element, 'margin-left', newIndentAmount > 0 ? newIndentAmount + indentUnits : '');
931                         };
932                 },
933
934                 selectedBlocks: function() {
935                         var ed = this.ed
936                         var selectedBlocks = ed.selection.getSelectedBlocks();
937                         return selectedBlocks.length == 0 ? [ ed.dom.getRoot() ] : selectedBlocks;
938                 },
939
940                 getInfo: function() {
941                         return {
942                                 longname : 'Lists',
943                                 author : 'Moxiecode Systems AB',
944                                 authorurl : 'http://tinymce.moxiecode.com',
945                                 infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/lists',
946                                 version : tinymce.majorVersion + "." + tinymce.minorVersion
947                         };
948                 }
949         });
950         tinymce.PluginManager.add("lists", tinymce.plugins.Lists);
951 }());