@mirrormedia/lilith-draft-renderer
Version:
## Introduction
455 lines (389 loc) • 15.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TableEditorBlock = exports.TableBlock = void 0;
var _react = _interopRequireWildcard(require("react"));
var _styledComponents = _interopRequireDefault(require("styled-components"));
var _draftJs = require("draft-js");
var _cloneDeep = _interopRequireDefault(require("lodash/cloneDeep"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
const _ = {
cloneDeep: _cloneDeep.default
};
var ActionType;
(function (ActionType) {
ActionType["Insert"] = "insert";
ActionType["Delete"] = "delete";
ActionType["Update"] = "update";
})(ActionType || (ActionType = {}));
var TableEnum;
(function (TableEnum) {
TableEnum["Row"] = "row";
TableEnum["Column"] = "column";
})(TableEnum || (TableEnum = {}));
function createEmptyRow(colLen = 0, emptyValue) {
const rtn = [];
for (let i = 0; i < colLen; i++) {
rtn.push(emptyValue);
}
return rtn;
}
function resolveTableStyles(action, tableStyles) {
switch (action === null || action === void 0 ? void 0 : action.type) {
case ActionType.Insert:
{
if (action.target === TableEnum.Row) {
const rows = [...tableStyles.rows.slice(0, action.index), {}, ...tableStyles.rows.slice(action.index)];
return Object.assign({}, tableStyles, {
rows
});
} // TODO: handle target === TableEnum.Column if needed
return tableStyles;
}
case ActionType.Delete:
{
if (action.target === TableEnum.Row) {
const rows = [...tableStyles.rows.slice(0, action.index), ...tableStyles.rows.slice(action.index + 1)];
return Object.assign({}, tableStyles, {
rows
});
} // TODO: handle target === TableEnum.Column if needed
return tableStyles;
}
// TODO: handle action.type === ActionType.Update if needed
default:
{
return tableStyles;
}
}
}
function resolveTableData(action, tableData) {
switch (action === null || action === void 0 ? void 0 : action.type) {
case ActionType.Insert:
{
var _tableData$;
if (typeof (action === null || action === void 0 ? void 0 : action.index) !== 'number') {
return tableData;
}
if ((action === null || action === void 0 ? void 0 : action.target) === TableEnum.Column) {
// add the new column at specific position in each row
return tableData.map(r => [...r.slice(0, action === null || action === void 0 ? void 0 : action.index), _draftJs.EditorState.createEmpty(), ...r.slice(action === null || action === void 0 ? void 0 : action.index)]);
} // add the new row
return [...tableData.slice(0, action === null || action === void 0 ? void 0 : action.index), createEmptyRow(tableData === null || tableData === void 0 ? void 0 : (_tableData$ = tableData[0]) === null || _tableData$ === void 0 ? void 0 : _tableData$.length, _draftJs.EditorState.createEmpty()), ...tableData.slice(action === null || action === void 0 ? void 0 : action.index)];
}
case ActionType.Delete:
{
if (typeof (action === null || action === void 0 ? void 0 : action.index) !== 'number') {
return tableData;
}
if ((action === null || action === void 0 ? void 0 : action.target) === 'column') {
// delete the column at specific position in each row
return tableData.map(r => [...r.slice(0, action.index), ...r.slice(action.index + 1)]);
} // delete the column
return [...tableData.slice(0, action.index), ...tableData.slice(action.index + 1)];
}
case ActionType.Update:
{
// The reason we copy the array is to make sure
// that React component re-renders.
const copiedData = [...tableData];
if (typeof (action === null || action === void 0 ? void 0 : action.rIndex) !== 'number' || typeof (action === null || action === void 0 ? void 0 : action.cIndex) !== 'number') {
return copiedData;
}
copiedData[action.rIndex][action.cIndex] = action === null || action === void 0 ? void 0 : action.value;
return copiedData;
}
default:
{
return tableData;
}
}
}
function convertTableDataFromRaw(rawTableData) {
return rawTableData.map(rowData => {
return rowData.map(colData => {
const contentState = (0, _draftJs.convertFromRaw)(colData);
return _draftJs.EditorState.createWithContent(contentState);
});
});
}
function convertTableDataToRaw(tableData) {
return tableData.map(rowData => {
return rowData.map(colData => {
return (0, _draftJs.convertToRaw)(colData.getCurrentContent());
});
});
}
const Table = _styledComponents.default.div`
display: table;
width: 95%;
border-collapse: collapse;
`;
const Tr = _styledComponents.default.div`
display: table-row;
`;
const Td = _styledComponents.default.div`
display: table-cell;
border-width: 1px;
min-width: 100px;
min-height: 40px;
padding: 10px;
`;
const StyledFirstRow = _styledComponents.default.div`
display: table-row;
height: 10px;
div {
display: table-cell;
position: relative;
}
span {
cursor: pointer;
line-height: 10px;
}
span:first-child {
position: absolute;
right: 50%;
transform: translateX(50%);
}
span:first-child:before {
content: '•';
}
span:first-child:hover:before {
content: '➖';
}
span:last-child {
position: absolute;
right: -5px;
}
span:last-child:before {
content: '•';
}
span:last-child:hover:before {
content: '➕';
}
`;
const StyledFirstColumn = _styledComponents.default.div`
display: table-cell;
width: 10px;
position: relative;
span {
cursor: pointer;
}
span:first-child {
position: absolute;
bottom: 50%;
right: 0px;
transform: translateY(50%);
}
span:first-child:before {
content: '•';
}
span:first-child:hover:before {
content: '➖';
}
span:last-child {
position: absolute;
bottom: -10px;
right: 0px;
}
span:last-child:before {
content: '•';
}
span:last-child:hover:before {
content: '➕';
}
`;
const TableBlockContainer = _styledComponents.default.div`
margin: 15px 0;
position: relative;
overflow: scroll;
padding: 15px;
`;
const StyledTable = _styledComponents.default.div`
display: table;
width: 95%;
border-collapse: collapse;
`;
const StyledTr = _styledComponents.default.div`
display: table-row;
`;
const StyledTd = _styledComponents.default.div`
display: table-cell;
border: 1px solid #e1e5e9;
min-width: 100px;
min-height: 40px;
padding: 10px;
`;
const TableEditorBlock = props => {
var _tableData$2;
const {
block,
blockProps,
contentState
} = props;
const {
onEditStart,
onEditFinish,
getMainEditorReadOnly
} = blockProps;
const entityKey = block.getEntityAt(0);
const entity = contentState.getEntity(entityKey);
const {
tableData: rawTableData,
tableStyles: _tableStyles
} = entity.getData();
const [tableData, setTableData] = (0, _react.useState)(convertTableDataFromRaw(rawTableData)); // deep clone `_tableStyles` to prevent updating the entity data directly
const [tableStyles, setTableStyles] = (0, _react.useState)(_.cloneDeep(_tableStyles));
const tableRef = (0, _react.useRef)(null); // `TableBlock` will render other inner/nested DraftJS Editors inside the main Editor.
// However, main Editor's `readOnly` needs to be mutually exclusive with nested Editors' `readOnly`.
// If the main Editor and nested Editor are editable (`readOnly={false}`) at the same time,
// there will be a DraftJS Edtior Selection bug.
const [cellEditorReadOnly, setCellEditorReadOnly] = (0, _react.useState)(!getMainEditorReadOnly()); // The user clicks the table for editing
const onTableClick = () => {
// call `onEditStart` function to tell the main DraftJS Editor
// that we are going to interact with the custom atomic block.
onEditStart(); // make nested DraftJS Editors editable
setCellEditorReadOnly(false);
};
(0, _react.useEffect)(() => {
// The user clicks other places except the table,
// so we think he/she doesn't want to edit the table anymore.
// Therefore, we call `onEditFinish` function to pass modified table data
// back to the main DraftJS Edtior.
function handleClickOutside(event) {
// `!cellEditorReadOnly` condition is needed.
// If there are two tables in the main Editor,
// this `handleClickOutside` will only handle the just updated one.
if (tableRef.current && !tableRef.current.contains(event.target) && !cellEditorReadOnly) {
// make inner DraftJS Editors NOT editable
setCellEditorReadOnly(true); // call `onEditFinish` function tell the main DraftJS Editor
// that we are finishing interacting with the custom atomic block.
onEditFinish({
entityKey,
entityData: {
tableData: convertTableDataToRaw(tableData),
tableStyles
}
});
}
}
console.debug('(rich-text-editor/table): add click outside event listener');
document.addEventListener('mousedown', handleClickOutside);
return () => {
// Unbind the event listener on clean up
console.debug('(rich-text-editor/table): remove click outside event listener');
document.removeEventListener('mousedown', handleClickOutside);
};
}, // Skip running effect if `tableData` and `cellEditorReadOnly` are not changed.
[tableData, cellEditorReadOnly]);
return /*#__PURE__*/_react.default.createElement(TableBlockContainer, null, /*#__PURE__*/_react.default.createElement(Table, {
key: entityKey,
onClick: onTableClick,
ref: tableRef
}, /*#__PURE__*/_react.default.createElement(StyledFirstRow, null, /*#__PURE__*/_react.default.createElement("div", null), tableData === null || tableData === void 0 ? void 0 : (_tableData$2 = tableData[0]) === null || _tableData$2 === void 0 ? void 0 : _tableData$2.map((colData, cIndex) => {
return /*#__PURE__*/_react.default.createElement("div", {
key: `col_${cIndex + 1}`
}, /*#__PURE__*/_react.default.createElement("span", {
onClick: () => {
const deleteColumn = {
type: ActionType.Delete,
target: TableEnum.Column,
index: cIndex
};
const updatedTableData = resolveTableData(deleteColumn, tableData);
setTableData(updatedTableData);
}
}), /*#__PURE__*/_react.default.createElement("span", {
onClick: () => {
const insertColumn = {
type: ActionType.Insert,
target: TableEnum.Column,
index: cIndex + 1
};
const updatedTableData = resolveTableData(insertColumn, tableData);
setTableData(updatedTableData);
}
}));
})), tableData.map((rowData, rIndex) => {
var _tableStyles$rows;
const colsJsx = rowData.map((colData, cIndex) => {
return /*#__PURE__*/_react.default.createElement(Td, {
key: `col_${cIndex}`
}, /*#__PURE__*/_react.default.createElement(_draftJs.Editor, {
onChange: editorState => {
const updateAction = {
type: ActionType.Update,
cIndex,
rIndex,
value: editorState
};
const updatedTableData = resolveTableData(updateAction, tableData);
setTableData(updatedTableData);
},
editorState: colData,
readOnly: cellEditorReadOnly
}));
});
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {
key: `row_${rIndex}`
}, /*#__PURE__*/_react.default.createElement(Tr, {
style: tableStyles === null || tableStyles === void 0 ? void 0 : (_tableStyles$rows = tableStyles.rows) === null || _tableStyles$rows === void 0 ? void 0 : _tableStyles$rows[rIndex]
}, /*#__PURE__*/_react.default.createElement(StyledFirstColumn, null, /*#__PURE__*/_react.default.createElement("span", {
onClick: () => {
const deleteRowAction = {
type: ActionType.Delete,
target: TableEnum.Row,
index: rIndex
};
const updatedTableData = resolveTableData(deleteRowAction, tableData);
setTableData(updatedTableData);
setTableStyles(resolveTableStyles(deleteRowAction, tableStyles));
}
}), /*#__PURE__*/_react.default.createElement("span", {
onClick: () => {
const addRowAction = {
type: ActionType.Insert,
target: TableEnum.Row,
index: rIndex + 1
};
const updatedTableData = resolveTableData(addRowAction, tableData);
setTableData(updatedTableData);
setTableStyles(resolveTableStyles(addRowAction, tableStyles));
}
})), colsJsx));
})));
};
exports.TableEditorBlock = TableEditorBlock;
const TableBlock = props => {
const {
block,
contentState
} = props;
const entityKey = block.getEntityAt(0);
const entity = contentState.getEntity(entityKey);
const {
tableData: rawTableData
} = entity.getData();
const [tableData] = (0, _react.useState)(convertTableDataFromRaw(rawTableData));
const tableRef = (0, _react.useRef)(null);
return /*#__PURE__*/_react.default.createElement(TableBlockContainer, null, /*#__PURE__*/_react.default.createElement(StyledTable, {
key: entityKey,
ref: tableRef
}, tableData.map((rowData, rIndex) => {
const colsJsx = rowData.map((colData, cIndex) => {
return /*#__PURE__*/_react.default.createElement(StyledTd, {
key: `col_${cIndex}`
}, /*#__PURE__*/_react.default.createElement(_draftJs.Editor, {
editorState: colData,
readOnly: true
}));
});
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {
key: `row_${rIndex}`
}, /*#__PURE__*/_react.default.createElement(StyledTr, null, colsJsx));
})));
};
exports.TableBlock = TableBlock;