]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/TinyMCE/js/plugins/lists/editor_plugin_src.js
Merge branch 'master' into testing
[quix0rs-gnu-social.git] / plugins / TinyMCE / js / 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) { return e.previousSibling; });
24         }
25         
26         function skipWhitespaceNodesForwards(e) {
27                 return skipWhitespaceNodes(e, function(e) { return e.nextSibling; });
28         }
29         
30         function hasParentInList(ed, e, list) {
31                 return ed.dom.getParent(e, function(p) {
32                         return tinymce.inArray(list, p) !== -1;
33                 });
34         }
35         
36         function isList(e) {
37                 return e && (e.tagName === 'OL' || e.tagName === 'UL');
38         }
39         
40         function splitNestedLists(element, dom) {
41                 var tmp, nested, wrapItem;
42                 tmp = skipWhitespaceNodesBackwards(element.lastChild);
43                 while (isList(tmp)) {
44                         nested = tmp;
45                         tmp = skipWhitespaceNodesBackwards(nested.previousSibling);
46                 }
47                 if (nested) {
48                         wrapItem = dom.create('li', { style: 'list-style-type: none;'});
49                         dom.split(element, nested);
50                         dom.insertAfter(wrapItem, nested);
51                         wrapItem.appendChild(nested);
52                         wrapItem.appendChild(nested);
53                         element = wrapItem.previousSibling;
54                 }
55                 return element;
56         }
57         
58         function attemptMergeWithAdjacent(e, allowDifferentListStyles, mergeParagraphs) {
59                 e = attemptMergeWithPrevious(e, allowDifferentListStyles, mergeParagraphs);
60                 return attemptMergeWithNext(e, allowDifferentListStyles, mergeParagraphs);
61         }
62         
63         function attemptMergeWithPrevious(e, allowDifferentListStyles, mergeParagraphs) {
64                 var prev = skipWhitespaceNodesBackwards(e.previousSibling);
65                 if (prev) {
66                         return attemptMerge(prev, e, allowDifferentListStyles ? prev : false, mergeParagraphs);
67                 } else {
68                         return e;
69                 }
70         }
71         
72         function attemptMergeWithNext(e, allowDifferentListStyles, mergeParagraphs) {
73                 var next = skipWhitespaceNodesForwards(e.nextSibling);
74                 if (next) {
75                         return attemptMerge(e, next, allowDifferentListStyles ? next : false, mergeParagraphs);
76                 } else {
77                         return e;
78                 }
79         }
80         
81         function attemptMerge(e1, e2, differentStylesMasterElement, mergeParagraphs) {
82                 if (canMerge(e1, e2, !!differentStylesMasterElement, mergeParagraphs)) {
83                         return merge(e1, e2, differentStylesMasterElement);
84                 } else if (e1 && e1.tagName === 'LI' && isList(e2)) {
85                         // Fix invalidly nested lists.
86                         e1.appendChild(e2);
87                 }
88                 return e2;
89         }
90         
91         function canMerge(e1, e2, allowDifferentListStyles, mergeParagraphs) {
92                 if (!e1 || !e2) {
93                         return false;
94                 } else if (e1.tagName === 'LI' && e2.tagName === 'LI') {
95                         return e2.style.listStyleType === 'none' || containsOnlyAList(e2);
96                 } else if (isList(e1)) {
97                         return (e1.tagName === e2.tagName && (allowDifferentListStyles || e1.style.listStyleType === e2.style.listStyleType)) || isListForIndent(e2);
98                 } else if (mergeParagraphs && e1.tagName === 'P' && e2.tagName === 'P') {
99                         return true;
100                 } else {
101                         return false;
102                 }
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, url) {
144                         var enterDownInEmptyList = false;
145
146                         function isTriggerKey(e) {
147                                 return e.keyCode === 9 && (ed.queryCommandState('InsertUnorderedList') || ed.queryCommandState('InsertOrderedList'));
148                         };
149
150                         function isEnterInEmptyListItem(ed, e) {
151                                 var sel = ed.selection, n;
152                                 if (e.keyCode === 13) {
153                                         n = sel.getStart();
154
155                                         // Get start will return BR if the LI only contains a BR
156                                         if (n.tagName == 'BR' && n.parentNode.tagName == 'LI')
157                                                 n = n.parentNode;
158
159                                         // Check for empty LI or a LI with just one BR since Gecko and WebKit uses BR elements to place the caret
160                                         enterDownInEmptyList = sel.isCollapsed() && n && n.tagName === 'LI' && (n.childNodes.length === 0 || (n.firstChild.nodeName == 'BR' && n.childNodes.length === 1));
161                                         return enterDownInEmptyList;
162                                 }
163                         };
164
165                         function cancelKeys(ed, e) {
166                                 if (isTriggerKey(e) || isEnterInEmptyListItem(ed, e)) {
167                                         return Event.cancel(e);
168                                 }
169                         };
170
171                         function imageJoiningListItem(ed, e) {
172                                 if (!tinymce.isGecko)
173                                         return;
174
175                                 var n = ed.selection.getStart();
176                                 if (e.keyCode != 8 || n.tagName !== 'IMG') 
177                                         return;
178
179                                 function lastLI(node) {
180                                         var child = node.firstChild;
181                                         var li = null;
182                                         do {
183                                                 if (!child)
184                                                         break;
185
186                                                 if (child.tagName === 'LI')
187                                                         li = child;
188                                         } while (child = child.nextSibling);
189
190                                         return li;
191                                 }
192
193                                 function addChildren(parentNode, destination) {
194                                         while (parentNode.childNodes.length > 0)
195                                                 destination.appendChild(parentNode.childNodes[0]);
196                                 }
197
198                                 var ul;
199                                 if (n.parentNode.previousSibling.tagName === 'UL' || n.parentNode.previousSibling.tagName === 'OL')
200                                         ul = n.parentNode.previousSibling;
201                                 else if (n.parentNode.previousSibling.previousSibling.tagName === 'UL' || n.parentNode.previousSibling.previousSibling.tagName === 'OL')
202                                         ul = n.parentNode.previousSibling.previousSibling;
203                                 else
204                                         return;
205
206                                 var li = lastLI(ul);
207
208                                 // move the caret to the end of the list item
209                                 var rng = ed.dom.createRng();
210                                 rng.setStart(li, 1);
211                                 rng.setEnd(li, 1);
212                                 ed.selection.setRng(rng);
213                                 ed.selection.collapse(true);
214
215                                 // save a bookmark at the end of the list item
216                                 var bookmark = ed.selection.getBookmark();
217
218                                 // copy the image an its text to the list item
219                                 var clone = n.parentNode.cloneNode(true);
220                                 if (clone.tagName === 'P' || clone.tagName === 'DIV')
221                                         addChildren(clone, li);
222                                 else
223                                         li.appendChild(clone);
224                                         
225                                 // remove the old copy of the image
226                                 n.parentNode.parentNode.removeChild(n.parentNode);
227
228                                 // move the caret where we saved the bookmark
229                                 ed.selection.moveToBookmark(bookmark);
230                         }
231
232                         this.ed = ed;
233                         ed.addCommand('Indent', this.indent, this);
234                         ed.addCommand('Outdent', this.outdent, this);
235                         ed.addCommand('InsertUnorderedList', function() {
236                                 this.applyList('UL', 'OL');
237                         }, this);
238                         ed.addCommand('InsertOrderedList', function() {
239                                 this.applyList('OL', 'UL');
240                         }, this);
241                         
242                         ed.onInit.add(function() {
243                                 ed.editorCommands.addCommands({
244                                         'outdent': function() {
245                                                 var sel = ed.selection, dom = ed.dom;
246                                                 function hasStyleIndent(n) {
247                                                         n = dom.getParent(n, dom.isBlock);
248                                                         return n && (parseInt(ed.dom.getStyle(n, 'margin-left') || 0, 10) + parseInt(ed.dom.getStyle(n, 'padding-left') || 0, 10)) > 0;
249                                                 }
250                                                 return hasStyleIndent(sel.getStart()) || hasStyleIndent(sel.getEnd()) || ed.queryCommandState('InsertOrderedList') || ed.queryCommandState('InsertUnorderedList');
251                                         }
252                                 }, 'state');
253                         });
254                         
255                         ed.onKeyUp.add(function(ed, e) {
256                                 var n, rng;
257                                 if (isTriggerKey(e)) {
258                                         ed.execCommand(e.shiftKey ? 'Outdent' : 'Indent', true, null);
259                                         return Event.cancel(e);
260                                 } else if (enterDownInEmptyList && isEnterInEmptyListItem(ed, e)) {
261                                         if (ed.queryCommandState('InsertOrderedList')) {
262                                                 ed.execCommand('InsertOrderedList');
263                                         } else {
264                                                 ed.execCommand('InsertUnorderedList');
265                                         }
266                                         n = ed.selection.getStart();
267                                         if (n && n.tagName === 'LI') {
268                                                 // Fix the caret position on IE since it jumps back up to the previous list item.
269                                                 n = ed.dom.getParent(n, 'ol,ul').nextSibling;
270                                                 if (n && n.tagName === 'P') {
271                                                         if (!n.firstChild) {
272                                                                 n.appendChild(ed.getDoc().createTextNode(''));
273                                                         }
274                                                         rng = ed.dom.createRng();
275                                                         rng.setStart(n.firstChild, 1);
276                                                         rng.setEnd(n.firstChild, 1);
277                                                         ed.selection.setRng(rng);
278                                                 }
279                                         }
280                                         return Event.cancel(e);
281                                 }
282                         });
283                         ed.onKeyPress.add(cancelKeys);
284                         ed.onKeyDown.add(cancelKeys);
285                         ed.onKeyDown.add(imageJoiningListItem);
286                 },
287                 
288                 applyList: function(targetListType, oppositeListType) {
289                         var t = this, ed = t.ed, dom = ed.dom, applied = [], hasSameType = false, hasOppositeType = false, hasNonList = false, actions,
290                                 selectedBlocks = ed.selection.getSelectedBlocks();
291                         
292                         function cleanupBr(e) {
293                                 if (e && e.tagName === 'BR') {
294                                         dom.remove(e);
295                                 }
296                         }
297                         
298                         function makeList(element) {
299                                 var list = dom.create(targetListType), li;
300                                 function adjustIndentForNewList(element) {
301                                         // If there's a margin-left, outdent one level to account for the extra list margin.
302                                         if (element.style.marginLeft || element.style.paddingLeft) {
303                                                 t.adjustPaddingFunction(false)(element);
304                                         }
305                                 }
306                                 
307                                 if (element.tagName === 'LI') {
308                                         // No change required.
309                                 } else if (element.tagName === 'P' || element.tagName === 'DIV' || element.tagName === 'BODY') {
310                                         processBrs(element, function(startSection, br, previousBR) {
311                                                 doWrapList(startSection, br, element.tagName === 'BODY' ? null : startSection.parentNode);
312                                                 li = startSection.parentNode;
313                                                 adjustIndentForNewList(li);
314                                                 cleanupBr(br);
315                                         });
316                                         if (element.tagName === 'P' || selectedBlocks.length > 1) {
317                                                 dom.split(li.parentNode.parentNode, li.parentNode);
318                                         }
319                                         attemptMergeWithAdjacent(li.parentNode, true);
320                                         return;
321                                 } else {
322                                         // Put the list around the element.
323                                         li = dom.create('li');
324                                         dom.insertAfter(li, element);
325                                         li.appendChild(element);
326                                         adjustIndentForNewList(element);
327                                         element = li;
328                                 }
329                                 dom.insertAfter(list, element);
330                                 list.appendChild(element);
331                                 attemptMergeWithAdjacent(list, true);
332                                 applied.push(element);
333                         }
334                         
335                         function doWrapList(start, end, template) {
336                                 var li, n = start, tmp, i;
337                                 while (!dom.isBlock(start.parentNode) && start.parentNode !== dom.getRoot()) {
338                                         start = dom.split(start.parentNode, start.previousSibling);
339                                         start = start.nextSibling;
340                                         n = start;
341                                 }
342                                 if (template) {
343                                         li = template.cloneNode(true);
344                                         start.parentNode.insertBefore(li, start);
345                                         while (li.firstChild) dom.remove(li.firstChild);
346                                         li = dom.rename(li, 'li');
347                                 } else {
348                                         li = dom.create('li');
349                                         start.parentNode.insertBefore(li, start);
350                                 }
351                                 while (n && n != end) {
352                                         tmp = n.nextSibling;
353                                         li.appendChild(n);
354                                         n = tmp;
355                                 }
356                                 if (li.childNodes.length === 0) {
357                                         li.innerHTML = '<br _mce_bogus="1" />';
358                                 }
359                                 makeList(li);
360                         }
361                         
362                         function processBrs(element, callback) {
363                                 var startSection, previousBR, END_TO_START = 3, START_TO_END = 1,
364                                         breakElements = 'br,ul,ol,p,div,h1,h2,h3,h4,h5,h6,table,blockquote,address,pre,form,center,dl';
365                                 function isAnyPartSelected(start, end) {
366                                         var r = dom.createRng(), sel;
367                                         bookmark.keep = true;
368                                         ed.selection.moveToBookmark(bookmark);
369                                         bookmark.keep = false;
370                                         sel = ed.selection.getRng(true);
371                                         if (!end) {
372                                                 end = start.parentNode.lastChild;
373                                         }
374                                         r.setStartBefore(start);
375                                         r.setEndAfter(end);
376                                         return !(r.compareBoundaryPoints(END_TO_START, sel) > 0 || r.compareBoundaryPoints(START_TO_END, sel) <= 0);
377                                 }
378                                 function nextLeaf(br) {
379                                         if (br.nextSibling)
380                                                 return br.nextSibling;
381                                         if (!dom.isBlock(br.parentNode) && br.parentNode !== dom.getRoot())
382                                                 return nextLeaf(br.parentNode);
383                                 }
384                                 // Split on BRs within the range and process those.
385                                 startSection = element.firstChild;
386                                 // First mark the BRs that have any part of the previous section selected.
387                                 var trailingContentSelected = false;
388                                 each(dom.select(breakElements, element), function(br) {
389                                         var b;
390                                         if (br.hasAttribute && br.hasAttribute('_mce_bogus')) {
391                                                 return true; // Skip the bogus Brs that are put in to appease Firefox and Safari.
392                                         }
393                                         if (isAnyPartSelected(startSection, br)) {
394                                                 dom.addClass(br, '_mce_tagged_br');
395                                                 startSection = nextLeaf(br);
396                                         }
397                                 });
398                                 trailingContentSelected = (startSection && isAnyPartSelected(startSection, undefined));
399                                 startSection = element.firstChild;
400                                 each(dom.select(breakElements, element), function(br) {
401                                         // Got a section from start to br.
402                                         var tmp = nextLeaf(br);
403                                         if (br.hasAttribute && br.hasAttribute('_mce_bogus')) {
404                                                 return true; // Skip the bogus Brs that are put in to appease Firefox and Safari.
405                                         }
406                                         if (dom.hasClass(br, '_mce_tagged_br')) {
407                                                 callback(startSection, br, previousBR);
408                                                 previousBR = null;
409                                         } else {
410                                                 previousBR = br;
411                                         }
412                                         startSection = tmp;
413                                 });
414                                 if (trailingContentSelected) {
415                                         callback(startSection, undefined, previousBR);
416                                 }
417                         }
418                         
419                         function wrapList(element) {
420                                 processBrs(element, function(startSection, br, previousBR) {
421                                         // Need to indent this part
422                                         doWrapList(startSection, br);
423                                         cleanupBr(br);
424                                         cleanupBr(previousBR);
425                                 });
426                         }
427                         
428                         function changeList(element) {
429                                 if (tinymce.inArray(applied, element) !== -1) {
430                                         return;
431                                 }
432                                 if (element.parentNode.tagName === oppositeListType) {
433                                         dom.split(element.parentNode, element);
434                                         makeList(element);
435                                         attemptMergeWithNext(element.parentNode, false);
436                                 }
437                                 applied.push(element);
438                         }
439                         
440                         function convertListItemToParagraph(element) {
441                                 var child, nextChild, mergedElement, splitLast;
442                                 if (tinymce.inArray(applied, element) !== -1) {
443                                         return;
444                                 }
445                                 element = splitNestedLists(element, dom);
446                                 while (dom.is(element.parentNode, 'ol,ul,li')) {
447                                         dom.split(element.parentNode, element);
448                                 }
449                                 // Push the original element we have from the selection, not the renamed one.
450                                 applied.push(element);
451                                 element = dom.rename(element, 'p');
452                                 mergedElement = attemptMergeWithAdjacent(element, false, ed.settings.force_br_newlines);
453                                 if (mergedElement === element) {
454                                         // Now split out any block elements that can't be contained within a P.
455                                         // Manually iterate to ensure we handle modifications correctly (doesn't work with tinymce.each)
456                                         child = element.firstChild;
457                                         while (child) {
458                                                 if (dom.isBlock(child)) {
459                                                         child = dom.split(child.parentNode, child);
460                                                         splitLast = true;
461                                                         nextChild = child.nextSibling && child.nextSibling.firstChild; 
462                                                 } else {
463                                                         nextChild = child.nextSibling;
464                                                         if (splitLast && child.tagName === 'BR') {
465                                                                 dom.remove(child);
466                                                         }
467                                                         splitLast = false;
468                                                 }
469                                                 child = nextChild;
470                                         }
471                                 }
472                         }
473                         
474                         each(selectedBlocks, function(e) {
475                                 e = findItemToOperateOn(e, dom);
476                                 if (e.tagName === oppositeListType || (e.tagName === 'LI' && e.parentNode.tagName === oppositeListType)) {
477                                         hasOppositeType = true;
478                                 } else if (e.tagName === targetListType || (e.tagName === 'LI' && e.parentNode.tagName === targetListType)) {
479                                         hasSameType = true;
480                                 } else {
481                                         hasNonList = true;
482                                 }
483                         });
484
485                         if (hasNonList || hasOppositeType || selectedBlocks.length === 0) {
486                                 actions = {
487                                         'LI': changeList,
488                                         'H1': makeList,
489                                         'H2': makeList,
490                                         'H3': makeList,
491                                         'H4': makeList,
492                                         'H5': makeList,
493                                         'H6': makeList,
494                                         'P': makeList,
495                                         'BODY': makeList,
496                                         'DIV': selectedBlocks.length > 1 ? makeList : wrapList,
497                                         defaultAction: wrapList
498                                 };
499                         } else {
500                                 actions = {
501                                         defaultAction: convertListItemToParagraph
502                                 };
503                         }
504                         this.process(actions);
505                 },
506                 
507                 indent: function() {
508                         var ed = this.ed, dom = ed.dom, indented = [];
509                         
510                         function createWrapItem(element) {
511                                 var wrapItem = dom.create('li', { style: 'list-style-type: none;'});
512                                 dom.insertAfter(wrapItem, element);
513                                 return wrapItem;
514                         }
515                         
516                         function createWrapList(element) {
517                                 var wrapItem = createWrapItem(element),
518                                         list = dom.getParent(element, 'ol,ul'),
519                                         listType = list.tagName,
520                                         listStyle = dom.getStyle(list, 'list-style-type'),
521                                         attrs = {},
522                                         wrapList;
523                                 if (listStyle !== '') {
524                                         attrs.style = 'list-style-type: ' + listStyle + ';';
525                                 }
526                                 wrapList = dom.create(listType, attrs);
527                                 wrapItem.appendChild(wrapList);
528                                 return wrapList;
529                         }
530                         
531                         function indentLI(element) {
532                                 if (!hasParentInList(ed, element, indented)) {
533                                         element = splitNestedLists(element, dom);
534                                         var wrapList = createWrapList(element);
535                                         wrapList.appendChild(element);
536                                         attemptMergeWithAdjacent(wrapList.parentNode, false);
537                                         attemptMergeWithAdjacent(wrapList, false);
538                                         indented.push(element);
539                                 }
540                         }
541                         
542                         this.process({
543                                 'LI': indentLI,
544                                 defaultAction: this.adjustPaddingFunction(true)
545                         });
546                         
547                 },
548                 
549                 outdent: function() {
550                         var t = this, ed = t.ed, dom = ed.dom, outdented = [];
551                         
552                         function outdentLI(element) {
553                                 var listElement, targetParent, align;
554                                 if (!hasParentInList(ed, element, outdented)) {
555                                         if (dom.getStyle(element, 'margin-left') !== '' || dom.getStyle(element, 'padding-left') !== '') {
556                                                 return t.adjustPaddingFunction(false)(element);
557                                         }
558                                         align = dom.getStyle(element, 'text-align', true);
559                                         if (align === 'center' || align === 'right') {
560                                                 dom.setStyle(element, 'text-align', 'left');
561                                                 return;
562                                         }
563                                         element = splitNestedLists(element, dom);
564                                         listElement = element.parentNode;
565                                         targetParent = element.parentNode.parentNode;
566                                         if (targetParent.tagName === 'P') {
567                                                 dom.split(targetParent, element.parentNode);
568                                         } else {
569                                                 dom.split(listElement, element);
570                                                 if (targetParent.tagName === 'LI') {
571                                                         // Nested list, need to split the LI and go back out to the OL/UL element.
572                                                         dom.split(targetParent, element);
573                                                 } else if (!dom.is(targetParent, 'ol,ul')) {
574                                                         dom.rename(element, 'p');
575                                                 }
576                                         }
577                                         outdented.push(element);
578                                 }
579                         }
580                         
581                         this.process({
582                                 'LI': outdentLI,
583                                 defaultAction: this.adjustPaddingFunction(false)
584                         });
585                         
586                         each(outdented, attemptMergeWithAdjacent);
587                 },
588                 
589                 process: function(actions) {
590                         var t = this, sel = t.ed.selection, dom = t.ed.dom, selectedBlocks, r;
591                         function processElement(element) {
592                                 dom.removeClass(element, '_mce_act_on');
593                                 if (!element || element.nodeType !== 1) {
594                                         return;
595                                 }
596                                 element = findItemToOperateOn(element, dom);
597                                 var action = actions[element.tagName];
598                                 if (!action) {
599                                         action = actions.defaultAction;
600                                 }
601                                 action(element);
602                         }
603                         function recurse(element) {
604                                 t.splitSafeEach(element.childNodes, processElement);
605                         }
606                         function brAtEdgeOfSelection(container, offset) {
607                                 return offset >= 0 && container.hasChildNodes() && offset < container.childNodes.length &&
608                                                 container.childNodes[offset].tagName === 'BR';
609                         }
610                         selectedBlocks = sel.getSelectedBlocks();
611                         if (selectedBlocks.length === 0) {
612                                 selectedBlocks = [ dom.getRoot() ];
613                         }
614
615                         r = sel.getRng(true);
616                         if (!r.collapsed) {
617                                 if (brAtEdgeOfSelection(r.endContainer, r.endOffset - 1)) {
618                                         r.setEnd(r.endContainer, r.endOffset - 1);
619                                         sel.setRng(r);
620                                 }
621                                 if (brAtEdgeOfSelection(r.startContainer, r.startOffset)) {
622                                         r.setStart(r.startContainer, r.startOffset + 1);
623                                         sel.setRng(r);
624                                 }
625                         }
626                         bookmark = sel.getBookmark();
627                         actions.OL = actions.UL = recurse;
628                         t.splitSafeEach(selectedBlocks, processElement);
629                         sel.moveToBookmark(bookmark);
630                         bookmark = null;
631                         // Avoids table or image handles being left behind in Firefox.
632                         t.ed.execCommand('mceRepaint');
633                 },
634                 
635                 splitSafeEach: function(elements, f) {
636                         if (tinymce.isGecko && (/Firefox\/[12]\.[0-9]/.test(navigator.userAgent) ||
637                                         /Firefox\/3\.[0-4]/.test(navigator.userAgent))) {
638                                 this.classBasedEach(elements, f);
639                         } else {
640                                 each(elements, f);
641                         }
642                 },
643                 
644                 classBasedEach: function(elements, f) {
645                         var dom = this.ed.dom, nodes, element;
646                         // Mark nodes
647                         each(elements, function(element) {
648                                 dom.addClass(element, '_mce_act_on');
649                         });
650                         nodes = dom.select('._mce_act_on');
651                         while (nodes.length > 0) {
652                                 element = nodes.shift();
653                                 dom.removeClass(element, '_mce_act_on');
654                                 f(element);
655                                 nodes = dom.select('._mce_act_on');
656                         }
657                 },
658                 
659                 adjustPaddingFunction: function(isIndent) {
660                         var indentAmount, indentUnits, ed = this.ed;
661                         indentAmount = ed.settings.indentation;
662                         indentUnits = /[a-z%]+/i.exec(indentAmount);
663                         indentAmount = parseInt(indentAmount, 10);
664                         return function(element) {
665                                 var currentIndent, newIndentAmount;
666                                 currentIndent = parseInt(ed.dom.getStyle(element, 'margin-left') || 0, 10) + parseInt(ed.dom.getStyle(element, 'padding-left') || 0, 10);
667                                 if (isIndent) {
668                                         newIndentAmount = currentIndent + indentAmount;
669                                 } else {
670                                         newIndentAmount = currentIndent - indentAmount;
671                                 }
672                                 ed.dom.setStyle(element, 'padding-left', '');
673                                 ed.dom.setStyle(element, 'margin-left', newIndentAmount > 0 ? newIndentAmount + indentUnits : '');
674                         };
675                 },
676                 
677                 getInfo: function() {
678                         return {
679                                 longname : 'Lists',
680                                 author : 'Moxiecode Systems AB',
681                                 authorurl : 'http://tinymce.moxiecode.com',
682                                 infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/lists',
683                                 version : tinymce.majorVersion + "." + tinymce.minorVersion
684                         };
685                 }
686         });
687         tinymce.PluginManager.add("lists", tinymce.plugins.Lists);
688 }());