@kedao/editor
Version:
Rich Text Editor Based On Draft.js
607 lines • 29.3 kB
JavaScript
import React from 'react';
import Immutable from 'immutable';
import languages from './languages';
import * as TableUtils from './utils';
import Editor from '../../editor';
const getIndexFromEvent = (event, ignoredTarget = '') => {
var _a;
if (!isNaN(event)) {
return event * 1;
}
else if (ignoredTarget &&
event &&
event.target &&
event.target.dataset.role === ignoredTarget) {
return false;
}
else if ((_a = event === null || event === void 0 ? void 0 : event.currentTarget) === null || _a === void 0 ? void 0 : _a.dataset.index) {
return event.currentTarget.dataset.index * 1;
}
return false;
};
export const getLanguage = (editor) => {
const lang = editor.editorProps.language;
if (typeof lang === 'function') {
return lang(languages, '@kedao/table');
}
else {
return languages[lang] || languages.zh;
}
};
export class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
tableRows: [],
colToolHandlers: [],
rowToolHandlers: [],
defaultColWidth: 0,
colResizing: false,
colResizeOffset: 0,
selectedCells: [],
selectedRowIndex: -1,
selectedColumnIndex: -1,
setFirstRowAsHead: false,
dragSelecting: false,
draggingRectBounding: null,
cellsMergeable: false,
cellSplittable: false,
contextMenuPosition: null
};
this.__tableRef = null;
this.__colRefs = {};
this.__rowRefs = {};
this.__colResizeIndex = 0;
this.__colResizeStartAt = 0;
this.__startCellKey = null;
this.__endCellKey = null;
this.__dragSelecting = false;
this.__dragSelected = false;
this.__dragSelectingStartColumnIndex = null;
this.__dragSelectingStartRowIndex = null;
this.__dragSelectingEndColumnIndex = null;
this.__dragSelectingEndRowIndex = null;
this.__draggingRectBoundingUpdating = false;
this.__selectedCellsCleared = false;
this.handleToolbarMouseDown = (event) => {
event.preventDefault();
};
this.handleKeyDown = (event) => {
if (event.keyCode === 8) {
const { selectedColumnIndex, selectedRowIndex } = this.state;
if (selectedColumnIndex > -1) {
this.removeColumn();
event.preventDefault();
}
else if (selectedRowIndex > -1) {
this.removeRow();
event.preventDefault();
}
}
};
this.handleMouseUp = (event) => {
if (event.button !== 0) {
return false;
}
if (this.state.colResizing) {
const { defaultColWidth, colToolHandlers, colResizeOffset } = this.state;
const nextColToolHandlers = [...colToolHandlers];
nextColToolHandlers[this.__colResizeIndex - 1].width =
(nextColToolHandlers[this.__colResizeIndex - 1].width ||
defaultColWidth) + colResizeOffset;
nextColToolHandlers[this.__colResizeIndex].width =
(nextColToolHandlers[this.__colResizeIndex].width || defaultColWidth) -
colResizeOffset;
this.__colResizeIndex = 0;
this.__colResizeStartAt = 0;
this.setState({
contextMenuPosition: null,
colToolHandlers: nextColToolHandlers,
colResizeOffset: 0,
colResizing: false
}, () => {
this.renderCells();
this.updateCellsData({
colgroupData: nextColToolHandlers.map((item) => ({
width: item.width
}))
});
});
}
else {
this.setState({
contextMenuPosition: null
});
}
};
this.handleMouseMove = (event) => {
if (this.state.colResizing) {
this.setState({
colResizeOffset: this.getResizeOffset(event.clientX - this.__colResizeStartAt)
});
}
};
this.handleColResizerMouseDown = (event) => {
this.__colResizeIndex = event.currentTarget.dataset.index * 1;
this.__colResizeStartAt = event.clientX;
this.setState({ colResizing: true });
};
this.handleCellContexrMenu = (event) => {
const { selectedCells } = this.state;
const { cellKey } = event.currentTarget.dataset;
if (!~selectedCells.indexOf(cellKey)) {
this.selectCell(event);
}
const { top: tableTop, left: tableLeft, width: tableWidth } = this.__tableRef.getBoundingClientRect();
const top = event.clientY - tableTop + 15;
let left = event.clientX - tableLeft + 10;
if (left + 150 > tableWidth) {
left = tableWidth - 150;
}
this.setState({
contextMenuPosition: { top, left }
});
event.preventDefault();
};
this.handleContextMenuContextMenu = (event) => {
event.preventDefault();
};
this.handleCellMouseDown = (event) => {
if (this.state.colResizing) {
event.preventDefault();
event.stopPropagation();
return false;
}
this.__dragSelecting = true;
this.__dragSelectingStartColumnIndex = event.currentTarget.dataset.colIndex;
this.__dragSelectingStartRowIndex = event.currentTarget.dataset.rowIndex;
this.__draggingStartPoint = {
x: event.clientX,
y: event.clientY
};
this.setState({
dragSelecting: true
});
};
this.handleCellMouseUp = () => {
this.__dragSelecting = false;
this.__dragSelected = false;
this.__dragSelectingStartColumnIndex = null;
this.__dragSelectingStartRowIndex = null;
this.__dragSelectingEndColumnIndex = null;
this.__dragSelectingEndRowIndex = null;
this.setState({
dragSelecting: false,
draggingRectBounding: null
});
};
this.handleCellMouseEnter = (event) => {
if (this.__dragSelecting) {
this.__dragSelectingEndColumnIndex = event.currentTarget.dataset.colIndex;
this.__dragSelectingEndRowIndex = event.currentTarget.dataset.rowIndex;
if (this.__dragSelectingEndColumnIndex !==
this.__dragSelectingStartColumnIndex ||
this.__dragSelectingEndRowIndex !== this.__dragSelectingStartRowIndex) {
this.__dragSelected = true;
event.preventDefault();
}
else {
this.__dragSelected = false;
}
this.confirmDragSelecting();
}
};
this.handleTableMouseMove = (event) => {
if (this.__dragSelecting && this.__dragSelected) {
this.updateDraggingRectBounding(event);
event.preventDefault();
}
};
this.handleTableMouseLeave = (event) => {
if (this.__dragSelecting &&
event.currentTarget &&
event.currentTarget.dataset.role === 'table') {
this.handleCellMouseUp();
}
event.preventDefault();
};
this.confirmDragSelecting = () => {
if (!this.__dragSelectingStartColumnIndex ||
!this.__dragSelectingStartRowIndex ||
!this.__dragSelectingEndColumnIndex ||
!this.__dragSelectingEndRowIndex) {
return false;
}
const { cellKeys: selectedCells, spannedCellBlockKeys } = TableUtils.getCellsInsideRect(this.props.editorState, this.tableKey, [
this.__dragSelectingStartColumnIndex,
this.__dragSelectingStartRowIndex
], [this.__dragSelectingEndColumnIndex, this.__dragSelectingEndRowIndex]);
if (selectedCells.length < 2) {
return false;
}
this.setState({
selectedColumnIndex: -1,
selectedRowIndex: -1,
cellsMergeable: spannedCellBlockKeys.length === 0,
cellSplittable: false,
selectedCells: selectedCells
}, this.renderCells);
};
this.updateDraggingRectBounding = (mouseEvent) => {
if (this.__draggingRectBoundingUpdating || !this.__dragSelecting) {
return false;
}
this.__draggingRectBoundingUpdating = true;
const tableBounding = this.__tableRef.getBoundingClientRect();
const { x: startX, y: startY } = this.__draggingStartPoint;
const { clientX: currentX, clientY: currentY } = mouseEvent;
const draggingRectBounding = {};
if (currentX <= startX) {
draggingRectBounding.right =
tableBounding.left + tableBounding.width - startX;
}
else {
draggingRectBounding.left = startX - tableBounding.left + 9;
}
if (currentY <= startY) {
draggingRectBounding.bottom =
tableBounding.top + tableBounding.height - startY;
}
else {
draggingRectBounding.top = startY - tableBounding.top + 9;
}
draggingRectBounding.width = Math.abs(currentX - startX);
draggingRectBounding.height = Math.abs(currentY - startY);
this.setState({ draggingRectBounding }, () => {
setTimeout(() => {
this.__draggingRectBoundingUpdating = false;
}, 100);
});
};
this.selectCell = (event) => {
const { selectedCells } = this.state;
const { cellKey } = event.currentTarget.dataset;
const { colSpan, rowSpan } = event.currentTarget;
const nextSelectedCells = ~selectedCells.indexOf(cellKey) ? [] : [cellKey];
const cellSplittable = nextSelectedCells.length && (colSpan > 1 || rowSpan > 1);
this.setState({
selectedCells: nextSelectedCells,
cellSplittable: cellSplittable,
cellsMergeable: false,
selectedRowIndex: -1,
selectedColumnIndex: -1
}, this.renderCells);
};
this.selectColumn = (event) => {
const selectedColumnIndex = getIndexFromEvent(event, 'insert-column');
if (selectedColumnIndex === false) {
return false;
}
if (this.state.selectedColumnIndex === selectedColumnIndex) {
this.setState({
selectedCells: [],
cellsMergeable: false,
cellSplittable: false,
selectedColumnIndex: -1
}, this.renderCells);
return false;
}
const { cellKeys: selectedCells, spannedCellBlockKeys } = TableUtils.getCellsInsideRect(this.props.editorState, this.tableKey, [selectedColumnIndex, 0], [selectedColumnIndex, this.state.rowToolHandlers.length - 1]);
this.setState({
selectedColumnIndex: selectedColumnIndex,
selectedRowIndex: -1,
cellSplittable: false,
cellsMergeable: spannedCellBlockKeys.length === 0,
selectedCells: selectedCells
}, this.renderCells);
};
this.selectRow = (event) => {
const selectedRowIndex = getIndexFromEvent(event, 'insert-row');
if (selectedRowIndex === false) {
return false;
}
if (this.state.selectedRowIndex === selectedRowIndex) {
this.setState({
selectedCells: [],
cellsMergeable: false,
cellSplittable: false,
selectedRowIndex: -1
}, this.renderCells);
return false;
}
const { cellKeys: selectedCells, spannedCellBlockKeys } = TableUtils.getCellsInsideRect(this.props.editorState, this.tableKey, [0, selectedRowIndex], [this.state.colToolHandlers.length, selectedRowIndex]);
this.setState({
selectedColumnIndex: -1,
selectedRowIndex: selectedRowIndex,
cellSplittable: false,
cellsMergeable: spannedCellBlockKeys.length === 0,
selectedCells: selectedCells
}, this.renderCells);
};
this.insertColumn = (event) => {
const columnIndex = getIndexFromEvent(event);
if (columnIndex === false) {
return false;
}
const nextColToolHandlers = this.state.colToolHandlers.map((item) => (Object.assign(Object.assign({}, item), { width: 0 })));
this.setState({
selectedCells: [],
selectedRowIndex: -1,
selectedColumnIndex: -1,
colToolHandlers: nextColToolHandlers
}, () => {
this.props.editor.setValue(TableUtils.insertColumn(this.props.editorState, this.tableKey, this.state.tableRows.length, columnIndex
// nextColToolHandlers
));
});
};
this.removeColumn = () => {
const { selectedColumnIndex } = this.state;
const nextColToolHandlers = this.state.colToolHandlers.map((item) => (Object.assign(Object.assign({}, item), { width: 0 })));
if (selectedColumnIndex >= 0) {
this.setState({
selectedColumnIndex: -1,
colToolHandlers: nextColToolHandlers
}, () => {
this.props.editor.draftInstance.blur();
setImmediate(() => {
const result = TableUtils.removeColumn(this.props.editorState, this.tableKey, selectedColumnIndex
// nextColToolHandlers
);
this.props.editor.setValue(this.validateContent(result));
});
});
}
};
this.insertRow = (event) => {
const rowIndex = getIndexFromEvent(event);
if (rowIndex === false) {
return false;
}
this.setState({
selectedCells: [],
selectedRowIndex: -1,
selectedColumnIndex: -1
}, () => {
this.props.editor.setValue(TableUtils.insertRow(this.props.editorState, this.tableKey, this.colLength, rowIndex));
});
};
// 校验一下删除行、列之后的内容还有没有,没有的话则创建一个空的editorState,防止后续取不到值报错
this.validateContent = (editorState) => {
const len = editorState.toRAW(true).blocks.length;
return len ? editorState : Editor.createEditorState(null);
};
this.removeRow = () => {
const { selectedRowIndex } = this.state;
if (selectedRowIndex >= 0) {
this.setState({
selectedRowIndex: -1
}, () => {
this.props.editor.draftInstance.blur();
setImmediate(() => {
const result = TableUtils.removeRow(this.props.editorState, this.tableKey, selectedRowIndex);
this.props.editor.setValue(this.validateContent(result));
});
});
}
};
this.mergeCells = () => {
const { selectedCells, cellsMergeable } = this.state;
if (cellsMergeable && selectedCells.length > 1) {
this.setState({
selectedCells: [selectedCells[0]],
cellSplittable: true,
cellsMergeable: false,
selectedRowIndex: -1,
selectedColumnIndex: -1
}, () => {
this.props.editor.setValue(TableUtils.mergeCells(this.props.editorState, this.tableKey, selectedCells));
});
}
};
this.splitCell = () => {
const { selectedCells, cellSplittable } = this.state;
if (cellSplittable && selectedCells.length === 1) {
this.setState({
cellSplittable: false,
cellsMergeable: false,
selectedRowIndex: -1,
selectedColumnIndex: -1
}, () => {
this.props.editor.setValue(TableUtils.splitCell(this.props.editorState, this.tableKey, selectedCells[0]));
});
}
};
this.removeTable = () => {
this.props.editor.setValue(TableUtils.removeTable(this.props.editorState, this.tableKey));
};
this.language = getLanguage(props.editor);
}
componentDidMount() {
this.renderCells(this.props);
document.body.addEventListener('keydown', this.handleKeyDown, false);
document.body.addEventListener('mousemove', this.handleMouseMove, false);
document.body.addEventListener('mouseup', this.handleMouseUp, false);
}
UNSAFE_componentWillReceiveProps(nextProps) {
this.renderCells(nextProps);
}
componentWillUnmount() {
document.body.removeEventListener('keydown', this.handleKeyDown, false);
document.body.removeEventListener('mousemove', this.handleMouseMove, false);
document.body.removeEventListener('mouseup', this.handleMouseUp, false);
}
getResizeOffset(offset) {
let leftLimit = 0;
let rightLimit = 0;
const { colToolHandlers, defaultColWidth } = this.state;
leftLimit =
-1 *
((colToolHandlers[this.__colResizeIndex - 1].width || defaultColWidth) -
30);
rightLimit =
(colToolHandlers[this.__colResizeIndex].width || defaultColWidth) - 30;
offset = offset < leftLimit ? leftLimit : offset;
offset = offset > rightLimit ? rightLimit : offset;
return offset;
}
adjustToolbarHandlers() {
let needUpdate = false;
const rowToolHandlers = [...this.state.rowToolHandlers];
Object.keys(this.__rowRefs).forEach((index) => {
const rowHeight = this.__rowRefs[index]
? this.__rowRefs[index].getBoundingClientRect().height
: 40;
if (rowToolHandlers[index] &&
rowToolHandlers[index].height !== rowHeight) {
needUpdate = true;
rowToolHandlers[index].height = rowHeight;
}
});
if (needUpdate) {
this.setState({ rowToolHandlers });
}
}
updateCellsData(blockData) {
this.props.editor.setValue(TableUtils.updateAllTableBlocks(this.props.editorState, this.tableKey, blockData));
}
renderCells(props) {
props = props || this.props;
this.colLength = 0;
const tableRows = [];
const colToolHandlers = [];
const rowToolHandlers = [];
const { editorState, children } = props;
const tableWidth = this.__tableRef.getBoundingClientRect().width;
this.__startCellKey = children[0].key;
this.__endCellKey = children[children.length - 1].key;
children.forEach((cell, cellIndex) => {
const cellBlock = editorState
.getCurrentContent()
.getBlockForKey(cell.key);
const cellBlockData = cellBlock.getData();
const tableKey = cellBlockData.get('tableKey');
const colIndex = cellBlockData.get('colIndex') * 1;
const rowIndex = cellBlockData.get('rowIndex') * 1;
const colSpan = cellBlockData.get('colSpan');
const rowSpan = cellBlockData.get('rowSpan');
this.tableKey = tableKey;
if (rowIndex === 0) {
const colgroupData = cellBlockData.get('colgroupData') || [];
const totalColgroupWidth = colgroupData.reduce((width, col) => width + col.width * 1, 0);
const colSpan = (cellBlockData.get('colSpan') || 1) * 1;
for (let ii = this.colLength; ii < this.colLength + colSpan; ii++) {
colToolHandlers[ii] = {
key: cell.key,
width: this.state.colToolHandlers[ii]
? this.state.colToolHandlers[ii].width
: colgroupData[ii]
? (colgroupData[ii].width / totalColgroupWidth) * tableWidth * 1
: 0
};
}
this.colLength += colSpan;
}
const newCell = React.cloneElement(cell, {
'data-active': !!~this.state.selectedCells.indexOf(cell.key),
'data-row-index': rowIndex,
'data-col-index': colIndex || (tableRows[rowIndex] || []).length,
'data-cell-index': cellIndex,
'data-cell-key': cell.key,
'data-table-key': tableKey,
className: `bf-table-cell ${cell.props.className}`,
colSpan: colSpan,
rowSpan: rowSpan,
onClick: this.selectCell,
onContextMenu: this.handleCellContexrMenu,
onMouseDown: this.handleCellMouseDown,
onMouseUp: this.handleCellMouseUp,
onMouseEnter: this.handleCellMouseEnter
});
for (let jj = rowIndex; jj < rowIndex + rowSpan; jj++) {
rowToolHandlers[jj] = { key: cell.key, height: 0 };
tableRows[jj] = tableRows[jj] || [];
}
if (!tableRows[rowIndex]) {
tableRows[rowIndex] = [newCell];
}
else {
tableRows[rowIndex].push(newCell);
}
});
const defaultColWidth = tableWidth / this.colLength;
this.setState({ tableRows, colToolHandlers, rowToolHandlers, defaultColWidth }, this.adjustToolbarHandlers);
}
createColGroup() {
return (React.createElement("colgroup", null, this.state.colToolHandlers.map((item, index) => (React.createElement("col", { ref: (ref) => (this.__colRefs[index] = ref), width: item.width || this.state.defaultColWidth, key: index })))));
}
createColTools() {
const { colResizing, colResizeOffset, colToolHandlers, selectedColumnIndex, defaultColWidth } = this.state;
return (React.createElement("div", { "data-active": selectedColumnIndex >= 0, contentEditable: false, "data-key": "bf-col-toolbar", className: `bf-table-col-tools${colResizing ? ' resizing' : ''}`, onMouseDown: this.handleToolbarMouseDown }, colToolHandlers.map((item, index) => (React.createElement("div", { key: index, "data-key": item.key, "data-index": index, "data-active": selectedColumnIndex == index, className: "bf-col-tool-handler", style: { width: item.width || defaultColWidth }, onClick: this.selectColumn },
this.props.columnResizable && index !== 0
? (React.createElement("div", { "data-index": index, "data-key": item.key, className: `bf-col-resizer${colResizing && this.__colResizeIndex === index
? ' active'
: ''}`, style: colResizing && this.__colResizeIndex === index
? { transform: `translateX(${colResizeOffset}px)` }
: null, onMouseDown: this.handleColResizerMouseDown }))
: null,
React.createElement("div", { className: "bf-col-tool-left" },
React.createElement("div", { "data-index": index, "data-role": "insert-column", className: "bf-insert-col-before", onClick: this.insertColumn },
React.createElement("i", { className: "bfi-add" }))),
React.createElement("div", { className: "bf-col-tool-center" },
React.createElement("div", { "data-index": index, "data-role": "remove-col", className: "bf-remove-col", onClick: this.removeColumn },
React.createElement("i", { className: "bfi-bin" }))),
React.createElement("div", { className: "bf-col-tool-right" },
React.createElement("div", { "data-index": index + 1, "data-role": "insert-column", className: "bf-insert-col-after", onClick: this.insertColumn },
React.createElement("i", { className: "bfi-add" }))))))));
}
createRowTools() {
const { rowToolHandlers, selectedRowIndex } = this.state;
return (React.createElement("div", { "data-active": selectedRowIndex >= 0, contentEditable: false, className: "bf-table-row-tools", onMouseDown: this.handleToolbarMouseDown }, rowToolHandlers.map((item, index) => (React.createElement("div", { key: index, "data-key": item.key, "data-index": index, "data-active": selectedRowIndex == index, className: "bf-row-tool-handler", style: { height: item.height }, onClick: this.selectRow },
React.createElement("div", { className: "bf-row-tool-up" },
React.createElement("div", { "data-index": index, "data-role": "insert-row", className: "bf-insert-row-before", onClick: this.insertRow },
React.createElement("i", { className: "bfi-add" }))),
React.createElement("div", { className: "bf-row-tool-center" },
React.createElement("div", { "data-index": index, "data-role": "remove-row", className: "bf-remove-row", onClick: this.removeRow },
React.createElement("i", { className: "bfi-bin" }))),
React.createElement("div", { className: "bf-row-tool-down" },
React.createElement("div", { "data-index": index + 1, "data-role": "insert-row", className: "bf-insert-row-after", onClick: this.insertRow },
React.createElement("i", { className: "bfi-add" }))))))));
}
createContextMenu() {
const { cellsMergeable, cellSplittable, contextMenuPosition } = this.state;
if (!contextMenuPosition) {
return null;
}
return (React.createElement("div", { className: "bf-table-context-menu", onContextMenu: this.handleContextMenuContextMenu, contentEditable: false, style: contextMenuPosition },
React.createElement("div", { className: "context-menu-item", onMouseDown: this.mergeCells, "data-disabled": !cellsMergeable }, this.language.mergeCells),
React.createElement("div", { className: "context-menu-item", onMouseDown: this.splitCell, "data-disabled": !cellSplittable }, this.language.splitCell),
React.createElement("div", { className: "context-menu-item", onMouseDown: this.removeTable }, this.language.removeTable)));
}
render() {
const { tableRows, dragSelecting, draggingRectBounding } = this.state;
const { readOnly } = this.props.editor.props;
return (React.createElement("div", { className: "bf-table-container" },
React.createElement("table", { "data-role": "table", className: `bf-table${dragSelecting ? ' dragging' : ''}`, ref: (ref) => (this.__tableRef = ref),
// onMouseDown={this.handleTableMouseDown}
// onMouseUp={this.hanldeTableMouseUp}
onMouseMove: this.handleTableMouseMove, onMouseLeave: this.handleTableMouseLeave },
this.createColGroup(),
React.createElement("tbody", null, tableRows.map((cells, rowIndex) => (React.createElement("tr", { ref: (ref) => (this.__rowRefs[rowIndex] = ref), key: rowIndex }, cells))))),
dragSelecting
? (React.createElement("div", { className: "dragging-rect", style: draggingRectBounding }))
: null,
!readOnly && this.createContextMenu(),
!readOnly && this.createColTools(),
!readOnly && this.createRowTools()));
}
}
export const tableRenderMap = (options) => (props) => {
return Immutable.Map({
'table-cell': {
element: 'td',
wrapper: (React.createElement(Table, { columnResizable: options.columnResizable, editor: props.editor, editorState: props.editorState }))
}
});
};
//# sourceMappingURL=render.js.map