suneditor
Version:
Pure JavaScript based WYSIWYG web editor
202 lines (167 loc) • 7.85 kB
JavaScript
/*
* wysiwyg web editor
*
* suneditor.js
* Copyright 2017 JiHong Lee.
* MIT license.
*/
'use strict';
export default {
name: 'formatBlock',
add: function (core, targetElement) {
const context = core.context;
context.formatBlock = {
_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 html */
targetElement.parentNode.appendChild(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; i < len; i++) {
format = formatList[i];
if (typeof format === 'string' && defaultFormats.indexOf(format) > -1) {
tagName = format.toLowerCase();
command = tagName === 'pre' || tagName === 'blockquote' ? 'range' : 'replace';
h = /^h/.test(tagName) ? tagName.match(/\d+/)[0] : '';
name = lang_toolbar['tag_' + (h ? 'h' : tagName)] + h;
attrs = '';
} else {
tagName = format.tag.toLowerCase();
command = format.command;
name = format.name || tagName;
attrs = format.class ? ' class="' + format.class + '"' : '';
}
list += '<li>' +
'<button type="button" class="se-btn-list" data-command="' + command + '" data-value="' + tagName + '" title="' + name + '">' +
'<' + tagName + attrs + '>' + name + '</' + tagName + '>' +
'</button></li>';
}
list += '</ul></div>';
listDiv.innerHTML = list;
return listDiv;
},
on: function () {
const formatContext = this.context.formatBlock;
const formatList = formatContext._formatList;
const currentFormat = (this.commandMap.FORMAT.getAttribute('data-focus') || 'P').toLowerCase();
if (currentFormat !== formatContext.currentFormat) {
for (let i = 0, len = formatList.length; i < len; i++) {
if (currentFormat === formatList[i].getAttribute('data-value')) {
this.util.addClass(formatList[i], 'active');
} else {
this.util.removeClass(formatList[i], 'active');
}
}
formatContext.currentFormat = currentFormat;
}
},
pickUp: function (e) {
e.preventDefault();
e.stopPropagation();
let target = e.target;
let command = null, value = null, tag = null;
while (!command && !/UL/i.test(target.tagName)) {
command = target.getAttribute('data-command');
value = target.getAttribute('data-value');
if (command) {
tag = target.firstChild;
break;
}
target = target.parentNode;
}
if (!command) return;
// blockquote, pre
if (command === 'range') {
const rangeElement = tag.cloneNode(false);
this.applyRangeFormatElement(rangeElement);
}
// others
else {
const range = this.getRange();
const startOffset = range.startOffset;
const endOffset = range.endOffset;
let selectedFormsts = this.getSelectedElementsAndComponents();
if (selectedFormsts.length === 0) return;
let first = selectedFormsts[0];
let last = selectedFormsts[selectedFormsts.length - 1];
const firstPath = this.util.getNodePath(range.startContainer, first, null);
const lastPath = this.util.getNodePath(range.endContainer, last, null);
// remove list
let rangeArr = {};
let listFirst = false;
let listLast = false;
const passComponent = function (current) { return !this.isComponent(current); }.bind(this.util);
for (let i = 0, len = selectedFormsts.length, r, o, lastIndex, isList; i < len; i++) {
lastIndex = i === len - 1;
o = this.util.getRangeFormatElement(selectedFormsts[i], passComponent);
isList = this.util.isList(o);
if (!r && isList) {
r = o;
rangeArr = {r: r, f: [this.util.getParentElement(selectedFormsts[i], 'LI')]};
if (i === 0) listFirst = true;
} else if (r && isList) {
if (r !== o) {
const edge = this.detachRangeFormatElement(rangeArr.r, rangeArr.f, null, false, true);
if (listFirst) {
first = edge.sc;
listFirst = false;
}
if (lastIndex) last = edge.ec;
if (isList) {
r = o;
rangeArr = {r: r, f: [this.util.getParentElement(selectedFormsts[i], 'LI')]};
if (lastIndex) listLast = true;
} else {
r = null;
}
} else {
rangeArr.f.push(this.util.getParentElement(selectedFormsts[i], 'LI'));
if (lastIndex) listLast = true;
}
}
if (lastIndex && this.util.isList(r)) {
const edge = this.detachRangeFormatElement(rangeArr.r, rangeArr.f, null, false, true);
if (listLast || len === 1) {
last = edge.ec;
if (listFirst) first = edge.sc || last;
}
}
}
// change format tag
this.setRange(this.util.getNodeFromPath(firstPath, first), startOffset, this.util.getNodeFromPath(lastPath, last), endOffset);
selectedFormsts = this.getSelectedElementsAndComponents();
for (let i = 0, len = selectedFormsts.length, node, newFormat; i < len; i++) {
node = selectedFormsts[i];
if (node.nodeName.toLowerCase() !== value.toLowerCase() && !this.util.isComponent(node)) {
newFormat = tag.cloneNode(false);
this.util.copyFormatAttributes(newFormat, node);
newFormat.innerHTML = node.innerHTML;
node.parentNode.insertBefore(newFormat, node);
this.util.removeItem(node);
}
if (i === 0) first = newFormat || node;
if (i === len - 1) last = newFormat || node;
newFormat = null;
}
this.setRange(this.util.getNodeFromPath(firstPath, first), startOffset, this.util.getNodeFromPath(lastPath, last), endOffset);
// history stack
this.history.push(false);
}
this.submenuOff();
}
};