scripts/core/editor3/reducers/table.tsx
import {RichUtils} from 'draft-js';
import {onChange} from './editor3';
import insertAtomicBlockWithoutEmptyLines from '../helpers/insertAtomicBlockWithoutEmptyLines';
import {getCell, setCell, getData, setData, IEditor3TableData} from '../helpers/table';
import {CustomEditor3Entity} from '../constants';
/**
* @description Contains the list of table related reducers.
*/
const table = (state = {}, action) => {
switch (action.type) {
case 'TOOLBAR_ADD_TABLE':
return addTable(state, action.payload);
case 'TOOLBAR_ADD_ROW_AFTER':
return addRowAfter(state);
case 'TOOLBAR_ADD_COL_AFTER':
return addColAfter(state);
case 'TOOLBAR_REMOVE_ROW':
return removeRow(state);
case 'TOOLBAR_REMOVE_COL':
return removeCol(state);
case 'TOOLBAR_TABLE_HEADER':
return toggleTableHeader(state);
case 'TOOLBAR_TABLE_STYLE':
return toggleTableStyle(state, action.payload);
case 'TOOLBAR_MULTI-LINE_STYLE':
return toggleMultiLineQuoteBlockStyle(state, action.payload);
default:
return state;
}
};
/**
* @ngdoc method
* @name addTable
* @param {Object} data Table data (numRows, numCols, cells).
* @description Adds a table into the content.
*/
const addTable = (state, data) => {
const contentState = state.editorState.getCurrentContent();
const contentStateWithEntity = contentState.createEntity(CustomEditor3Entity.TABLE, 'MUTABLE', {data});
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const {editorState} = insertAtomicBlockWithoutEmptyLines(
state.editorState,
entityKey,
' ',
);
return onChange(state, editorState);
};
/**
* @ngdoc method
* @name addRowAfter
* @description Adds a row after the currently active one.
*/
const addRowAfter = (state) =>
processCells(state, (prevCells, numCols, nRows, i, j, withHeader) => {
const numRows = nRows + 1;
const cells = [];
prevCells.forEach((row, index) => {
cells.push(row);
if (index === i) {
cells.push([]);
}
});
return {data: {cells, numRows, numCols, withHeader}};
});
/**
* @ngdoc method
* @name removeRow
* @description Removes the currently active row.
*/
const removeRow = (state) =>
processCells(state, (cells, numCols, nRows, i, j, withHeader) => {
let numRows = nRows;
if (numRows > 1) {
cells.splice(i, 1);
numRows -= 1;
}
return {data: {cells, numRows, numCols, withHeader}};
});
/**
* @ngdoc method
* @name addColAfter
* @description Adds a column after the currently active one.
*/
const addColAfter = (state) =>
processCells(state, (cells, numCols, numRows, i, j, withHeader) => ({
data: {
numRows: numRows,
numCols: numCols + 1,
withHeader: withHeader,
cells: cells.map((_, ii) =>
Array.from(new Array(numCols + 1))
.map((__, jj) => {
if (jj === j + 1) {
return null;
}
let orig = jj;
if (jj > j + 1) {
orig -= 1;
}
if (cells[ii] && cells[ii][orig]) {
return cells[ii][orig];
}
return null;
})),
},
}));
/**
* @ngdoc method
* @name removeCol
* @description Removes the currently active column.
*/
const removeCol = (state) =>
processCells(state, (prevCells, nCols, numRows, i, j, withHeader) => {
let numCols = nCols;
let cells = prevCells;
if (numCols > 1) {
numCols -= 1;
cells = prevCells.map((cell) => {
cell.splice(j, 1);
return cell;
});
}
return {data: {cells, numRows, numCols, withHeader}};
});
/**
* @ngdoc method
* @name processCells
* @param {Function} fn Function that is called with parameters
* (cells, numCols, numRows, i, j, withHeader) and is expected to return the new
* data that should be placed on the table entity. The expected return
* is an object with keys {cells, numRows, numCols}.
* @description Helper function to help process the cells in the currently active
* table and transform the entity data to a new form, using a callback function.
*/
export const processCells = (state, fn) => {
const {activeCell, editorState} = state;
if (activeCell === null) {
return state;
}
const {i, j, key, currentStyle, selection} = activeCell;
const contentState = editorState.getCurrentContent();
const block = contentState.getBlockForKey(key);
const {cells, numRows, numCols, withHeader} = getData(contentState, block.getKey());
const {data, newCurrentStyle, popup} = fn(cells, numCols, numRows, i, j, withHeader, currentStyle, selection);
const newEditorState = setData(editorState, block, data, 'change-block-data');
let newState = state;
if (newCurrentStyle !== null) {
// We have to set the popup to the result from `fn` only when it has
// been modified so we don't break existing logic checking popup.type
// when the editor reloads. e.g. after setting an inline style
if (popup != null) {
newState = {
...state,
popup,
activeCell: {
...activeCell,
currentStyle: newCurrentStyle,
selection: selection,
},
};
} else {
newState = {
...state,
activeCell: {
...activeCell,
currentStyle: newCurrentStyle,
selection: selection,
},
};
}
}
return onChange(newState, newEditorState, true);
};
/**
* @ngdoc method
* @name toggleTableHeader
* @description Toggles the table's header.
*/
const toggleTableHeader = (state) =>
processCells(
state,
(cells, numCols, numRows, i, j, withHeader, currentStyle) => {
const newData = {
cells: cells,
numRows: numRows,
numCols: numCols,
withHeader: !withHeader,
currentStyle: currentStyle,
};
return {
data: newData,
newCurrentStyle: currentStyle,
};
},
);
/**
* @ngdoc method
* @name toggleTableStyle
* @description Toggles the table's style.
*/
const toggleTableStyle = (state, inlineStyle) =>
processCells(
state,
(cells, numCols, numRows, i, j, withHeader, currentStyle, selection) => {
const data = {cells, numRows, numCols, withHeader};
const cellStateEditor = getCell(data, i, j, currentStyle, selection);
const newCellEditorState = RichUtils.toggleInlineStyle(cellStateEditor, inlineStyle);
const newCurrentStyle = newCellEditorState.getCurrentInlineStyle().toArray();
const newData = setCell(data, i, j, newCellEditorState).data;
return {
data: newData,
newCurrentStyle: newCurrentStyle,
};
},
);
const toggleMultiLineQuoteBlockStyle = (state, blockType) =>
processCells(
state,
(cells, numCols, numRows, i, j, withHeader, currentStyle, selection) => {
const data: IEditor3TableData = {cells, numRows, numCols, withHeader};
const cellStateEditor = getCell(data, i, j, currentStyle, selection);
const newCellEditorState = RichUtils.toggleBlockType(cellStateEditor, blockType);
const newCurrentStyle = newCellEditorState.getCurrentInlineStyle().toArray();
const newData = setCell(data, i, j, newCellEditorState).data;
return {
data: newData,
newCurrentStyle: newCurrentStyle,
};
},
);
export default table;