UNPKG

suneditor

Version:

Pure JavaScript based WYSIWYG web editor

272 lines (224 loc) 11.1 kB
/* * wysiwyg web editor * * suneditor.js * Copyright 2017 JiHong Lee. * MIT license. */ 'use strict'; export default { name: 'formatBlock', display: 'submenu', add: function (core, targetElement) { const context = core.context; context.formatBlock = { targetText: targetElement.querySelector('.txt'), targetTooltip: targetElement.parentNode.querySelector('.se-tooltip-text'), _formatList: null, currentFormat: '' }; /** set submenu */ let listDiv = this.setSubmenu.call(core); /** add event listeners */ listDiv.querySelector('ul').addEventListener('click', this.pickUp.bind(core)); context.formatBlock._formatList = listDiv.querySelectorAll('li button'); /** append target button menu */ core.initMenuTarget(this.name, targetElement, listDiv); /** empty memory */ listDiv = null; }, setSubmenu: function () { const option = this.context.option; const lang_toolbar = this.lang.toolbar; const listDiv = this.util.createElement('DIV'); listDiv.className = 'se-submenu se-list-layer'; const defaultFormats = ['p', 'div', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']; const formatList = !option.formats || option.formats.length === 0 ? defaultFormats : option.formats; let list = '<div class="se-list-inner"><ul class="se-list-basic se-list-format">'; for (let i = 0, len = formatList.length, format, tagName, command, name, h, attrs, className; i < len; i++) { format = formatList[i]; if (typeof format === 'string' && defaultFormats.indexOf(format) > -1) { tagName = format.toLowerCase(); command = tagName === 'blockquote' ? 'range' : tagName === 'pre' ? 'free' : 'replace'; h = /^h/.test(tagName) ? tagName.match(/\d+/)[0] : ''; name = lang_toolbar['tag_' + (h ? 'h' : tagName)] + h; className = ''; attrs = ''; } else { tagName = format.tag.toLowerCase(); command = format.command; name = format.name || tagName; className = format.class; attrs = className ? ' class="' + className + '"' : ''; } list += '<li>' + '<button type="button" class="se-btn-list" data-command="' + command + '" data-value="' + tagName + '" data-class="' + className + '" title="' + name + '">' + '<' + tagName + attrs + '>' + name + '</' + tagName + '>' + '</button></li>'; } list += '</ul></div>'; listDiv.innerHTML = list; return listDiv; }, /** * @overriding core */ active: function (element) { let formatTitle = this.lang.toolbar.formats; const target = this.context.formatBlock.targetText; const tooltip = this.context.formatBlock.targetTooltip; if (!element) { this.util.changeTxt(target, formatTitle); this.util.changeTxt(tooltip, formatTitle); } else if (this.util.isFormatElement(element)) { const formatContext = this.context.formatBlock; const formatList = formatContext._formatList; const nodeName = element.nodeName.toLowerCase(); const className = (element.className.match(/(\s|^)__se__format__[^\s]+/) || [''])[0].trim(); for (let i = 0, len = formatList.length, f; i < len; i++) { f = formatList[i]; if (nodeName === f.getAttribute('data-value') && className === f.getAttribute('data-class')) { formatTitle = f.title; break; } } this.util.changeTxt(target, formatTitle); this.util.changeTxt(tooltip, formatTitle); target.setAttribute('data-value', nodeName); target.setAttribute('data-class', className); return true; } return false; }, /** * @overriding submenu */ on: function () { const formatContext = this.context.formatBlock; const formatList = formatContext._formatList; const target = formatContext.targetText; const currentFormat = (target.getAttribute('data-value') || '') + (target.getAttribute('data-class') || ''); if (currentFormat !== formatContext.currentFormat) { for (let i = 0, len = formatList.length, f; i < len; i++) { f = formatList[i]; if (currentFormat === f.getAttribute('data-value') + f.getAttribute('data-class')) { this.util.addClass(f, 'active'); } else { this.util.removeClass(f, 'active'); } } formatContext.currentFormat = currentFormat; } }, pickUp: function (e) { e.preventDefault(); e.stopPropagation(); let target = e.target; let command = null, value = null, tag = null, className = ''; while (!command && !/UL/i.test(target.tagName)) { command = target.getAttribute('data-command'); value = target.getAttribute('data-value'); className = target.getAttribute('data-class'); if (command) { tag = target.firstChild; break; } target = target.parentNode; } if (!command) return; // blockquote if (command === 'range') { const rangeElement = tag.cloneNode(false); this.applyRangeFormatElement(rangeElement); } // free, replace else { const range = this.getRange(); const startOffset = range.startOffset; const endOffset = range.endOffset; const util = this.util; const selectedFormsts = this.getSelectedElementsAndComponents(false); if (selectedFormsts.length === 0) return; let first = selectedFormsts[0]; let last = selectedFormsts[selectedFormsts.length - 1]; const firstPath = util.getNodePath(range.startContainer, first, null, null); const lastPath = util.getNodePath(range.endContainer, last, null, null); // remove selected list const rlist = this.detachList(selectedFormsts, false); if (rlist.sc) first = rlist.sc; if (rlist.ec) last = rlist.ec; // change format tag this.setRange(util.getNodeFromPath(firstPath, first), startOffset, util.getNodeFromPath(lastPath, last), endOffset); const modifiedFormsts = this.getSelectedElementsAndComponents(false); // free format if (command === 'free') { const len = modifiedFormsts.length - 1; let parentNode = modifiedFormsts[len].parentNode; let freeElement = tag.cloneNode(false); const focusElement = freeElement; for (let i = len, f, html, before, next, inner, isComp, first = true; i >= 0; i--) { f = modifiedFormsts[i]; if (f === (!modifiedFormsts[i + 1] ? null : modifiedFormsts[i + 1].parentNode)) continue; isComp = util.isComponent(f); html = isComp ? '' : f.innerHTML.replace(/(?!>)\s+(?=<)|\n/g, ' '); before = util.getParentElement(f, function (current) { return current.parentNode === parentNode; }); if (parentNode !== f.parentNode || isComp) { if (util.isFormatElement(parentNode)) { parentNode.parentNode.insertBefore(freeElement, parentNode.nextSibling); parentNode = parentNode.parentNode; } else { parentNode.insertBefore(freeElement, before ? before.nextSibling : null); parentNode = f.parentNode; } next = freeElement.nextSibling; if (next && freeElement.nodeName === next.nodeName && util.isSameAttributes(freeElement, next)) { freeElement.innerHTML += '<BR>' + next.innerHTML; util.removeItem(next); } freeElement = tag.cloneNode(false); first = true; } inner = freeElement.innerHTML; freeElement.innerHTML = ((first || !html || !inner || /<br>$/i.test(html)) ? html : html + '<BR>') + inner; if (i === 0) { parentNode.insertBefore(freeElement, f); next = f.nextSibling; if (next && freeElement.nodeName === next.nodeName && util.isSameAttributes(freeElement, next)) { freeElement.innerHTML += '<BR>' + next.innerHTML; util.removeItem(next); } const prev = freeElement.previousSibling; if (prev && freeElement.nodeName === prev.nodeName && util.isSameAttributes(freeElement, prev)) { prev.innerHTML += '<BR>' + freeElement.innerHTML; util.removeItem(freeElement); } } if (!isComp) util.removeItem(f); if (!!html) first = false; } this.setRange(focusElement, 0, focusElement, 0); } // replace format else { for (let i = 0, len = modifiedFormsts.length, node, newFormat; i < len; i++) { node = modifiedFormsts[i]; if ((node.nodeName.toLowerCase() !== value.toLowerCase() || (node.className.match(/(\s|^)__se__format__[^\s]+/) || [''])[0].trim() !== className) && !util.isComponent(node)) { newFormat = tag.cloneNode(false); util.copyFormatAttributes(newFormat, node); newFormat.innerHTML = node.innerHTML; node.parentNode.replaceChild(newFormat, node); } if (i === 0) first = newFormat || node; if (i === len - 1) last = newFormat || node; newFormat = null; } this.setRange(util.getNodeFromPath(firstPath, first), startOffset, util.getNodeFromPath(lastPath, last), endOffset); } // history stack this.history.push(false); } this.submenuOff(); } };