UNPKG

suneditor

Version:

Pure JavaScript based WYSIWYG web editor

444 lines (371 loc) 17.8 kB
/* * wysiwyg web editor * * suneditor.js * Copyright 2017 JiHong Lee. * MIT license. */ 'use strict'; export default { name: 'list', display: 'submenu', add: function (core, targetElement) { const context = core.context; context.list = { targetButton: targetElement, _list: null, currentList: '', icons: { bullets: core.icons.list_bullets, number: core.icons.list_number } }; /** set submenu */ let listDiv = this.setSubmenu.call(core); let listUl = listDiv.querySelector('ul'); /** add event listeners */ listUl.addEventListener('click', this.pickup.bind(core)); context.list._list = listUl.querySelectorAll('li button'); /** append target button menu */ core.initMenuTarget(this.name, targetElement, listDiv); /** empty memory */ listDiv = null, listUl = null; }, setSubmenu: function () { const lang = this.lang; const listDiv = this.util.createElement('DIV'); listDiv.className = 'se-submenu se-list-layer'; listDiv.innerHTML = '' + '<div class="se-list-inner">' + '<ul class="se-list-basic">' + '<li><button type="button" class="se-btn-list se-tooltip" data-command="OL">' + this.icons.list_number + '<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.toolbar.orderList + '</span></span>' + '</button></li>' + '<li><button type="button" class="se-btn-list se-tooltip" data-command="UL">' + this.icons.list_bullets + '<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.toolbar.unorderList + '</span></span>' + '</button></li>' + '</ul>' + '</div>'; return listDiv; }, /** * @overriding core */ active: function (element) { const button = this.context.list.targetButton; const icon = button.querySelector('svg'); const util = this.util; if (!element) { button.removeAttribute('data-focus'); util.changeElement(icon, this.context.list.icons.number); util.removeClass(button, 'active'); } else if (util.isList(element)) { const nodeName = element.nodeName; button.setAttribute('data-focus', nodeName); util.addClass(button, 'active'); if (/UL/i.test(nodeName)) { util.changeElement(icon, this.context.list.icons.bullets); } else { util.changeElement(icon, this.context.list.icons.number); } return true; } return false; }, /** * @overriding submenu */ on: function () { const listContext = this.context.list; const list = listContext._list; const currentList = listContext.targetButton.getAttribute('data-focus') || ''; if (currentList !== listContext.currentList) { for (let i = 0, len = list.length; i < len; i++) { if (currentList === list[i].getAttribute('data-command')) { this.util.addClass(list[i], 'active'); } else { this.util.removeClass(list[i], 'active'); } } listContext.currentList = currentList; } }, editList: function (command, selectedCells, detach) { const selectedFormats = !selectedCells ? this.getSelectedElementsAndComponents(false) : selectedCells; if (!selectedFormats || selectedFormats.length === 0) return; const util = this.util; util.sortByDepth(selectedFormats, true); // merge let firstSel = selectedFormats[0]; let lastSel = selectedFormats[selectedFormats.length - 1]; let topEl = (util.isListCell(firstSel) || util.isComponent(firstSel)) && !firstSel.previousElementSibling ? firstSel.parentNode.previousElementSibling : firstSel.previousElementSibling; let bottomEl = (util.isListCell(lastSel) || util.isComponent(lastSel)) && !lastSel.nextElementSibling ? lastSel.parentNode.nextElementSibling : lastSel.nextElementSibling; const range = this.getRange(); const originRange = { sc: range.startContainer, so: range.startOffset, ec: range.endContainer, eo: range.endOffset }; let isRemove = true; for (let i = 0, len = selectedFormats.length; i < len; i++) { if (!util.isList(util.getRangeFormatElement(selectedFormats[i], function (current) { return this.getRangeFormatElement(current) && current !== selectedFormats[i]; }.bind(util)))) { isRemove = false; break; } } if (isRemove && (!topEl || (firstSel.tagName !== topEl.tagName || command !== topEl.tagName.toUpperCase())) && (!bottomEl || (lastSel.tagName !== bottomEl.tagName || command !== bottomEl.tagName.toUpperCase()))) { if (detach) { for (let i = 0, len = selectedFormats.length; i < len; i++) { for (let j = i - 1; j >= 0; j--) { if (selectedFormats[j].contains(selectedFormats[i])) { selectedFormats.splice(i, 1); i--; len--; break; } } } } const currentFormat = util.getRangeFormatElement(firstSel); const cancel = currentFormat && currentFormat.tagName === command; let rangeArr, tempList; const passComponent = function (current) { return !this.isComponent(current); }.bind(util); if (!cancel) tempList = util.createElement(command); for (let i = 0, len = selectedFormats.length, r, o; i < len; i++) { o = util.getRangeFormatElement(selectedFormats[i], passComponent); if (!o || !util.isList(o)) continue; if (!r) { r = o; rangeArr = {r: r, f: [util.getParentElement(selectedFormats[i], 'LI')]}; } else { if (r !== o) { if (detach && util.isListCell(o.parentNode)) { this.plugins.list._detachNested.call(this, rangeArr.f); } else { this.detachRangeFormatElement(rangeArr.f[0].parentNode, rangeArr.f, tempList, false, true); } o = selectedFormats[i].parentNode; if (!cancel) tempList = util.createElement(command); r = o; rangeArr = {r: r, f: [util.getParentElement(selectedFormats[i], 'LI')]}; } else { rangeArr.f.push(util.getParentElement(selectedFormats[i], 'LI')); } } if (i === len - 1) { if (detach && util.isListCell(o.parentNode)) { this.plugins.list._detachNested.call(this, rangeArr.f); } else { this.detachRangeFormatElement(rangeArr.f[0].parentNode, rangeArr.f, tempList, false, true); } } } } else { const topElParent = topEl ? topEl.parentNode : topEl; const bottomElParent = bottomEl ? bottomEl.parentNode : bottomEl; topEl = topElParent && !util.isWysiwygDiv(topElParent) && topElParent.nodeName === command ? topElParent : topEl; bottomEl = bottomElParent && !util.isWysiwygDiv(bottomElParent) && bottomElParent.nodeName === command ? bottomElParent : bottomEl; const mergeTop = topEl && topEl.tagName === command; const mergeBottom = bottomEl && bottomEl.tagName === command; let list = mergeTop ? topEl : util.createElement(command); let firstList = null; let lastList = null; let topNumber = null; let bottomNumber = null; const passComponent = function (current) { return !this.isComponent(current) && !this.isList(current); }.bind(util); for (let i = 0, len = selectedFormats.length, newCell, fTag, isCell, next, originParent, nextParent, parentTag, siblingTag, rangeTag; i < len; i++) { fTag = selectedFormats[i]; if (fTag.childNodes.length === 0 && !util._isIgnoreNodeChange(fTag)) { util.removeItem(fTag); continue; } next = selectedFormats[i + 1]; originParent = fTag.parentNode; nextParent = next ? next.parentNode : null; isCell = util.isListCell(fTag); rangeTag = util.isRangeFormatElement(originParent) ? originParent : null; parentTag = isCell && !util.isWysiwygDiv(originParent) ? originParent.parentNode : originParent; siblingTag = isCell && !util.isWysiwygDiv(originParent) ? (!next || util.isListCell(parentTag)) ? originParent : originParent.nextSibling : fTag.nextSibling; newCell = util.createElement('LI'); util.copyFormatAttributes(newCell, fTag); if (util.isComponent(fTag)) { const isHR = /^HR$/i.test(fTag.nodeName); if (!isHR) newCell.innerHTML = '<br>'; newCell.innerHTML += fTag.outerHTML; if (isHR) newCell.innerHTML += '<br>'; } else { const fChildren = fTag.childNodes; while (fChildren[0]) { newCell.appendChild(fChildren[0]); } } list.appendChild(newCell); if (!next) lastList = list; if (!next || parentTag !== nextParent || util.isRangeFormatElement(siblingTag)) { if (!firstList) firstList = list; if ((!mergeTop || !next || parentTag !== nextParent) && !(next && util.isList(nextParent) && nextParent === originParent)) { if (list.parentNode !== parentTag) parentTag.insertBefore(list, siblingTag); } } util.removeItem(fTag); if (mergeTop && topNumber === null) topNumber = list.children.length - 1; if (next && (util.getRangeFormatElement(nextParent, passComponent) !== util.getRangeFormatElement(originParent, passComponent) || (util.isList(nextParent) && util.isList(originParent) && util.getElementDepth(nextParent) !== util.getElementDepth(originParent)))) { list = util.createElement(command); } if (rangeTag && rangeTag.children.length === 0) util.removeItem(rangeTag); } if (topNumber) { firstList = firstList.children[topNumber]; } if (mergeBottom) { bottomNumber = list.children.length - 1; list.innerHTML += bottomEl.innerHTML; lastList = list.children[bottomNumber]; util.removeItem(bottomEl); } } this.effectNode = null; return originRange; }, _detachNested: function (cells) { const first = cells[0]; const last = cells[cells.length - 1]; const next = last.nextElementSibling; const originList = first.parentNode; const sibling = originList.parentNode.nextElementSibling; const parentNode = originList.parentNode.parentNode; for (let c = 0, cLen = cells.length; c < cLen; c++) { parentNode.insertBefore(cells[c], sibling); } if (next && originList.children.length > 0) { const newList = originList.cloneNode(false); const children = originList.childNodes; const index = this.util.getPositionIndex(next); while (children[index]) { newList.appendChild(children[index]); } last.appendChild(newList); } if (originList.children.length === 0) this.util.removeItem(originList); this.util.mergeSameTags(parentNode); const edge = this.util.getEdgeChildNodes(first, last); return { cc: first.parentNode, sc: edge.sc, ec: edge.ec }; }, editInsideList: function (remove, selectedCells) { selectedCells = !selectedCells ? this.getSelectedElements().filter(function (el) { return this.isListCell(el); }.bind(this.util)) : selectedCells; const cellsLen = selectedCells.length; if (cellsLen === 0 || (!remove && (!this.util.isListCell(selectedCells[0].previousElementSibling) && !this.util.isListCell(selectedCells[cellsLen - 1].nextElementSibling)))) { return { sc: selectedCells[0], so: 0, ec: selectedCells[cellsLen - 1], eo: 1 }; } let originList = selectedCells[0].parentNode; let lastCell = selectedCells[cellsLen - 1]; let range = null; if (remove) { if (originList !== lastCell.parentNode && this.util.isList(lastCell.parentNode.parentNode) && lastCell.nextElementSibling) { lastCell = lastCell.nextElementSibling; while (lastCell) { selectedCells.push(lastCell); lastCell = lastCell.nextElementSibling; } } range = this.plugins.list.editList.call(this, originList.nodeName.toUpperCase(), selectedCells, true); } else { let innerList = this.util.createElement(originList.nodeName); let prev = selectedCells[0].previousElementSibling; let next = lastCell.nextElementSibling; const nodePath = { s: null, e: null, sl: originList, el: originList }; for (let i = 0, len = cellsLen, c; i < len; i++) { c = selectedCells[i]; if (c.parentNode !== originList) { this.plugins.list._insiedList.call(this, originList, innerList, prev, next, nodePath); originList = c.parentNode; innerList = this.util.createElement(originList.nodeName); } prev = c.previousElementSibling; next = c.nextElementSibling; innerList.appendChild(c); } this.plugins.list._insiedList.call(this, originList, innerList, prev, next, nodePath); const sc = this.util.getNodeFromPath(nodePath.s, nodePath.sl); const ec = this.util.getNodeFromPath(nodePath.e, nodePath.el); range = { sc: sc, so: 0, ec: ec, eo: ec.textContent.length }; } return range; }, _insiedList: function (originList, innerList, prev, next, nodePath) { let insertPrev = false; if (prev && innerList.tagName === prev.tagName) { const children = innerList.children; while (children[0]) { prev.appendChild(children[0]); } innerList = prev; insertPrev = true; } if (next && innerList.tagName === next.tagName) { const children = next.children; while (children[0]) { innerList.appendChild(children[0]); } const temp = next.nextElementSibling; next.parentNode.removeChild(next); next = temp; } if (!insertPrev) { if (this.util.isListCell(prev)) { originList = prev; next = null; } originList.insertBefore(innerList, next); if (!nodePath.s) { nodePath.s = this.util.getNodePath(innerList.firstElementChild.firstChild, originList, null); nodePath.sl = originList; } const slPath = originList.contains(nodePath.sl) ? this.util.getNodePath(nodePath.sl, originList) : null; nodePath.e = this.util.getNodePath(innerList.lastElementChild.firstChild, originList, null); nodePath.el = originList; this.util.mergeSameTags(originList, [nodePath.s, nodePath.e, slPath], false); this.util.mergeNestedTags(originList); if (slPath) nodePath.sl = this.util.getNodeFromPath(slPath, originList); } return innerList; }, pickup: function (e) { e.preventDefault(); e.stopPropagation(); let target = e.target; let command = ''; while (!command && !/^UL$/i.test(target.tagName)) { command = target.getAttribute('data-command'); target = target.parentNode; } if (!command) return; const range = this.plugins.list.editList.call(this, command, null, false); this.setRange(range.sc, range.so, range.ec, range.eo); this.submenuOff(); // history stack this.history.push(false); } };