Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 

966 rader
23 KiB

  1. /**
  2. * plugin.js
  3. *
  4. * Released under LGPL License.
  5. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  6. *
  7. * License: http://www.tinymce.com/license
  8. * Contributing: http://www.tinymce.com/contributing
  9. */
  10. /*global tinymce:true */
  11. /*eslint consistent-this:0 */
  12. tinymce.PluginManager.add('lists', function(editor) {
  13. var self = this;
  14. function isChildOfBody(elm) {
  15. return editor.$.contains(editor.getBody(), elm);
  16. }
  17. function isBr(node) {
  18. return node && node.nodeName == 'BR';
  19. }
  20. function isListNode(node) {
  21. return node && (/^(OL|UL|DL)$/).test(node.nodeName) && isChildOfBody(node);
  22. }
  23. function isListItemNode(node) {
  24. return node && /^(LI|DT|DD)$/.test(node.nodeName);
  25. }
  26. function isFirstChild(node) {
  27. return node.parentNode.firstChild == node;
  28. }
  29. function isLastChild(node) {
  30. return node.parentNode.lastChild == node;
  31. }
  32. function isTextBlock(node) {
  33. return node && !!editor.schema.getTextBlockElements()[node.nodeName];
  34. }
  35. function isEditorBody(elm) {
  36. return elm === editor.getBody();
  37. }
  38. function isTextNode(node) {
  39. return node && node.nodeType === 3;
  40. }
  41. function getNormalizedEndPoint(container, offset) {
  42. var node = tinymce.dom.RangeUtils.getNode(container, offset);
  43. if (isListItemNode(container) && isTextNode(node)) {
  44. var textNodeOffset = offset >= container.childNodes.length ? node.data.length : 0;
  45. return {container: node, offset: textNodeOffset};
  46. }
  47. return {container: container, offset: offset};
  48. }
  49. function normalizeRange(rng) {
  50. var outRng = rng.cloneRange();
  51. var rangeStart = getNormalizedEndPoint(rng.startContainer, rng.startOffset);
  52. outRng.setStart(rangeStart.container, rangeStart.offset);
  53. var rangeEnd = getNormalizedEndPoint(rng.endContainer, rng.endOffset);
  54. outRng.setEnd(rangeEnd.container, rangeEnd.offset);
  55. return outRng;
  56. }
  57. editor.on('init', function() {
  58. var dom = editor.dom, selection = editor.selection;
  59. function isEmpty(elm, keepBookmarks) {
  60. var empty = dom.isEmpty(elm);
  61. if (keepBookmarks && dom.select('span[data-mce-type=bookmark]').length > 0) {
  62. return false;
  63. }
  64. return empty;
  65. }
  66. /**
  67. * Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with
  68. * index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans
  69. * added to them since they can be restored after a dom operation.
  70. *
  71. * So this: <p><b>|</b><b>|</b></p>
  72. * becomes: <p><b><span data-mce-type="bookmark">|</span></b><b data-mce-type="bookmark">|</span></b></p>
  73. *
  74. * @param {DOMRange} rng DOM Range to get bookmark on.
  75. * @return {Object} Bookmark object.
  76. */
  77. function createBookmark(rng) {
  78. var bookmark = {};
  79. function setupEndPoint(start) {
  80. var offsetNode, container, offset;
  81. container = rng[start ? 'startContainer' : 'endContainer'];
  82. offset = rng[start ? 'startOffset' : 'endOffset'];
  83. if (container.nodeType == 1) {
  84. offsetNode = dom.create('span', {'data-mce-type': 'bookmark'});
  85. if (container.hasChildNodes()) {
  86. offset = Math.min(offset, container.childNodes.length - 1);
  87. if (start) {
  88. container.insertBefore(offsetNode, container.childNodes[offset]);
  89. } else {
  90. dom.insertAfter(offsetNode, container.childNodes[offset]);
  91. }
  92. } else {
  93. container.appendChild(offsetNode);
  94. }
  95. container = offsetNode;
  96. offset = 0;
  97. }
  98. bookmark[start ? 'startContainer' : 'endContainer'] = container;
  99. bookmark[start ? 'startOffset' : 'endOffset'] = offset;
  100. }
  101. setupEndPoint(true);
  102. if (!rng.collapsed) {
  103. setupEndPoint();
  104. }
  105. return bookmark;
  106. }
  107. /**
  108. * Moves the selection to the current bookmark and removes any selection container wrappers.
  109. *
  110. * @param {Object} bookmark Bookmark object to move selection to.
  111. */
  112. function moveToBookmark(bookmark) {
  113. function restoreEndPoint(start) {
  114. var container, offset, node;
  115. function nodeIndex(container) {
  116. var node = container.parentNode.firstChild, idx = 0;
  117. while (node) {
  118. if (node == container) {
  119. return idx;
  120. }
  121. // Skip data-mce-type=bookmark nodes
  122. if (node.nodeType != 1 || node.getAttribute('data-mce-type') != 'bookmark') {
  123. idx++;
  124. }
  125. node = node.nextSibling;
  126. }
  127. return -1;
  128. }
  129. container = node = bookmark[start ? 'startContainer' : 'endContainer'];
  130. offset = bookmark[start ? 'startOffset' : 'endOffset'];
  131. if (!container) {
  132. return;
  133. }
  134. if (container.nodeType == 1) {
  135. offset = nodeIndex(container);
  136. container = container.parentNode;
  137. dom.remove(node);
  138. }
  139. bookmark[start ? 'startContainer' : 'endContainer'] = container;
  140. bookmark[start ? 'startOffset' : 'endOffset'] = offset;
  141. }
  142. restoreEndPoint(true);
  143. restoreEndPoint();
  144. var rng = dom.createRng();
  145. rng.setStart(bookmark.startContainer, bookmark.startOffset);
  146. if (bookmark.endContainer) {
  147. rng.setEnd(bookmark.endContainer, bookmark.endOffset);
  148. }
  149. selection.setRng(normalizeRange(rng));
  150. }
  151. function createNewTextBlock(contentNode, blockName) {
  152. var node, textBlock, fragment = dom.createFragment(), hasContentNode;
  153. var blockElements = editor.schema.getBlockElements();
  154. if (editor.settings.forced_root_block) {
  155. blockName = blockName || editor.settings.forced_root_block;
  156. }
  157. if (blockName) {
  158. textBlock = dom.create(blockName);
  159. if (textBlock.tagName === editor.settings.forced_root_block) {
  160. dom.setAttribs(textBlock, editor.settings.forced_root_block_attrs);
  161. }
  162. fragment.appendChild(textBlock);
  163. }
  164. if (contentNode) {
  165. while ((node = contentNode.firstChild)) {
  166. var nodeName = node.nodeName;
  167. if (!hasContentNode && (nodeName != 'SPAN' || node.getAttribute('data-mce-type') != 'bookmark')) {
  168. hasContentNode = true;
  169. }
  170. if (blockElements[nodeName]) {
  171. fragment.appendChild(node);
  172. textBlock = null;
  173. } else {
  174. if (blockName) {
  175. if (!textBlock) {
  176. textBlock = dom.create(blockName);
  177. fragment.appendChild(textBlock);
  178. }
  179. textBlock.appendChild(node);
  180. } else {
  181. fragment.appendChild(node);
  182. }
  183. }
  184. }
  185. }
  186. if (!editor.settings.forced_root_block) {
  187. fragment.appendChild(dom.create('br'));
  188. } else {
  189. // BR is needed in empty blocks on non IE browsers
  190. if (!hasContentNode && (!tinymce.Env.ie || tinymce.Env.ie > 10)) {
  191. textBlock.appendChild(dom.create('br', {'data-mce-bogus': '1'}));
  192. }
  193. }
  194. return fragment;
  195. }
  196. function getSelectedListItems() {
  197. return tinymce.grep(selection.getSelectedBlocks(), function(block) {
  198. return isListItemNode(block);
  199. });
  200. }
  201. function splitList(ul, li, newBlock) {
  202. var tmpRng, fragment, bookmarks, node;
  203. function removeAndKeepBookmarks(targetNode) {
  204. tinymce.each(bookmarks, function(node) {
  205. targetNode.parentNode.insertBefore(node, li.parentNode);
  206. });
  207. dom.remove(targetNode);
  208. }
  209. bookmarks = dom.select('span[data-mce-type="bookmark"]', ul);
  210. newBlock = newBlock || createNewTextBlock(li);
  211. tmpRng = dom.createRng();
  212. tmpRng.setStartAfter(li);
  213. tmpRng.setEndAfter(ul);
  214. fragment = tmpRng.extractContents();
  215. for (node = fragment.firstChild; node; node = node.firstChild) {
  216. if (node.nodeName == 'LI' && dom.isEmpty(node)) {
  217. dom.remove(node);
  218. break;
  219. }
  220. }
  221. if (!dom.isEmpty(fragment)) {
  222. dom.insertAfter(fragment, ul);
  223. }
  224. dom.insertAfter(newBlock, ul);
  225. if (isEmpty(li.parentNode)) {
  226. removeAndKeepBookmarks(li.parentNode);
  227. }
  228. dom.remove(li);
  229. if (isEmpty(ul)) {
  230. dom.remove(ul);
  231. }
  232. }
  233. var shouldMerge = function (listBlock, sibling) {
  234. var targetStyle = editor.dom.getStyle(listBlock, 'list-style-type', true);
  235. var style = editor.dom.getStyle(sibling, 'list-style-type', true);
  236. return targetStyle === style;
  237. };
  238. function mergeWithAdjacentLists(listBlock) {
  239. var sibling, node;
  240. sibling = listBlock.nextSibling;
  241. if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName && shouldMerge(listBlock, sibling)) {
  242. while ((node = sibling.firstChild)) {
  243. listBlock.appendChild(node);
  244. }
  245. dom.remove(sibling);
  246. }
  247. sibling = listBlock.previousSibling;
  248. if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName && shouldMerge(listBlock, sibling)) {
  249. while ((node = sibling.firstChild)) {
  250. listBlock.insertBefore(node, listBlock.firstChild);
  251. }
  252. dom.remove(sibling);
  253. }
  254. }
  255. function normalizeLists(element) {
  256. tinymce.each(tinymce.grep(dom.select('ol,ul', element)), normalizeList);
  257. }
  258. function normalizeList(ul) {
  259. var sibling, parentNode = ul.parentNode;
  260. // Move UL/OL to previous LI if it's the only child of a LI
  261. if (parentNode.nodeName == 'LI' && parentNode.firstChild == ul) {
  262. sibling = parentNode.previousSibling;
  263. if (sibling && sibling.nodeName == 'LI') {
  264. sibling.appendChild(ul);
  265. if (isEmpty(parentNode)) {
  266. dom.remove(parentNode);
  267. }
  268. } else {
  269. dom.setStyle(parentNode, 'listStyleType', 'none');
  270. }
  271. }
  272. // Append OL/UL to previous LI if it's in a parent OL/UL i.e. old HTML4
  273. if (isListNode(parentNode)) {
  274. sibling = parentNode.previousSibling;
  275. if (sibling && sibling.nodeName == 'LI') {
  276. sibling.appendChild(ul);
  277. }
  278. }
  279. }
  280. function outdent(li) {
  281. var ul = li.parentNode, ulParent = ul.parentNode, newBlock;
  282. function removeEmptyLi(li) {
  283. if (isEmpty(li)) {
  284. dom.remove(li);
  285. }
  286. }
  287. if (isEditorBody(ul)) {
  288. return true;
  289. }
  290. if (li.nodeName == 'DD') {
  291. dom.rename(li, 'DT');
  292. return true;
  293. }
  294. if (isFirstChild(li) && isLastChild(li)) {
  295. if (ulParent.nodeName == "LI") {
  296. dom.insertAfter(li, ulParent);
  297. removeEmptyLi(ulParent);
  298. dom.remove(ul);
  299. } else if (isListNode(ulParent)) {
  300. dom.remove(ul, true);
  301. } else {
  302. ulParent.insertBefore(createNewTextBlock(li), ul);
  303. dom.remove(ul);
  304. }
  305. return true;
  306. } else if (isFirstChild(li)) {
  307. if (ulParent.nodeName == "LI") {
  308. dom.insertAfter(li, ulParent);
  309. li.appendChild(ul);
  310. removeEmptyLi(ulParent);
  311. } else if (isListNode(ulParent)) {
  312. ulParent.insertBefore(li, ul);
  313. } else {
  314. ulParent.insertBefore(createNewTextBlock(li), ul);
  315. dom.remove(li);
  316. }
  317. return true;
  318. } else if (isLastChild(li)) {
  319. if (ulParent.nodeName == "LI") {
  320. dom.insertAfter(li, ulParent);
  321. } else if (isListNode(ulParent)) {
  322. dom.insertAfter(li, ul);
  323. } else {
  324. dom.insertAfter(createNewTextBlock(li), ul);
  325. dom.remove(li);
  326. }
  327. return true;
  328. }
  329. if (ulParent.nodeName == 'LI') {
  330. ul = ulParent;
  331. newBlock = createNewTextBlock(li, 'LI');
  332. } else if (isListNode(ulParent)) {
  333. newBlock = createNewTextBlock(li, 'LI');
  334. } else {
  335. newBlock = createNewTextBlock(li);
  336. }
  337. splitList(ul, li, newBlock);
  338. normalizeLists(ul.parentNode);
  339. return true;
  340. }
  341. function indent(li) {
  342. var sibling, newList, listStyle;
  343. function mergeLists(from, to) {
  344. var node;
  345. if (isListNode(from)) {
  346. while ((node = li.lastChild.firstChild)) {
  347. to.appendChild(node);
  348. }
  349. dom.remove(from);
  350. }
  351. }
  352. if (li.nodeName == 'DT') {
  353. dom.rename(li, 'DD');
  354. return true;
  355. }
  356. sibling = li.previousSibling;
  357. if (sibling && isListNode(sibling)) {
  358. sibling.appendChild(li);
  359. return true;
  360. }
  361. if (sibling && sibling.nodeName == 'LI' && isListNode(sibling.lastChild)) {
  362. sibling.lastChild.appendChild(li);
  363. mergeLists(li.lastChild, sibling.lastChild);
  364. return true;
  365. }
  366. sibling = li.nextSibling;
  367. if (sibling && isListNode(sibling)) {
  368. sibling.insertBefore(li, sibling.firstChild);
  369. return true;
  370. }
  371. /*if (sibling && sibling.nodeName == 'LI' && isListNode(li.lastChild)) {
  372. return false;
  373. }*/
  374. sibling = li.previousSibling;
  375. if (sibling && sibling.nodeName == 'LI') {
  376. newList = dom.create(li.parentNode.nodeName);
  377. listStyle = dom.getStyle(li.parentNode, 'listStyleType');
  378. if (listStyle) {
  379. dom.setStyle(newList, 'listStyleType', listStyle);
  380. }
  381. sibling.appendChild(newList);
  382. newList.appendChild(li);
  383. mergeLists(li.lastChild, newList);
  384. return true;
  385. }
  386. return false;
  387. }
  388. function indentSelection() {
  389. var listElements = getSelectedListItems();
  390. if (listElements.length) {
  391. var bookmark = createBookmark(selection.getRng(true));
  392. for (var i = 0; i < listElements.length; i++) {
  393. if (!indent(listElements[i]) && i === 0) {
  394. break;
  395. }
  396. }
  397. moveToBookmark(bookmark);
  398. editor.nodeChanged();
  399. return true;
  400. }
  401. }
  402. function outdentSelection() {
  403. var listElements = getSelectedListItems();
  404. if (listElements.length) {
  405. var bookmark = createBookmark(selection.getRng(true));
  406. var i, y, root = editor.getBody();
  407. i = listElements.length;
  408. while (i--) {
  409. var node = listElements[i].parentNode;
  410. while (node && node != root) {
  411. y = listElements.length;
  412. while (y--) {
  413. if (listElements[y] === node) {
  414. listElements.splice(i, 1);
  415. break;
  416. }
  417. }
  418. node = node.parentNode;
  419. }
  420. }
  421. for (i = 0; i < listElements.length; i++) {
  422. if (!outdent(listElements[i]) && i === 0) {
  423. break;
  424. }
  425. }
  426. moveToBookmark(bookmark);
  427. editor.nodeChanged();
  428. return true;
  429. }
  430. }
  431. function applyList(listName, detail) {
  432. var rng = selection.getRng(true), bookmark, listItemName = 'LI';
  433. if (dom.getContentEditable(selection.getNode()) === "false") {
  434. return;
  435. }
  436. listName = listName.toUpperCase();
  437. if (listName == 'DL') {
  438. listItemName = 'DT';
  439. }
  440. function getSelectedTextBlocks() {
  441. var textBlocks = [], root = editor.getBody();
  442. function getEndPointNode(start) {
  443. var container, offset;
  444. container = rng[start ? 'startContainer' : 'endContainer'];
  445. offset = rng[start ? 'startOffset' : 'endOffset'];
  446. // Resolve node index
  447. if (container.nodeType == 1) {
  448. container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
  449. }
  450. while (container.parentNode != root) {
  451. if (isTextBlock(container)) {
  452. return container;
  453. }
  454. if (/^(TD|TH)$/.test(container.parentNode.nodeName)) {
  455. return container;
  456. }
  457. container = container.parentNode;
  458. }
  459. return container;
  460. }
  461. var startNode = getEndPointNode(true);
  462. var endNode = getEndPointNode();
  463. var block, siblings = [];
  464. for (var node = startNode; node; node = node.nextSibling) {
  465. siblings.push(node);
  466. if (node == endNode) {
  467. break;
  468. }
  469. }
  470. tinymce.each(siblings, function(node) {
  471. if (isTextBlock(node)) {
  472. textBlocks.push(node);
  473. block = null;
  474. return;
  475. }
  476. if (dom.isBlock(node) || isBr(node)) {
  477. if (isBr(node)) {
  478. dom.remove(node);
  479. }
  480. block = null;
  481. return;
  482. }
  483. var nextSibling = node.nextSibling;
  484. if (tinymce.dom.BookmarkManager.isBookmarkNode(node)) {
  485. if (isTextBlock(nextSibling) || (!nextSibling && node.parentNode == root)) {
  486. block = null;
  487. return;
  488. }
  489. }
  490. if (!block) {
  491. block = dom.create('p');
  492. node.parentNode.insertBefore(block, node);
  493. textBlocks.push(block);
  494. }
  495. block.appendChild(node);
  496. });
  497. return textBlocks;
  498. }
  499. bookmark = createBookmark(rng);
  500. tinymce.each(getSelectedTextBlocks(), function(block) {
  501. var listBlock, sibling;
  502. var hasCompatibleStyle = function (sib) {
  503. var sibStyle = dom.getStyle(sib, 'list-style-type');
  504. var detailStyle = detail ? detail['list-style-type'] : '';
  505. detailStyle = detailStyle === null ? '' : detailStyle;
  506. return sibStyle === detailStyle;
  507. };
  508. sibling = block.previousSibling;
  509. if (sibling && isListNode(sibling) && sibling.nodeName == listName && hasCompatibleStyle(sibling)) {
  510. listBlock = sibling;
  511. block = dom.rename(block, listItemName);
  512. sibling.appendChild(block);
  513. } else {
  514. listBlock = dom.create(listName);
  515. block.parentNode.insertBefore(listBlock, block);
  516. listBlock.appendChild(block);
  517. block = dom.rename(block, listItemName);
  518. }
  519. updateListStyle(listBlock, detail);
  520. mergeWithAdjacentLists(listBlock);
  521. });
  522. moveToBookmark(bookmark);
  523. }
  524. var updateListStyle = function (el, detail) {
  525. dom.setStyle(el, 'list-style-type', detail ? detail['list-style-type'] : null);
  526. };
  527. function removeList() {
  528. var bookmark = createBookmark(selection.getRng(true)), root = editor.getBody();
  529. tinymce.each(getSelectedListItems(), function(li) {
  530. var node, rootList;
  531. if (isEditorBody(li.parentNode)) {
  532. return;
  533. }
  534. if (isEmpty(li)) {
  535. outdent(li);
  536. return;
  537. }
  538. for (node = li; node && node != root; node = node.parentNode) {
  539. if (isListNode(node)) {
  540. rootList = node;
  541. }
  542. }
  543. splitList(rootList, li);
  544. normalizeLists(rootList.parentNode);
  545. });
  546. moveToBookmark(bookmark);
  547. }
  548. function toggleList(listName, detail) {
  549. var parentList = dom.getParent(selection.getStart(), 'OL,UL,DL');
  550. if (isEditorBody(parentList)) {
  551. return;
  552. }
  553. if (parentList) {
  554. if (parentList.nodeName == listName) {
  555. removeList(listName);
  556. } else {
  557. var bookmark = createBookmark(selection.getRng(true));
  558. updateListStyle(parentList, detail);
  559. mergeWithAdjacentLists(dom.rename(parentList, listName));
  560. moveToBookmark(bookmark);
  561. }
  562. } else {
  563. applyList(listName, detail);
  564. }
  565. }
  566. function queryListCommandState(listName) {
  567. return function() {
  568. var parentList = dom.getParent(editor.selection.getStart(), 'UL,OL,DL');
  569. return parentList && parentList.nodeName == listName;
  570. };
  571. }
  572. function isBogusBr(node) {
  573. if (!isBr(node)) {
  574. return false;
  575. }
  576. if (dom.isBlock(node.nextSibling) && !isBr(node.previousSibling)) {
  577. return true;
  578. }
  579. return false;
  580. }
  581. function findNextCaretContainer(rng, isForward) {
  582. var node = rng.startContainer, offset = rng.startOffset;
  583. var nonEmptyBlocks, walker;
  584. if (node.nodeType == 3 && (isForward ? offset < node.data.length : offset > 0)) {
  585. return node;
  586. }
  587. nonEmptyBlocks = editor.schema.getNonEmptyElements();
  588. if (node.nodeType == 1) {
  589. node = tinymce.dom.RangeUtils.getNode(node, offset);
  590. }
  591. walker = new tinymce.dom.TreeWalker(node, editor.getBody());
  592. // Delete at <li>|<br></li> then jump over the bogus br
  593. if (isForward) {
  594. if (isBogusBr(node)) {
  595. walker.next();
  596. }
  597. }
  598. while ((node = walker[isForward ? 'next' : 'prev2']())) {
  599. if (node.nodeName == 'LI' && !node.hasChildNodes()) {
  600. return node;
  601. }
  602. if (nonEmptyBlocks[node.nodeName]) {
  603. return node;
  604. }
  605. if (node.nodeType == 3 && node.data.length > 0) {
  606. return node;
  607. }
  608. }
  609. }
  610. function mergeLiElements(fromElm, toElm) {
  611. var node, listNode, ul = fromElm.parentNode;
  612. if (!isChildOfBody(fromElm) || !isChildOfBody(toElm)) {
  613. return;
  614. }
  615. if (isListNode(toElm.lastChild)) {
  616. listNode = toElm.lastChild;
  617. }
  618. if (ul == toElm.lastChild) {
  619. if (isBr(ul.previousSibling)) {
  620. dom.remove(ul.previousSibling);
  621. }
  622. }
  623. node = toElm.lastChild;
  624. if (node && isBr(node) && fromElm.hasChildNodes()) {
  625. dom.remove(node);
  626. }
  627. if (isEmpty(toElm, true)) {
  628. dom.$(toElm).empty();
  629. }
  630. if (!isEmpty(fromElm, true)) {
  631. while ((node = fromElm.firstChild)) {
  632. toElm.appendChild(node);
  633. }
  634. }
  635. if (listNode) {
  636. toElm.appendChild(listNode);
  637. }
  638. dom.remove(fromElm);
  639. if (isEmpty(ul) && !isEditorBody(ul)) {
  640. dom.remove(ul);
  641. }
  642. }
  643. function backspaceDeleteCaret(isForward) {
  644. var li = dom.getParent(selection.getStart(), 'LI'), ul, rng, otherLi;
  645. if (li) {
  646. ul = li.parentNode;
  647. if (isEditorBody(ul) && dom.isEmpty(ul)) {
  648. return true;
  649. }
  650. rng = normalizeRange(selection.getRng(true));
  651. otherLi = dom.getParent(findNextCaretContainer(rng, isForward), 'LI');
  652. if (otherLi && otherLi != li) {
  653. var bookmark = createBookmark(rng);
  654. if (isForward) {
  655. mergeLiElements(otherLi, li);
  656. } else {
  657. mergeLiElements(li, otherLi);
  658. }
  659. moveToBookmark(bookmark);
  660. return true;
  661. } else if (!otherLi) {
  662. if (!isForward && removeList(ul.nodeName)) {
  663. return true;
  664. }
  665. }
  666. }
  667. }
  668. function backspaceDeleteRange() {
  669. var startListParent = editor.dom.getParent(editor.selection.getStart(), 'LI,DT,DD');
  670. if (startListParent || getSelectedListItems().length > 0) {
  671. editor.undoManager.transact(function() {
  672. editor.execCommand('Delete');
  673. normalizeLists(editor.getBody());
  674. });
  675. return true;
  676. }
  677. return false;
  678. }
  679. self.backspaceDelete = function(isForward) {
  680. return selection.isCollapsed() ? backspaceDeleteCaret(isForward) : backspaceDeleteRange();
  681. };
  682. editor.on('BeforeExecCommand', function(e) {
  683. var cmd = e.command.toLowerCase(), isHandled;
  684. if (cmd == "indent") {
  685. if (indentSelection()) {
  686. isHandled = true;
  687. }
  688. } else if (cmd == "outdent") {
  689. if (outdentSelection()) {
  690. isHandled = true;
  691. }
  692. }
  693. if (isHandled) {
  694. editor.fire('ExecCommand', {command: e.command});
  695. e.preventDefault();
  696. return true;
  697. }
  698. });
  699. editor.addCommand('InsertUnorderedList', function(ui, detail) {
  700. toggleList('UL', detail);
  701. });
  702. editor.addCommand('InsertOrderedList', function(ui, detail) {
  703. toggleList('OL', detail);
  704. });
  705. editor.addCommand('InsertDefinitionList', function(ui, detail) {
  706. toggleList('DL', detail);
  707. });
  708. editor.addQueryStateHandler('InsertUnorderedList', queryListCommandState('UL'));
  709. editor.addQueryStateHandler('InsertOrderedList', queryListCommandState('OL'));
  710. editor.addQueryStateHandler('InsertDefinitionList', queryListCommandState('DL'));
  711. editor.on('keydown', function(e) {
  712. // Check for tab but not ctrl/cmd+tab since it switches browser tabs
  713. if (e.keyCode != 9 || tinymce.util.VK.metaKeyPressed(e)) {
  714. return;
  715. }
  716. if (editor.dom.getParent(editor.selection.getStart(), 'LI,DT,DD')) {
  717. e.preventDefault();
  718. if (e.shiftKey) {
  719. outdentSelection();
  720. } else {
  721. indentSelection();
  722. }
  723. }
  724. });
  725. });
  726. editor.addButton('indent', {
  727. icon: 'indent',
  728. title: 'Increase indent',
  729. cmd: 'Indent',
  730. onPostRender: function() {
  731. var ctrl = this;
  732. editor.on('nodechange', function() {
  733. var blocks = editor.selection.getSelectedBlocks();
  734. var disable = false;
  735. for (var i = 0, l = blocks.length; !disable && i < l; i++) {
  736. var tag = blocks[i].nodeName;
  737. disable = (tag == 'LI' && isFirstChild(blocks[i]) || tag == 'UL' || tag == 'OL' || tag == 'DD');
  738. }
  739. ctrl.disabled(disable);
  740. });
  741. }
  742. });
  743. editor.on('keydown', function(e) {
  744. if (e.keyCode == tinymce.util.VK.BACKSPACE) {
  745. if (self.backspaceDelete()) {
  746. e.preventDefault();
  747. }
  748. } else if (e.keyCode == tinymce.util.VK.DELETE) {
  749. if (self.backspaceDelete(true)) {
  750. e.preventDefault();
  751. }
  752. }
  753. });
  754. });