(function ( wpI18n, wpBlocks, wpElement, wpEditor, wpComponents ) { const { __ } = wpI18n; const { Component, Fragment } = wpElement; const { registerBlockType, createBlock } = wpBlocks; const { InspectorControls, BlockControls, RichText, PanelColorSettings } = wpEditor; const { PanelBody, BaseControl, RangeControl, SelectControl, ToggleControl, TextControl, IconButton, Button, Toolbar, DropdownMenu, Tooltip } = wpComponents; const { times } = lodash; const tableBlockIcon = ( ); let willSetContent = null; let lastValue = ''; class AdvTable extends Component { constructor() { super( ...arguments ); this.state = { initRow: 3, initCol: 3, selectedCell: null, rangeSelected: null, multiSelected: null, sectionSelected: null, updated: false, }; this.calculateRealColIndex = this.calculateRealColIndex.bind( this ); this.isMultiSelected = this.isMultiSelected.bind( this ); this.isRangeSelected = this.isRangeSelected.bind( this ); } componentWillMount() { const { attributes, setAttributes } = this.props; const currentBlockConfig = advgbDefaultConfig['advgb-table']; // No override attributes of blocks inserted before if (attributes.changed !== true) { if (typeof currentBlockConfig === 'object' && currentBlockConfig !== null) { Object.keys(currentBlockConfig).map((attribute) => { if (typeof attributes[attribute] === 'boolean') { attributes[attribute] = !!currentBlockConfig[attribute]; } else { attributes[attribute] = currentBlockConfig[attribute]; } }); } // Finally set changed attribute to true, so we don't modify anything again setAttributes( { changed: true } ); } } componentDidMount() { this.calculateRealColIndex( 'head' ); } componentDidUpdate() { const { isSelected } = this.props; const { selectedCell, updated } = this.state; if ( ! isSelected && selectedCell ) { this.setState( { selectedCell: null, rangeSelected: null, multiSelected: null, } ); } if (updated) { this.calculateRealColIndex(); this.setState( { updated: false } ); } } createTable() { const { setAttributes } = this.props; const { initRow, initCol } = this.state; this.setState( { updated: true } ); return setAttributes( { body: times( parseInt(initRow), () => ( { cells: times( parseInt(initCol), () => ( { content: '', } ) ) } ) ) } ); } // Check if is multi cells selected isMultiSelected() { const { multiSelected } = this.state; return ( multiSelected && multiSelected.length > 1 ); } // Check if is range cells selected isRangeSelected() { const { rangeSelected } = this.state; return (rangeSelected && rangeSelected.toCell); } calculateRealColIndex() { const { attributes, setAttributes } = this.props; [ 'head', 'body', 'foot' ].forEach( ( section ) => { if (!attributes[section].length) return null; const newSection = attributes[section].map( (row, cRow) => { return { cells: row.cells.map( (cell, cCol) => { cell.cI = cCol; for (let i=0;i < cRow; i++) { for (let j=0; j < attributes[section][i].cells.length; j++) { if (attributes[section][i].cells[j] && attributes[section][i].cells[j].colSpan) { if (attributes[section][i].cells[j].rowSpan && i + parseInt(attributes[section][i].cells[j].rowSpan) > cRow) { if (cCol === 0) { if (attributes[section][i].cells[j].cI <= cell.cI) { cell.cI += parseInt( attributes[section][i].cells[j].colSpan ); } } else { const lastColSpan = !isNaN(parseInt(row.cells[cCol-1].colSpan)) ? parseInt(row.cells[cCol-1].colSpan) : 0; if (attributes[section][i].cells[j].cI === row.cells[cCol - 1].cI + 1 || attributes[section][i].cells[j].cI <= row.cells[cCol - 1].cI + lastColSpan ) { cell.cI += parseInt( attributes[section][i].cells[j].colSpan ); } } } } } } for (let j=0; j < cCol; j++) { if (row.cells[j]) { if (row.cells[j].colSpan) { cell.cI += parseInt( row.cells[j].colSpan ) - 1; } } } return cell; } ) } } ); setAttributes( { [section] : newSection } ); } ) } insertRow( offset ) { const { selectedCell, sectionSelected } = this.state; if (!selectedCell) { return null; } const { attributes, setAttributes } = this.props; const currentSection = attributes[ sectionSelected ]; const { rowIndex } = selectedCell; const newRow = jQuery.extend( true, {}, currentSection[rowIndex] ); newRow.cells.map( ( cell ) => { cell.content = ''; return cell; } ); newRow.cells = newRow.cells.filter( ( cCell ) => !cCell.rowSpan ); const newSection = [ ...currentSection.slice( 0, rowIndex + offset ), newRow, ...currentSection.slice( rowIndex + offset ), ].map( ( row, rowIdx ) => ( { cells: row.cells.map( ( cell ) => { if (cell.rowSpan) { if (rowIdx <= rowIndex && ( (rowIdx + parseInt(cell.rowSpan) - 1) >= rowIndex) ) { cell.rowSpan = parseInt(cell.rowSpan) + 1; } } return cell; } ) } ) ); this.setState( { selectedCell: null, sectionSelected: null, updated: true, } ); setAttributes( { [ sectionSelected ]: newSection } ); } deleteRow() { const { selectedCell, sectionSelected } = this.state; if (!selectedCell) { return null; } const { attributes, setAttributes } = this.props; const currentSection = attributes[ sectionSelected ]; const { rowIndex } = selectedCell; const newSection = currentSection.map( (row, cRowIdx) => ( { cells: row.cells.map( (cell) => { if (cell.rowSpan) { if (cRowIdx <= rowIndex && parseInt(cell.rowSpan) + cRowIdx > rowIndex) { cell.rowSpan = parseInt(cell.rowSpan) - 1; if (cRowIdx === rowIndex) { const findColIdx = currentSection[cRowIdx + 1].cells.findIndex( (elm) => elm.cI === cell.cI || elm.cI > cell.cI ); currentSection[cRowIdx + 1].cells.splice( findColIdx, 0, cell ); } } } return cell; } ) } ) ); this.setState( { selectedCell: null, sectionSelected: null, updated: true, } ); if (newSection.length < 2) { alert( __( 'At least 1 row of current section must present.' ) ); return false; } setAttributes( { [ sectionSelected ]: newSection.filter( (row, index) => index !== rowIndex ), } ); } insertColumn( offset ) { const { selectedCell } = this.state; if (!selectedCell) { return null; } const { attributes, setAttributes } = this.props; const { cI } = selectedCell; let countRowSpan = 0; this.setState( { selectedCell: null, updated: true } ); [ 'head', 'body', 'foot' ].forEach( ( section ) => ( setAttributes( { [ section ]: attributes[ section ].map( ( row ) => { if (countRowSpan > 0) { // Skip if previous cell has row span countRowSpan--; return row; } let findColIdx = row.cells.findIndex( (cell, idx) => cell.cI === cI || (row.cells[idx + 1] && row.cells[idx + 1].cI > cI) ); if (findColIdx === -1) { findColIdx = row.cells.length - 1; } if (row.cells[findColIdx].colSpan && row.cells[findColIdx].cI < cI + offset && row.cells[findColIdx].cI + parseInt(row.cells[findColIdx].colSpan) > cI + offset ) { row.cells[findColIdx].colSpan++; if (row.cells[findColIdx].rowSpan) { countRowSpan = parseInt(row.cells[findColIdx].rowSpan) - 1; } return row; } else { let realOffset = offset; if (row.cells[findColIdx].cI > cI && offset === 1) { realOffset = 0; } else if (row.cells[findColIdx].cI < cI && offset === 0) { realOffset = 1; } return { cells: [ ...row.cells.slice( 0, findColIdx + realOffset ), { content: '' }, ...row.cells.slice( findColIdx + realOffset ), ], } } } ), } ) ) ) } deleteColumn() { const { selectedCell } = this.state; if (!selectedCell) { return null; } const { attributes, setAttributes } = this.props; const { cI } = selectedCell; let countRowSpan = 0; this.setState( { selectedCell: null, updated: true } ); [ 'head', 'body', 'foot' ].forEach( ( section ) => ( setAttributes( { [ section ]: attributes[ section ].map( ( row ) => { if (countRowSpan > 0) { countRowSpan--; return row; } const findColIdx = row.cells.findIndex( (cell, idx) => cell.cI === cI || (row.cells[idx + 1] && row.cells[idx + 1].cI > cI) ); if (row.cells[findColIdx].rowSpan) { countRowSpan = parseInt(row.cells[findColIdx].rowSpan) - 1; } if (row.cells[findColIdx].colSpan) { row.cells[findColIdx].colSpan--; if (row.cells[findColIdx].colSpan <= 1) { delete row.cells[findColIdx].colSpan; } return row; } return { cells: row.cells.filter( ( cell, index ) => index !== findColIdx ), } } ), } ) ) ) } mergeCells() { const { rangeSelected, sectionSelected } = this.state; if (!this.isRangeSelected()) { return null; } const { attributes, setAttributes } = this.props; const { fromCell, toCell } = rangeSelected; const currentSection = attributes[ sectionSelected ]; const fCell = currentSection[fromCell.rowIdx].cells[fromCell.colIdx]; const tCell = currentSection[toCell.rowIdx].cells[toCell.colIdx]; const fcSpan = typeof fCell.colSpan === 'undefined' ? 0 : parseInt(fCell.colSpan) - 1; const frSpan = typeof fCell.rowSpan === 'undefined' ? 0 : parseInt(fCell.rowSpan) - 1; const tcSpan = typeof tCell.colSpan === 'undefined' ? 0 : parseInt(tCell.colSpan) - 1; const trSpan = typeof tCell.rowSpan === 'undefined' ? 0 : parseInt(tCell.rowSpan) - 1; const minRowIdx = Math.min(fromCell.rowIdx, toCell.rowIdx); const maxRowIdx = Math.max(fromCell.rowIdx + frSpan, toCell.rowIdx + trSpan); const minColIdx = Math.min(fromCell.RCI, toCell.RCI); const maxColIdx = Math.max(fromCell.RCI + fcSpan, toCell.RCI + tcSpan); const newSection = currentSection.map( ( row, curRowIndex ) => { if (curRowIndex < minRowIdx || curRowIndex > maxRowIdx) { return row; } return { cells: row.cells.map( ( cell, curColIndex ) => { if (curColIndex === Math.min(fromCell.colIdx, toCell.colIdx) && curRowIndex === Math.min(fromCell.rowIdx, toCell.rowIdx) ) { const rowSpan = Math.abs(maxRowIdx - minRowIdx) + 1; const colSpan = Math.abs(maxColIdx - minColIdx) + 1; return { ...cell, rowSpan: rowSpan > 1 ? rowSpan : undefined, colSpan: colSpan > 1 ? colSpan : undefined, } } return cell; } ).filter( (cell, cCol) => cell.cI < minColIdx || ( cCol === Math.min(fromCell.colIdx, toCell.colIdx) && curRowIndex === Math.min(fromCell.rowIdx, toCell.rowIdx) ) || cell.cI > maxColIdx ) } } ); setAttributes( { [ sectionSelected ]: newSection } ); this.setState( { selectedCell: null, sectionSelected: null, rangeSelected: null, updated: true, } ); } splitMergedCells() { const { selectedCell, sectionSelected } = this.state; if (!selectedCell) { return null; } const { attributes, setAttributes } = this.props; const { colIndex, rowIndex, cI } = selectedCell; const cellColSpan = attributes[ sectionSelected ][rowIndex].cells[colIndex].colSpan ? parseInt(attributes[ sectionSelected ][rowIndex].cells[colIndex].colSpan) : 1; const cellRowSpan = attributes[ sectionSelected ][rowIndex].cells[colIndex].rowSpan ? parseInt(attributes[ sectionSelected ][rowIndex].cells[colIndex].rowSpan) : 1; attributes[ sectionSelected ][rowIndex].cells[colIndex].colSpan = undefined; attributes[ sectionSelected ][rowIndex].cells[colIndex].rowSpan = undefined; const newSection = attributes[ sectionSelected ].map( (row, curRowIndex) => { if (curRowIndex >= rowIndex && curRowIndex < (rowIndex + cellRowSpan) ) { const findColIdx = row.cells.findIndex( (cell) => cell.cI >= cI ); let startRowFix = 0; if (curRowIndex === rowIndex) { startRowFix = 1; } return { cells: [ ...row.cells.slice( 0, findColIdx + startRowFix ), ...times( cellColSpan - startRowFix, () => ( { content: '' } ) ), ...row.cells.slice( findColIdx + startRowFix ), ], } } return row; } ); setAttributes( { [ sectionSelected ]: newSection } ); this.setState( { selectedCell: null, sectionSelected: null, updated: true, } ); } // Parse styles from HTML form to React styles object static parseStyles( styles ) { if (typeof styles !== 'string') { return styles; } return styles .split(';') .filter(style => style.split(':')[0] && style.split(':')[1]) .map(style => [ style.split(':')[0].trim().replace(/-./g, c => c.substr(1).toUpperCase()), style.split(':')[1].trim() ]) .reduce((styleObj, style) => ({ ...styleObj, [style[0]]: style[1], }), {}); } getCellStyles( style ) { const { selectedCell, sectionSelected } = this.state; const section = this.props.attributes[ sectionSelected ]; if (!selectedCell) return undefined; const { rowIndex, colIndex } = selectedCell; if (style === 'borderColor') { return section[rowIndex].cells[colIndex].borderColorSaved; } const styles = AdvTable.parseStyles(section[rowIndex].cells[colIndex].styles); if (typeof styles === 'object') { let convertedStyles = styles[style]; if (convertedStyles && typeof convertedStyles !== 'number' && convertedStyles.indexOf( 'px' )) { convertedStyles = styles[style].replace( /px/g, '' ); } return typeof convertedStyles === 'undefined' && style === 'borderStyle' ? 'solid' : convertedStyles; } else { if (typeof styles !== 'undefined') { let convertedStyles = styles[style]; } return typeof convertedStyles === 'undefined' && style === 'borderStyle' ? 'solid' : undefined; } } updateCellsStyles( style ) { const { selectedCell, rangeSelected, multiSelected, sectionSelected } = this.state; if (!selectedCell && !this.isRangeSelected() && !this.isMultiSelected() ) { return null; } const { attributes, setAttributes } = this.props; const { rowIndex, colIndex } = selectedCell; const section = attributes[ sectionSelected ]; let minRowIdx, maxRowIdx, minColIdx, maxColIdx; if (this.isRangeSelected()) { const { fromCell, toCell } = rangeSelected; const fCell = section[fromCell.rowIdx].cells[fromCell.colIdx]; const tCell = section[toCell.rowIdx].cells[toCell.colIdx]; const fcSpan = typeof fCell.colSpan === 'undefined' ? 0 : parseInt(fCell.colSpan) - 1; const frSpan = typeof fCell.rowSpan === 'undefined' ? 0 : parseInt(fCell.rowSpan) - 1; const tcSpan = typeof tCell.colSpan === 'undefined' ? 0 : parseInt(tCell.colSpan) - 1; const trSpan = typeof tCell.rowSpan === 'undefined' ? 0 : parseInt(tCell.rowSpan) - 1; minRowIdx = Math.min(fromCell.rowIdx, toCell.rowIdx); maxRowIdx = Math.max(fromCell.rowIdx + frSpan, toCell.rowIdx + trSpan); minColIdx = Math.min(fromCell.RCI, toCell.RCI); maxColIdx = Math.max(fromCell.RCI + fcSpan, toCell.RCI + tcSpan); } const newSection = section.map( ( row, curRowIndex ) => { if (!this.isRangeSelected() && !this.isMultiSelected() && curRowIndex !== rowIndex || (this.isRangeSelected() && (curRowIndex < minRowIdx || curRowIndex > maxRowIdx) ) || (this.isMultiSelected() && multiSelected.findIndex( (c) => c.rowIndex === curRowIndex ) === -1) ) { return row; } return { cells: row.cells.map( ( cell, curColIndex ) => { if (!this.isRangeSelected() && !this.isMultiSelected() && curColIndex === colIndex || (this.isRangeSelected() && (cell.cI >= minColIdx && cell.cI <= maxColIdx) ) || (this.isMultiSelected() && multiSelected.findIndex( (c) => c.colIndex === curColIndex && c.rowIndex === curRowIndex ) > -1) ) { cell.styles = AdvTable.parseStyles( cell.styles ); if (style.borderColor) { // Set border color if (cell.styles.borderTopColor) { cell.styles = { ...cell.styles, borderTopColor: style.borderColor }; } if (cell.styles.borderRightColor) { cell.styles = { ...cell.styles, borderRightColor: style.borderColor }; } if (cell.styles.borderBottomColor) { cell.styles = { ...cell.styles, borderBottomColor: style.borderColor }; } if (cell.styles.borderLeftColor) { cell.styles = { ...cell.styles, borderLeftColor: style.borderColor }; } cell.borderColorSaved = style.borderColor; } else if (style.setBorder) { // Set border const cellBorderColor = cell.borderColorSaved || '#000'; const cellColSpan = !cell.colSpan ? 0 : parseInt(cell.colSpan) - 1; const cellRowSpan = !cell.rowSpan ? 0 : parseInt(cell.rowSpan) - 1; switch (style.setBorder) { case 'top': cell.styles = { ...cell.styles, borderTopColor: cellBorderColor }; break; case 'right': cell.styles = { ...cell.styles, borderRightColor: cellBorderColor }; break; case 'bottom': cell.styles = { ...cell.styles, borderBottomColor: cellBorderColor }; break; case 'left': cell.styles = { ...cell.styles, borderLeftColor: cellBorderColor }; break; case 'all': cell.styles = { ...cell.styles, borderTopColor: cellBorderColor, borderRightColor: cellBorderColor, borderBottomColor: cellBorderColor, borderLeftColor: cellBorderColor, }; break; case 'none': cell.styles = { ...cell.styles, borderTopColor: undefined, borderRightColor: undefined, borderBottomColor: undefined, borderLeftColor: undefined, }; break; case 'vert': if (cell.cI === minColIdx) { cell.styles = { ...cell.styles, borderRightColor: cellBorderColor, }; } else if (cell.cI + cellColSpan === maxColIdx) { cell.styles = { ...cell.styles, borderLeftColor: cellBorderColor, }; } else { cell.styles = { ...cell.styles, borderRightColor: cellBorderColor, borderLeftColor: cellBorderColor, }; } break; case 'horz': if (curRowIndex === minRowIdx) { cell.styles = { ...cell.styles, borderBottomColor: cellBorderColor, }; } else if (curRowIndex + cellRowSpan === maxRowIdx) { cell.styles = { ...cell.styles, borderTopColor: cellBorderColor, }; } else { cell.styles = { ...cell.styles, borderTopColor: cellBorderColor, borderBottomColor: cellBorderColor, }; } break; case 'inner': if (curRowIndex === minRowIdx) { cell.styles = { ...cell.styles, borderBottomColor: cellBorderColor, }; } else if (curRowIndex + cellRowSpan === maxRowIdx) { cell.styles = { ...cell.styles, borderTopColor: cellBorderColor, }; } else { cell.styles = { ...cell.styles, borderTopColor: cellBorderColor, borderBottomColor: cellBorderColor, }; } if (cell.cI === minColIdx) { cell.styles = { ...cell.styles, borderRightColor: cellBorderColor, }; } else if (cell.cI + cellColSpan === maxColIdx) { cell.styles = { ...cell.styles, borderLeftColor: cellBorderColor, }; } else { cell.styles = { ...cell.styles, borderRightColor: cellBorderColor, borderLeftColor: cellBorderColor, }; } break; case 'outer': if (curRowIndex === minRowIdx) { cell.styles = { ...cell.styles, borderTopColor: cellBorderColor, }; } else if (curRowIndex + cellRowSpan === maxRowIdx) { cell.styles = { ...cell.styles, borderBottomColor: cellBorderColor, }; } if (cell.cI === minColIdx) { cell.styles = { ...cell.styles, borderLeftColor: cellBorderColor, }; } else if (cell.cI + cellColSpan === maxColIdx) { cell.styles = { ...cell.styles, borderRightColor: cellBorderColor, }; } break; default: // Nothing break; } } else { cell.styles = { ...cell.styles, ...style }; } } return cell; } ) } } ); setAttributes( { [ section ]: newSection } ); } updateCellContent( content, cell = null ) { const { selectedCell, sectionSelected } = this.state; if (!selectedCell && !cell) { return null; } let rowIndex, colIndex; if (cell) { rowIndex = cell.rowIndex; colIndex = cell.colIndex; } else { rowIndex = selectedCell.rowIndex; colIndex = selectedCell.colIndex; } const { attributes, setAttributes } = this.props; const newSection = attributes[ sectionSelected ].map( ( row, curRowIndex ) => { if (curRowIndex !== rowIndex) { return row; } return { cells: row.cells.map( ( cell, curColIndex ) => { if (curColIndex !== colIndex) { return cell; } return { ...cell, content, } } ) } } ); setAttributes( { [ sectionSelected ]: newSection } ); } toggleSection( section ) { const { attributes, setAttributes } = this.props; const { sectionSelected } = this.state; const { body } = attributes; const cellsToAdd = [ { cells: body[0].cells.map( (cell) => ( { cI: cell.cI, colSpan: cell.colSpan } ) ) } ]; if (sectionSelected === section) { this.setState( { selectedCell: null, sectionSelected: null, } ) } if (!attributes[section].length) { return setAttributes( { [section] : cellsToAdd } ); } return setAttributes( { [section] : [] } ); } renderSection( section ) { const { attributes } = this.props; const { selectedCell, multiSelected, rangeSelected, sectionSelected } = this.state; return attributes[ section ].map( ( { cells }, rowIndex ) => (