/**
* plugin.js
*
- * Copyright, Moxiecode Systems AB
* Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
tinymce.PluginManager.add('lists', function(editor) {
var self = this;
+ function isChildOfBody(elm) {
+ return editor.$.contains(editor.getBody(), elm);
+ }
+
+ function isBr(node) {
+ return node && node.nodeName == 'BR';
+ }
+
function isListNode(node) {
- return node && (/^(OL|UL|DL)$/).test(node.nodeName);
+ return node && (/^(OL|UL|DL)$/).test(node.nodeName) && isChildOfBody(node);
}
function isFirstChild(node) {
return node && !!editor.schema.getTextBlockElements()[node.nodeName];
}
+ function isEditorBody(elm) {
+ return elm === editor.getBody();
+ }
+
editor.on('init', function() {
var dom = editor.dom, selection = editor.selection;
+ function isEmpty(elm, keepBookmarks) {
+ var empty = dom.isEmpty(elm);
+
+ if (keepBookmarks && dom.select('span[data-mce-type=bookmark]').length > 0) {
+ return false;
+ }
+
+ return empty;
+ }
+
/**
* Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with
* index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans
dom.insertAfter(newBlock, ul);
- if (dom.isEmpty(li.parentNode)) {
+ if (isEmpty(li.parentNode)) {
removeAndKeepBookmarks(li.parentNode);
}
dom.remove(li);
- if (dom.isEmpty(ul)) {
+ if (isEmpty(ul)) {
dom.remove(ul);
}
}
+ var shouldMerge = function (listBlock, sibling) {
+ var targetStyle = editor.dom.getStyle(listBlock, 'list-style-type', true);
+ var style = editor.dom.getStyle(sibling, 'list-style-type', true);
+ return targetStyle === style;
+ };
+
function mergeWithAdjacentLists(listBlock) {
var sibling, node;
sibling = listBlock.nextSibling;
- if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName) {
+ if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName && shouldMerge(listBlock, sibling)) {
while ((node = sibling.firstChild)) {
listBlock.appendChild(node);
}
}
sibling = listBlock.previousSibling;
- if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName) {
+ if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName && shouldMerge(listBlock, sibling)) {
while ((node = sibling.firstChild)) {
listBlock.insertBefore(node, listBlock.firstChild);
}
if (sibling && sibling.nodeName == 'LI') {
sibling.appendChild(ul);
- if (dom.isEmpty(parentNode)) {
+ if (isEmpty(parentNode)) {
dom.remove(parentNode);
}
}
var ul = li.parentNode, ulParent = ul.parentNode, newBlock;
function removeEmptyLi(li) {
- if (dom.isEmpty(li)) {
+ if (isEmpty(li)) {
dom.remove(li);
}
}
+ if (isEditorBody(ul)) {
+ return true;
+ }
+
if (li.nodeName == 'DD') {
dom.rename(li, 'DT');
return true;
}
return true;
- } else {
- if (ulParent.nodeName == 'LI') {
- ul = ulParent;
- newBlock = createNewTextBlock(li, 'LI');
- } else if (isListNode(ulParent)) {
- newBlock = createNewTextBlock(li, 'LI');
- } else {
- newBlock = createNewTextBlock(li);
- }
-
- splitList(ul, li, newBlock);
- normalizeList(ul.parentNode);
+ }
- return true;
+ if (ulParent.nodeName == 'LI') {
+ ul = ulParent;
+ newBlock = createNewTextBlock(li, 'LI');
+ } else if (isListNode(ulParent)) {
+ newBlock = createNewTextBlock(li, 'LI');
+ } else {
+ newBlock = createNewTextBlock(li);
}
- return false;
+ splitList(ul, li, newBlock);
+ normalizeList(ul.parentNode);
+
+ return true;
}
function indent(li) {
- var sibling, newList;
+ var sibling, newList, listStyle;
function mergeLists(from, to) {
var node;
return true;
}
- if (sibling && sibling.nodeName == 'LI' && isListNode(li.lastChild)) {
+ /*if (sibling && sibling.nodeName == 'LI' && isListNode(li.lastChild)) {
return false;
- }
+ }*/
sibling = li.previousSibling;
if (sibling && sibling.nodeName == 'LI') {
newList = dom.create(li.parentNode.nodeName);
+ listStyle = dom.getStyle(li.parentNode, 'listStyleType');
+ if (listStyle) {
+ dom.setStyle(newList, 'listStyleType', listStyle);
+ }
sibling.appendChild(newList);
newList.appendChild(li);
mergeLists(li.lastChild, newList);
}
}
- function applyList(listName) {
- var rng = selection.getRng(true), bookmark = createBookmark(rng), listItemName = 'LI';
+ function applyList(listName, detail) {
+ var rng = selection.getRng(true), bookmark, listItemName = 'LI';
+
+ if (dom.getContentEditable(selection.getNode()) === "false") {
+ return;
+ }
listName = listName.toUpperCase();
return;
}
- if (dom.isBlock(node) || node.nodeName == 'BR') {
- if (node.nodeName == 'BR') {
+ if (dom.isBlock(node) || isBr(node)) {
+ if (isBr(node)) {
dom.remove(node);
}
return textBlocks;
}
+ bookmark = createBookmark(rng);
+
tinymce.each(getSelectedTextBlocks(), function(block) {
var listBlock, sibling;
+ var hasCompatibleStyle = function (sib) {
+ var sibStyle = dom.getStyle(sib, 'list-style-type');
+ var detailStyle = detail ? detail['list-style-type'] : '';
+
+ detailStyle = detailStyle === null ? '' : detailStyle;
+
+ return sibStyle === detailStyle;
+ };
+
sibling = block.previousSibling;
- if (sibling && isListNode(sibling) && sibling.nodeName == listName) {
+ if (sibling && isListNode(sibling) && sibling.nodeName == listName && hasCompatibleStyle(sibling)) {
listBlock = sibling;
block = dom.rename(block, listItemName);
sibling.appendChild(block);
block = dom.rename(block, listItemName);
}
+ updateListStyle(listBlock, detail);
mergeWithAdjacentLists(listBlock);
});
moveToBookmark(bookmark);
}
+ var updateListStyle = function (el, detail) {
+ dom.setStyle(el, 'list-style-type', detail ? detail['list-style-type'] : null);
+ };
+
function removeList() {
var bookmark = createBookmark(selection.getRng(true)), root = editor.getBody();
tinymce.each(getSelectedListItems(), function(li) {
var node, rootList;
- if (dom.isEmpty(li)) {
+ if (isEditorBody(li.parentNode)) {
+ return;
+ }
+
+ if (isEmpty(li)) {
outdent(li);
return;
}
moveToBookmark(bookmark);
}
- function toggleList(listName) {
+ function toggleList(listName, detail) {
var parentList = dom.getParent(selection.getStart(), 'OL,UL,DL');
+ if (isEditorBody(parentList)) {
+ return;
+ }
+
if (parentList) {
if (parentList.nodeName == listName) {
removeList(listName);
} else {
var bookmark = createBookmark(selection.getRng(true));
+ updateListStyle(parentList, detail);
mergeWithAdjacentLists(dom.rename(parentList, listName));
+
moveToBookmark(bookmark);
}
} else {
- applyList(listName);
+ applyList(listName, detail);
}
}
};
}
+ function isBogusBr(node) {
+ if (!isBr(node)) {
+ return false;
+ }
+
+ if (dom.isBlock(node.nextSibling) && !isBr(node.previousSibling)) {
+ return true;
+ }
+
+ return false;
+ }
+
self.backspaceDelete = function(isForward) {
function findNextCaretContainer(rng, isForward) {
var node = rng.startContainer, offset = rng.startOffset;
}
nonEmptyBlocks = editor.schema.getNonEmptyElements();
- walker = new tinymce.dom.TreeWalker(rng.startContainer);
+ if (node.nodeType == 1) {
+ node = tinymce.dom.RangeUtils.getNode(node, offset);
+ }
+
+ walker = new tinymce.dom.TreeWalker(node, editor.getBody());
- while ((node = walker[isForward ? 'next' : 'prev']())) {
+ // Delete at <li>|<br></li> then jump over the bogus br
+ if (isForward) {
+ if (isBogusBr(node)) {
+ walker.next();
+ }
+ }
+
+ while ((node = walker[isForward ? 'next' : 'prev2']())) {
if (node.nodeName == 'LI' && !node.hasChildNodes()) {
return node;
}
function mergeLiElements(fromElm, toElm) {
var node, listNode, ul = fromElm.parentNode;
+ if (!isChildOfBody(fromElm) || !isChildOfBody(toElm)) {
+ return;
+ }
+
if (isListNode(toElm.lastChild)) {
listNode = toElm.lastChild;
}
+ if (ul == toElm.lastChild) {
+ if (isBr(ul.previousSibling)) {
+ dom.remove(ul.previousSibling);
+ }
+ }
+
node = toElm.lastChild;
- if (node && node.nodeName == 'BR' && fromElm.hasChildNodes()) {
+ if (node && isBr(node) && fromElm.hasChildNodes()) {
dom.remove(node);
}
- if (dom.isEmpty(toElm)) {
+ if (isEmpty(toElm, true)) {
dom.$(toElm).empty();
}
- if (!dom.isEmpty(fromElm)) {
+ if (!isEmpty(fromElm, true)) {
while ((node = fromElm.firstChild)) {
toElm.appendChild(node);
}
dom.remove(fromElm);
- if (dom.isEmpty(ul)) {
+ if (isEmpty(ul) && !isEditorBody(ul)) {
dom.remove(ul);
}
}
if (selection.isCollapsed()) {
- var li = dom.getParent(selection.getStart(), 'LI');
+ var li = dom.getParent(selection.getStart(), 'LI'), ul, rng, otherLi;
if (li) {
- var rng = selection.getRng(true);
- var otherLi = dom.getParent(findNextCaretContainer(rng, isForward), 'LI');
+ ul = li.parentNode;
+ if (isEditorBody(ul) && dom.isEmpty(ul)) {
+ return true;
+ }
+
+ rng = selection.getRng(true);
+ otherLi = dom.getParent(findNextCaretContainer(rng, isForward), 'LI');
if (otherLi && otherLi != li) {
var bookmark = createBookmark(rng);
return true;
} else if (!otherLi) {
- if (!isForward && removeList(li.parentNode.nodeName)) {
+ if (!isForward && removeList(ul.nodeName)) {
return true;
}
}
}
});
- editor.addCommand('InsertUnorderedList', function() {
- toggleList('UL');
+ editor.addCommand('InsertUnorderedList', function(ui, detail) {
+ toggleList('UL', detail);
});
- editor.addCommand('InsertOrderedList', function() {
- toggleList('OL');
+ editor.addCommand('InsertOrderedList', function(ui, detail) {
+ toggleList('OL', detail);
});
- editor.addCommand('InsertDefinitionList', function() {
- toggleList('DL');
+ editor.addCommand('InsertDefinitionList', function(ui, detail) {
+ toggleList('DL', detail);
});
editor.addQueryStateHandler('InsertUnorderedList', queryListCommandState('UL'));