UNPKG

suneditor

Version:

Pure JavaScript based WYSIWYG web editor

1,163 lines (990 loc) 61.6 kB
/* * 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._