suneditor
Version:
Pure JavaScript based WYSIWYG web editor
1,163 lines (990 loc) • 61.6 kB
JavaScript
/*
* wysiwyg web editor
*
* suneditor.js
* Copyright 2017 JiHong Lee.
* MIT license.
*/
'use strict';
export default {
name: 'table',
display: 'submenu',
add: function (core, targetElement) {
const context = core.context;
context.table = {
_element: null,
_tdElement: null,
_trElement: null,
_trElements: null,
_tableXY: [],
_maxWidth: true,
_fixedColumn: false,
resizeText: null,
headerButton: null,
mergeButton: null,
splitButton: null,
splitMenu: null,
maxText: core.lang.controller.maxSize,
minText: core.lang.controller.minSize,
_physical_cellCnt: 0,
_logical_cellCnt: 0,
_rowCnt: 0,
_rowIndex: 0,
_physical_cellIndex: 0,
_logical_cellIndex: 0,
_current_colSpan: 0,
_current_rowSpan: 0,
icons: {
expansion: core.icons.expansion,
reduction: core.icons.reduction
}
};
/** set submenu */
let listDiv = this.setSubmenu.call(core);
let tablePicker = listDiv.querySelector('.se-controller-table-picker');
context.table.tableHighlight = listDiv.querySelector('.se-table-size-highlighted');
context.table.tableUnHighlight = listDiv.querySelector('.se-table-size-unhighlighted');
context.table.tableDisplay = listDiv.querySelector('.se-table-size-display');
/** set table controller */
let tableController = this.setController_table.call(core);
context.table.tableController = tableController;
context.table.resizeButton = tableController.querySelector('._se_table_resize');
context.table.resizeText = tableController.querySelector('._se_table_resize > span > span');
context.table.columnFixedButton = tableController.querySelector('._se_table_fixed_column');
context.table.headerButton = tableController.querySelector('._se_table_header');
tableController.addEventListener('mousedown', function (e) { e.stopPropagation(); }, false);
/** set resizing */
let resizeDiv = this.setController_tableEditor.call(core);
context.table.resizeDiv = resizeDiv;
context.table.splitMenu = resizeDiv.querySelector('.se-btn-group-sub');
context.table.mergeButton = resizeDiv.querySelector('._se_table_merge_button');
context.table.splitButton = resizeDiv.querySelector('._se_table_split_button');
context.table.insertRowAboveButton = resizeDiv.querySelector('._se_table_insert_row_a');
context.table.insertRowBelowButton = resizeDiv.querySelector('._se_table_insert_row_b');
resizeDiv.addEventListener('mousedown', function (e) { e.stopPropagation(); }, false);
/** add event listeners */
tablePicker.addEventListener('mousemove', this.onMouseMove_tablePicker.bind(core));
tablePicker.addEventListener('click', this.appendTable.bind(core));
resizeDiv.addEventListener('click', this.onClick_tableController.bind(core));
tableController.addEventListener('click', this.onClick_tableController.bind(core));
/** append target button menu */
core.initMenuTarget(this.name, targetElement, listDiv);
/** append controller */
context.element.relative.appendChild(resizeDiv);
context.element.relative.appendChild(tableController);
/** empty memory */
listDiv = null, tablePicker = null, resizeDiv = null, tableController = null;
},
setSubmenu: function () {
const listDiv = this.util.createElement('DIV');
listDiv.className = 'se-submenu se-selector-table';
listDiv.innerHTML = '' +
'<div class="se-table-size">' +
'<div class="se-table-size-picker se-controller-table-picker"></div>' +
'<div class="se-table-size-highlighted"></div>' +
'<div class="se-table-size-unhighlighted"></div>' +
'</div>' +
'<div class="se-table-size-display">1 x 1</div>';
return listDiv;
},
setController_table: function () {
const lang = this.lang;
const icons = this.icons;
const tableResize = this.util.createElement('DIV');
tableResize.className = 'se-controller se-controller-table';
tableResize.innerHTML = '' +
'<div>' +
'<div class="se-btn-group">' +
'<button type="button" data-command="resize" class="se-btn se-tooltip _se_table_resize">' +
icons.expansion +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.maxSize + '</span></span>' +
'</button>' +
'<button type="button" data-command="layout" class="se-btn se-tooltip _se_table_fixed_column">' +
icons.fixed_column_width +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.fixedColumnWidth + '</span></span>' +
'</button>' +
'<button type="button" data-command="header" class="se-btn se-tooltip _se_table_header">' +
icons.table_header +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.tableHeader + '</span></span>' +
'</button>' +
'<button type="button" data-command="remove" class="se-btn se-tooltip">' +
icons.delete +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.remove + '</span></span>' +
'</button>' +
'</div>' +
'</div>';
return tableResize;
},
setController_tableEditor: function () {
const lang = this.lang;
const icons = this.icons;
const tableResize = this.util.createElement('DIV');
tableResize.className = 'se-controller se-controller-table-cell';
tableResize.innerHTML = '' +
'<div class="se-arrow se-arrow-up"></div>' +
'<div class="se-btn-group">' +
'<button type="button" data-command="insert" data-value="row" data-option="up" class="se-btn se-tooltip _se_table_insert_row_a">' +
icons.insert_row_above +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.insertRowAbove + '</span></span>' +
'</button>' +
'<button type="button" data-command="insert" data-value="row" data-option="down" class="se-btn se-tooltip _se_table_insert_row_b">' +
icons.insert_row_below +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.insertRowBelow + '</span></span>' +
'</button>' +
'<button type="button" data-command="delete" data-value="row" class="se-btn se-tooltip">' +
icons.delete_row +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.deleteRow + '</span></span>' +
'</button>' +
'<button type="button" data-command="merge" class="_se_table_merge_button se-btn se-tooltip" disabled>' +
icons.merge_cell +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.mergeCells + '</span></span>' +
'</button>' +
'</div>' +
'<div class="se-btn-group" style="padding-top: 0;">' +
'<button type="button" data-command="insert" data-value="cell" data-option="left" class="se-btn se-tooltip">' +
icons.insert_column_left +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.insertColumnBefore + '</span></span>' +
'</button>' +
'<button type="button" data-command="insert" data-value="cell" data-option="right" class="se-btn se-tooltip">' +
icons.insert_column_right +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.insertColumnAfter + '</span></span>' +
'</button>' +
'<button type="button" data-command="delete" data-value="cell" class="se-btn se-tooltip">' +
icons.delete_column +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.deleteColumn + '</span></span>' +
'</button>' +
'<button type="button" data-command="onsplit" class="_se_table_split_button se-btn se-tooltip">' +
icons.split_cell +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.splitCells + '</span></span>' +
'</button>' +
'<div class="se-btn-group-sub sun-editor-common se-list-layer">' +
'<div class="se-list-inner">' +
'<ul class="se-list-basic">' +
'<li class="se-btn-list" data-command="split" data-value="vertical" style="line-height:32px;" title="' + lang.controller.VerticalSplit + '">' +
lang.controller.VerticalSplit + '</li>' +
'<li class="se-btn-list" data-command="split" data-value="horizontal" style="line-height:32px;" title="' + lang.controller.HorizontalSplit + '">' +
lang.controller.HorizontalSplit + '</li>' +
'</ul>' +
'</div>' +
'</div>' +
'</div>';
return tableResize;
},
appendTable: function () {
const oTable = this.util.createElement('TABLE');
const createCells = this.plugins.table.createCells;
const x = this.context.table._tableXY[0];
let y = this.context.table._tableXY[1];
let tableHTML = '<tbody>';
while (y > 0) {
tableHTML += '<tr>' + createCells.call(this, 'td', x) + '</tr>';
--y;
}
tableHTML += '</tbody>';
oTable.innerHTML = tableHTML;
this.insertComponent(oTable, false);
const firstTd = oTable.querySelector('td div');
this.setRange(firstTd, 0, firstTd, 0);
this.plugins.table.reset_table_picker.call(this);
},
createCells: function (nodeName, cnt, returnElement) {
nodeName = nodeName.toLowerCase();
if (!returnElement) {
let cellsHTML = '';
while (cnt > 0) {
cellsHTML += '<' +nodeName + '><div><br></div></' + nodeName + '>';
cnt--;
}
return cellsHTML;
} else {
const cell = this.util.createElement(nodeName);
cell.innerHTML = '<div><br></div>';
return cell;
}
},
onMouseMove_tablePicker: function (e) {
e.stopPropagation();
let x = this._w.Math.ceil(e.offsetX / 18);
let y = this._w.Math.ceil(e.offsetY / 18);
x = x < 1 ? 1 : x;
y = y < 1 ? 1 : y;
this.context.table.tableHighlight.style.width = x + 'em';
this.context.table.tableHighlight.style.height = y + 'em';
let x_u = 10; // x < 5 ? 5 : (x > 9 ? 10 : x + 1);
let y_u = 10; //y < 5 ? 5 : (y > 9 ? 10 : y + 1);
this.context.table.tableUnHighlight.style.width = x_u + 'em';
this.context.table.tableUnHighlight.style.height = y_u + 'em';
this.util.changeTxt(this.context.table.tableDisplay, x + ' x ' + y);
this.context.table._tableXY = [x, y];
},
reset_table_picker: function () {
if (!this.context.table.tableHighlight) return;
const highlight = this.context.table.tableHighlight.style;
const unHighlight = this.context.table.tableUnHighlight.style;
highlight.width = '1em';
highlight.height = '1em';
unHighlight.width = '10em';
unHighlight.height = '10em';
this.util.changeTxt(this.context.table.tableDisplay, '1 x 1');
this.submenuOff();
},
init: function () {
const contextTable = this.context.table;
const tablePlugin = this.plugins.table;
tablePlugin._removeEvents.call(this);
if (tablePlugin._selectedTable) {
const selectedCells = tablePlugin._selectedTable.querySelectorAll('.se-table-selected-cell');
for (let i = 0, len = selectedCells.length; i < len; i++) {
this.util.removeClass(selectedCells[i], 'se-table-selected-cell');
}
}
tablePlugin._toggleEditor.call(this, true);
contextTable._element = null;
contextTable._tdElement = null;
contextTable._trElement = null;
contextTable._trElements = null;
contextTable._tableXY = [];
contextTable._maxWidth = true;
contextTable._fixedColumn = false;
contextTable._physical_cellCnt = 0;
contextTable._logical_cellCnt = 0;
contextTable._rowCnt = 0;
contextTable._rowIndex = 0;
contextTable._physical_cellIndex = 0;
contextTable._logical_cellIndex = 0;
contextTable._current_colSpan = 0;
contextTable._current_rowSpan = 0;
tablePlugin._shift = false;
tablePlugin._selectedCells = null;
tablePlugin._selectedTable = null;
tablePlugin._ref = null;
tablePlugin._fixedCell = null;
tablePlugin._selectedCell = null;
tablePlugin._fixedCellName = null;
},
/** table edit controller */
call_controller_tableEdit: function (tdElement) {
const tablePlugin = this.plugins.table;
if (!this.getSelection().isCollapsed && !tablePlugin._selectedCell) {
this.controllersOff();
this.util.removeClass(tdElement, 'se-table-selected-cell');
return;
}
const contextTable = this.context.table;
const tableController = contextTable.tableController;
tablePlugin.setPositionControllerDiv.call(this, tdElement, tablePlugin._shift);
const tableElement = contextTable._element;
contextTable._maxWidth = this.util.hasClass(tableElement, 'se-table-size-100') || tableElement.style.width === '100%' || (!tableElement.style.width && !this.util.hasClass(tableElement, 'se-table-size-auto'));
contextTable._fixedColumn = this.util.hasClass(tableElement, 'se-table-layout-fixed') || tableElement.style.tableLayout === 'fixed';
tablePlugin.setTableStyle.call(this, contextTable._maxWidth ? 'width|column' : 'width');
tablePlugin.setPositionControllerTop.call(this, tableElement);
if (!tablePlugin._shift) this.controllersOn(contextTable.resizeDiv, tableController, tablePlugin.init.bind(this), tdElement, 'table');
},
setPositionControllerTop: function (tableElement) {
const tableController = this.context.table.tableController;
const offset = this.util.getOffset(tableElement, this.context.element.wysiwygFrame);
tableController.style.left = offset.left + 'px';
tableController.style.display = 'block';
tableController.style.top = (offset.top - tableController.offsetHeight - 2) + 'px';
},
setPositionControllerDiv: function (tdElement, reset) {
const contextTable = this.context.table;
const resizeDiv = contextTable.resizeDiv;
this.plugins.table.setCellInfo.call(this, tdElement, reset);
resizeDiv.style.display = 'block';
const offset = this.util.getOffset(tdElement, this.context.element.wysiwygFrame);
resizeDiv.style.left = (offset.left - this.context.element.wysiwygFrame.scrollLeft) + 'px';
resizeDiv.style.top = (offset.top + tdElement.offsetHeight + 12) + 'px';
const overLeft = this.context.element.wysiwygFrame.offsetWidth - (resizeDiv.offsetLeft + resizeDiv.offsetWidth);
if (overLeft < 0) {
resizeDiv.style.left = (resizeDiv.offsetLeft + overLeft) + 'px';
resizeDiv.firstElementChild.style.left = (20 - overLeft) + 'px';
} else {
resizeDiv.firstElementChild.style.left = '20px';
}
},
setCellInfo: function (tdElement, reset) {
const contextTable = this.context.table;
const table = contextTable._element = this.plugins.table._selectedTable || this.util.getParentElement(tdElement, 'TABLE');
if (/THEAD/i.test(table.firstElementChild.nodeName)) {
this.util.addClass(contextTable.headerButton, 'active');
} else {
this.util.removeClass(contextTable.headerButton, 'active');
}
if (reset || contextTable._physical_cellCnt === 0) {
if (contextTable._tdElement !== tdElement) {
contextTable._tdElement = tdElement;
contextTable._trElement = tdElement.parentNode;
}
const rows = contextTable._trElements = table.rows;
const cellIndex = tdElement.cellIndex;
let cellCnt = 0;
for (let i = 0, cells = rows[0].cells, len = rows[0].cells.length; i < len; i++) {
cellCnt += cells[i].colSpan;
}
// row cnt, row index
const rowIndex = contextTable._rowIndex = contextTable._trElement.rowIndex;
contextTable._rowCnt = rows.length;
// cell cnt, physical cell index
contextTable._physical_cellCnt = contextTable._trElement.cells.length;
contextTable._logical_cellCnt = cellCnt;
contextTable._physical_cellIndex = cellIndex;
// span
contextTable._current_colSpan = contextTable._tdElement.colSpan - 1;
contextTable._current_rowSpan - contextTable._trElement.cells[cellIndex].rowSpan - 1;
// find logcal cell index
let rowSpanArr = [];
let spanIndex = [];
for (let i = 0, cells, colSpan; i <= rowIndex; i++) {
cells = rows[i].cells;
colSpan = 0;
for (let c = 0, cLen = cells.length, cell, cs, rs, logcalIndex; c < cLen; c++) {
cell = cells[c];
cs = cell.colSpan - 1;
rs = cell.rowSpan - 1;
logcalIndex = c + colSpan;
if (spanIndex.length > 0) {
for (let r = 0, arr; r < spanIndex.length; r++) {
arr = spanIndex[r];
if (arr.row > i) continue;
if (logcalIndex >= arr.index) {
colSpan += arr.cs;
logcalIndex += arr.cs;
arr.rs -= 1;
arr.row = i + 1;
if (arr.rs < 1) {
spanIndex.splice(r, 1);
r--;
}
} else if (c === cLen - 1) {
arr.rs -= 1;
arr.row = i + 1;
if (arr.rs < 1) {
spanIndex.splice(r, 1);
r--;
}
}
}
}
// logcal cell index
if (i === rowIndex && c === cellIndex) {
contextTable._logical_cellIndex = logcalIndex;
break;
}
if (rs > 0) {
rowSpanArr.push({
index: logcalIndex,
cs: cs + 1,
rs: rs,
row: -1
});
}
colSpan += cs;
}
spanIndex = spanIndex.concat(rowSpanArr).sort(function (a, b) {return a.index - b.index;});
rowSpanArr = [];
}
rowSpanArr = null;
spanIndex = null;
}
},
editTable: function (type, option) {
const tablePlugin = this.plugins.table;
const contextTable = this.context.table;
const table = contextTable._element;
const isRow = type === 'row';
if (isRow) {
const tableAttr = contextTable._trElement.parentNode;
if (/^THEAD$/i.test(tableAttr.nodeName)) {
if (option === 'up') {
return;
} else if (!tableAttr.nextElementSibling || !/^TBODY$/i.test(tableAttr.nextElementSibling.nodeName)) {
table.innerHTML += '<tbody><tr>' + tablePlugin.createCells.call(this, 'td', contextTable._logical_cellCnt, false) + '</tr></tbody>';
return;
}
}
}
// multi
if (tablePlugin._ref) {
const positionCell = contextTable._tdElement;
const selectedCells = tablePlugin._selectedCells;
// multi - row
if (isRow) {
// remove row
if (!option) {
let row = selectedCells[0].parentNode;
const removeCells = [selectedCells[0]];
for (let i = 1, len = selectedCells.length, cell; i < len; i++) {
cell = selectedCells[i];
if (row !== cell.parentNode) {
removeCells.push(cell);
row = cell.parentNode;
}
}
for (let i = 0, len = removeCells.length; i < len; i++) {
tablePlugin.setCellInfo.call(this, removeCells[i], true);
tablePlugin.editRow.call(this, option);
}
} else { // edit row
tablePlugin.setCellInfo.call(this, option === 'up' ? selectedCells[0] : selectedCells[selectedCells.length - 1], true);
tablePlugin.editRow.call(this, option, positionCell);
}
} else { // multi - cell
const firstRow = selectedCells[0].parentNode;
// remove cell
if (!option) {
const removeCells = [selectedCells[0]];
for (let i = 1, len = selectedCells.length, cell; i < len; i++) {
cell = selectedCells[i];
if (firstRow === cell.parentNode) {
removeCells.push(cell);
} else {
break;
}
}
for (let i = 0, len = removeCells.length; i < len; i++) {
tablePlugin.setCellInfo.call(this, removeCells[i], true);
tablePlugin.editCell.call(this, option);
}
} else { // edit cell
let rightCell = null;
for (let i = 0, len = selectedCells.length - 1; i < len; i++) {
if (firstRow !== selectedCells[i + 1].parentNode) {
rightCell = selectedCells[i];
break;
}
}
tablePlugin.setCellInfo.call(this, option === 'left' ? selectedCells[0] : rightCell || selectedCells[0], true);
tablePlugin.editCell.call(this, option, positionCell);
}
}
if (!option) tablePlugin.init.call(this);
} // one
else {
tablePlugin[isRow ? 'editRow' : 'editCell'].call(this, option);
}
// after remove
if (!option) {
const children = table.children;
for (let i = 0; i < children.length; i++) {
if (children[i].children.length === 0) {
this.util.removeItem(children[i]);
i--;
}
}
if (table.children.length === 0) this.util.removeItem(table);
}
},
editRow: function (option, positionResetElement) {
const contextTable = this.context.table;
const remove = !option;
const up = option === 'up';
const originRowIndex = contextTable._rowIndex;
const rowIndex = remove || up ? originRowIndex : originRowIndex + contextTable._current_rowSpan + 1;
const sign = remove ? -1 : 1;
const rows = contextTable._trElements;
let cellCnt = contextTable._logical_cellCnt;
for (let i = 0, len = originRowIndex + (remove ? -1 : 0), cell; i <= len; i++) {
cell = rows[i].cells;
if (cell.length === 0) return;
for (let c = 0, cLen = cell.length, rs, cs; c < cLen; c++) {
rs = cell[c].rowSpan;
cs = cell[c].colSpan;
if (rs < 2 && cs < 2) continue;
if (rs + i > rowIndex && rowIndex > i) {
cell[c].rowSpan = rs + sign;
cellCnt -= cs;
}
}
}
if (remove) {
const next = rows[originRowIndex + 1];
if (next) {
const spanCells = [];
let cells = rows[originRowIndex].cells;
let colSpan = 0;
for (let i = 0, len = cells.length, cell, logcalIndex; i < len; i++) {
cell = cells[i];
logcalIndex = i + colSpan;
colSpan += cell.colSpan - 1;
if (cell.rowSpan > 1) {
cell.rowSpan -= 1;
spanCells.push({cell: cell.cloneNode(false), index: logcalIndex});
}
}
if (spanCells.length > 0) {
let spanCell = spanCells.shift();
cells = next.cells;
colSpan = 0;
for (let i = 0, len = cells.length, cell, logcalIndex; i < len; i++) {
cell = cells[i];
logcalIndex = i + colSpan;
colSpan += cell.colSpan - 1;
if (logcalIndex >= spanCell.index) {
i--, colSpan--;
colSpan += spanCell.cell.colSpan - 1;
next.insertBefore(spanCell.cell, cell);
spanCell = spanCells.shift();
if (!spanCell) break;
}
}
if (spanCell) {
next.appendChild(spanCell.cell);
for (let i = 0, len = spanCells.length; i < len; i++) {
next.appendChild(spanCells[i].cell);
}
}
}
}
contextTable._element.deleteRow(rowIndex);
} else {
const newRow = contextTable._element.insertRow(rowIndex);
newRow.innerHTML = this.plugins.table.createCells.call(this, 'td', cellCnt, false);
}
if (!remove) {
this.plugins.table.setPositionControllerDiv.call(this, positionResetElement || contextTable._tdElement, true);
} else {
this.controllersOff();
}
},
editCell: function (option, positionResetElement) {
const contextTable = this.context.table;
const util = this.util;
const remove = !option;
const left = option === 'left';
const colSpan = contextTable._current_colSpan;
const cellIndex = remove || left ? contextTable._logical_cellIndex : contextTable._logical_cellIndex + colSpan + 1;
const rows = contextTable._trElements;
let rowSpanArr = [];
let spanIndex = [];
let passCell = 0;
const removeCell = [];
const removeSpanArr = [];
for (let i = 0, len = contextTable._rowCnt, row, insertIndex, cells, newCell, applySpan, cellColSpan; i < len; i++) {
row = rows[i];
insertIndex = cellIndex;
applySpan = false;
cells = row.cells;
cellColSpan = 0;
for (let c = 0, cell, cLen = cells.length, rs, cs, removeIndex; c < cLen; c++) {
cell = cells[c];
if (!cell) break;
rs = cell.rowSpan - 1;
cs = cell.colSpan - 1;
if (!remove) {
if (c >= insertIndex) break;
if (cs > 0) {
if (passCell < 1 && cs + c >= insertIndex) {
cell.colSpan += 1;
insertIndex = null;
passCell = rs + 1;
break;
}
insertIndex -= cs;
}
if (!applySpan) {
for (let r = 0, arr; r < spanIndex.length; r++) {
arr = spanIndex[r];
insertIndex -= arr.cs;
arr.rs -= 1;
if (arr.rs < 1) {
spanIndex.splice(r, 1);
r--;
}
}
applySpan = true;
}
} else {
removeIndex = c + cellColSpan;
if (spanIndex.length > 0) {
const lastCell = !cells[c + 1];
for (let r = 0, arr; r < spanIndex.length; r++) {
arr = spanIndex[r];
if (arr.row > i) continue;
if (removeIndex >= arr.index) {
cellColSpan += arr.cs;
removeIndex = c + cellColSpan;
arr.rs -= 1;
arr.row = i + 1;
if (arr.rs < 1) {
spanIndex.splice(r, 1);
r--;
}
} else if (lastCell) {
arr.rs -= 1;
arr.row = i + 1;
if (arr.rs < 1) {
spanIndex.splice(r, 1);
r--;
}
}
}
}
if (rs > 0) {
rowSpanArr.push({
rs: rs,
cs: cs + 1,
index: removeIndex,
row: -1
});
}
if (removeIndex >= insertIndex && removeIndex + cs <= insertIndex + colSpan) {
removeCell.push(cell);
} else if (removeIndex <= insertIndex + colSpan && removeIndex + cs >= insertIndex) {
cell.colSpan -= util.getOverlapRangeAtIndex(cellIndex, cellIndex + colSpan, removeIndex, removeIndex + cs);
} else if (rs > 0 && (removeIndex < insertIndex || removeIndex + cs > insertIndex + colSpan)) {
removeSpanArr.push({
cell: cell,
i: i,
rs: i + rs
});
}
cellColSpan += cs;
}
}
spanIndex = spanIndex.concat(rowSpanArr).sort(function (a, b) {return a.index - b.index;});
rowSpanArr = [];
if (!remove) {
if (passCell > 0) {
passCell -= 1;
continue;
}
if (insertIndex !== null && cells.length > 0) {
newCell = this.plugins.table.createCells.call(this, cells[0].nodeName, 0, true);
newCell = row.insertBefore(newCell, cells[insertIndex]);
}
}
}
if (remove) {
let removeFirst, removeEnd;
for (let r = 0, rLen = removeCell.length, row; r < rLen; r++) {
row = removeCell[r].parentNode;
util.removeItem(removeCell[r]);
if (row.cells.length === 0) {
if (!removeFirst) removeFirst = util.getArrayIndex(rows, row);
removeEnd = util.getArrayIndex(rows, row);
util.removeItem(row);
}
}
for (let c = 0, cLen = removeSpanArr.length, rowSpanCell; c < cLen; c++) {
rowSpanCell = removeSpanArr[c];
rowSpanCell.cell.rowSpan = util.getOverlapRangeAtIndex(removeFirst, removeEnd, rowSpanCell.i, rowSpanCell.rs);
}
this.controllersOff();
} else {
this.plugins.table.setPositionControllerDiv.call(this, positionResetElement || contextTable._tdElement, true);
}
},
_closeSplitMenu: null,
openSplitMenu: function () {
this.util.addClass(this.context.table.splitButton, 'on');
this.context.table.splitMenu.style.display = 'inline-table';
this.plugins.table._closeSplitMenu = function () {
this.util.removeClass(this.context.table.splitButton, 'on');
this.context.table.splitMenu.style.display = 'none';
this.removeDocEvent('mousedown', this.plugins.table._closeSplitMenu);
this.plugins.table._closeSplitMenu = null;
}.bind(this);
this.addDocEvent('mousedown', this.plugins.table._closeSplitMenu);
},
splitCells: function (direction) {
const util = this.util;
const vertical = direction === 'vertical';
const contextTable = this.context.table;
const currentCell = contextTable._tdElement;
const rows = contextTable._trElements;
const currentRow = contextTable._trElement;
const index = contextTable._logical_cellIndex;
const rowIndex = contextTable._rowIndex;
const newCell = this.plugins.table.createCells.call(this, currentCell.nodeName, 0, true);
// vertical
if (vertical) {
const currentColSpan = currentCell.colSpan;
newCell.rowSpan = currentCell.rowSpan;
// colspan > 1
if (currentColSpan > 1) {
newCell.colSpan = this._w.Math.floor(currentColSpan/2);
currentCell.colSpan = currentColSpan - newCell.colSpan;
currentRow.insertBefore(newCell, currentCell.nextElementSibling);
} else { // colspan - 1
let rowSpanArr = [];
let spanIndex = [];
for (let i = 0, len = contextTable._rowCnt, cells, colSpan; i < len; i++) {
cells = rows[i].cells;
colSpan = 0;
for (let c = 0, cLen = cells.length, cell, cs, rs, logcalIndex; c < cLen; c++) {
cell = cells[c];
cs = cell.colSpan - 1;
rs = cell.rowSpan - 1;
logcalIndex = c + colSpan;
if (spanIndex.length > 0) {
for (let r = 0, arr; r < spanIndex.length; r++) {
arr = spanIndex[r];
if (arr.row > i) continue;
if (logcalIndex >= arr.index) {
colSpan += arr.cs;
logcalIndex += arr.cs;
arr.rs -= 1;
arr.row = i + 1;
if (arr.rs < 1) {
spanIndex.splice(r, 1);
r--;
}
} else if (c === cLen - 1) {
arr.rs -= 1;
arr.row = i + 1;
if (arr.rs < 1) {
spanIndex.splice(r, 1);
r--;
}
}
}
}
if (logcalIndex <= index && rs > 0) {
rowSpanArr.push({
index: logcalIndex,
cs: cs + 1,
rs: rs,
row: -1
});
}
if (cell !== currentCell && logcalIndex <= index && logcalIndex + cs >= index + currentColSpan - 1) {
cell.colSpan += 1;
break;
}
if (logcalIndex > index) break;
colSpan += cs;
}
spanIndex = spanIndex.concat(rowSpanArr).sort(function (a, b) {return a.index - b.index;});
rowSpanArr = [];
}
currentRow.insertBefore(newCell, currentCell.nextElementSibling);
}
} else { // horizontal
const currentRowSpan = currentCell.rowSpan;
newCell.colSpan = currentCell.colSpan;
// rowspan > 1
if (currentRowSpan > 1) {
newCell.rowSpan = this._w.Math.floor(currentRowSpan/2);
const newRowSpan = currentRowSpan - newCell.rowSpan;
const rowSpanArr = [];
const nextRowIndex = util.getArrayIndex(rows, currentRow) + newRowSpan;
for (let i = 0, cells, colSpan; i < nextRowIndex; i++) {
cells = rows[i].cells;
colSpan = 0;
for (let c = 0, cLen = cells.length, cell, cs, logcalIndex; c < cLen; c++) {
logcalIndex = c + colSpan;
if (logcalIndex >= index) break;
cell = cells[c];
cs = cell.rowSpan - 1;
if (cs > 0 && cs + i >= nextRowIndex && logcalIndex < index) {
rowSpanArr.push({
index: logcalIndex,
cs: cell.colSpan
});
}
colSpan += cell.colSpan - 1;
}
}
const nextRow = rows[nextRowIndex];
const nextCells = nextRow.cells;
let rs = rowSpanArr.shift();
for (let c = 0, cLen = nextCells.length, colSpan = 0, cell, cs, logcalIndex, insertIndex; c < cLen; c++) {
logcalIndex = c + colSpan;
cell = nextCells[c];
cs = cell.colSpan - 1;
insertIndex = logcalIndex + cs + 1;
if (rs && insertIndex >= rs.index) {
colSpan += rs.cs;
insertIndex += rs.cs;
rs = rowSpanArr.shift();
}
if (insertIndex >= index || c === cLen - 1) {
nextRow.insertBefore(newCell, cell.nextElementSibling);
break;
}
colSpan += cs;
}
currentCell.rowSpan = newRowSpan;
} else { // rowspan - 1
newCell.rowSpan = currentCell.rowSpan;
const newRow = util.createElement('TR');
newRow.appendChild(newCell);
for (let i = 0, cells; i < rowIndex; i++) {
cells = rows[i].cells;
if (cells.length === 0) return;
for (let c = 0, cLen = cells.length; c < cLen; c++) {
if (i + cells[c].rowSpan - 1 >= rowIndex) {
cells[c].rowSpan += 1;
}
}
}
const physicalIndex = contextTable._physical_cellIndex;
const cells = currentRow.cells;
for (let c = 0, cLen = cells.length; c < cLen; c++) {
if (c === physicalIndex) continue;
cells[c].rowSpan += 1;
}
currentRow.parentNode.insertBefore(newRow, currentRow.nextElementSibling);
}
}
this.focusEdge(currentCell);
this.plugins.table.setPositionControllerDiv.call(this, currentCell, true);
},
mergeCells: function () {
const tablePlugin = this.plugins.table;
const contextTable = this.context.table;
const util = this.util;
const ref = tablePlugin._ref;
const selectedCells = tablePlugin._selectedCells;
const mergeCell = selectedCells[0];
let emptyRowFirst = null;
let emptyRowLast = null;
let cs = (ref.ce - ref.cs) + 1;
let rs = (ref.re - ref.rs) + 1;
let mergeHTML = '';
let row = null;
for (let i = 1, len = selectedCells.length, cell, ch; i < len; i++) {
cell = selectedCells[i];
if (row !== cell.parentNode) row = cell.parentNode;
ch = cell.children;
for (let c = 0, cLen = ch.length; c < cLen; c++) {
if (util.isFormatElement(ch[c]) && util.onlyZeroWidthSpace(ch[c].textContent)) {
util.removeItem(ch[c]);
}
}
mergeHTML += cell.innerHTML;
util.removeItem(cell);
if (row.cells.length === 0) {
if (!emptyRowFirst) emptyRowFirst = row;
else emptyRowLast = row;
rs -= 1;
}
}
if (emptyRowFirst) {
const rows = contextTable._trElements;
const rowIndexFirst = util.getArrayIndex(rows, emptyRowFirst);
const rowIndexLast = util.getArrayIndex(rows, emptyRowLast || emptyRowFirst);
const removeRows = [];
for (let i = 0, cells; i <= rowIndexLast; i++) {
cells = rows[i].cells;
if (cells.length === 0) {
removeRows.push(rows[i]);
continue;
}
for (let c = 0, cLen = cells.length, cell, rs; c < cLen; c++) {
cell = cells[c];
rs = cell.rowSpan - 1;
if (rs > 0 && i + rs >= rowIndexFirst) {
cell.rowSpan -= util.getOverlapRangeAtIndex(rowIndexFirst, rowIndexLast, i, i + rs);
}
}
}
for (let i = 0, len = removeRows.length; i < len; i++) {
util.removeItem(removeRows[i]);
}
}
mergeCell.innerHTML += mergeHTML;
mergeCell.colSpan = cs;
mergeCell.rowSpan = rs;
this.controllersOff();
tablePlugin.setActiveButton.call(this, true, false);
tablePlugin.call_controller_tableEdit.call(this, mergeCell);
util.addClass(mergeCell, 'se-table-selected-cell');
this.focusEdge(mergeCell);
},
toggleHeader: function () {
const util = this.util;
const headerButton = this.context.table.headerButton;
const active = util.hasClass(headerButton, 'active');
const table = this.context.table._element;
if (!active) {
const header = util.createElement('THEAD');
header.innerHTML = '<tr>' + this.plugins.table.createCells.call(this, 'th', this.context.table._logical_cellCnt, false) + '</tr>';
table.insertBefore(header, table.firstElementChild);
} else {
util.removeItem(table.querySelector('thead'));
}
util.toggleClass(headerButton, 'active');
if (/TH/i.test(this.context.table._tdElement.nodeName)) {
this.controllersOff();
} else {
this.plugins.table.setPositionControllerDiv.call(this, this.context.table._tdElement, false);
}
},
setTableStyle: function (styles) {
const contextTable = this.context.table;
const tableElement = contextTable._element;
let icon, span, sizeIcon, text;
if (styles.indexOf('width') > -1) {
icon = contextTable.resizeButton.querySelector('svg');
span = contextTable.resizeText;
if (!contextTable._maxWidth) {
sizeIcon = contextTable.icons.expansion;
text = contextTable.maxText;
contextTable.columnFixedButton.style.display = 'none';
this.util.removeClass(tableElement, 'se-table-size-100');
this.util.addClass(tableElement, 'se-table-size-auto');
} else {
sizeIcon = contextTable.icons.reduction;
text = contextTable.minText;
contextTable.columnFixedButton.style.display = 'block';
this.util.removeClass(tableElement, 'se-table-size-auto');
this.util.addClass(tableElement, 'se-table-size-100');
}
this.util.changeElement(icon, sizeIcon);
this.util.changeTxt(span, text);
}
if (styles.indexOf('column') > -1) {
if (!contextTable._fixedColumn) {
this.util.removeClass(tableElement, 'se-table-layout-fixed');
this.util.addClass(tableElement, 'se-table-layout-auto');
this.util.removeClass(contextTable.columnFixedButton, 'active');
} else {
this.util.removeClass(tableElement, 'se-table-layout-auto');
this.util.addClass(tableElement, 'se-table-layout-fixed');
this.util.addClass(contextTable.columnFixedButton, 'active');
}
}
},
setActiveButton: function (fixedCell, selectedCell) {
const contextTable = this.context.table;
if (/^TH$/i.test(fixedCell.nodeName)) {
contextTable.insertRowAboveButton.setAttribute('disabled', true);
contextTable.insertRowBelowButton.setAttribute('disabled', true);
} else {
contextTable.insertRowAboveButton.removeAttribute('disabled');
contextTable.insertRowBelowButton.removeAttribute('disabled');
}
if (!selectedCell || fixedCell === selectedCell) {
contextTable.splitButton.removeAttribute('disabled');
contextTable.mergeButton.setAttribute('disabled', true);
} else {
contextTable.splitButton.setAttribute('disabled', true);
contextTable.mergeButton.removeAttribute('disabled');
}
},
// multi selecte
_bindOnSelect: null,
_bindOffSelect: null,
_bindOffShift: null,
_selectedCells: null,
_shift: false,
_fixedCell: null,
_fixedCellName: null,
_selectedCell: null,
_selectedTable: null,
_ref: null,
_toggleEditor: function (enabled) {
this.context.element.wysiwyg.setAttribute('contenteditable', enabled);
if (enabled) this.util.removeClass(this.context.element.wysiwyg, 'se-disabled');
else this.util.addClass(this.context.element.wysiwyg, 'se-disabled');
},
_offCellMultiSelect: function (e) {
e.stopPropagation();
const tablePlugin = this.plugins.table;
if (!tablePlugin._shift) {
tablePlugin._removeEvents.call(this);
tablePlugin._toggleEditor.call(this, true);
} else if (tablePlugin._initBind) {
this._wd.removeEventListener('touchmove', tablePlugin._