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 isListItemNode(node) {
+ return node && /^(LI|DT|DD)$/.test(node.nodeName);
}
function isFirstChild(node) {
return elm === editor.getBody();
}
+ function isTextNode(node) {
+ return node && node.nodeType === 3;
+ }
+
+ function getNormalizedEndPoint(container, offset) {
+ var node = tinymce.dom.RangeUtils.getNode(container, offset);
+
+ if (isListItemNode(container) && isTextNode(node)) {
+ var textNodeOffset = offset >= container.childNodes.length ? node.data.length : 0;
+ return {container: node, offset: textNodeOffset};
+ }
+
+ return {container: container, offset: offset};
+ }
+
+ function normalizeRange(rng) {
+ var outRng = rng.cloneRange();
+
+ var rangeStart = getNormalizedEndPoint(rng.startContainer, rng.startOffset);
+ outRng.setStart(rangeStart.container, rangeStart.offset);
+
+ var rangeEnd = getNormalizedEndPoint(rng.endContainer, rng.endOffset);
+ outRng.setEnd(rangeEnd.container, rangeEnd.offset);
+
+ return outRng;
+ }
+
editor.on('init', function() {
var dom = editor.dom, selection = editor.selection;
rng.setEnd(bookmark.endContainer, bookmark.endOffset);
}
- selection.setRng(rng);
+ selection.setRng(normalizeRange(rng));
}
function createNewTextBlock(contentNode, blockName) {
function getSelectedListItems() {
return tinymce.grep(selection.getSelectedBlocks(), function(block) {
- return /^(LI|DT|DD)$/.test(block.nodeName);
+ return isListItemNode(block);
});
}
}
}
+ 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);
}
}
}
- /**
- * Normalizes the all lists in the specified element.
- */
- function normalizeList(element) {
- tinymce.each(tinymce.grep(dom.select('ol,ul', element)), function(ul) {
- var sibling, parentNode = ul.parentNode;
-
- // Move UL/OL to previous LI if it's the only child of a LI
- if (parentNode.nodeName == 'LI' && parentNode.firstChild == ul) {
- sibling = parentNode.previousSibling;
- if (sibling && sibling.nodeName == 'LI') {
- sibling.appendChild(ul);
-
- if (isEmpty(parentNode)) {
- dom.remove(parentNode);
- }
+ function normalizeLists(element) {
+ tinymce.each(tinymce.grep(dom.select('ol,ul', element)), normalizeList);
+ }
+
+ function normalizeList(ul) {
+ var sibling, parentNode = ul.parentNode;
+
+ // Move UL/OL to previous LI if it's the only child of a LI
+ if (parentNode.nodeName == 'LI' && parentNode.firstChild == ul) {
+ sibling = parentNode.previousSibling;
+ if (sibling && sibling.nodeName == 'LI') {
+ sibling.appendChild(ul);
+
+ if (isEmpty(parentNode)) {
+ dom.remove(parentNode);
}
+ } else {
+ dom.setStyle(parentNode, 'listStyleType', 'none');
}
+ }
- // Append OL/UL to previous LI if it's in a parent OL/UL i.e. old HTML4
- if (isListNode(parentNode)) {
- sibling = parentNode.previousSibling;
- if (sibling && sibling.nodeName == 'LI') {
- sibling.appendChild(ul);
- }
+ // Append OL/UL to previous LI if it's in a parent OL/UL i.e. old HTML4
+ if (isListNode(parentNode)) {
+ sibling = parentNode.previousSibling;
+ if (sibling && sibling.nodeName == 'LI') {
+ sibling.appendChild(ul);
}
- });
+ }
}
function outdent(li) {
}
splitList(ul, li, newBlock);
- normalizeList(ul.parentNode);
+ normalizeLists(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();
}
splitList(rootList, li);
+ normalizeLists(rootList.parentNode);
});
moveToBookmark(bookmark);
}
- function toggleList(listName) {
+ function toggleList(listName, detail) {
var parentList = dom.getParent(selection.getStart(), 'OL,UL,DL');
if (isEditorBody(parentList)) {
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);
}
}
};
}
- self.backspaceDelete = function(isForward) {
- function findNextCaretContainer(rng, isForward) {
- var node = rng.startContainer, offset = rng.startOffset;
- var nonEmptyBlocks, walker;
+ function isBogusBr(node) {
+ if (!isBr(node)) {
+ return false;
+ }
- if (node.nodeType == 3 && (isForward ? offset < node.data.length : offset > 0)) {
- return node;
- }
+ if (dom.isBlock(node.nextSibling) && !isBr(node.previousSibling)) {
+ return true;
+ }
- nonEmptyBlocks = editor.schema.getNonEmptyElements();
- walker = new tinymce.dom.TreeWalker(rng.startContainer);
+ return false;
+ }
- while ((node = walker[isForward ? 'next' : 'prev']())) {
- if (node.nodeName == 'LI' && !node.hasChildNodes()) {
- return node;
- }
+ function findNextCaretContainer(rng, isForward) {
+ var node = rng.startContainer, offset = rng.startOffset;
+ var nonEmptyBlocks, walker;
- if (nonEmptyBlocks[node.nodeName]) {
- return node;
- }
+ if (node.nodeType == 3 && (isForward ? offset < node.data.length : offset > 0)) {
+ return node;
+ }
- if (node.nodeType == 3 && node.data.length > 0) {
- return node;
- }
- }
+ nonEmptyBlocks = editor.schema.getNonEmptyElements();
+ if (node.nodeType == 1) {
+ node = tinymce.dom.RangeUtils.getNode(node, offset);
}
- function mergeLiElements(fromElm, toElm) {
- var node, listNode, ul = fromElm.parentNode;
+ walker = new tinymce.dom.TreeWalker(node, editor.getBody());
- if (isListNode(toElm.lastChild)) {
- listNode = toElm.lastChild;
+ // Delete at <li>|<br></li> then jump over the bogus br
+ if (isForward) {
+ if (isBogusBr(node)) {
+ walker.next();
}
+ }
- node = toElm.lastChild;
- if (node && node.nodeName == 'BR' && fromElm.hasChildNodes()) {
- dom.remove(node);
+ while ((node = walker[isForward ? 'next' : 'prev2']())) {
+ if (node.nodeName == 'LI' && !node.hasChildNodes()) {
+ return node;
}
- if (isEmpty(toElm, true)) {
- dom.$(toElm).empty();
+ if (nonEmptyBlocks[node.nodeName]) {
+ return node;
}
- if (!isEmpty(fromElm, true)) {
- while ((node = fromElm.firstChild)) {
- toElm.appendChild(node);
- }
+ if (node.nodeType == 3 && node.data.length > 0) {
+ return node;
}
+ }
+ }
- if (listNode) {
- toElm.appendChild(listNode);
+ 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);
}
+ }
- dom.remove(fromElm);
+ node = toElm.lastChild;
+ if (node && isBr(node) && fromElm.hasChildNodes()) {
+ dom.remove(node);
+ }
- if (isEmpty(ul) && !isEditorBody(ul)) {
- dom.remove(ul);
+ if (isEmpty(toElm, true)) {
+ dom.$(toElm).empty();
+ }
+
+ if (!isEmpty(fromElm, true)) {
+ while ((node = fromElm.firstChild)) {
+ toElm.appendChild(node);
}
}
- if (selection.isCollapsed()) {
- var li = dom.getParent(selection.getStart(), 'LI'), ul, rng, otherLi;
+ if (listNode) {
+ toElm.appendChild(listNode);
+ }
- if (li) {
- ul = li.parentNode;
- if (isEditorBody(ul) && dom.isEmpty(ul)) {
- return true;
- }
+ dom.remove(fromElm);
- rng = selection.getRng(true);
- otherLi = dom.getParent(findNextCaretContainer(rng, isForward), 'LI');
+ if (isEmpty(ul) && !isEditorBody(ul)) {
+ dom.remove(ul);
+ }
+ }
- if (otherLi && otherLi != li) {
- var bookmark = createBookmark(rng);
+ function backspaceDeleteCaret(isForward) {
+ var li = dom.getParent(selection.getStart(), 'LI'), ul, rng, otherLi;
- if (isForward) {
- mergeLiElements(otherLi, li);
- } else {
- mergeLiElements(li, otherLi);
- }
+ if (li) {
+ ul = li.parentNode;
+ if (isEditorBody(ul) && dom.isEmpty(ul)) {
+ return true;
+ }
- moveToBookmark(bookmark);
+ rng = normalizeRange(selection.getRng(true));
+ otherLi = dom.getParent(findNextCaretContainer(rng, isForward), 'LI');
+
+ if (otherLi && otherLi != li) {
+ var bookmark = createBookmark(rng);
+
+ if (isForward) {
+ mergeLiElements(otherLi, li);
+ } else {
+ mergeLiElements(li, otherLi);
+ }
+
+ moveToBookmark(bookmark);
+ return true;
+ } else if (!otherLi) {
+ if (!isForward && removeList(ul.nodeName)) {
return true;
- } else if (!otherLi) {
- if (!isForward && removeList(ul.nodeName)) {
- return true;
- }
}
}
}
+ }
+
+ function backspaceDeleteRange() {
+ var startListParent = editor.dom.getParent(editor.selection.getStart(), 'LI,DT,DD');
+
+ if (startListParent || getSelectedListItems().length > 0) {
+ editor.undoManager.transact(function() {
+ editor.execCommand('Delete');
+ normalizeLists(editor.getBody());
+ });
+
+ return true;
+ }
+
+ return false;
+ }
+
+ self.backspaceDelete = function(isForward) {
+ return selection.isCollapsed() ? backspaceDeleteCaret(isForward) : backspaceDeleteRange();
};
editor.on('BeforeExecCommand', function(e) {
}
});
- 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'));