init
This commit is contained in:
15
public/tinymce/src/plugins/lists/demo/html/demo.html
Normal file
15
public/tinymce/src/plugins/lists/demo/html/demo.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Lists Demo Page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Lists Demo Page</h2>
|
||||
<div id="ephox-ui">
|
||||
<textarea name="" id="" cols="30" rows="10" class="tinymce"></textarea>
|
||||
</div>
|
||||
<script src="../../../../../js/tinymce/tinymce.js"></script>
|
||||
<script src="../../../../../scratch/demos/plugins/lists/demo.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
22
public/tinymce/src/plugins/lists/demo/ts/demo/Demo.ts
Normal file
22
public/tinymce/src/plugins/lists/demo/ts/demo/Demo.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Demo.js
|
||||
*
|
||||
* Released under LGPL License.
|
||||
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
declare let tinymce: any;
|
||||
|
||||
tinymce.init({
|
||||
selector: 'textarea.tinymce',
|
||||
theme: 'modern',
|
||||
skin_url: '../../../../../js/tinymce/skins/lightgray',
|
||||
plugins: 'lists code',
|
||||
toolbar: 'numlist bullist | outdent indent | code',
|
||||
height: 600
|
||||
});
|
||||
|
||||
export {};
|
||||
22
public/tinymce/src/plugins/lists/main/ts/Plugin.ts
Normal file
22
public/tinymce/src/plugins/lists/main/ts/Plugin.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import PluginManager from 'tinymce/core/api/PluginManager';
|
||||
import Api from './api/Api';
|
||||
import Commands from './api/Commands';
|
||||
import Keyboard from './core/Keyboard';
|
||||
import Buttons from './ui/Buttons';
|
||||
|
||||
PluginManager.add('lists', function (editor) {
|
||||
Keyboard.setup(editor);
|
||||
Buttons.register(editor);
|
||||
Commands.register(editor);
|
||||
|
||||
return Api.get(editor);
|
||||
});
|
||||
|
||||
export default function () { }
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import { Arr } from '@ephox/katamari';
|
||||
import { Element} from '@ephox/sugar';
|
||||
import { Editor } from 'tinymce/core/api/Editor';
|
||||
import { Indentation } from '../listModel/Indentation';
|
||||
import { listsIndentation } from '../listModel/ListsIndendation';
|
||||
import { dlIndentation } from '../core/DlIndentation';
|
||||
import Range from '../core/Range';
|
||||
import Selection from '../core/Selection';
|
||||
|
||||
const selectionIndentation = (editor: Editor, indentation: Indentation): boolean => {
|
||||
const lists = Arr.map(Selection.getSelectedListRoots(editor), Element.fromDom);
|
||||
const dlItems = Arr.map(Selection.getSelectedDlItems(editor), Element.fromDom);
|
||||
let isHandled = false;
|
||||
|
||||
if (lists.length || dlItems.length) {
|
||||
const bookmark = editor.selection.getBookmark();
|
||||
|
||||
listsIndentation(editor, lists, indentation);
|
||||
dlIndentation(editor, indentation, dlItems);
|
||||
|
||||
editor.selection.moveToBookmark(bookmark);
|
||||
editor.selection.setRng(Range.normalizeRange(editor.selection.getRng()));
|
||||
editor.nodeChanged();
|
||||
isHandled = true;
|
||||
}
|
||||
|
||||
return isHandled;
|
||||
};
|
||||
|
||||
const indentListSelection = (editor: Editor): boolean => {
|
||||
return selectionIndentation(editor, Indentation.Indent);
|
||||
};
|
||||
|
||||
const outdentListSelection = (editor: Editor): boolean => {
|
||||
return selectionIndentation(editor, Indentation.Outdent);
|
||||
};
|
||||
|
||||
const flattenListSelection = (editor: Editor): boolean => {
|
||||
return selectionIndentation(editor, Indentation.Flatten);
|
||||
};
|
||||
|
||||
export {
|
||||
indentListSelection,
|
||||
outdentListSelection,
|
||||
flattenListSelection
|
||||
};
|
||||
282
public/tinymce/src/plugins/lists/main/ts/actions/ToggleList.ts
Normal file
282
public/tinymce/src/plugins/lists/main/ts/actions/ToggleList.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import BookmarkManager from 'tinymce/core/api/dom/BookmarkManager';
|
||||
import Tools from 'tinymce/core/api/util/Tools';
|
||||
import Bookmark from '../core/Bookmark';
|
||||
import NodeType from '../core/NodeType';
|
||||
import Selection from '../core/Selection';
|
||||
import { HTMLElement } from '@ephox/dom-globals';
|
||||
import { flattenListSelection } from './Indendation';
|
||||
|
||||
const updateListStyle = function (dom, el, detail) {
|
||||
const type = detail['list-style-type'] ? detail['list-style-type'] : null;
|
||||
dom.setStyle(el, 'list-style-type', type);
|
||||
};
|
||||
|
||||
const setAttribs = function (elm, attrs) {
|
||||
Tools.each(attrs, function (value, key) {
|
||||
elm.setAttribute(key, value);
|
||||
});
|
||||
};
|
||||
|
||||
const updateListAttrs = function (dom, el, detail) {
|
||||
setAttribs(el, detail['list-attributes']);
|
||||
Tools.each(dom.select('li', el), function (li) {
|
||||
setAttribs(li, detail['list-item-attributes']);
|
||||
});
|
||||
};
|
||||
|
||||
const updateListWithDetails = function (dom, el, detail) {
|
||||
updateListStyle(dom, el, detail);
|
||||
updateListAttrs(dom, el, detail);
|
||||
};
|
||||
|
||||
const removeStyles = (dom, element: HTMLElement, styles: string[]) => {
|
||||
Tools.each(styles, (style) => dom.setStyle(element, { [style]: '' }));
|
||||
};
|
||||
|
||||
const getEndPointNode = function (editor, rng, start, root) {
|
||||
let container, offset;
|
||||
|
||||
container = rng[start ? 'startContainer' : 'endContainer'];
|
||||
offset = rng[start ? 'startOffset' : 'endOffset'];
|
||||
|
||||
// Resolve node index
|
||||
if (container.nodeType === 1) {
|
||||
container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
|
||||
}
|
||||
|
||||
if (!start && NodeType.isBr(container.nextSibling)) {
|
||||
container = container.nextSibling;
|
||||
}
|
||||
|
||||
while (container.parentNode !== root) {
|
||||
if (NodeType.isTextBlock(editor, container)) {
|
||||
return container;
|
||||
}
|
||||
|
||||
if (/^(TD|TH)$/.test(container.parentNode.nodeName)) {
|
||||
return container;
|
||||
}
|
||||
|
||||
container = container.parentNode;
|
||||
}
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
const getSelectedTextBlocks = function (editor, rng, root) {
|
||||
const textBlocks = [], dom = editor.dom;
|
||||
|
||||
const startNode = getEndPointNode(editor, rng, true, root);
|
||||
const endNode = getEndPointNode(editor, rng, false, root);
|
||||
let block;
|
||||
const siblings = [];
|
||||
|
||||
for (let node = startNode; node; node = node.nextSibling) {
|
||||
siblings.push(node);
|
||||
|
||||
if (node === endNode) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Tools.each(siblings, function (node) {
|
||||
if (NodeType.isTextBlock(editor, node)) {
|
||||
textBlocks.push(node);
|
||||
block = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (dom.isBlock(node) || NodeType.isBr(node)) {
|
||||
if (NodeType.isBr(node)) {
|
||||
dom.remove(node);
|
||||
}
|
||||
|
||||
block = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const nextSibling = node.nextSibling;
|
||||
if (BookmarkManager.isBookmarkNode(node)) {
|
||||
if (NodeType.isTextBlock(editor, nextSibling) || (!nextSibling && node.parentNode === root)) {
|
||||
block = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!block) {
|
||||
block = dom.create('p');
|
||||
node.parentNode.insertBefore(block, node);
|
||||
textBlocks.push(block);
|
||||
}
|
||||
|
||||
block.appendChild(node);
|
||||
});
|
||||
|
||||
return textBlocks;
|
||||
};
|
||||
|
||||
const hasCompatibleStyle = function (dom, sib, detail) {
|
||||
const sibStyle = dom.getStyle(sib, 'list-style-type');
|
||||
let detailStyle = detail ? detail['list-style-type'] : '';
|
||||
|
||||
detailStyle = detailStyle === null ? '' : detailStyle;
|
||||
|
||||
return sibStyle === detailStyle;
|
||||
};
|
||||
|
||||
const applyList = function (editor, listName: string, detail = {}) {
|
||||
const rng = editor.selection.getRng(true);
|
||||
let bookmark;
|
||||
let listItemName = 'LI';
|
||||
const root = Selection.getClosestListRootElm(editor, editor.selection.getStart(true));
|
||||
const dom = editor.dom;
|
||||
|
||||
if (dom.getContentEditable(editor.selection.getNode()) === 'false') {
|
||||
return;
|
||||
}
|
||||
|
||||
listName = listName.toUpperCase();
|
||||
|
||||
if (listName === 'DL') {
|
||||
listItemName = 'DT';
|
||||
}
|
||||
|
||||
bookmark = Bookmark.createBookmark(rng);
|
||||
|
||||
Tools.each(getSelectedTextBlocks(editor, rng, root), function (block) {
|
||||
let listBlock, sibling;
|
||||
|
||||
sibling = block.previousSibling;
|
||||
if (sibling && NodeType.isListNode(sibling) && sibling.nodeName === listName && hasCompatibleStyle(dom, sibling, detail)) {
|
||||
listBlock = sibling;
|
||||
block = dom.rename(block, listItemName);
|
||||
sibling.appendChild(block);
|
||||
} else {
|
||||
listBlock = dom.create(listName);
|
||||
block.parentNode.insertBefore(listBlock, block);
|
||||
listBlock.appendChild(block);
|
||||
block = dom.rename(block, listItemName);
|
||||
}
|
||||
|
||||
removeStyles(dom, block, [
|
||||
'margin', 'margin-right', 'margin-bottom', 'margin-left', 'margin-top',
|
||||
'padding', 'padding-right', 'padding-bottom', 'padding-left', 'padding-top',
|
||||
]);
|
||||
|
||||
updateListWithDetails(dom, listBlock, detail);
|
||||
mergeWithAdjacentLists(editor.dom, listBlock);
|
||||
});
|
||||
|
||||
editor.selection.setRng(Bookmark.resolveBookmark(bookmark));
|
||||
};
|
||||
|
||||
const isValidLists = function (list1, list2) {
|
||||
return list1 && list2 && NodeType.isListNode(list1) && list1.nodeName === list2.nodeName;
|
||||
};
|
||||
|
||||
const hasSameListStyle = function (dom, list1, list2) {
|
||||
const targetStyle = dom.getStyle(list1, 'list-style-type', true);
|
||||
const style = dom.getStyle(list2, 'list-style-type', true);
|
||||
return targetStyle === style;
|
||||
};
|
||||
|
||||
const hasSameClasses = function (elm1, elm2) {
|
||||
return elm1.className === elm2.className;
|
||||
};
|
||||
|
||||
const shouldMerge = function (dom, list1, list2) {
|
||||
return isValidLists(list1, list2) && hasSameListStyle(dom, list1, list2) && hasSameClasses(list1, list2);
|
||||
};
|
||||
|
||||
const mergeWithAdjacentLists = function (dom, listBlock) {
|
||||
let sibling, node;
|
||||
|
||||
sibling = listBlock.nextSibling;
|
||||
if (shouldMerge(dom, listBlock, sibling)) {
|
||||
while ((node = sibling.firstChild)) {
|
||||
listBlock.appendChild(node);
|
||||
}
|
||||
|
||||
dom.remove(sibling);
|
||||
}
|
||||
|
||||
sibling = listBlock.previousSibling;
|
||||
if (shouldMerge(dom, listBlock, sibling)) {
|
||||
while ((node = sibling.lastChild)) {
|
||||
listBlock.insertBefore(node, listBlock.firstChild);
|
||||
}
|
||||
|
||||
dom.remove(sibling);
|
||||
}
|
||||
};
|
||||
|
||||
const updateList = function (dom, list, listName, detail) {
|
||||
if (list.nodeName !== listName) {
|
||||
const newList = dom.rename(list, listName);
|
||||
updateListWithDetails(dom, newList, detail);
|
||||
} else {
|
||||
updateListWithDetails(dom, list, detail);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleMultipleLists = function (editor, parentList, lists, listName, detail) {
|
||||
if (parentList.nodeName === listName && !hasListStyleDetail(detail)) {
|
||||
flattenListSelection(editor);
|
||||
} else {
|
||||
const bookmark = Bookmark.createBookmark(editor.selection.getRng(true));
|
||||
|
||||
Tools.each([parentList].concat(lists), function (elm) {
|
||||
updateList(editor.dom, elm, listName, detail);
|
||||
});
|
||||
|
||||
editor.selection.setRng(Bookmark.resolveBookmark(bookmark));
|
||||
}
|
||||
};
|
||||
|
||||
const hasListStyleDetail = function (detail) {
|
||||
return 'list-style-type' in detail;
|
||||
};
|
||||
|
||||
const toggleSingleList = function (editor, parentList, listName, detail) {
|
||||
if (parentList === editor.getBody()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parentList) {
|
||||
if (parentList.nodeName === listName && !hasListStyleDetail(detail)) {
|
||||
flattenListSelection(editor);
|
||||
} else {
|
||||
const bookmark = Bookmark.createBookmark(editor.selection.getRng(true));
|
||||
updateListWithDetails(editor.dom, parentList, detail);
|
||||
mergeWithAdjacentLists(editor.dom, editor.dom.rename(parentList, listName));
|
||||
editor.selection.setRng(Bookmark.resolveBookmark(bookmark));
|
||||
}
|
||||
} else {
|
||||
applyList(editor, listName, detail);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleList = function (editor, listName, detail) {
|
||||
const parentList = Selection.getParentList(editor);
|
||||
const selectedSubLists = Selection.getSelectedSubLists(editor);
|
||||
|
||||
detail = detail ? detail : {};
|
||||
|
||||
if (parentList && selectedSubLists.length > 0) {
|
||||
toggleMultipleLists(editor, parentList, selectedSubLists, listName, detail);
|
||||
} else {
|
||||
toggleSingleList(editor, parentList, listName, detail);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
toggleList,
|
||||
mergeWithAdjacentLists
|
||||
};
|
||||
20
public/tinymce/src/plugins/lists/main/ts/api/Api.ts
Normal file
20
public/tinymce/src/plugins/lists/main/ts/api/Api.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import Delete from '../core/Delete';
|
||||
|
||||
const get = function (editor) {
|
||||
return {
|
||||
backspaceDelete (isForward) {
|
||||
Delete.backspaceDelete(editor, isForward);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
get
|
||||
};
|
||||
52
public/tinymce/src/plugins/lists/main/ts/api/Commands.ts
Normal file
52
public/tinymce/src/plugins/lists/main/ts/api/Commands.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import ToggleList from '../actions/ToggleList';
|
||||
import { indentListSelection, outdentListSelection, flattenListSelection } from '../actions/Indendation';
|
||||
|
||||
const queryListCommandState = function (editor, listName) {
|
||||
return function () {
|
||||
const parentList = editor.dom.getParent(editor.selection.getStart(), 'UL,OL,DL');
|
||||
return parentList && parentList.nodeName === listName;
|
||||
};
|
||||
};
|
||||
|
||||
const register = function (editor) {
|
||||
editor.on('BeforeExecCommand', function (e) {
|
||||
const cmd = e.command.toLowerCase();
|
||||
|
||||
if (cmd === 'indent') {
|
||||
indentListSelection(editor);
|
||||
} else if (cmd === 'outdent') {
|
||||
outdentListSelection(editor);
|
||||
}
|
||||
});
|
||||
|
||||
editor.addCommand('InsertUnorderedList', function (ui, detail) {
|
||||
ToggleList.toggleList(editor, 'UL', detail);
|
||||
});
|
||||
|
||||
editor.addCommand('InsertOrderedList', function (ui, detail) {
|
||||
ToggleList.toggleList(editor, 'OL', detail);
|
||||
});
|
||||
|
||||
editor.addCommand('InsertDefinitionList', function (ui, detail) {
|
||||
ToggleList.toggleList(editor, 'DL', detail);
|
||||
});
|
||||
|
||||
editor.addCommand('RemoveList', () => {
|
||||
flattenListSelection(editor);
|
||||
});
|
||||
|
||||
editor.addQueryStateHandler('InsertUnorderedList', queryListCommandState(editor, 'UL'));
|
||||
editor.addQueryStateHandler('InsertOrderedList', queryListCommandState(editor, 'OL'));
|
||||
editor.addQueryStateHandler('InsertDefinitionList', queryListCommandState(editor, 'DL'));
|
||||
};
|
||||
|
||||
export default {
|
||||
register
|
||||
};
|
||||
14
public/tinymce/src/plugins/lists/main/ts/api/Settings.ts
Normal file
14
public/tinymce/src/plugins/lists/main/ts/api/Settings.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
const shouldIndentOnTab = function (editor) {
|
||||
return editor.getParam('lists_indent_on_tab', true);
|
||||
};
|
||||
|
||||
export default {
|
||||
shouldIndentOnTab
|
||||
};
|
||||
126
public/tinymce/src/plugins/lists/main/ts/core/Bookmark.ts
Normal file
126
public/tinymce/src/plugins/lists/main/ts/core/Bookmark.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import DOMUtils from 'tinymce/core/api/dom/DOMUtils';
|
||||
import Range from './Range';
|
||||
|
||||
const DOM = DOMUtils.DOM;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* added to them since they can be restored after a dom operation.
|
||||
*
|
||||
* So this: <p><b>|</b><b>|</b></p>
|
||||
* becomes: <p><b><span data-mce-type="bookmark">|</span></b><b data-mce-type="bookmark">|</span></b></p>
|
||||
*
|
||||
* @param {DOMRange} rng DOM Range to get bookmark on.
|
||||
* @return {Object} Bookmark object.
|
||||
*/
|
||||
const createBookmark = function (rng) {
|
||||
const bookmark = {};
|
||||
|
||||
const setupEndPoint = function (start?) {
|
||||
let offsetNode, container, offset;
|
||||
|
||||
container = rng[start ? 'startContainer' : 'endContainer'];
|
||||
offset = rng[start ? 'startOffset' : 'endOffset'];
|
||||
|
||||
if (container.nodeType === 1) {
|
||||
offsetNode = DOM.create('span', { 'data-mce-type': 'bookmark' });
|
||||
|
||||
if (container.hasChildNodes()) {
|
||||
offset = Math.min(offset, container.childNodes.length - 1);
|
||||
|
||||
if (start) {
|
||||
container.insertBefore(offsetNode, container.childNodes[offset]);
|
||||
} else {
|
||||
DOM.insertAfter(offsetNode, container.childNodes[offset]);
|
||||
}
|
||||
} else {
|
||||
container.appendChild(offsetNode);
|
||||
}
|
||||
|
||||
container = offsetNode;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
bookmark[start ? 'startContainer' : 'endContainer'] = container;
|
||||
bookmark[start ? 'startOffset' : 'endOffset'] = offset;
|
||||
};
|
||||
|
||||
setupEndPoint(true);
|
||||
|
||||
if (!rng.collapsed) {
|
||||
setupEndPoint();
|
||||
}
|
||||
|
||||
return bookmark;
|
||||
};
|
||||
|
||||
const resolveBookmark = function (bookmark) {
|
||||
function restoreEndPoint(start?) {
|
||||
let container, offset, node;
|
||||
|
||||
const nodeIndex = function (container) {
|
||||
let node = container.parentNode.firstChild, idx = 0;
|
||||
|
||||
while (node) {
|
||||
if (node === container) {
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Skip data-mce-type=bookmark nodes
|
||||
if (node.nodeType !== 1 || node.getAttribute('data-mce-type') !== 'bookmark') {
|
||||
idx++;
|
||||
}
|
||||
|
||||
node = node.nextSibling;
|
||||
}
|
||||
|
||||
return -1;
|
||||
};
|
||||
|
||||
container = node = bookmark[start ? 'startContainer' : 'endContainer'];
|
||||
offset = bookmark[start ? 'startOffset' : 'endOffset'];
|
||||
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (container.nodeType === 1) {
|
||||
offset = nodeIndex(container);
|
||||
container = container.parentNode;
|
||||
DOM.remove(node);
|
||||
|
||||
if (!container.hasChildNodes() && DOM.isBlock(container)) {
|
||||
container.appendChild(DOM.create('br'));
|
||||
}
|
||||
}
|
||||
|
||||
bookmark[start ? 'startContainer' : 'endContainer'] = container;
|
||||
bookmark[start ? 'startOffset' : 'endOffset'] = offset;
|
||||
}
|
||||
|
||||
restoreEndPoint(true);
|
||||
restoreEndPoint();
|
||||
|
||||
const rng = DOM.createRng();
|
||||
|
||||
rng.setStart(bookmark.startContainer, bookmark.startOffset);
|
||||
|
||||
if (bookmark.endContainer) {
|
||||
rng.setEnd(bookmark.endContainer, bookmark.endOffset);
|
||||
}
|
||||
|
||||
return Range.normalizeRange(rng);
|
||||
};
|
||||
|
||||
export default {
|
||||
createBookmark,
|
||||
resolveBookmark
|
||||
};
|
||||
265
public/tinymce/src/plugins/lists/main/ts/core/Delete.ts
Normal file
265
public/tinymce/src/plugins/lists/main/ts/core/Delete.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import RangeUtils from 'tinymce/core/api/dom/RangeUtils';
|
||||
import TreeWalker from 'tinymce/core/api/dom/TreeWalker';
|
||||
import VK from 'tinymce/core/api/util/VK';
|
||||
import ToggleList from '../actions/ToggleList';
|
||||
import Bookmark from './Bookmark';
|
||||
import NodeType from './NodeType';
|
||||
import NormalizeLists from './NormalizeLists';
|
||||
import Range from './Range';
|
||||
import Selection from './Selection';
|
||||
import { flattenListSelection } from '../actions/Indendation';
|
||||
import { Arr } from '@ephox/katamari';
|
||||
import { Element, Compare } from '@ephox/sugar';
|
||||
|
||||
const findNextCaretContainer = function (editor, rng, isForward, root) {
|
||||
let node = rng.startContainer;
|
||||
const offset = rng.startOffset;
|
||||
let nonEmptyBlocks, walker;
|
||||
|
||||
if (node.nodeType === 3 && (isForward ? offset < node.data.length : offset > 0)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
nonEmptyBlocks = editor.schema.getNonEmptyElements();
|
||||
if (node.nodeType === 1) {
|
||||
node = RangeUtils.getNode(node, offset);
|
||||
}
|
||||
|
||||
walker = new TreeWalker(node, root);
|
||||
|
||||
// Delete at <li>|<br></li> then jump over the bogus br
|
||||
if (isForward) {
|
||||
if (NodeType.isBogusBr(editor.dom, node)) {
|
||||
walker.next();
|
||||
}
|
||||
}
|
||||
|
||||
while ((node = walker[isForward ? 'next' : 'prev2']())) {
|
||||
if (node.nodeName === 'LI' && !node.hasChildNodes()) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (nonEmptyBlocks[node.nodeName]) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (node.nodeType === 3 && node.data.length > 0) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const hasOnlyOneBlockChild = function (dom, elm) {
|
||||
const childNodes = elm.childNodes;
|
||||
return childNodes.length === 1 && !NodeType.isListNode(childNodes[0]) && dom.isBlock(childNodes[0]);
|
||||
};
|
||||
|
||||
const unwrapSingleBlockChild = function (dom, elm) {
|
||||
if (hasOnlyOneBlockChild(dom, elm)) {
|
||||
dom.remove(elm.firstChild, true);
|
||||
}
|
||||
};
|
||||
|
||||
const moveChildren = function (dom, fromElm, toElm) {
|
||||
let node, targetElm;
|
||||
|
||||
targetElm = hasOnlyOneBlockChild(dom, toElm) ? toElm.firstChild : toElm;
|
||||
unwrapSingleBlockChild(dom, fromElm);
|
||||
|
||||
if (!NodeType.isEmpty(dom, fromElm, true)) {
|
||||
while ((node = fromElm.firstChild)) {
|
||||
targetElm.appendChild(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const mergeLiElements = function (dom, fromElm, toElm) {
|
||||
let node, listNode;
|
||||
const ul = fromElm.parentNode;
|
||||
|
||||
if (!NodeType.isChildOfBody(dom, fromElm) || !NodeType.isChildOfBody(dom, toElm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NodeType.isListNode(toElm.lastChild)) {
|
||||
listNode = toElm.lastChild;
|
||||
}
|
||||
|
||||
if (ul === toElm.lastChild) {
|
||||
if (NodeType.isBr(ul.previousSibling)) {
|
||||
dom.remove(ul.previousSibling);
|
||||
}
|
||||
}
|
||||
|
||||
node = toElm.lastChild;
|
||||
if (node && NodeType.isBr(node) && fromElm.hasChildNodes()) {
|
||||
dom.remove(node);
|
||||
}
|
||||
|
||||
if (NodeType.isEmpty(dom, toElm, true)) {
|
||||
dom.$(toElm).empty();
|
||||
}
|
||||
|
||||
moveChildren(dom, fromElm, toElm);
|
||||
|
||||
if (listNode) {
|
||||
toElm.appendChild(listNode);
|
||||
}
|
||||
|
||||
const contains = Compare.contains(Element.fromDom(toElm), Element.fromDom(fromElm));
|
||||
|
||||
const nestedLists = contains ? dom.getParents(fromElm, NodeType.isListNode, toElm) : [];
|
||||
|
||||
dom.remove(fromElm);
|
||||
|
||||
Arr.each(nestedLists, (list) => {
|
||||
if (NodeType.isEmpty(dom, list) && list !== dom.getRoot()) {
|
||||
dom.remove(list);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const mergeIntoEmptyLi = function (editor, fromLi, toLi) {
|
||||
editor.dom.$(toLi).empty();
|
||||
mergeLiElements(editor.dom, fromLi, toLi);
|
||||
editor.selection.setCursorLocation(toLi);
|
||||
};
|
||||
|
||||
const mergeForward = function (editor, rng, fromLi, toLi) {
|
||||
const dom = editor.dom;
|
||||
|
||||
if (dom.isEmpty(toLi)) {
|
||||
mergeIntoEmptyLi(editor, fromLi, toLi);
|
||||
} else {
|
||||
const bookmark = Bookmark.createBookmark(rng);
|
||||
mergeLiElements(dom, fromLi, toLi);
|
||||
editor.selection.setRng(Bookmark.resolveBookmark(bookmark));
|
||||
}
|
||||
};
|
||||
|
||||
const mergeBackward = function (editor, rng, fromLi, toLi) {
|
||||
const bookmark = Bookmark.createBookmark(rng);
|
||||
mergeLiElements(editor.dom, fromLi, toLi);
|
||||
const resolvedBookmark = Bookmark.resolveBookmark(bookmark);
|
||||
editor.selection.setRng(resolvedBookmark);
|
||||
};
|
||||
|
||||
const backspaceDeleteFromListToListCaret = function (editor, isForward) {
|
||||
const dom = editor.dom, selection = editor.selection;
|
||||
const selectionStartElm = selection.getStart();
|
||||
const root = Selection.getClosestListRootElm(editor, selectionStartElm);
|
||||
const li = dom.getParent(selection.getStart(), 'LI', root);
|
||||
let ul, rng, otherLi;
|
||||
|
||||
if (li) {
|
||||
ul = li.parentNode;
|
||||
if (ul === editor.getBody() && NodeType.isEmpty(dom, ul)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
rng = Range.normalizeRange(selection.getRng(true));
|
||||
otherLi = dom.getParent(findNextCaretContainer(editor, rng, isForward, root), 'LI', root);
|
||||
|
||||
if (otherLi && otherLi !== li) {
|
||||
if (isForward) {
|
||||
mergeForward(editor, rng, otherLi, li);
|
||||
} else {
|
||||
mergeBackward(editor, rng, li, otherLi);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (!otherLi) {
|
||||
if (!isForward) {
|
||||
flattenListSelection(editor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const removeBlock = function (dom, block, root) {
|
||||
const parentBlock = dom.getParent(block.parentNode, dom.isBlock, root);
|
||||
|
||||
dom.remove(block);
|
||||
if (parentBlock && dom.isEmpty(parentBlock)) {
|
||||
dom.remove(parentBlock);
|
||||
}
|
||||
};
|
||||
|
||||
const backspaceDeleteIntoListCaret = function (editor, isForward) {
|
||||
const dom = editor.dom;
|
||||
const selectionStartElm = editor.selection.getStart();
|
||||
const root = Selection.getClosestListRootElm(editor, selectionStartElm);
|
||||
const block = dom.getParent(selectionStartElm, dom.isBlock, root);
|
||||
|
||||
if (block && dom.isEmpty(block)) {
|
||||
const rng = Range.normalizeRange(editor.selection.getRng(true));
|
||||
const otherLi = dom.getParent(findNextCaretContainer(editor, rng, isForward, root), 'LI', root);
|
||||
|
||||
if (otherLi) {
|
||||
editor.undoManager.transact(function () {
|
||||
removeBlock(dom, block, root);
|
||||
ToggleList.mergeWithAdjacentLists(dom, otherLi.parentNode);
|
||||
editor.selection.select(otherLi, true);
|
||||
editor.selection.collapse(isForward);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const backspaceDeleteCaret = function (editor, isForward) {
|
||||
return backspaceDeleteFromListToListCaret(editor, isForward) || backspaceDeleteIntoListCaret(editor, isForward);
|
||||
};
|
||||
|
||||
const backspaceDeleteRange = function (editor) {
|
||||
const selectionStartElm = editor.selection.getStart();
|
||||
const root = Selection.getClosestListRootElm(editor, selectionStartElm);
|
||||
const startListParent = editor.dom.getParent(selectionStartElm, 'LI,DT,DD', root);
|
||||
|
||||
if (startListParent || Selection.getSelectedListItems(editor).length > 0) {
|
||||
editor.undoManager.transact(function () {
|
||||
editor.execCommand('Delete');
|
||||
NormalizeLists.normalizeLists(editor.dom, editor.getBody());
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const backspaceDelete = function (editor, isForward) {
|
||||
return editor.selection.isCollapsed() ? backspaceDeleteCaret(editor, isForward) : backspaceDeleteRange(editor);
|
||||
};
|
||||
|
||||
const setup = function (editor) {
|
||||
editor.on('keydown', function (e) {
|
||||
if (e.keyCode === VK.BACKSPACE) {
|
||||
if (backspaceDelete(editor, false)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
} else if (e.keyCode === VK.DELETE) {
|
||||
if (backspaceDelete(editor, true)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
setup,
|
||||
backspaceDelete
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import { Editor } from 'tinymce/core/api/Editor';
|
||||
import { Compare, Replication, Element, Traverse } from '@ephox/sugar';
|
||||
import SplitList from './SplitList';
|
||||
import { Indentation } from '../listModel/Indentation';
|
||||
import { Arr } from '@ephox/katamari';
|
||||
|
||||
const outdentDlItem = (editor: Editor, item: Element): void => {
|
||||
if (Compare.is(item, 'DD')) {
|
||||
Replication.mutate(item, 'DT');
|
||||
} else if (Compare.is(item, 'DT')) {
|
||||
Traverse.parent(item).each((dl) => SplitList.splitList(editor, dl.dom(), item.dom()));
|
||||
}
|
||||
};
|
||||
|
||||
const indentDlItem = (item: Element): void => {
|
||||
if (Compare.is(item, 'DT')) {
|
||||
Replication.mutate(item, 'DD');
|
||||
}
|
||||
};
|
||||
|
||||
const dlIndentation = (editor: Editor, indentation: Indentation, dlItems: Element[]) => {
|
||||
if (indentation === Indentation.Indent) {
|
||||
Arr.each(dlItems, indentDlItem);
|
||||
} else {
|
||||
Arr.each(dlItems, (item) => outdentDlItem(editor, item));
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
dlIndentation
|
||||
};
|
||||
38
public/tinymce/src/plugins/lists/main/ts/core/Keyboard.ts
Normal file
38
public/tinymce/src/plugins/lists/main/ts/core/Keyboard.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import VK from 'tinymce/core/api/util/VK';
|
||||
import Settings from '../api/Settings';
|
||||
import Delete from './Delete';
|
||||
import { outdentListSelection, indentListSelection } from '../actions/Indendation';
|
||||
|
||||
const setupTabKey = function (editor) {
|
||||
editor.on('keydown', function (e) {
|
||||
// Check for tab but not ctrl/cmd+tab since it switches browser tabs
|
||||
if (e.keyCode !== VK.TAB || VK.metaKeyPressed(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.undoManager.transact(() => {
|
||||
if (e.shiftKey ? outdentListSelection(editor) : indentListSelection(editor)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const setup = function (editor) {
|
||||
if (Settings.shouldIndentOnTab(editor)) {
|
||||
setupTabKey(editor);
|
||||
}
|
||||
|
||||
Delete.setup(editor);
|
||||
};
|
||||
|
||||
export default {
|
||||
setup
|
||||
};
|
||||
95
public/tinymce/src/plugins/lists/main/ts/core/NodeType.ts
Normal file
95
public/tinymce/src/plugins/lists/main/ts/core/NodeType.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import { Node, Text } from '@ephox/dom-globals';
|
||||
|
||||
const isTextNode = function (node: Node): node is Text {
|
||||
return node && node.nodeType === 3;
|
||||
};
|
||||
|
||||
const isListNode = function (node) {
|
||||
return node && (/^(OL|UL|DL)$/).test(node.nodeName);
|
||||
};
|
||||
|
||||
const isOlUlNode = function (node) {
|
||||
return node && (/^(OL|UL)$/).test(node.nodeName);
|
||||
};
|
||||
|
||||
const isListItemNode = function (node) {
|
||||
return node && /^(LI|DT|DD)$/.test(node.nodeName);
|
||||
};
|
||||
|
||||
const isDlItemNode = function (node) {
|
||||
return node && /^(DT|DD)$/.test(node.nodeName);
|
||||
};
|
||||
|
||||
const isTableCellNode = function (node) {
|
||||
return node && /^(TH|TD)$/.test(node.nodeName);
|
||||
};
|
||||
|
||||
const isBr = function (node) {
|
||||
return node && node.nodeName === 'BR';
|
||||
};
|
||||
|
||||
const isFirstChild = function (node) {
|
||||
return node.parentNode.firstChild === node;
|
||||
};
|
||||
|
||||
const isLastChild = function (node) {
|
||||
return node.parentNode.lastChild === node;
|
||||
};
|
||||
|
||||
const isTextBlock = function (editor, node) {
|
||||
return node && !!editor.schema.getTextBlockElements()[node.nodeName];
|
||||
};
|
||||
|
||||
const isBlock = function (node, blockElements) {
|
||||
return node && node.nodeName in blockElements;
|
||||
};
|
||||
|
||||
const isBogusBr = function (dom, node) {
|
||||
if (!isBr(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dom.isBlock(node.nextSibling) && !isBr(node.previousSibling)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const isEmpty = function (dom, elm, keepBookmarks?) {
|
||||
const empty = dom.isEmpty(elm);
|
||||
|
||||
if (keepBookmarks && dom.select('span[data-mce-type=bookmark]', elm).length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return empty;
|
||||
};
|
||||
|
||||
const isChildOfBody = function (dom, elm) {
|
||||
return dom.isChildOf(elm, dom.getRoot());
|
||||
};
|
||||
|
||||
export default {
|
||||
isTextNode,
|
||||
isListNode,
|
||||
isOlUlNode,
|
||||
isDlItemNode,
|
||||
isListItemNode,
|
||||
isTableCellNode,
|
||||
isBr,
|
||||
isFirstChild,
|
||||
isLastChild,
|
||||
isTextBlock,
|
||||
isBlock,
|
||||
isBogusBr,
|
||||
isEmpty,
|
||||
isChildOfBody
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import DOMUtils from 'tinymce/core/api/dom/DOMUtils';
|
||||
import Tools from 'tinymce/core/api/util/Tools';
|
||||
import NodeType from './NodeType';
|
||||
|
||||
const DOM = DOMUtils.DOM;
|
||||
|
||||
const normalizeList = function (dom, ul) {
|
||||
let sibling;
|
||||
const 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 (NodeType.isEmpty(dom, 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 (NodeType.isListNode(parentNode)) {
|
||||
sibling = parentNode.previousSibling;
|
||||
if (sibling && sibling.nodeName === 'LI') {
|
||||
sibling.appendChild(ul);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeLists = function (dom, element) {
|
||||
Tools.each(Tools.grep(dom.select('ol,ul', element)), function (ul) {
|
||||
normalizeList(dom, ul);
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
normalizeList,
|
||||
normalizeLists
|
||||
};
|
||||
58
public/tinymce/src/plugins/lists/main/ts/core/Range.ts
Normal file
58
public/tinymce/src/plugins/lists/main/ts/core/Range.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import RangeUtils from 'tinymce/core/api/dom/RangeUtils';
|
||||
import NodeType from './NodeType';
|
||||
import { Range, Node } from '@ephox/dom-globals';
|
||||
|
||||
interface Point {
|
||||
container: Node;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
const getNormalizedPoint = (container: Node, offset: number): Point => {
|
||||
if (NodeType.isTextNode(container)) {
|
||||
return { container, offset };
|
||||
}
|
||||
|
||||
const node = RangeUtils.getNode(container, offset);
|
||||
if (NodeType.isTextNode(node)) {
|
||||
return {
|
||||
container: node,
|
||||
offset: offset >= container.childNodes.length ? node.data.length : 0
|
||||
};
|
||||
} else if (node.previousSibling && NodeType.isTextNode(node.previousSibling)) {
|
||||
return {
|
||||
container: node.previousSibling,
|
||||
offset: node.previousSibling.data.length
|
||||
};
|
||||
} else if (node.nextSibling && NodeType.isTextNode(node.nextSibling)) {
|
||||
return {
|
||||
container: node.nextSibling,
|
||||
offset: 0
|
||||
};
|
||||
}
|
||||
|
||||
return { container, offset };
|
||||
};
|
||||
|
||||
const normalizeRange = (rng: Range): Range => {
|
||||
const outRng = rng.cloneRange();
|
||||
|
||||
const rangeStart = getNormalizedPoint(rng.startContainer, rng.startOffset);
|
||||
outRng.setStart(rangeStart.container, rangeStart.offset);
|
||||
|
||||
const rangeEnd = getNormalizedPoint(rng.endContainer, rng.endOffset);
|
||||
outRng.setEnd(rangeEnd.container, rangeEnd.offset);
|
||||
|
||||
return outRng;
|
||||
};
|
||||
|
||||
export default {
|
||||
getNormalizedPoint,
|
||||
normalizeRange
|
||||
};
|
||||
108
public/tinymce/src/plugins/lists/main/ts/core/Selection.ts
Normal file
108
public/tinymce/src/plugins/lists/main/ts/core/Selection.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import { Node } from '@ephox/dom-globals';
|
||||
import { Arr, Option } from '@ephox/katamari';
|
||||
import { HTMLElement } from '@ephox/sand';
|
||||
import DomQuery from 'tinymce/core/api/dom/DomQuery';
|
||||
import { Editor } from 'tinymce/core/api/Editor';
|
||||
import Tools from 'tinymce/core/api/util/Tools';
|
||||
import NodeType from './NodeType';
|
||||
|
||||
const getParentList = function (editor) {
|
||||
const selectionStart = editor.selection.getStart(true);
|
||||
|
||||
return editor.dom.getParent(selectionStart, 'OL,UL,DL', getClosestListRootElm(editor, selectionStart));
|
||||
};
|
||||
|
||||
const isParentListSelected = function (parentList, selectedBlocks) {
|
||||
return parentList && selectedBlocks.length === 1 && selectedBlocks[0] === parentList;
|
||||
};
|
||||
|
||||
const findSubLists = function (parentList) {
|
||||
return Tools.grep(parentList.querySelectorAll('ol,ul,dl'), function (elm) {
|
||||
return NodeType.isListNode(elm);
|
||||
});
|
||||
};
|
||||
|
||||
const getSelectedSubLists = function (editor) {
|
||||
const parentList = getParentList(editor);
|
||||
const selectedBlocks = editor.selection.getSelectedBlocks();
|
||||
|
||||
if (isParentListSelected(parentList, selectedBlocks)) {
|
||||
return findSubLists(parentList);
|
||||
} else {
|
||||
return Tools.grep(selectedBlocks, function (elm) {
|
||||
return NodeType.isListNode(elm) && parentList !== elm;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const findParentListItemsNodes = function (editor, elms) {
|
||||
const listItemsElms = Tools.map(elms, function (elm) {
|
||||
const parentLi = editor.dom.getParent(elm, 'li,dd,dt', getClosestListRootElm(editor, elm));
|
||||
|
||||
return parentLi ? parentLi : elm;
|
||||
});
|
||||
|
||||
return DomQuery.unique(listItemsElms);
|
||||
};
|
||||
|
||||
const getSelectedListItems = function (editor) {
|
||||
const selectedBlocks = editor.selection.getSelectedBlocks();
|
||||
return Tools.grep(findParentListItemsNodes(editor, selectedBlocks), function (block) {
|
||||
return NodeType.isListItemNode(block);
|
||||
});
|
||||
};
|
||||
|
||||
const getSelectedDlItems = (editor: Editor): Node[] => {
|
||||
return Arr.filter(getSelectedListItems(editor), NodeType.isDlItemNode);
|
||||
};
|
||||
|
||||
const getClosestListRootElm = function (editor, elm) {
|
||||
const parentTableCell = editor.dom.getParents(elm, 'TD,TH');
|
||||
const root = parentTableCell.length > 0 ? parentTableCell[0] : editor.getBody();
|
||||
|
||||
return root;
|
||||
};
|
||||
|
||||
const findLastParentListNode = (editor: Editor, elm: Node): Option<Node> => {
|
||||
const parentLists = editor.dom.getParents(elm, 'ol,ul', getClosestListRootElm(editor, elm));
|
||||
return Arr.last(parentLists);
|
||||
};
|
||||
|
||||
const getSelectedLists = (editor: Editor): Node[] => {
|
||||
const firstList = findLastParentListNode(editor, editor.selection.getStart());
|
||||
const subsequentLists = Arr.filter(editor.selection.getSelectedBlocks(), NodeType.isOlUlNode);
|
||||
|
||||
return firstList.toArray().concat(subsequentLists);
|
||||
};
|
||||
|
||||
const getSelectedListRoots = (editor: Editor): Node[] => {
|
||||
const selectedLists = getSelectedLists(editor);
|
||||
return getUniqueListRoots(editor, selectedLists);
|
||||
};
|
||||
|
||||
const getUniqueListRoots = (editor: Editor, lists: Node[]): Node[] => {
|
||||
const listRoots = Arr.map(lists, (list) => findLastParentListNode(editor, list).getOr(list));
|
||||
return DomQuery.unique(listRoots);
|
||||
};
|
||||
|
||||
const isList = (editor: Editor): boolean => {
|
||||
const list = getParentList(editor);
|
||||
return HTMLElement.isPrototypeOf(list);
|
||||
};
|
||||
|
||||
export default {
|
||||
isList,
|
||||
getParentList,
|
||||
getSelectedSubLists,
|
||||
getSelectedListItems,
|
||||
getClosestListRootElm,
|
||||
getSelectedDlItems,
|
||||
getSelectedListRoots
|
||||
};
|
||||
59
public/tinymce/src/plugins/lists/main/ts/core/SplitList.ts
Normal file
59
public/tinymce/src/plugins/lists/main/ts/core/SplitList.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import DOMUtils from 'tinymce/core/api/dom/DOMUtils';
|
||||
import NodeType from './NodeType';
|
||||
import { createTextBlock } from './TextBlock';
|
||||
import Tools from 'tinymce/core/api/util/Tools';
|
||||
|
||||
const DOM = DOMUtils.DOM;
|
||||
|
||||
const splitList = function (editor, ul, li) {
|
||||
let tmpRng, fragment, bookmarks, node, newBlock;
|
||||
|
||||
const removeAndKeepBookmarks = function (targetNode) {
|
||||
Tools.each(bookmarks, function (node) {
|
||||
targetNode.parentNode.insertBefore(node, li.parentNode);
|
||||
});
|
||||
|
||||
DOM.remove(targetNode);
|
||||
};
|
||||
|
||||
bookmarks = DOM.select('span[data-mce-type="bookmark"]', ul);
|
||||
newBlock = createTextBlock(editor, li);
|
||||
tmpRng = DOM.createRng();
|
||||
tmpRng.setStartAfter(li);
|
||||
tmpRng.setEndAfter(ul);
|
||||
fragment = tmpRng.extractContents();
|
||||
|
||||
for (node = fragment.firstChild; node; node = node.firstChild) {
|
||||
if (node.nodeName === 'LI' && editor.dom.isEmpty(node)) {
|
||||
DOM.remove(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!editor.dom.isEmpty(fragment)) {
|
||||
DOM.insertAfter(fragment, ul);
|
||||
}
|
||||
|
||||
DOM.insertAfter(newBlock, ul);
|
||||
|
||||
if (NodeType.isEmpty(editor.dom, li.parentNode)) {
|
||||
removeAndKeepBookmarks(li.parentNode);
|
||||
}
|
||||
|
||||
DOM.remove(li);
|
||||
|
||||
if (NodeType.isEmpty(editor.dom, ul)) {
|
||||
DOM.remove(ul);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
splitList
|
||||
};
|
||||
75
public/tinymce/src/plugins/lists/main/ts/core/TextBlock.ts
Normal file
75
public/tinymce/src/plugins/lists/main/ts/core/TextBlock.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import Env from 'tinymce/core/api/Env';
|
||||
import NodeType from './NodeType';
|
||||
import { DocumentFragment, Node } from '@ephox/dom-globals';
|
||||
import { Editor } from 'tinymce/core/api/Editor';
|
||||
|
||||
const createTextBlock = (editor: Editor, contentNode: Node): DocumentFragment => {
|
||||
const dom = editor.dom;
|
||||
const blockElements = editor.schema.getBlockElements();
|
||||
const fragment = dom.createFragment();
|
||||
let node, textBlock, blockName, hasContentNode;
|
||||
|
||||
if (editor.settings.forced_root_block) {
|
||||
blockName = editor.settings.forced_root_block;
|
||||
}
|
||||
|
||||
if (blockName) {
|
||||
textBlock = dom.create(blockName);
|
||||
|
||||
if (textBlock.tagName === editor.settings.forced_root_block) {
|
||||
dom.setAttribs(textBlock, editor.settings.forced_root_block_attrs);
|
||||
}
|
||||
|
||||
if (!NodeType.isBlock(contentNode.firstChild, blockElements)) {
|
||||
fragment.appendChild(textBlock);
|
||||
}
|
||||
}
|
||||
|
||||
if (contentNode) {
|
||||
while ((node = contentNode.firstChild)) {
|
||||
const nodeName = node.nodeName;
|
||||
|
||||
if (!hasContentNode && (nodeName !== 'SPAN' || node.getAttribute('data-mce-type') !== 'bookmark')) {
|
||||
hasContentNode = true;
|
||||
}
|
||||
|
||||
if (NodeType.isBlock(node, blockElements)) {
|
||||
fragment.appendChild(node);
|
||||
textBlock = null;
|
||||
} else {
|
||||
if (blockName) {
|
||||
if (!textBlock) {
|
||||
textBlock = dom.create(blockName);
|
||||
fragment.appendChild(textBlock);
|
||||
}
|
||||
|
||||
textBlock.appendChild(node);
|
||||
} else {
|
||||
fragment.appendChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!editor.settings.forced_root_block) {
|
||||
fragment.appendChild(dom.create('br'));
|
||||
} else {
|
||||
// BR is needed in empty blocks on non IE browsers
|
||||
if (!hasContentNode && (!Env.ie || Env.ie > 10)) {
|
||||
textBlock.appendChild(dom.create('br', { 'data-mce-bogus': '1' }));
|
||||
}
|
||||
}
|
||||
|
||||
return fragment;
|
||||
};
|
||||
|
||||
export {
|
||||
createTextBlock
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import { Document } from '@ephox/dom-globals';
|
||||
import { Arr, Option, Options } from '@ephox/katamari';
|
||||
import { Attr, Css, Element, Insert, InsertAll, Node, Replication } from '@ephox/sugar';
|
||||
import { Entry } from './Entry';
|
||||
import { ListType } from './Util';
|
||||
|
||||
interface Segment {
|
||||
list: Element;
|
||||
item: Element;
|
||||
}
|
||||
|
||||
const joinSegment = (parent: Segment, child: Segment): void => {
|
||||
Insert.append(parent.item, child.list);
|
||||
};
|
||||
|
||||
const joinSegments = (segments: Segment[]): void => {
|
||||
for (let i = 1; i < segments.length; i++) {
|
||||
joinSegment(segments[i - 1], segments[i]);
|
||||
}
|
||||
};
|
||||
|
||||
const appendSegments = (head: Segment[], tail: Segment[]): void => {
|
||||
Options.liftN([ Arr.last(head), Arr.head(tail)], joinSegment);
|
||||
};
|
||||
|
||||
const createSegment = (scope: Document, listType: ListType): Segment => {
|
||||
const segment: Segment = {
|
||||
list: Element.fromTag(listType, scope),
|
||||
item: Element.fromTag('li', scope)
|
||||
};
|
||||
Insert.append(segment.list, segment.item);
|
||||
return segment;
|
||||
};
|
||||
|
||||
const createSegments = (scope: Document, entry: Entry, size: number): Segment[] => {
|
||||
const segments: Segment[] = [];
|
||||
for (let i = 0; i < size; i++) {
|
||||
segments.push(createSegment(scope, entry.listType));
|
||||
}
|
||||
return segments;
|
||||
};
|
||||
|
||||
const populateSegments = (segments: Segment[], entry: Entry): void => {
|
||||
for (let i = 0; i < segments.length - 1; i++) {
|
||||
Css.set(segments[i].item, 'list-style-type', 'none');
|
||||
}
|
||||
Arr.last(segments).each((segment) => {
|
||||
Attr.setAll(segment.list, entry.listAttributes);
|
||||
Attr.setAll(segment.item, entry.itemAttributes);
|
||||
InsertAll.append(segment.item, entry.content);
|
||||
});
|
||||
};
|
||||
|
||||
const normalizeSegment = (segment: Segment, entry: Entry): void => {
|
||||
if (Node.name(segment.list) !== entry.listType) {
|
||||
segment.list = Replication.mutate(segment.list, entry.listType);
|
||||
}
|
||||
Attr.setAll(segment.list, entry.listAttributes);
|
||||
};
|
||||
|
||||
const createItem = (scope: Document, attr: Record<string, any>, content: Element[]): Element => {
|
||||
const item = Element.fromTag('li', scope);
|
||||
Attr.setAll(item, attr);
|
||||
InsertAll.append(item, content);
|
||||
return item;
|
||||
};
|
||||
|
||||
const appendItem = (segment: Segment, item: Element): void => {
|
||||
Insert.append(segment.list, item);
|
||||
segment.item = item;
|
||||
};
|
||||
|
||||
const writeShallow = (scope: Document, cast: Segment[], entry: Entry): Segment[] => {
|
||||
const newCast = cast.slice(0, entry.depth);
|
||||
|
||||
Arr.last(newCast).each((segment) => {
|
||||
const item = createItem(scope, entry.itemAttributes, entry.content);
|
||||
appendItem(segment, item);
|
||||
normalizeSegment(segment, entry);
|
||||
});
|
||||
|
||||
return newCast;
|
||||
};
|
||||
|
||||
const writeDeep = (scope: Document, cast: Segment[], entry: Entry): Segment[] => {
|
||||
const segments = createSegments(scope, entry, entry.depth - cast.length);
|
||||
joinSegments(segments);
|
||||
populateSegments(segments, entry);
|
||||
appendSegments(cast, segments);
|
||||
|
||||
return cast.concat(segments);
|
||||
};
|
||||
|
||||
const composeList = (scope: Document, entries: Entry[]): Option<Element> => {
|
||||
const cast: Segment[] = Arr.foldl(entries, (cast, entry) => {
|
||||
return entry.depth > cast.length ? writeDeep(scope, cast, entry) : writeShallow(scope, cast, entry);
|
||||
}, []);
|
||||
|
||||
return Arr.head(cast).map((segment) => segment.list);
|
||||
};
|
||||
|
||||
export { composeList };
|
||||
67
public/tinymce/src/plugins/lists/main/ts/listModel/Entry.ts
Normal file
67
public/tinymce/src/plugins/lists/main/ts/listModel/Entry.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import { Element, Traverse, Replication, Attr, Node } from '@ephox/sugar';
|
||||
import { Arr, Option } from '@ephox/katamari';
|
||||
import { hasLastChildList, ListType } from './Util';
|
||||
|
||||
/*
|
||||
General workflow: Parse lists to entries -> Manipulate entries -> Compose entries to lists
|
||||
|
||||
0-------1---2--------->Depth
|
||||
<ol> |
|
||||
<li>a</li> | Entry { depth: 1, content: [a], listType: ListType.OL, ... }
|
||||
<li>b | Entry { depth: 1, content: [b], listType: ListType.OL, ... }
|
||||
<ul> |
|
||||
<li>c</li> | Entry { depth: 2, content: [c], listType: ListType.UL, ... }
|
||||
</ul> |
|
||||
</li> |
|
||||
</ol> |
|
||||
0-------1---2--------->Depth
|
||||
*/
|
||||
|
||||
export interface Entry {
|
||||
depth: number;
|
||||
content: Element[];
|
||||
isSelected: boolean;
|
||||
listType: ListType;
|
||||
listAttributes: Record<string, any>;
|
||||
itemAttributes: Record<string, any>;
|
||||
}
|
||||
|
||||
const isIndented = (entry: Entry) => {
|
||||
return entry.depth > 0;
|
||||
};
|
||||
|
||||
const isSelected = (entry: Entry) => {
|
||||
return entry.isSelected;
|
||||
};
|
||||
|
||||
const cloneItemContent = (li: Element): Element[] => {
|
||||
const children = Traverse.children(li);
|
||||
const content = hasLastChildList(li) ? children.slice(0, -1) : children;
|
||||
return Arr.map(content, Replication.deep);
|
||||
};
|
||||
|
||||
const createEntry = (li: Element, depth: number, isSelected: boolean): Option<Entry> => {
|
||||
return Traverse.parent(li).map((list) => {
|
||||
return {
|
||||
depth,
|
||||
isSelected,
|
||||
content: cloneItemContent(li),
|
||||
itemAttributes: Attr.clone(li),
|
||||
listAttributes: Attr.clone(list),
|
||||
listType: Node.name(list) as ListType
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
createEntry,
|
||||
isIndented,
|
||||
isSelected
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import { Entry } from './Entry';
|
||||
|
||||
export const enum Indentation {
|
||||
Indent = 'Indent',
|
||||
Outdent = 'Outdent',
|
||||
Flatten = 'Flatten'
|
||||
}
|
||||
|
||||
export const indentEntry = (indentation: Indentation, entry: Entry): void => {
|
||||
switch (indentation) {
|
||||
case Indentation.Indent:
|
||||
entry.depth ++;
|
||||
break;
|
||||
|
||||
case Indentation.Outdent:
|
||||
entry.depth --;
|
||||
break;
|
||||
|
||||
case Indentation.Flatten:
|
||||
entry.depth = 0;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import { Arr, Fun, Option, Options } from '@ephox/katamari';
|
||||
import { Element, Fragment, InsertAll, Remove } from '@ephox/sugar';
|
||||
import { Editor } from 'tinymce/core/api/Editor';
|
||||
import Selection from '../core/Selection';
|
||||
import { composeList } from './ComposeList';
|
||||
import { Entry, isIndented, isSelected } from './Entry';
|
||||
import { indentEntry, Indentation } from './Indentation';
|
||||
import { normalizeEntries } from './NormalizeEntries';
|
||||
import { EntrySet, ItemSelection, parseLists } from './ParseLists';
|
||||
import { hasFirstChildList } from './Util';
|
||||
import { createTextBlock } from '../core/TextBlock';
|
||||
|
||||
const outdentedComposer = (editor: Editor, entries: Entry[]): Element[] => {
|
||||
return Arr.map(entries, (entry) => {
|
||||
const content = Fragment.fromElements(entry.content);
|
||||
return Element.fromDom(createTextBlock(editor, content.dom()));
|
||||
});
|
||||
};
|
||||
|
||||
const indentedComposer = (editor: Editor, entries: Entry[]): Element[] => {
|
||||
normalizeEntries(entries);
|
||||
return composeList(editor.contentDocument, entries).toArray();
|
||||
};
|
||||
|
||||
const composeEntries = (editor, entries: Entry[]): Element[] => {
|
||||
return Arr.bind(Arr.groupBy(entries, isIndented), (entries) => {
|
||||
const groupIsIndented = Arr.head(entries).map(isIndented).getOr(false);
|
||||
return groupIsIndented ? indentedComposer(editor, entries) : outdentedComposer(editor, entries);
|
||||
});
|
||||
};
|
||||
|
||||
const indentSelectedEntries = (entries: Entry[], indentation: Indentation): void => {
|
||||
Arr.each(Arr.filter(entries, isSelected), (entry) => indentEntry(indentation, entry));
|
||||
};
|
||||
|
||||
const getItemSelection = (editor: Editor): Option<ItemSelection> => {
|
||||
const selectedListItems = Arr.map(Selection.getSelectedListItems(editor), Element.fromDom);
|
||||
|
||||
return Options.liftN([
|
||||
Arr.find(selectedListItems, Fun.not(hasFirstChildList)),
|
||||
Arr.find(Arr.reverse(selectedListItems), Fun.not(hasFirstChildList))
|
||||
], (start, end) => ({ start, end }));
|
||||
};
|
||||
|
||||
const listsIndentation = (editor: Editor, lists: Element[], indentation: Indentation) => {
|
||||
const entrySets: EntrySet[] = parseLists(lists, getItemSelection(editor));
|
||||
|
||||
Arr.each(entrySets, (entrySet) => {
|
||||
indentSelectedEntries(entrySet.entries, indentation);
|
||||
InsertAll.before(entrySet.sourceList, composeEntries(editor, entrySet.entries));
|
||||
Remove.remove(entrySet.sourceList);
|
||||
});
|
||||
};
|
||||
|
||||
export { listsIndentation };
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import { Entry } from './Entry';
|
||||
import { Arr, Merger, Option } from '@ephox/katamari';
|
||||
|
||||
const cloneListProperties = (target: Entry, source: Entry): void => {
|
||||
target.listType = source.listType;
|
||||
target.listAttributes = Merger.merge({}, source.listAttributes);
|
||||
};
|
||||
|
||||
// Closest entry above in the same list
|
||||
const previousSiblingEntry = (entries: Entry[], start: number): Option<Entry> => {
|
||||
const depth = entries[start].depth;
|
||||
for (let i = start - 1; i >= 0; i--) {
|
||||
if (entries[i].depth === depth) {
|
||||
return Option.some(entries[i]);
|
||||
}
|
||||
if (entries[i].depth < depth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Option.none();
|
||||
};
|
||||
|
||||
const normalizeEntries = (entries: Entry[]): void => {
|
||||
Arr.each(entries, (entry, i) => {
|
||||
previousSiblingEntry(entries, i).each((matchingEntry) => {
|
||||
cloneListProperties(entry, matchingEntry);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
normalizeEntries
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import { Arr, Cell, Option } from '@ephox/katamari';
|
||||
import { Compare, Element, Traverse } from '@ephox/sugar';
|
||||
import { createEntry, Entry } from './Entry';
|
||||
import { isList } from './Util';
|
||||
|
||||
type Parser = (depth: number, itemSelection: Option<ItemSelection>, selectionState: Cell<boolean>, element: Element) => Entry[];
|
||||
|
||||
export interface ItemSelection {
|
||||
start: Element;
|
||||
end: Element;
|
||||
}
|
||||
|
||||
export interface EntrySet {
|
||||
entries: Entry[];
|
||||
sourceList: Element;
|
||||
}
|
||||
|
||||
const parseItem: Parser = (depth: number, itemSelection: Option<ItemSelection>, selectionState: Cell<boolean>, item: Element): Entry[] => {
|
||||
return Traverse.firstChild(item).filter(isList).fold(() => {
|
||||
|
||||
// Update selectionState (start)
|
||||
itemSelection.each((selection) => {
|
||||
if (Compare.eq(selection.start, item)) {
|
||||
selectionState.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
const currentItemEntry = createEntry(item, depth, selectionState.get());
|
||||
|
||||
// Update selectionState (end)
|
||||
itemSelection.each((selection) => {
|
||||
if (Compare.eq(selection.end, item)) {
|
||||
selectionState.set(false);
|
||||
}
|
||||
});
|
||||
|
||||
const childListEntries: Entry[] = Traverse.lastChild(item)
|
||||
.filter(isList)
|
||||
.map((list) => parseList(depth, itemSelection, selectionState, list))
|
||||
.getOr([]);
|
||||
|
||||
return currentItemEntry.toArray().concat(childListEntries);
|
||||
}, (list) => parseList(depth, itemSelection, selectionState, list));
|
||||
};
|
||||
|
||||
const parseList: Parser = (depth: number, itemSelection: Option<ItemSelection>, selectionState: Cell<boolean>, list: Element): Entry[] => {
|
||||
return Arr.bind(Traverse.children(list), (element) => {
|
||||
const parser = isList(element) ? parseList : parseItem;
|
||||
const newDepth = depth + 1;
|
||||
return parser(newDepth, itemSelection, selectionState, element);
|
||||
});
|
||||
};
|
||||
|
||||
const parseLists = (lists: Element[], itemSelection: Option<ItemSelection>): EntrySet[] => {
|
||||
const selectionState = Cell(false);
|
||||
const initialDepth = 0;
|
||||
|
||||
return Arr.map(lists, (list) => ({
|
||||
sourceList: list,
|
||||
entries: parseList(initialDepth, itemSelection, selectionState, list)
|
||||
}));
|
||||
};
|
||||
|
||||
export { parseLists };
|
||||
31
public/tinymce/src/plugins/lists/main/ts/listModel/Util.ts
Normal file
31
public/tinymce/src/plugins/lists/main/ts/listModel/Util.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import { Element, Traverse, Compare } from '@ephox/sugar';
|
||||
|
||||
export const enum ListType {
|
||||
OL = 'ol',
|
||||
UL = 'ul'
|
||||
}
|
||||
|
||||
const isList = (el: Element) => {
|
||||
return Compare.is(el, 'OL,UL');
|
||||
};
|
||||
|
||||
const hasFirstChildList = (el: Element) => {
|
||||
return Traverse.firstChild(el).map(isList).getOr(false);
|
||||
};
|
||||
|
||||
const hasLastChildList = (el: Element) => {
|
||||
return Traverse.lastChild(el).map(isList).getOr(false);
|
||||
};
|
||||
|
||||
export {
|
||||
isList,
|
||||
hasFirstChildList,
|
||||
hasLastChildList
|
||||
};
|
||||
65
public/tinymce/src/plugins/lists/main/ts/ui/Buttons.ts
Normal file
65
public/tinymce/src/plugins/lists/main/ts/ui/Buttons.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
|
||||
import Tools from 'tinymce/core/api/util/Tools';
|
||||
import NodeType from '../core/NodeType';
|
||||
|
||||
const findIndex = function (list, predicate) {
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const element = list[index];
|
||||
|
||||
if (predicate(element)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
const listState = function (editor, listName) {
|
||||
return function (e) {
|
||||
const ctrl = e.control;
|
||||
|
||||
editor.on('NodeChange', function (e) {
|
||||
const tableCellIndex = findIndex(e.parents, NodeType.isTableCellNode);
|
||||
const parents = tableCellIndex !== -1 ? e.parents.slice(0, tableCellIndex) : e.parents;
|
||||
const lists = Tools.grep(parents, NodeType.isListNode);
|
||||
ctrl.active(lists.length > 0 && lists[0].nodeName === listName);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const register = function (editor) {
|
||||
const hasPlugin = function (editor, plugin) {
|
||||
const plugins = editor.settings.plugins ? editor.settings.plugins : '';
|
||||
return Tools.inArray(plugins.split(/[ ,]/), plugin) !== -1;
|
||||
};
|
||||
|
||||
if (!hasPlugin(editor, 'advlist')) {
|
||||
editor.addButton('numlist', {
|
||||
active: false,
|
||||
title: 'Numbered list',
|
||||
cmd: 'InsertOrderedList',
|
||||
onPostRender: listState(editor, 'OL')
|
||||
});
|
||||
|
||||
editor.addButton('bullist', {
|
||||
active: false,
|
||||
title: 'Bullet list',
|
||||
cmd: 'InsertUnorderedList',
|
||||
onPostRender: listState(editor, 'UL')
|
||||
});
|
||||
}
|
||||
|
||||
editor.addButton('indent', {
|
||||
icon: 'indent',
|
||||
title: 'Increase indent',
|
||||
cmd: 'Indent'
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
register
|
||||
};
|
||||
133
public/tinymce/src/plugins/lists/test/ts/browser/ApplyDlTest.ts
Normal file
133
public/tinymce/src/plugins/lists/test/ts/browser/ApplyDlTest.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { Pipeline } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { LegacyUnit, TinyLoader } from '@ephox/mcagar';
|
||||
|
||||
import Plugin from 'tinymce/plugins/lists/Plugin';
|
||||
import Theme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('tinymce.lists.browser.ApplyTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
const suite = LegacyUnit.createSuite();
|
||||
|
||||
Plugin();
|
||||
Theme();
|
||||
|
||||
suite.test('Apply DL list to multiple Ps', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<p>a</p>' +
|
||||
'<p>b</p>' +
|
||||
'<p>c</p>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0, 'p:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertDefinitionList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<dl>' +
|
||||
'<dt>a</dt>' +
|
||||
'<dt>b</dt>' +
|
||||
'<dt>c</dt>' +
|
||||
'</dl>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'DT');
|
||||
});
|
||||
|
||||
suite.test('Apply OL list to single P', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<p>a</p>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertDefinitionList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<dl><dt>a</dt></dl>');
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'DT');
|
||||
});
|
||||
|
||||
suite.test('Apply DL to P and merge with adjacent lists', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<dl>' +
|
||||
'<dt>a</dt>' +
|
||||
'</dl>' +
|
||||
'<p>b</p>' +
|
||||
'<dl>' +
|
||||
'<dt>c</dt>' +
|
||||
'</dl>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertDefinitionList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<dl>' +
|
||||
'<dt>a</dt>' +
|
||||
'<dt>b</dt>' +
|
||||
'<dt>c</dt>' +
|
||||
'</dl>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'DT');
|
||||
});
|
||||
|
||||
suite.test('Indent single DT in DL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<dl>' +
|
||||
'<dt>a</dt>' +
|
||||
'</dl>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'dt', 0);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<dl>' +
|
||||
'<dd>a</dd>' +
|
||||
'</dl>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'DD');
|
||||
});
|
||||
|
||||
suite.test('Outdent single DD in DL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<dl>' +
|
||||
'<dd>a</dd>' +
|
||||
'</dl>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'dd', 1);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<dl>' +
|
||||
'<dt>a</dt>' +
|
||||
'</dl>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'DT');
|
||||
});
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
Pipeline.async({}, suite.toSteps(editor), onSuccess, onFailure);
|
||||
}, {
|
||||
plugins: 'lists',
|
||||
add_unload_trigger: false,
|
||||
disable_nodechange: true,
|
||||
indent: false,
|
||||
entities: 'raw',
|
||||
valid_elements:
|
||||
'li[style|class|data-custom],ol[style|class|data-custom],' +
|
||||
'ul[style|class|data-custom],dl,dt,dd,em,strong,span,#p,div,br',
|
||||
valid_styles: {
|
||||
'*': 'color,font-size,font-family,background-color,font-weight,' +
|
||||
'font-style,text-decoration,float,margin,margin-top,margin-right,' +
|
||||
'margin-bottom,margin-left,display,position,top,left,list-style-type'
|
||||
},
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { GeneralSteps, Logger, Pipeline } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { TinyApis, TinyLoader, TinyUi } from '@ephox/mcagar';
|
||||
|
||||
import ListsPlugin from 'tinymce/plugins/lists/Plugin';
|
||||
import ModernTheme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('browser.tinymce.plugins.lists.ApplyListOnParagraphWithStylesTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
|
||||
ModernTheme();
|
||||
ListsPlugin();
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
const tinyApis = TinyApis(editor);
|
||||
const tinyUi = TinyUi(editor);
|
||||
|
||||
Pipeline.async({}, [
|
||||
Logger.t('remove margin from p when applying list on it, but leave other styles', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<p style="color: blue;margin: 30px;margin-right: 30px;margin-bottom: 30px;margin-left: 30px;margin-top: 30px;">test</p>'),
|
||||
tinyApis.sSetCursor([0, 0], 0),
|
||||
tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] button'),
|
||||
tinyApis.sAssertContent('<ul><li style="color: blue;">test</li></ul>')
|
||||
])),
|
||||
Logger.t('remove padding from p when applying list on it, but leave other styles', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<p style="color: red;padding: 30px;padding-right: 30px;padding-bottom: 30px;padding-left: 30px;padding-top: 30px;">test</p>'),
|
||||
tinyApis.sSetCursor([0, 0], 0),
|
||||
tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] button'),
|
||||
tinyApis.sAssertContent('<ul><li style="color: red;">test</li></ul>')
|
||||
]))
|
||||
], onSuccess, onFailure);
|
||||
}, {
|
||||
indent: false,
|
||||
plugins: 'lists',
|
||||
toolbar: 'numlist bullist',
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
984
public/tinymce/src/plugins/lists/test/ts/browser/ApplyTest.ts
Normal file
984
public/tinymce/src/plugins/lists/test/ts/browser/ApplyTest.ts
Normal file
@@ -0,0 +1,984 @@
|
||||
import { Pipeline } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { LegacyUnit, TinyLoader } from '@ephox/mcagar';
|
||||
|
||||
import Env from 'tinymce/core/api/Env';
|
||||
import Plugin from 'tinymce/plugins/lists/Plugin';
|
||||
import Theme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('tinymce.lists.browser.ApplyTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
const suite = LegacyUnit.createSuite();
|
||||
|
||||
Plugin();
|
||||
Theme();
|
||||
|
||||
suite.test('Apply UL list to single P', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<p>a</p>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<ul><li>a</li></ul>');
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply UL list to single empty P', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<p><br></p>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(LegacyUnit.trimBrs(editor.getContent({ format: 'raw' })), '<ul><li></li></ul>');
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply UL list to multiple Ps', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<p>a</p>' +
|
||||
'<p>b</p>' +
|
||||
'<p>c</p>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0, 'p:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply OL list to single P', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<p>a</p>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<ol><li>a</li></ol>');
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply OL list to single empty P', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<p><br></p>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(LegacyUnit.trimBrs(editor.getContent({ format: 'raw' })), '<ol><li></li></ol>');
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply OL list to multiple Ps', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<p>a</p>' +
|
||||
'<p>b</p>' +
|
||||
'<p>c</p>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0, 'p:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply OL to UL list', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0, 'li:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test(
|
||||
'Apply OL to UL list with collapsed selection',
|
||||
function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
}
|
||||
);
|
||||
|
||||
suite.test('Apply UL to OL list', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0, 'li:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply UL to OL list collapsed selection', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply UL to P and merge with adjacent lists', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>' +
|
||||
'<p>b</p>' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply UL to OL and merge with adjacent lists', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>' +
|
||||
'<ol><li>b</li></ol>' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ol li', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply OL to P and merge with adjacent lists', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<p>b</p>' +
|
||||
'<ol>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply OL to UL and merge with adjacent lists', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>1a</li>' +
|
||||
'<li>1b</li>' +
|
||||
'</ol>' +
|
||||
'<ul><li>2a</li><li>2b</li></ul>' +
|
||||
'<ol>' +
|
||||
'<li>3a</li>' +
|
||||
'<li>3b</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>1a</li>' +
|
||||
'<li>1b</li>' +
|
||||
'<li>2a</li>' +
|
||||
'<li>2b</li>' +
|
||||
'<li>3a</li>' +
|
||||
'<li>3b</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test(
|
||||
'Apply OL to UL and DO not merge with adjacent lists because styles are different (exec has style)',
|
||||
function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<ul><li>b</li></ul>' +
|
||||
'<ol>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList', null, { 'list-style-type': 'lower-alpha' });
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<ol style="list-style-type: lower-alpha;"><li>b</li></ol>' +
|
||||
'<ol>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
}
|
||||
);
|
||||
|
||||
suite.test(
|
||||
'Apply OL to P and DO not merge with adjacent lists because styles are different (exec has style)',
|
||||
function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<p>b</p>' +
|
||||
'<ol>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList', null, { 'list-style-type': 'lower-alpha' });
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<ol style="list-style-type: lower-alpha;"><li>b</li></ol>' +
|
||||
'<ol>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
}
|
||||
);
|
||||
|
||||
suite.test(
|
||||
'Apply OL to UL and DO not merge with adjacent lists because styles are different (original has style)',
|
||||
function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol style="list-style-type: upper-roman;">' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<ul><li>b</li></ul>' +
|
||||
'<ol style="list-style-type: upper-roman;">' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol style="list-style-type: upper-roman;">' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<ol><li>b</li></ol>' +
|
||||
'<ol style="list-style-type: upper-roman;">' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
}
|
||||
);
|
||||
|
||||
suite.test(
|
||||
'Apply OL to UL should merge with adjacent lists because styles are the same (both have roman)',
|
||||
function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol style="list-style-type: upper-roman;">' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<ul><li>b</li></ul>' +
|
||||
'<ol style="list-style-type: upper-roman;">' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList', false, { 'list-style-type': 'upper-roman' });
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol style="list-style-type: upper-roman;">' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
}
|
||||
);
|
||||
|
||||
suite.test(
|
||||
'Apply OL to UL should merge with above list because styles are the same (both have lower-roman), but not below list',
|
||||
function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol style="list-style-type: lower-roman;">' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<ul><li>b</li></ul>' +
|
||||
'<ol style="list-style-type: upper-roman;">' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList', false, { 'list-style-type': 'lower-roman' });
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol style="list-style-type: lower-roman;">' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'<ol style="list-style-type: upper-roman;">' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
}
|
||||
);
|
||||
|
||||
suite.test(
|
||||
'Apply OL to UL should merge with below lists because styles are the same (both have roman), but not above list',
|
||||
function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol style="list-style-type: upper-roman;">' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<ul><li>b</li></ul>' +
|
||||
'<ol style="list-style-type: lower-roman;">' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList', false, { 'list-style-type': 'lower-roman' });
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol style="list-style-type: upper-roman;">' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<ol style="list-style-type: lower-roman;">' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
}
|
||||
);
|
||||
|
||||
suite.test(
|
||||
'Apply OL to UL and DO not merge with adjacent lists because classes are different',
|
||||
function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol class="a">' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<ul><li>b</li></ul>' +
|
||||
'<ol class="b">' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol class="a">' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'<ol><li>b</li></ol>' +
|
||||
'<ol class="b">' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
}
|
||||
);
|
||||
|
||||
suite.test('Apply UL list to single text line', function (editor) {
|
||||
editor.settings.forced_root_block = false;
|
||||
|
||||
editor.getBody().innerHTML = (
|
||||
'a'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'body', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<ul><li>a</li></ul>');
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
|
||||
editor.settings.forced_root_block = 'p';
|
||||
});
|
||||
|
||||
suite.test('Apply UL list to single text line with BR', function (editor) {
|
||||
editor.settings.forced_root_block = false;
|
||||
|
||||
editor.getBody().innerHTML = (
|
||||
'a<br>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'body', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<ul><li>a</li></ul>');
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
|
||||
editor.settings.forced_root_block = 'p';
|
||||
});
|
||||
|
||||
suite.test('Apply UL list to multiple lines separated by BR', function (editor) {
|
||||
editor.settings.forced_root_block = false;
|
||||
|
||||
editor.getBody().innerHTML = (
|
||||
'a<br>' +
|
||||
'b<br>' +
|
||||
'c'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
editor.execCommand('SelectAll');
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
|
||||
editor.settings.forced_root_block = 'p';
|
||||
});
|
||||
|
||||
suite.test(
|
||||
'Apply UL list to multiple lines separated by BR and with trailing BR',
|
||||
function (editor) {
|
||||
editor.settings.forced_root_block = false;
|
||||
|
||||
editor.getBody().innerHTML = (
|
||||
'a<br>' +
|
||||
'b<br>' +
|
||||
'c<br>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
editor.execCommand('SelectAll');
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
}
|
||||
);
|
||||
|
||||
suite.test('Apply UL list to multiple formatted lines separated by BR', function (editor) {
|
||||
editor.settings.forced_root_block = false;
|
||||
|
||||
editor.getBody().innerHTML = (
|
||||
'<strong>a</strong><br>' +
|
||||
'<span>b</span><br>' +
|
||||
'<em>c</em>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'strong', 0, 'em', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li><strong>a</strong></li>' +
|
||||
'<li><span>b</span></li>' +
|
||||
'<li><em>c</em></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'STRONG');
|
||||
// Old IE will return the end LI not a big deal
|
||||
LegacyUnit.equal(editor.selection.getEnd().nodeName, Env.ie && Env.ie < 9 ? 'LI' : 'EM');
|
||||
});
|
||||
|
||||
// Ignore on IE 7, 8 this is a known bug not worth fixing
|
||||
if (!Env.ie || Env.ie > 8) {
|
||||
suite.test('Apply UL list to br line and text block line', function (editor) {
|
||||
editor.settings.forced_root_block = false;
|
||||
|
||||
editor.setContent(
|
||||
'a' +
|
||||
'<p>b</p>'
|
||||
);
|
||||
|
||||
const rng = editor.dom.createRng();
|
||||
rng.setStart(editor.getBody().firstChild, 0);
|
||||
rng.setEnd(editor.getBody().lastChild.firstChild, 1);
|
||||
editor.selection.setRng(rng);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
LegacyUnit.equal(editor.selection.getEnd().nodeName, 'LI');
|
||||
});
|
||||
}
|
||||
|
||||
suite.test('Apply UL list to text block line and br line', function (editor) {
|
||||
editor.settings.forced_root_block = false;
|
||||
|
||||
editor.getBody().innerHTML = (
|
||||
'<p>a</p>' +
|
||||
'b'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
const rng = editor.dom.createRng();
|
||||
rng.setStart(editor.getBody().firstChild.firstChild, 0);
|
||||
rng.setEnd(editor.getBody().lastChild, 1);
|
||||
editor.selection.setRng(rng);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'LI');
|
||||
LegacyUnit.equal(editor.selection.getEnd().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply UL list to all BR lines (SelectAll)', function (editor) {
|
||||
editor.settings.forced_root_block = false;
|
||||
|
||||
editor.getBody().innerHTML = (
|
||||
'a<br>' +
|
||||
'b<br>' +
|
||||
'c<br>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
editor.execCommand('SelectAll');
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.settings.forced_root_block = 'p';
|
||||
});
|
||||
|
||||
suite.test('Apply UL list to all P lines (SelectAll)', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<p>a</p>' +
|
||||
'<p>b</p>' +
|
||||
'<p>c</p>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
editor.execCommand('SelectAll');
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Apply UL list to single P', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<p>a</p>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<ul><li>a</li></ul>');
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Apply UL list to more than two paragraphs', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<p>a</p>' +
|
||||
'<p>b</p>' +
|
||||
'<p>c</p>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p:nth-child(1)', 0, 'p:nth-child(3)', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList', false, { 'list-style-type': null });
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<ul><li>a</li><li>b</li><li>c</li></ul>');
|
||||
});
|
||||
|
||||
suite.test('Apply UL with custom attributes', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs('<p>a</p>');
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList', false, {
|
||||
'list-attributes': {
|
||||
'class': 'a',
|
||||
'data-custom': 'c1'
|
||||
}
|
||||
});
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<ul class="a" data-custom="c1"><li>a</li></ul>');
|
||||
});
|
||||
|
||||
suite.test('Apply UL and LI with custom attributes', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs('<p>a</p>');
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList', false, {
|
||||
'list-attributes': {
|
||||
'class': 'a',
|
||||
'data-custom': 'c1'
|
||||
},
|
||||
|
||||
'list-item-attributes': {
|
||||
'class': 'b',
|
||||
'data-custom1': 'c2',
|
||||
'data-custom2': ''
|
||||
}
|
||||
});
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<ul class="a" data-custom="c1"><li class="b" data-custom1="c2" data-custom2="">a</li></ul>');
|
||||
});
|
||||
|
||||
suite.test('Handle one empty unordered list items without error', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.execCommand('SelectAll');
|
||||
LegacyUnit.setSelection(editor, 'li:first', 0, 'li:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getBody().innerHTML,
|
||||
'<p>a</p>' +
|
||||
'<p>b</p>' +
|
||||
'<p><br data-mce-bogus="1"></p>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Handle several empty unordered list items without error', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li></li>' +
|
||||
'<li>c</li>' +
|
||||
'<li></li>' +
|
||||
'<li>d</li>' +
|
||||
'<li></li>' +
|
||||
'<li>e</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:first', 0, 'li:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getBody().innerHTML,
|
||||
'<p>a</p>' +
|
||||
'<p>b</p>' +
|
||||
'<p><br data-mce-bogus=\"1\"></p>' +
|
||||
'<p>c</p>' +
|
||||
'<p><br data-mce-bogus=\"1\"></p>' +
|
||||
'<p>d</p>' +
|
||||
'<p><br data-mce-bogus=\"1\"></p>' +
|
||||
'<p>e</p>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Handle one empty ordered list items without error', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li></li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.execCommand('SelectAll');
|
||||
LegacyUnit.setSelection(editor, 'li:first', 0, 'li:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getBody().innerHTML,
|
||||
'<p>a</p>' +
|
||||
'<p>b</p>' +
|
||||
'<p><br data-mce-bogus="1"></p>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Handle several empty ordered list items without error', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li></li>' +
|
||||
'<li>c</li>' +
|
||||
'<li></li>' +
|
||||
'<li>d</li>' +
|
||||
'<li></li>' +
|
||||
'<li>e</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:first', 0, 'li:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getBody().innerHTML,
|
||||
'<p>a</p>' +
|
||||
'<p>b</p>' +
|
||||
'<p><br data-mce-bogus=\"1\"></p>' +
|
||||
'<p>c</p>' +
|
||||
'<p><br data-mce-bogus=\"1\"></p>' +
|
||||
'<p>d</p>' +
|
||||
'<p><br data-mce-bogus=\"1\"></p>' +
|
||||
'<p>e</p>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Apply list on paragraphs with list between', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<p>a</p>' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'<p>c</p>'
|
||||
);
|
||||
|
||||
editor.execCommand('SelectAll');
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
LegacyUnit.equal(editor.getBody().innerHTML, '<ul><li>a</li></ul><ol><li>b</li></ol><ul><li>c</li></ul>');
|
||||
});
|
||||
|
||||
suite.test('Apply unordered list on children on a fully selected ordered list', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.execCommand('SelectAll');
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
LegacyUnit.equal(editor.getBody().innerHTML, '<ul><li>a<ul><li>b</li></ul></li><li>c</li></ul>');
|
||||
});
|
||||
|
||||
suite.test('Apply unordered list on empty table cell', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<table>' +
|
||||
'<tbody>' +
|
||||
'<tr>' +
|
||||
'<td>' +
|
||||
'<br />' +
|
||||
'</td>' +
|
||||
'</tr>' +
|
||||
'</tbody>' +
|
||||
'</table>'
|
||||
);
|
||||
|
||||
const rng = editor.dom.createRng();
|
||||
rng.setStart(editor.dom.select('td')[0], 0);
|
||||
rng.setEnd(editor.dom.select('td')[0], 1);
|
||||
editor.selection.setRng(rng);
|
||||
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
LegacyUnit.equal(editor.getBody().innerHTML, '<table><tbody><tr><td><ul><li><br></li></ul></td></tr></tbody></table>');
|
||||
});
|
||||
|
||||
suite.test('Apply unordered list on table cell with two lines br', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<table>' +
|
||||
'<tbody>' +
|
||||
'<tr>' +
|
||||
'<td>' +
|
||||
'a<br>b' +
|
||||
'</td>' +
|
||||
'</tr>' +
|
||||
'</tbody>' +
|
||||
'</table>'
|
||||
);
|
||||
|
||||
const rng = editor.dom.createRng();
|
||||
rng.setStart(editor.dom.select('td')[0].firstChild, 0);
|
||||
rng.setEnd(editor.dom.select('td')[0].firstChild, 0);
|
||||
editor.selection.setRng(rng);
|
||||
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
LegacyUnit.equal(editor.getBody().innerHTML, '<table><tbody><tr><td><ul><li>a</li></ul>b</td></tr></tbody></table>');
|
||||
});
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
Pipeline.async({}, suite.toSteps(editor), onSuccess, onFailure);
|
||||
}, {
|
||||
plugins: 'lists',
|
||||
add_unload_trigger: false,
|
||||
disable_nodechange: true,
|
||||
indent: false,
|
||||
entities: 'raw',
|
||||
valid_elements:
|
||||
'li[style|class|data-custom|data-custom1|data-custom2],ol[style|class|data-custom|data-custom1|data-custom2],' +
|
||||
'ul[style|class|data-custom|data-custom1|data-custom2],dl,dt,dd,em,strong,span,#p,div,br',
|
||||
valid_styles: {
|
||||
'*': 'color,font-size,font-family,background-color,font-weight,' +
|
||||
'font-style,text-decoration,float,margin,margin-top,margin-right,' +
|
||||
'margin-bottom,margin-left,display,position,top,left,list-style-type'
|
||||
},
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { GeneralSteps, Keys, Logger, Pipeline } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { TinyActions, TinyApis, TinyLoader } from '@ephox/mcagar';
|
||||
|
||||
import ListsPlugin from 'tinymce/plugins/lists/Plugin';
|
||||
import ModernTheme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('Browser Test: .RemoveTrailingBlockquoteTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
|
||||
ModernTheme();
|
||||
ListsPlugin();
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
const tinyApis = TinyApis(editor);
|
||||
const tinyActions = TinyActions(editor);
|
||||
|
||||
Pipeline.async({}, [
|
||||
Logger.t('backspace from p inside div into li', GeneralSteps.sequence([
|
||||
tinyApis.sFocus,
|
||||
tinyApis.sSetContent('<ul><li>a</li></ul><div><p><br /></p></div>'),
|
||||
tinyApis.sSetCursor([1, 0, 0], 0),
|
||||
tinyActions.sContentKeystroke(Keys.backspace(), { }),
|
||||
tinyApis.sAssertContent('<ul><li>a</li></ul>')
|
||||
])),
|
||||
Logger.t('backspace from p inside blockquote into li', GeneralSteps.sequence([
|
||||
tinyApis.sFocus,
|
||||
tinyApis.sSetContent('<ul><li>a</li></ul><blockquote><p><br /></p></blockquote>'),
|
||||
tinyApis.sSetCursor([1, 0, 0], 0),
|
||||
tinyActions.sContentKeystroke(Keys.backspace(), { }),
|
||||
tinyApis.sAssertContent('<ul><li>a</li></ul>')
|
||||
])),
|
||||
Logger.t('backspace from b inside p inside blockquote into li', GeneralSteps.sequence([
|
||||
tinyApis.sFocus,
|
||||
tinyApis.sSetContent('<ul><li>a</li></ul><blockquote><p><b><br /></b></p></blockquote>'),
|
||||
tinyApis.sSetCursor([1, 0, 0, 0], 0),
|
||||
tinyActions.sContentKeystroke(Keys.backspace(), { }),
|
||||
tinyApis.sAssertContent('<ul><li>a</li></ul>')
|
||||
])),
|
||||
Logger.t('backspace from span inside p inside blockquote into li', GeneralSteps.sequence([
|
||||
tinyApis.sFocus,
|
||||
tinyApis.sSetContent('<ul><li>a</li></ul><blockquote><p><span class="x"><br /></span></p></blockquote>'),
|
||||
tinyApis.sSetCursor([1, 0, 0, 0], 0),
|
||||
tinyActions.sContentKeystroke(Keys.backspace(), { }),
|
||||
tinyApis.sAssertContent('<ul><li>a</li></ul>')
|
||||
])),
|
||||
Logger.t('backspace from p into li', GeneralSteps.sequence([
|
||||
tinyApis.sFocus,
|
||||
tinyApis.sSetContent('<ul><li>a</li></ul><p><br /></p>'),
|
||||
tinyApis.sSetCursor([1, 0], 0),
|
||||
tinyActions.sContentKeystroke(Keys.backspace(), { }),
|
||||
tinyApis.sAssertContent('<ul><li>a</li></ul>')
|
||||
]))
|
||||
], onSuccess, onFailure);
|
||||
}, {
|
||||
indent: false,
|
||||
plugins: 'lists',
|
||||
toolbar: '',
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Pipeline } from '@ephox/agar';
|
||||
import { LegacyUnit } from '@ephox/mcagar';
|
||||
import DomQuery from 'tinymce/core/api/dom/DomQuery';
|
||||
import EditorManager from 'tinymce/core/api/EditorManager';
|
||||
import Plugin from 'tinymce/plugins/lists/Plugin';
|
||||
import ModernTheme from 'tinymce/themes/modern/Theme';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { document } from '@ephox/dom-globals';
|
||||
|
||||
UnitTest.asynctest('tinymce.lists.browser.BackspaceDeleteInlineTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
const suite = LegacyUnit.createSuite();
|
||||
|
||||
Plugin();
|
||||
ModernTheme();
|
||||
|
||||
suite.test('Backspace at beginning of LI on body UL', function (editor) {
|
||||
editor.focus();
|
||||
editor.selection.setCursorLocation(editor.getBody().firstChild.firstChild, 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
LegacyUnit.equal(DomQuery('#lists ul').length, 3);
|
||||
LegacyUnit.equal(DomQuery('#lists li').length, 3);
|
||||
});
|
||||
|
||||
suite.test('Delete at end of LI on body UL', function (editor) {
|
||||
editor.focus();
|
||||
editor.selection.setCursorLocation(editor.getBody().firstChild.firstChild, 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
LegacyUnit.equal(DomQuery('#lists ul').length, 3);
|
||||
LegacyUnit.equal(DomQuery('#lists li').length, 3);
|
||||
});
|
||||
|
||||
const teardown = function (editor, div) {
|
||||
editor.remove();
|
||||
div.parentNode.removeChild(div);
|
||||
};
|
||||
|
||||
const setup = function (success, failure) {
|
||||
const div = document.createElement('div');
|
||||
|
||||
div.innerHTML = (
|
||||
'<div id="lists">' +
|
||||
'<ul><li>before</li></ul>' +
|
||||
'<ul id="inline"><li>x</li></ul>' +
|
||||
'<ul><li>after</li></ul>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
document.body.appendChild(div);
|
||||
|
||||
EditorManager.init({
|
||||
selector: '#inline',
|
||||
inline: true,
|
||||
add_unload_trigger: false,
|
||||
skin: false,
|
||||
plugins: 'lists',
|
||||
disable_nodechange: true,
|
||||
init_instance_callback (editor) {
|
||||
Pipeline.async({}, suite.toSteps(editor), function () {
|
||||
teardown(editor, div);
|
||||
success();
|
||||
}, failure);
|
||||
},
|
||||
valid_styles: {
|
||||
'*': 'color,font-size,font-family,background-color,font-weight,font-style,text-decoration,float,' +
|
||||
'margin,margin-top,margin-right,margin-bottom,margin-left,display,position,top,left,list-style-type'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
setup(success, failure);
|
||||
});
|
||||
@@ -0,0 +1,917 @@
|
||||
import { Pipeline } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { LegacyUnit, TinyLoader } from '@ephox/mcagar';
|
||||
|
||||
import Plugin from 'tinymce/plugins/lists/Plugin';
|
||||
import Theme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('tinymce.lists.browser.BackspaceDeleteTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
const suite = LegacyUnit.createSuite();
|
||||
|
||||
Plugin();
|
||||
Theme();
|
||||
|
||||
suite.test('Backspace at beginning of single LI in UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<p>a</p>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of first LI in UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<p>a</p>' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of middle LI in UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>ab</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of start LI in UL inside UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li li', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>ab' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of middle LI in UL inside UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li li:nth-child(2)', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>bc</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of single LI in UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<p>a</p>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of first LI in UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<p>a</p>' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of middle LI in UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>ab</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of start LI in UL inside UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li li', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>ab' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of middle LI in UL inside UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li li:nth-child(2)', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>bc</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of LI with empty LI above in UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li></li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(3)', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().innerHTML, 'b');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of LI with BR padded empty LI above in UL', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li><br></li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(3)', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().innerHTML, 'b');
|
||||
});
|
||||
|
||||
suite.test('Backspace at empty LI (IE)', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li></li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().innerHTML, 'a');
|
||||
});
|
||||
|
||||
suite.test('Backspace at beginning of LI with empty LI with STRING and BR above in UL', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li><strong><br></strong></li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(3)', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().innerHTML, 'b');
|
||||
});
|
||||
|
||||
suite.test('Backspace at nested LI with adjacent BR', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>1' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'<br>' +
|
||||
'<ul>' +
|
||||
'<li>2</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li>3</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul ul ul li', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<ul><li>1<ul><li>2</li></ul></li><li>3</li></ul>');
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Backspace at LI selected with triple-click in UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(1)', 0, 'li:nth-child(2)', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(LegacyUnit.trimBrs(editor.getContent()),
|
||||
'<ul>' +
|
||||
'<li>b' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Backspace at partially selected list', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<p>abc</p>' +
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 1, 'li:nth-child(2)', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(LegacyUnit.trimBrs(editor.getContent()),
|
||||
'<p>ab</p>' +
|
||||
'<ul>' +
|
||||
'<li style="list-style-type: none;">' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
// Delete
|
||||
|
||||
suite.test('Delete at end of single LI in UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Delete at end of first LI in UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>ab</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Delete at end of middle LI in UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>bc</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Delete at end of start LI in UL inside UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li li', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>bc</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Delete at end of middle LI in UL inside UL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li li:nth-child(2)', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>cd</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Delete at end of LI before empty LI', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li></li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().innerHTML, 'a');
|
||||
});
|
||||
|
||||
suite.test('Delete at end of LI before BR padded empty LI', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li><br></li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().innerHTML, 'a');
|
||||
});
|
||||
|
||||
suite.test('Delete at end of LI before empty LI with STRONG', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li><strong><br></strong></li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().innerHTML, 'a');
|
||||
});
|
||||
|
||||
suite.test('Delete at nested LI with adjacent BR', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>1' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'<br>' +
|
||||
'<ul>' +
|
||||
'<li>2</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li>3</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
editor.selection.setCursorLocation(editor.$('ul ul li')[0], 0);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<ul><li>1<ul><li>2</li></ul></li><li>3</li></ul>');
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Delete at BR before text in LI', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>1</li>' +
|
||||
'<li>2<br></li>' +
|
||||
'<li>3</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
editor.selection.setCursorLocation(editor.$('li')[1], 1);
|
||||
editor.plugins.lists.backspaceDelete(false);
|
||||
|
||||
LegacyUnit.equal(editor.getContent(), '<ul><li>1</li><li>2</li><li>3</li></ul>');
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Backspace merge li elements', function (editor) {
|
||||
// IE allows you to place the caret inside a LI without children
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 0);
|
||||
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
LegacyUnit.equal(editor.selection.getRng(true).startContainer.nodeType, 3, 'Should be a text node');
|
||||
});
|
||||
|
||||
suite.test('Backspace at block inside li element into li without block element', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>1</li>' +
|
||||
'<li><p>2</p></li>' +
|
||||
'<li>3</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(
|
||||
editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>12</li>' +
|
||||
'<li>3</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Backspace at block inside li element into li with block element', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li><p>1</p></li>' +
|
||||
'<li><p>2</p></li>' +
|
||||
'<li>3</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2) p', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(
|
||||
editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li><p>12</p></li>' +
|
||||
'<li>3</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Backspace at block inside li element into li with multiple block elements', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li><p>1</p><p>2</p></li>' +
|
||||
'<li><p>3</p></li>' +
|
||||
'<li>4</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2) p', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(
|
||||
editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li><p>1</p><p>2</p>3</li>' +
|
||||
'<li>4</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Delete at block inside li element into li without block element', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li><p>1</p></li>' +
|
||||
'<li>2</li>' +
|
||||
'<li>3</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(
|
||||
editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li><p>12</p></li>' +
|
||||
'<li>3</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Delete at block inside li element into li with block element', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li><p>1</p></li>' +
|
||||
'<li><p>2</p></li>' +
|
||||
'<li>3</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(1) p', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(
|
||||
editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li><p>12</p></li>' +
|
||||
'<li>3</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Delete at block inside li element into li with multiple block elements', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ul>' +
|
||||
'<li>1</li>' +
|
||||
'<li><p>2</p><p>3</p></li>' +
|
||||
'<li>4</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(1)', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(
|
||||
editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>1<p>2</p><p>3</p></li>' +
|
||||
'<li>4</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Backspace from indented list', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li style="list-style-type: none;">' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ol li ol li ol li:nth-child(1)', 0);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
|
||||
LegacyUnit.equal(
|
||||
editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>ab</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Delete into indented list', function (editor) {
|
||||
editor.getBody().innerHTML = (
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li style="list-style-type: none;">' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ol li:nth-child(1)', 1);
|
||||
editor.plugins.lists.backspaceDelete(true);
|
||||
|
||||
LegacyUnit.equal(
|
||||
editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>ab</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
Pipeline.async({}, suite.toSteps(editor), onSuccess, onFailure);
|
||||
}, {
|
||||
plugins: 'lists',
|
||||
add_unload_trigger: false,
|
||||
disable_nodechange: true,
|
||||
indent: false,
|
||||
entities: 'raw',
|
||||
valid_elements:
|
||||
'li[style|class|data-custom],ol[style|class|data-custom],' +
|
||||
'ul[style|class|data-custom],dl,dt,dd,em,strong,span,#p,div,br',
|
||||
valid_styles: {
|
||||
'*': 'color,font-size,font-family,background-color,font-weight,' +
|
||||
'font-style,text-decoration,float,margin,margin-top,margin-right,' +
|
||||
'margin-bottom,margin-left,display,position,top,left,list-style-type'
|
||||
},
|
||||
content_style: '.mce-content-body { line-height: normal; }', // Breaks tests in phantomjs unless we have this
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import { GeneralSteps, Logger, Pipeline } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { TinyApis, TinyLoader, TinyUi } from '@ephox/mcagar';
|
||||
|
||||
import ListsPlugin from 'tinymce/plugins/lists/Plugin';
|
||||
import ModernTheme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('browser.tinymce.plugins.lists.ChangeListStyleTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
|
||||
ModernTheme();
|
||||
ListsPlugin();
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
const tinyApis = TinyApis(editor);
|
||||
const tinyUi = TinyUi(editor);
|
||||
|
||||
Pipeline.async({}, [
|
||||
Logger.t('ul to ol, cursor only in parent', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ul><li>a</li><ul><li>b</li></ul></ul>'),
|
||||
tinyApis.sSetCursor([0, 0, 0], 0),
|
||||
tinyUi.sClickOnToolbar('click numlist button', 'div[aria-label="Numbered list"] > button'),
|
||||
tinyApis.sAssertContent('<ol><li>a</li><ul><li>b</li></ul></ol>'),
|
||||
tinyApis.sAssertSelection([0, 0, 0], 0, [0, 0, 0], 0)
|
||||
])),
|
||||
Logger.t('ul to ol, selection from parent to sublist', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ul><li>a</li><ol><li>b</li></ol></ul>'),
|
||||
tinyApis.sSetSelection([0, 0, 0], 0, [0, 1, 0, 0], 1),
|
||||
tinyUi.sClickOnToolbar('click numlist button', 'div[aria-label="Numbered list"] > button'),
|
||||
tinyApis.sAssertContent('<ol><li>a</li><ol><li>b</li></ol></ol>'),
|
||||
tinyApis.sAssertSelection([0, 0, 0], 0, [0, 1, 0, 0], 1)
|
||||
])),
|
||||
Logger.t('ol to ul, cursor only in parent', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ol><li>a</li><ol><li>b</li></ol></ol>'),
|
||||
tinyApis.sSetCursor([0, 0, 0], 0),
|
||||
tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] > button'),
|
||||
tinyApis.sAssertContent('<ul><li>a</li><ol><li>b</li></ol></ul>'),
|
||||
tinyApis.sAssertSelection([0, 0, 0], 0, [0, 0, 0], 0)
|
||||
])),
|
||||
Logger.t('ol to ul, selection from parent to sublist', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ol><li>a</li><ul><li>b</li></ul></ol>'),
|
||||
tinyApis.sSetSelection([0, 0, 0], 0, [0, 1, 0, 0], 1),
|
||||
tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] > button'),
|
||||
tinyApis.sAssertContent('<ul><li>a</li><ul><li>b</li></ul></ul>'),
|
||||
tinyApis.sAssertSelection([0, 0, 0], 0, [0, 1, 0, 0], 1)
|
||||
])),
|
||||
Logger.t('alpha to ol, cursor only in parent', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ul style="list-style-type: lower-alpha;"><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ul>'),
|
||||
tinyApis.sSetCursor([0, 0, 0], 0),
|
||||
tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Numbered list"] > button'),
|
||||
tinyApis.sAssertContent('<ol><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ol>'),
|
||||
tinyApis.sAssertSelection([0, 0, 0], 0, [0, 0, 0], 0)
|
||||
])),
|
||||
Logger.t('alpha to ol, selection from parent to sublist', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ul style="list-style-type: lower-alpha;"><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ul>'),
|
||||
tinyApis.sSetSelection([0, 0, 0], 0, [0, 1, 0, 0], 1),
|
||||
tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Numbered list"] > button'),
|
||||
tinyApis.sAssertContent('<ol><li>a</li><ol><li>b</li></ol></ol>'),
|
||||
tinyApis.sAssertSelection([0, 0, 0], 0, [0, 1, 0, 0], 1)
|
||||
])),
|
||||
Logger.t('alpha to ul, cursor only in parent', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ol style="list-style-type: lower-alpha;"><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ol>'),
|
||||
tinyApis.sSetCursor([0, 0, 0], 0),
|
||||
tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] > button'),
|
||||
tinyApis.sAssertContent('<ul><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ul>'),
|
||||
tinyApis.sAssertSelection([0, 0, 0], 0, [0, 0, 0], 0)
|
||||
])),
|
||||
Logger.t('alpha to ul, selection from parent to sublist', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ol style="list-style-type: lower-alpha;"><li>a</li><ol style="list-style-type: lower-alpha;"><li>b</li></ol></ol>'),
|
||||
tinyApis.sSetSelection([0, 0, 0], 0, [0, 1, 0, 0], 1),
|
||||
tinyUi.sClickOnToolbar('click bullist button', 'div[aria-label="Bullet list"] > button'),
|
||||
tinyApis.sAssertContent('<ul><li>a</li><ul><li>b</li></ul></ul>'),
|
||||
tinyApis.sAssertSelection([0, 0, 0], 0, [0, 1, 0, 0], 1)
|
||||
]))
|
||||
], onSuccess, onFailure);
|
||||
}, {
|
||||
indent: false,
|
||||
plugins: 'lists',
|
||||
toolbar: 'numlist bullist',
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
402
public/tinymce/src/plugins/lists/test/ts/browser/IndentTest.ts
Normal file
402
public/tinymce/src/plugins/lists/test/ts/browser/IndentTest.ts
Normal file
@@ -0,0 +1,402 @@
|
||||
import { Pipeline } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { LegacyUnit, TinyLoader } from '@ephox/mcagar';
|
||||
|
||||
import Plugin from 'tinymce/plugins/lists/Plugin';
|
||||
import Theme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('tinymce.lists.browser.IndentTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
const suite = LegacyUnit.createSuite();
|
||||
|
||||
Plugin();
|
||||
Theme();
|
||||
|
||||
suite.test('Indent single LI in OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li style="list-style-type: none;">' +
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Indent middle LI in OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 0);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Indent single LI in OL and retain OLs list style in the new OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol style="list-style-type: lower-alpha;">' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 0);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol style="list-style-type: lower-alpha;">' +
|
||||
'<li>a' +
|
||||
'<ol style="list-style-type: lower-alpha;">' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Indent last LI in OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Indent in table cell in table inside of list should not do anything', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>' +
|
||||
'<table>' +
|
||||
'<tr>' +
|
||||
'<td></td>' +
|
||||
'</tr>' +
|
||||
'</table>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'td', 0);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>' +
|
||||
'<table>' +
|
||||
'<tr>' +
|
||||
'<td></td>' +
|
||||
'</tr>' +
|
||||
'</table>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'TD');
|
||||
});
|
||||
|
||||
suite.test('Indent last LI to same level as middle LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:last', 1);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Indent first LI and nested LI OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0, 'li li', 0);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li style="list-style-type: none;">' +
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Indent second LI to same level as nested LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 0);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Indent second LI to same level as nested LI 2', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li>cd' +
|
||||
'<ul>' +
|
||||
'<li>e</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 1);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>cd</li>' +
|
||||
'<li>e</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Indent second and third LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 0, 'li:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Indent second second li with next sibling to nested li', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul > li:nth-child(2)', 1);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Indent on second li with inner block element', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li><p>a</p></li>' +
|
||||
'<li><p>b</p></li>' +
|
||||
'<li><p>c</p></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul > li:nth-child(2) > p', 0);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'<p>a</p>' +
|
||||
'<ul><li><p>b</p></li></ul>' +
|
||||
'</li>' +
|
||||
'<li><p>c</p></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Indent already indented last li, ul in ol', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li', 0);
|
||||
LegacyUnit.execCommand(editor, 'Indent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li style="list-style-type: none;">' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
});
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
Pipeline.async({}, suite.toSteps(editor), onSuccess, onFailure);
|
||||
}, {
|
||||
plugins: 'lists',
|
||||
add_unload_trigger: false,
|
||||
disable_nodechange: true,
|
||||
indent: false,
|
||||
entities: 'raw',
|
||||
valid_elements:
|
||||
'li[style|class|data-custom],ol[style|class|data-custom],' +
|
||||
'ul[style|class|data-custom],dl,dt,dd,em,strong,span,#p,div,br,table,tr,td',
|
||||
valid_styles: {
|
||||
'*': 'color,font-size,font-family,background-color,font-weight,' +
|
||||
'font-style,text-decoration,float,margin,margin-top,margin-right,' +
|
||||
'margin-bottom,margin-left,display,position,top,left,list-style-type'
|
||||
},
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Pipeline } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { LegacyUnit, TinyLoader } from '@ephox/mcagar';
|
||||
|
||||
import Plugin from 'tinymce/plugins/lists/Plugin';
|
||||
import Theme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('tinymce.lists.browser.IndentTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
const suite = LegacyUnit.createSuite();
|
||||
|
||||
Plugin();
|
||||
Theme();
|
||||
|
||||
suite.test('Remove UL in inline body element contained in LI', function (editor) {
|
||||
editor.setContent('<ul><li>a</li></ul>');
|
||||
editor.selection.setCursorLocation();
|
||||
editor.execCommand('InsertUnorderedList');
|
||||
LegacyUnit.equal(editor.getContent(), '<p>a</p>');
|
||||
});
|
||||
|
||||
suite.test('Backspace in LI in UL in inline body element contained within LI', function (editor) {
|
||||
editor.setContent('<ul><li>a</li></ul>');
|
||||
editor.focus();
|
||||
editor.selection.select(editor.getBody(), true);
|
||||
editor.selection.collapse(true);
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
LegacyUnit.equal(editor.getContent(), '<p>a</p>');
|
||||
});
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
Pipeline.async({}, suite.toSteps(editor), onSuccess, onFailure);
|
||||
}, {
|
||||
inline: true,
|
||||
plugins: 'lists',
|
||||
add_unload_trigger: false,
|
||||
disable_nodechange: true,
|
||||
indent: false,
|
||||
entities: 'raw',
|
||||
valid_elements:
|
||||
'li[style|class|data-custom],ol[style|class|data-custom],' +
|
||||
'ul[style|class|data-custom],dl,dt,dd,em,strong,span,#p,div,br',
|
||||
valid_styles: {
|
||||
'*': 'color,font-size,font-family,background-color,font-weight,' +
|
||||
'font-style,text-decoration,float,margin,margin-top,margin-right,' +
|
||||
'margin-bottom,margin-left,display,position,top,left,list-style-type'
|
||||
},
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Arbitraries } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { document } from '@ephox/dom-globals';
|
||||
import { Arr, Option } from '@ephox/katamari';
|
||||
import { Element } from '@ephox/sugar';
|
||||
import Jsc from '@ephox/wrap-jsverify';
|
||||
import { composeList } from '../../../main/ts/listModel/ComposeList';
|
||||
import { Entry } from '../../../main/ts/listModel/Entry';
|
||||
import { normalizeEntries } from '../../../main/ts/listModel/NormalizeEntries';
|
||||
import { parseLists } from '../../../main/ts/listModel/ParseLists';
|
||||
import { ListType } from 'tinymce/plugins/lists/listModel/Util';
|
||||
|
||||
UnitTest.test('tinymce.lists.browser.ListModelTest', () => {
|
||||
const arbitratyContent = Jsc.bless({
|
||||
generator: Arbitraries.content('inline').generator.map((el) => [el])
|
||||
});
|
||||
|
||||
const arbitraryEntry = Jsc.record({
|
||||
isSelected: Jsc.constant(false),
|
||||
depth: Jsc.integer(1, 10),
|
||||
content: Jsc.small(arbitratyContent),
|
||||
listType: Jsc.oneof(Jsc.constant(ListType.OL), Jsc.constant(ListType.UL)),
|
||||
listAttributes: Jsc.oneof(Jsc.constant({}), Jsc.constant({style: 'list-style-type: lower-alpha;'})),
|
||||
itemAttributes: Jsc.oneof(Jsc.constant({}), Jsc.constant({style: 'color: red;'})),
|
||||
});
|
||||
|
||||
const arbitraryEntries = Jsc.array(arbitraryEntry);
|
||||
|
||||
const composeParseProperty = Jsc.forall(arbitraryEntries, (inputEntries: Entry[]) => {
|
||||
normalizeEntries(inputEntries);
|
||||
const outputEntries = composeParse(inputEntries);
|
||||
return isEqualEntries(inputEntries, outputEntries) || errorMessage(inputEntries, outputEntries);
|
||||
});
|
||||
|
||||
const composeParse = (entries: Entry[]): Entry[] => {
|
||||
return composeList(document, entries)
|
||||
.map((list) => parseLists([list], Option.none()))
|
||||
.bind(Arr.head)
|
||||
.map((entrySet) => entrySet.entries)
|
||||
.getOr([]);
|
||||
};
|
||||
|
||||
const isEqualEntries = (a: Entry[], b: Entry[]): boolean => {
|
||||
return stringifyEntries(a) === stringifyEntries(b);
|
||||
};
|
||||
|
||||
const errorMessage = (inputEntries: Entry[], outputEntries: Entry[]): string => {
|
||||
return `\nPretty print counterexample:\n` +
|
||||
`input: [${stringifyEntries(inputEntries)}\n]\n` +
|
||||
`output: [${stringifyEntries(outputEntries)}\n]`;
|
||||
};
|
||||
|
||||
const stringifyEntries = (entries: Entry[]): string => {
|
||||
return Arr.map(entries, stringifyEntry).join(',');
|
||||
};
|
||||
|
||||
const stringifyEntry = (entry: Entry): string => {
|
||||
return `\n {
|
||||
depth: ${entry.depth}
|
||||
content: ${entry.content.length > 0 ? serializeElements(entry.content) : '[Empty]'}
|
||||
listType: ${entry.listType}
|
||||
isSelected: ${entry.isSelected}
|
||||
listAttributes: ${JSON.stringify(entry.listAttributes)}
|
||||
itemAttributes: ${JSON.stringify(entry.itemAttributes)}
|
||||
}`;
|
||||
};
|
||||
|
||||
const serializeElements = (elms: Element[]): string => {
|
||||
return Arr.map(elms, (el) => el.dom().outerHTML).join('');
|
||||
};
|
||||
|
||||
Jsc.assert(composeParseProperty, {
|
||||
size: 500,
|
||||
tests: 500,
|
||||
quiet: true
|
||||
});
|
||||
|
||||
// Manual testing. To simplify debugging once a counterexample has been found.
|
||||
/* const inputEntries: Entry[] = [
|
||||
{
|
||||
depth: 2,
|
||||
content: [Element.fromHtml('<i>stuff</i>')],
|
||||
listType: ListType.OL,
|
||||
isSelected: false,
|
||||
listAttributes: {style: 'list-style-type: lower-alpha;'},
|
||||
itemAttributes: {}
|
||||
}
|
||||
];
|
||||
throw composeParse(inputEntries); */
|
||||
});
|
||||
425
public/tinymce/src/plugins/lists/test/ts/browser/OutdentTest.ts
Normal file
425
public/tinymce/src/plugins/lists/test/ts/browser/OutdentTest.ts
Normal file
@@ -0,0 +1,425 @@
|
||||
import { Pipeline } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { LegacyUnit, TinyLoader } from '@ephox/mcagar';
|
||||
|
||||
import Plugin from 'tinymce/plugins/lists/Plugin';
|
||||
import Theme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('tinymce.lists.browser.OutdentTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
const suite = LegacyUnit.createSuite();
|
||||
|
||||
Plugin();
|
||||
Theme();
|
||||
|
||||
suite.test('Outdent inside LI in beginning of OL in LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li li', 1);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b' +
|
||||
'<ol>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Outdent inside LI in middle of OL in LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li li:nth-child(2)', 1);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>c' +
|
||||
'<ol>' +
|
||||
'<li>d</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Outdent inside LI in end of OL in LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li li:last', 1);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
// Nested lists in OL elements
|
||||
|
||||
suite.test('Outdent inside LI in beginning of OL in OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ol ol li', 1);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b' +
|
||||
'<ol>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Outdent inside LI in middle of OL in OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ol>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ol ol li:nth-child(2)', 1);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>c' +
|
||||
'<ol>' +
|
||||
'<li>d</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Outdent inside first/last LI in inner OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>1' +
|
||||
'<ol>' +
|
||||
'<li>2</li>' +
|
||||
'<li>3</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>4</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ol ol li:nth-child(1)', 0, 'ol ol li:nth-child(2)', 1);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>1</li>' +
|
||||
'<li>2</li>' +
|
||||
'<li>3</li>' +
|
||||
'<li>4</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getRng(true).startContainer.nodeValue, '2');
|
||||
LegacyUnit.equal(editor.selection.getRng(true).endContainer.nodeValue, '3');
|
||||
});
|
||||
|
||||
suite.test('Outdent inside first LI in inner OL where OL is single child of parent LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ol ol li:first', 0);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b' +
|
||||
'<ol>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Outdent inside LI in end of OL in OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ol ol li:last', 1);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Outdent inside only child LI in OL in OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ol ol li', 0);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'LI');
|
||||
});
|
||||
|
||||
suite.test('Outdent multiple LI in OL and nested OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0, 'li li', 1);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<p>a</p>' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Outdent on li with inner block element', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li><p>a</p></li>' +
|
||||
'<li><p>b</p></li>' +
|
||||
'<li><p>c</p></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li:nth-child(2) p', 0);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li><p>a</p></li>' +
|
||||
'</ul>' +
|
||||
'<p>b</p>' +
|
||||
'<ul>' +
|
||||
'<li><p>c</p></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Outdent on nested li with inner block element', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'<p>a</p>' +
|
||||
'<ul><li><p>b</p></li></ul>' +
|
||||
'</li>' +
|
||||
'<li><p>c</p></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li:nth-child(1) li p', 0);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li><p>a</p></li>' +
|
||||
'<li><p>b</p></li>' +
|
||||
'<li><p>c</p></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Outdent nested ul in ol', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li style="list-style-type: none;">' +
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul', 0);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
});
|
||||
|
||||
suite.test('Outdenting an item should not affect its attributes', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li style="color: red;" class="xyz">a' +
|
||||
'<ul>' +
|
||||
'<li style="color: blue;">b</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul ul li', 0);
|
||||
LegacyUnit.execCommand(editor, 'Outdent');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li style="color: red;" class="xyz">a</li>' +
|
||||
'<li style="color: blue;">b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
});
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
Pipeline.async({}, suite.toSteps(editor), onSuccess, onFailure);
|
||||
}, {
|
||||
plugins: 'lists',
|
||||
add_unload_trigger: false,
|
||||
disable_nodechange: true,
|
||||
indent: false,
|
||||
entities: 'raw',
|
||||
valid_elements:
|
||||
'li[style|class|data-custom],ol[style|class|data-custom],' +
|
||||
'ul[style|class|data-custom],dl,dt,dd,em,strong,span,#p,div,br',
|
||||
valid_styles: {
|
||||
'*': 'color,font-size,font-family,background-color,font-weight,' +
|
||||
'font-style,text-decoration,float,margin,margin-top,margin-right,' +
|
||||
'margin-bottom,margin-left,display,position,top,left,list-style-type'
|
||||
},
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
505
public/tinymce/src/plugins/lists/test/ts/browser/RemoveTest.ts
Normal file
505
public/tinymce/src/plugins/lists/test/ts/browser/RemoveTest.ts
Normal file
@@ -0,0 +1,505 @@
|
||||
import { Pipeline } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { LegacyUnit, TinyLoader } from '@ephox/mcagar';
|
||||
|
||||
import Env from 'tinymce/core/api/Env';
|
||||
import Plugin from 'tinymce/plugins/lists/Plugin';
|
||||
import Theme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('tinymce.lists.browser.RemoveTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
const suite = LegacyUnit.createSuite();
|
||||
|
||||
Plugin();
|
||||
Theme();
|
||||
|
||||
suite.test('Remove UL at single LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<p>a</p>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove UL at start LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<p>a</p>' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove UL at start empty LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li><br></li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<p>\u00a0</p>' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove UL at middle LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>' +
|
||||
'<p>b</p>' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove UL at middle empty LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li><br></li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:nth-child(2)', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>' +
|
||||
'<p>\u00a0</p>' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove UL at end LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:last', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>' +
|
||||
'<p>c</p>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove UL at end empty LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'<li><br></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:last', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>' +
|
||||
'<p>\u00a0</p>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove UL at middle LI inside parent OL', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a</li>' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'<li>e</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li:nth-child(2)', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'<p>c</p>' +
|
||||
'<ol>' +
|
||||
'<li style="list-style-type: none;">' +
|
||||
'<ul>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li>e</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove UL at middle LI inside parent OL (html5)', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li>e</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ul li:nth-child(2)', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'<p>c</p>' +
|
||||
'<ol>' +
|
||||
'<li style="list-style-type: none;">' +
|
||||
'<ul>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li>e</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove OL on a deep nested LI', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c' +
|
||||
'<ol>' +
|
||||
'<li>d</li>' +
|
||||
'<li>e</li>' +
|
||||
'<li>f</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>g</li>' +
|
||||
'<li>h</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>i</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'ol ol ol li:nth-child(2)', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertOrderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ol>' +
|
||||
'<li>a' +
|
||||
'<ol>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c' +
|
||||
'<ol>' +
|
||||
'<li>d</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'<p>e</p>' +
|
||||
'<ol>' +
|
||||
'<li style="list-style-type: none;">' +
|
||||
'<ol>' +
|
||||
'<li style="list-style-type: none;">' +
|
||||
'<ol>' +
|
||||
'<li>f</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>g</li>' +
|
||||
'<li>h</li>' +
|
||||
'</ol>' +
|
||||
'</li>' +
|
||||
'<li>i</li>' +
|
||||
'</ol>'
|
||||
);
|
||||
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove UL with single LI in BR mode', function (editor) {
|
||||
editor.settings.forced_root_block = false;
|
||||
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'a'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'BODY');
|
||||
|
||||
editor.settings.forced_root_block = 'p';
|
||||
});
|
||||
|
||||
suite.test('Remove UL with multiple LI in BR mode', function (editor) {
|
||||
editor.settings.forced_root_block = false;
|
||||
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:first', 1, 'li:last', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'a<br />' +
|
||||
'b'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'BODY');
|
||||
|
||||
editor.settings.forced_root_block = 'p';
|
||||
});
|
||||
|
||||
suite.test('Remove empty UL between two textblocks', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<div>a</div>' +
|
||||
'<ul>' +
|
||||
'<li></li>' +
|
||||
'</ul>' +
|
||||
'<div>b</div>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:first', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<div>a</div>' +
|
||||
'<p>\u00a0</p>' +
|
||||
'<div>b</div>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove indented list with single item', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li li', 0, 'li li', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>' +
|
||||
'<p>b</p>' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getNode().nodeName, 'P');
|
||||
});
|
||||
|
||||
suite.test('Remove indented list with multiple items', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a' +
|
||||
'<ul>' +
|
||||
'<li>b</li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li li:first', 0, 'li li:last', 1);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>' +
|
||||
'<p>b</p>' +
|
||||
'<p>c</p>' +
|
||||
'<ul>' +
|
||||
'<li>d</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().firstChild.data, 'b');
|
||||
LegacyUnit.equal(editor.selection.getEnd().firstChild.data, 'c');
|
||||
});
|
||||
|
||||
suite.test('Remove indented list with multiple items', function (editor) {
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'<li><p>b</p></li>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'p', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<ul>' +
|
||||
'<li>a</li>' +
|
||||
'</ul>' +
|
||||
'<p>b</p>' +
|
||||
'<ul>' +
|
||||
'<li>c</li>' +
|
||||
'</ul>'
|
||||
);
|
||||
});
|
||||
|
||||
// Ignore on IE 7, 8 this is a known bug not worth fixing
|
||||
if (!Env.ie || Env.ie > 8) {
|
||||
suite.test('Remove empty UL between two textblocks in BR mode', function (editor) {
|
||||
editor.settings.forced_root_block = false;
|
||||
|
||||
editor.getBody().innerHTML = LegacyUnit.trimBrs(
|
||||
'<div>a</div>' +
|
||||
'<ul>' +
|
||||
'<li></li>' +
|
||||
'</ul>' +
|
||||
'<div>b</div>'
|
||||
);
|
||||
|
||||
editor.focus();
|
||||
LegacyUnit.setSelection(editor, 'li:first', 0);
|
||||
LegacyUnit.execCommand(editor, 'InsertUnorderedList');
|
||||
|
||||
LegacyUnit.equal(editor.getContent(),
|
||||
'<div>a</div>' +
|
||||
'<br />' +
|
||||
'<div>b</div>'
|
||||
);
|
||||
LegacyUnit.equal(editor.selection.getStart().nodeName, 'BR');
|
||||
|
||||
editor.settings.forced_root_block = 'p';
|
||||
});
|
||||
}
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
Pipeline.async({}, suite.toSteps(editor), onSuccess, onFailure);
|
||||
}, {
|
||||
plugins: 'lists',
|
||||
add_unload_trigger: false,
|
||||
disable_nodechange: true,
|
||||
indent: false,
|
||||
entities: 'raw',
|
||||
valid_elements:
|
||||
'li[style|class|data-custom],ol[style|class|data-custom],' +
|
||||
'ul[style|class|data-custom],dl,dt,dd,em,strong,span,#p,div,br',
|
||||
valid_styles: {
|
||||
'*': 'color,font-size,font-family,background-color,font-weight,' +
|
||||
'font-style,text-decoration,float,margin,margin-top,margin-right,' +
|
||||
'margin-bottom,margin-left,display,position,top,left,list-style-type'
|
||||
},
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import { GeneralSteps, Logger, Pipeline, Step, UiFinder } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { TinyApis, TinyDom, TinyLoader, TinyUi } from '@ephox/mcagar';
|
||||
|
||||
import ListsPlugin from 'tinymce/plugins/lists/Plugin';
|
||||
import ModernTheme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('browser.tinymce.plugins.lists.TableInListTest', function () {
|
||||
const success = arguments[arguments.length - 2];
|
||||
const failure = arguments[arguments.length - 1];
|
||||
|
||||
ModernTheme();
|
||||
ListsPlugin();
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
const tinyApis = TinyApis(editor);
|
||||
const tinyUi = TinyUi(editor);
|
||||
|
||||
Pipeline.async({}, [
|
||||
Logger.t('unlist table in list then add list inside table', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ul><li><table><tbody><tr><td>a</td><td>b</td></tr></tbody></table></li></ul>'),
|
||||
tinyApis.sSetCursor([0, 0, 0, 0, 0, 0, 0], 0),
|
||||
tinyUi.sClickOnToolbar('click list button', 'div[aria-label="Bullet list"] button'),
|
||||
tinyApis.sAssertContent('<ul><li><table><tbody><tr><td><ul><li>a</li></ul></td><td>b</td></tr></tbody></table></li></ul>'),
|
||||
tinyUi.sClickOnToolbar('click list button', 'div[aria-label="Bullet list"] button'),
|
||||
tinyApis.sAssertContent('<ul><li><table><tbody><tr><td><p>a</p></td><td>b</td></tr></tbody></table></li></ul>')
|
||||
])),
|
||||
Logger.t('delete list in table test', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ul><li><table><tbody><tr><td><ul><li><p>a</p></li></ul></td><td><p>b</p></td></tr></tbody></table></li></ul>'),
|
||||
tinyApis.sSetSelection([0, 0, 0, 0, 0, 0, 0, 0, 0], 0, [0, 0, 0, 0, 0, 0, 0, 0, 0], 1),
|
||||
Step.sync(function () {
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
editor.plugins.lists.backspaceDelete();
|
||||
}),
|
||||
tinyApis.sAssertSelection([0, 0, 0, 0, 0, 0, 0], 0, [0, 0, 0, 0, 0, 0, 0], 0),
|
||||
tinyApis.sAssertContent('<ul><li><table><tbody><tr><td><p> </p></td><td><p>b</p></td></tr></tbody></table></li></ul>')
|
||||
])),
|
||||
Logger.t('focus on table cell in list does not activate button', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ul><li><table><tbody><tr><td>a</td><td>b</td></tr></tbody></table></li></ul>'),
|
||||
tinyApis.sSetCursor([0, 0, 0, 0, 0, 0, 0], 0),
|
||||
UiFinder.sNotExists(TinyDom.fromDom(editor.getContainer()), 'div[aria-label="Bullet list"][aria-pressed="true"]')
|
||||
])),
|
||||
Logger.t('indent and outdent li in ul in list in table in list', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ul><li><table><tbody><tr><td><ul><li><p>a</p></li><li><p>b</p></li></ul></td><td><p>b</p></td></tr></tbody></table></li></ul>'),
|
||||
tinyApis.sSetSelection([0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 0, [0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 1),
|
||||
tinyUi.sClickOnToolbar('click increase indent', 'div[aria-label="Increase indent"] button'),
|
||||
tinyApis.sAssertContent('<ul><li><table><tbody><tr><td><ul><li><p>a</p><ul><li><p>b</p></li></ul></li></ul></td><td><p>b</p></td></tr></tbody></table></li></ul>'),
|
||||
tinyUi.sClickOnToolbar('click decrease indent', 'div[aria-label="Decrease indent"] button'),
|
||||
tinyApis.sAssertContent('<ul><li><table><tbody><tr><td><ul><li><p>a</p></li><li><p>b</p></li></ul></td><td><p>b</p></td></tr></tbody></table></li></ul>'),
|
||||
tinyUi.sClickOnToolbar('click decrease indent', 'div[aria-label="Decrease indent"] button'),
|
||||
tinyApis.sAssertContent('<ul><li><table><tbody><tr><td><ul><li><p>a</p></li></ul><p>b</p></td><td><p>b</p></td></tr></tbody></table></li></ul>')
|
||||
])),
|
||||
Logger.t('toggle from UL to OL in list in table in list only changes inner list', GeneralSteps.sequence([
|
||||
tinyApis.sSetContent('<ul><li><table><tbody><tr><td><ul><li><p>a</p></li><li><p>b</p></li></ul></td><td><p>b</p></td></tr></tbody></table></li></ul>'),
|
||||
tinyApis.sSetSelection([0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 0, [0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 1),
|
||||
tinyUi.sClickOnToolbar('click numlist button', 'div[aria-label="Numbered list"] button'),
|
||||
tinyApis.sAssertContent('<ul><li><table><tbody><tr><td><ol><li><p>a</p></li><li><p>b</p></li></ol></td><td><p>b</p></td></tr></tbody></table></li></ul>')
|
||||
]))
|
||||
], onSuccess, onFailure);
|
||||
}, {
|
||||
plugins: 'lists',
|
||||
toolbar: 'bullist numlist indent outdent',
|
||||
indent: false,
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { GeneralSteps, Logger, Pipeline } from '@ephox/agar';
|
||||
import { UnitTest } from '@ephox/bedrock';
|
||||
import { TinyApis, TinyLoader, TinyUi } from '@ephox/mcagar';
|
||||
|
||||
import ListsPlugin from 'tinymce/plugins/lists/Plugin';
|
||||
import ModernTheme from 'tinymce/themes/modern/Theme';
|
||||
|
||||
UnitTest.asynctest('tinymce.lists.browser.ToggleListWithEmptyLiTest', (success, failure) => {
|
||||
ModernTheme();
|
||||
ListsPlugin();
|
||||
|
||||
TinyLoader.setup(function (editor, onSuccess, onFailure) {
|
||||
const tinyApis = TinyApis(editor);
|
||||
const tinyUi = TinyUi(editor);
|
||||
|
||||
Pipeline.async({}, [
|
||||
Logger.t('toggle bullet list on list with two empty LIs', GeneralSteps.sequence([
|
||||
tinyApis.sFocus,
|
||||
tinyApis.sSetContent('<ul><li>a</li><li> </li><li> </li><li>b</li></ul>'),
|
||||
tinyApis.sSetSelection([0, 0, 0], 0, [0, 3, 0], 1),
|
||||
tinyUi.sClickOnToolbar('click list', 'div[aria-label="Bullet list"] > button'),
|
||||
tinyApis.sAssertContent('<p>a</p><p> </p><p> </p><p>b</p>')
|
||||
])),
|
||||
], onSuccess, onFailure);
|
||||
}, {
|
||||
indent: false,
|
||||
plugins: 'lists',
|
||||
toolbar: '',
|
||||
skin_url: '/project/js/tinymce/skins/lightgray'
|
||||
}, success, failure);
|
||||
});
|
||||
Reference in New Issue
Block a user