@kedao/editor
Version:
Rich Text Editor Based On Draft.js
594 lines • 26.2 kB
JavaScript
import { EditorState, ContentBlock, CharacterMetadata, genKey } from 'draft-js';
import Immutable from 'immutable';
import { ContentUtils } from '@kedao/utils';
// 简易的值比较方法
const valueComparison = (value1, value2, operator) => {
switch (operator) {
case '==':
// eslint-disable-next-line eqeqeq
return value1 == value2;
case '>=':
return value1 >= value2;
case '<=':
return value1 <= value2;
case '>':
return value1 > value2;
case '<':
return value1 < value2;
}
};
// 创建并返回一个单元格block
const createCellBlock = (cell) => {
cell = Object.assign({ colSpan: 1, rowSpan: 1, text: '' }, cell);
const { tableKey, key, colIndex, rowIndex, colSpan, rowSpan, text, isHead } = cell;
return new ContentBlock({
key: key || genKey(),
type: 'table-cell',
text: text,
data: Immutable.Map({
tableKey,
colIndex,
rowIndex,
colSpan,
rowSpan,
isHead
}),
characterList: Immutable.List(Immutable.Repeat(CharacterMetadata.create(), text.length))
});
};
const createUnstyledBlock = () => {
const key = genKey();
return [
key,
new ContentBlock({
key: key,
type: 'unstyled',
text: '',
data: Immutable.Map({}),
characterList: Immutable.List([])
})
];
};
// 创建并返回一行单元格block
const createRowBlocks = (tableKey, rowIndex, rowLength, firstCellText = '') => {
const cells = Immutable.Range(0, rowLength)
.map((index) => {
const cellBlock = createCellBlock({
tableKey: tableKey,
colIndex: index,
rowIndex: rowIndex,
text: index === 0 ? firstCellText : ''
});
return [cellBlock.getKey(), cellBlock];
})
.toArray();
return Immutable.OrderedMap(cells).toSeq();
};
// 将表格block更新到contentState
const updateTableBlocks = (contentState, selection, focusKey, tableBlocks, tableKey) => {
const contentBlocks = contentState.getBlockMap().toSeq();
const blocksBefore = contentBlocks.takeUntil((block) => {
return block.getData().get('tableKey') === tableKey;
});
const blocksAfter = contentBlocks
.skipUntil((block, key) => {
const nextBlockKey = contentState.getKeyAfter(key);
return (block.getData().get('tableKey') === tableKey &&
nextBlockKey &&
contentState.getBlockForKey(nextBlockKey).getData().get('tableKey') !==
tableKey);
})
.rest();
return contentState.merge({
blockMap: blocksBefore.concat(tableBlocks, blocksAfter).toOrderedMap(),
selectionBefore: selection,
selectionAfter: selection.merge({
anchorKey: focusKey,
anchorOffset: 0,
focusKey: focusKey,
focusOffset: 0,
hasFocus: false,
isBackward: false
})
});
};
// 使用简易值比较函数筛选符合条件的block
const findBlocks = (contentBlocks, propName, propValue, operator = '==') => {
return contentBlocks.filter((block) => {
return valueComparison(block.getData().get(propName), propValue, operator);
});
};
// 遍历以修正单元格的colIndex属性(表格blocks专用)
export const rebuildTableBlocks = (tableBlocks, addonBlockData = {}) => {
const skipedCells = {};
const cellCountOfRow = [];
return tableBlocks.map((block) => {
var _a;
const blockData = block.getData();
const rowIndex = blockData.get('rowIndex');
const colSpan = blockData.get('colSpan') || 1;
const rowSpan = blockData.get('rowSpan') || 1;
cellCountOfRow[rowIndex] = cellCountOfRow[rowIndex] || 0;
cellCountOfRow[rowIndex]++;
const cellIndex = cellCountOfRow[rowIndex] - 1;
let colIndex = cellIndex;
let xx, yy;
for (; (_a = skipedCells[rowIndex]) === null || _a === void 0 ? void 0 : _a[colIndex]; colIndex++, cellCountOfRow[rowIndex]++)
;
if (rowSpan > 1 || colSpan > 1) {
for (xx = rowIndex; xx < rowIndex + rowSpan; xx++) {
for (yy = colIndex; yy < colIndex + colSpan; yy++) {
skipedCells[xx] = skipedCells[xx] || {};
skipedCells[xx][yy] = true;
}
}
}
return block.merge({
data: Immutable.Map(Object.assign(Object.assign(Object.assign({}, blockData.toJS()), addonBlockData), { colIndex: colIndex }))
});
});
};
// 遍历以修正单元格的colSpan和rowSpan属性(表格DOM专用)
export const rebuildTableNode = (tableNode) => {
const tableKey = genKey();
const skipedCells = {};
[].forEach.call(tableNode.rows, (row, rowIndex) => {
[].forEach.call(row.cells, (cell, cellIndex) => {
var _a;
let colIndex = cellIndex;
let xx, yy;
for (; (_a = skipedCells[rowIndex]) === null || _a === void 0 ? void 0 : _a[colIndex]; colIndex++) {
/* _ */
}
const { rowSpan, colSpan } = cell;
if (rowSpan > 1 || colSpan > 1) {
for (xx = rowIndex; xx < rowIndex + rowSpan; xx++) {
for (yy = colIndex; yy < colIndex + colSpan; yy++) {
skipedCells[xx] = skipedCells[xx] || {};
skipedCells[xx][yy] = true;
}
}
}
cell.innerHTML = cell.innerHTML.replace(/\n\s*$/, '');
cell.dataset.tableKey = tableKey;
cell.dataset.colIndex = colIndex;
cell.dataset.rowIndex = rowIndex;
});
});
};
export const updateAllTableBlocks = (editorState, tableKey, blockData) => {
const selectionState = editorState.getSelection();
const contentState = editorState.getCurrentContent();
const contentBlocks = contentState.getBlockMap();
const tableBlocks = findBlocks(contentBlocks, 'tableKey', tableKey);
const nextTableBlocks = rebuildTableBlocks(tableBlocks, blockData);
const nextContentState = updateTableBlocks(contentState, editorState.getSelection(), selectionState.focusKey, nextTableBlocks, tableKey);
return EditorState.push(editorState, nextContentState, 'insert-table-row');
};
// 获取需要插入到某一行的单元格的数量
export const getCellCountForInsert = (tableBlocks, rowIndex) => {
return findBlocks(tableBlocks, 'rowIndex', rowIndex).reduce((count, block) => {
return count + (block.getData().get('colSpan') || 1) * 1;
}, 0);
};
// 获取指定范围内的单元格block
export const getCellsInsideRect = (editorState, tableKey, startLocation, endLocation) => {
const [startColIndex, startRowIndex] = startLocation;
const [endColIndex, endRowIndex] = endLocation;
const leftColIndex = Math.min(startColIndex, endColIndex);
const rightColIndex = Math.max(startColIndex, endColIndex);
const upRowIndex = Math.min(startRowIndex, endRowIndex);
const downRowIndex = Math.max(startRowIndex, endRowIndex);
const matchedCellLocations = [];
for (let ii = leftColIndex; ii <= rightColIndex; ii++) {
for (let jj = upRowIndex; jj <= downRowIndex; jj++) {
matchedCellLocations.push([ii, jj]);
}
}
if (matchedCellLocations.length === 0) {
return Immutable.OrderedMap([]);
}
const contentState = editorState.getCurrentContent();
const contentBlocks = contentState.getBlockMap();
const tableBlocks = findBlocks(contentBlocks, 'tableKey', tableKey);
const matchedCellBlockKeys = [];
const spannedCellBlockKeys = [];
let matchedCellBlocks = Immutable.List([]);
let spannedCellBlocks = Immutable.List([]);
tableBlocks.forEach((block) => {
const blockData = block.getData();
const blockKey = block.getKey();
const colIndex = blockData.get('colIndex');
const rowIndex = blockData.get('rowIndex');
const colSpan = blockData.get('colSpan');
const rowSpan = blockData.get('rowSpan');
matchedCellLocations.forEach(([x, y]) => {
if (colIndex === x && rowIndex === y) {
!matchedCellBlockKeys.includes(blockKey) &&
(matchedCellBlocks = matchedCellBlocks.push(block)) &&
matchedCellBlockKeys.push(blockKey);
(colSpan > 1 || rowSpan > 1) &&
!spannedCellBlockKeys.includes(blockKey) &&
(spannedCellBlocks = spannedCellBlocks.push(block)) &&
spannedCellBlockKeys.push(blockKey);
}
else if (colSpan > 1 || rowSpan > 1) {
if (colIndex <= x &&
colIndex + colSpan > x &&
rowIndex <= y &&
rowIndex + rowSpan > y) {
!spannedCellBlockKeys.includes(blockKey) &&
(spannedCellBlocks = spannedCellBlocks.push(block)) &&
spannedCellBlockKeys.push(blockKey);
}
}
});
});
return {
cellBlocks: matchedCellBlocks.merge(spannedCellBlocks),
cellKeys: [...matchedCellBlockKeys, ...spannedCellBlockKeys],
spannedCellBlocks: spannedCellBlocks,
spannedCellBlockKeys: spannedCellBlockKeys
};
};
// 插入一个单元格block到表格的block列表中
export const insertCell = (tableBlocks, cell) => {
let colIndex, rowIndex, cellBlock;
if (cell instanceof ContentBlock) {
colIndex = cell.getData().get('colIndex');
rowIndex = cell.getData().get('rowIndex');
cellBlock = cell;
}
else {
colIndex = cell.colIndex;
rowIndex = cell.rowIndex;
cellBlock = createCellBlock(cell);
}
const blocksBefore = tableBlocks.takeUntil((block) => {
const blockRowIndex = block.getData().get('rowIndex');
const blockColIndex = block.getData().get('colIndex');
return ((blockColIndex >= colIndex && blockRowIndex === rowIndex) ||
blockRowIndex > rowIndex);
});
const blocksAfter = tableBlocks.skipUntil((block) => {
const blockRowIndex = block.getData().get('rowIndex');
const blockColIndex = block.getData().get('colIndex');
return ((blockColIndex >= colIndex && blockRowIndex === rowIndex) ||
blockRowIndex > rowIndex);
});
const nextTableBlocks = blocksBefore.concat(Immutable.OrderedMap([[cellBlock.getKey(), cellBlock]]).toSeq(), blocksAfter);
return nextTableBlocks;
};
// 插入多个单元格block到表格的block列表中
export const insertCells = (tableBlocks, cells = []) => {
return cells.reduce((nextTableBlocks, cell) => {
return insertCell(nextTableBlocks, cell);
}, tableBlocks);
};
// 插入表格
export const insertTable = (editorState, columns = 3, rows = 3) => {
if (ContentUtils.selectionContainsStrictBlock(editorState)) {
return editorState;
}
const selectionState = editorState.getSelection();
const contentState = editorState.getCurrentContent();
const contentBlocks = contentState.getBlockMap();
const tableKey = genKey();
const cellBlocks = [createUnstyledBlock()];
for (let ii = 0; ii < rows; ii++) {
for (let jj = 0; jj < columns; jj++) {
const cellBlock = createCellBlock({
tableKey: tableKey,
colIndex: jj,
rowIndex: ii
});
cellBlocks.push([cellBlock.getKey(), cellBlock]);
}
}
cellBlocks.push(createUnstyledBlock());
const startKey = selectionState.getStartKey();
const currentBlock = contentState.getBlockForKey(startKey);
const blocksBefore = contentBlocks.toSeq().takeUntil((block) => {
return block === currentBlock;
});
const blocksAfter = contentBlocks
.toSeq()
.skipUntil((block) => {
return block === currentBlock;
})
.rest();
const tableBlocks = Immutable.OrderedMap(cellBlocks).toSeq();
const firstCellKey = cellBlocks[1][0];
const nextContentBlocks = blocksBefore
.concat(Immutable.OrderedMap([[startKey, currentBlock]]).toSeq(), tableBlocks, blocksAfter)
.toOrderedMap();
const nextContentState = contentState.merge({
blockMap: nextContentBlocks,
selectionBefore: selectionState,
selectionAfter: selectionState.merge({
anchorKey: firstCellKey,
anchorOffset: selectionState.getStartOffset(),
focusKey: firstCellKey,
focusOffset: selectionState.getStartOffset(),
isBackward: false
})
});
return EditorState.push(editorState, nextContentState, 'insert-table');
};
// 删除整个表格
export const removeTable = (editorState, tableKey) => {
if (!tableKey) {
return editorState;
}
const selectionState = editorState.getSelection();
const contentState = editorState.getCurrentContent();
const contentBlocks = contentState.getBlockMap();
const tableBlocks = findBlocks(contentBlocks, 'tableKey', tableKey);
const nextContentBlocks = contentBlocks.filter((block) => {
if (block.getType() === 'table-cell' &&
block.getData().get('tableKey') === tableKey) {
return false;
}
else {
return true;
}
});
const focusCellKey = (tableBlocks.first()
? contentState.getBlockBefore(tableBlocks.first().getKey()) ||
nextContentBlocks.first()
: nextContentBlocks.first()).getKey();
const nextContentState = contentState.merge({
blockMap: nextContentBlocks,
selectionBefore: selectionState,
selectionAfter: selectionState.merge({
anchorKey: focusCellKey,
anchorOffset: selectionState.getStartOffset(),
focusKey: focusCellKey,
focusOffset: selectionState.getStartOffset(),
isBackward: false
})
});
return EditorState.push(editorState, nextContentState, 'remove-table');
};
// 插入一列单元格到表格中
export const insertColumn = (editorState, tableKey, cellCounts, colIndex) => {
const contentState = editorState.getCurrentContent();
const contentBlocks = contentState.getBlockMap();
const tableBlocks = findBlocks(contentBlocks, 'tableKey', tableKey);
const cellsToBeAdded = [];
if (colIndex === 0) {
for (let ii = 0; ii < cellCounts; ii++) {
cellsToBeAdded.push({
text: '',
colIndex: 0,
rowIndex: ii,
colSpan: 1,
rowSpan: 1,
tableKey: tableKey
});
}
}
const nextTableBlocks = tableBlocks.map((block) => {
const blockData = block.getData().toJS();
const { colIndex: blockColIndex, rowIndex: blockRowIndex, colSpan: blockColSpan, rowSpan: blockRowSpan } = blockData;
const nextColIndex = blockColIndex < colIndex ? blockColIndex : blockColIndex + 1;
const nextColSpan = blockColIndex < colIndex &&
blockColSpan > 1 &&
blockColIndex + blockColSpan > colIndex
? blockColSpan + 1
: blockColSpan;
const needUpdate = nextColIndex !== blockColIndex || nextColSpan !== blockColSpan;
if ((blockColSpan === 1 && blockColIndex === colIndex - 1) ||
(blockColSpan > 1 && blockColIndex + blockColSpan === colIndex)) {
for (let jj = blockRowIndex; jj < blockRowIndex + blockRowSpan; jj++) {
cellsToBeAdded.push({
text: '',
colIndex: colIndex,
rowIndex: jj,
colSpan: 1,
rowSpan: 1,
tableKey: tableKey
});
}
}
return needUpdate
? block.merge({
data: Immutable.Map(Object.assign(Object.assign({}, blockData), { colIndex: nextColIndex, colSpan: nextColSpan }))
})
: block;
});
if (cellsToBeAdded.length === 0) {
return editorState;
}
const focusCellKey = (cellsToBeAdded[0].key = genKey());
const nextContentState = updateTableBlocks(contentState, editorState.getSelection(), focusCellKey, insertCells(nextTableBlocks, cellsToBeAdded), tableKey);
return EditorState.push(editorState, nextContentState, 'insert-table-column');
};
// 从表格中移除指定的某一列单元格
export const removeColumn = (editorState, tableKey, colIndex) => {
const contentState = editorState.getCurrentContent();
const contentBlocks = contentState.getBlockMap().toSeq();
const tableBlocks = findBlocks(contentBlocks, 'tableKey', tableKey);
const cellsToBeAdded = findBlocks(tableBlocks, 'colIndex', colIndex).reduce((cellsToBeAdded, block) => {
const { colIndex, rowIndex, colSpan, rowSpan } = block.getData().toJS();
if (colSpan > 1) {
cellsToBeAdded.push({
text: block.getText(),
tableKey: tableKey,
colIndex: colIndex,
rowIndex: rowIndex,
colSpan: colSpan - 1,
rowSpan: rowSpan
});
}
return cellsToBeAdded;
}, []);
const nextTableBlocks = tableBlocks
.filter((block) => {
return block.getData().get('colIndex') * 1 !== colIndex;
})
.map((block) => {
const blockData = block.getData().toJS();
const { colIndex: blockColIndex, colSpan: blockColSpan } = blockData;
const newColIndex = blockColIndex > colIndex ? blockColIndex - 1 : blockColIndex;
const newColSpan = blockColIndex < colIndex && blockColIndex + blockColSpan > colIndex
? blockColSpan - 1
: blockColSpan;
const needUpdate = newColIndex !== blockColIndex || newColSpan !== blockColSpan;
return needUpdate
? block.merge({
data: Object.assign(Object.assign({}, blockData), { colIndex: newColIndex, colSpan: newColSpan })
})
: block;
});
const focusCellKey = (nextTableBlocks.first() ||
contentState.getBlockBefore(tableBlocks.first().getKey()) ||
contentState.getBlockAfter(tableBlocks.first().getKey())).getKey();
const nextContentState = updateTableBlocks(contentState, editorState.getSelection(), focusCellKey, insertCells(nextTableBlocks, cellsToBeAdded), tableKey);
return EditorState.push(editorState, nextContentState, 'remove-table-column');
};
// 插入一行单元格到表格中
export const insertRow = (editorState, tableKey, cellCounts, rowIndex, addonBlockData = {}) => {
const contentState = editorState.getCurrentContent();
const contentBlocks = contentState.getBlockMap().toSeq();
const tableBlocks = findBlocks(contentBlocks, 'tableKey', tableKey);
const blocksBefore = findBlocks(tableBlocks, 'rowIndex', rowIndex, '<').map((block) => {
const blockData = block.getData().toJS();
const { rowIndex: blockRowIndex, rowSpan: blockRowSpan } = blockData;
if (blockRowIndex > rowIndex) {
return block;
}
else {
const needUpdate = blockRowSpan && blockRowIndex + blockRowSpan > rowIndex;
const newRowSpan = needUpdate ? blockRowSpan * 1 + 1 : blockRowSpan;
return block.merge({
data: Immutable.Map(Object.assign(Object.assign({}, blockData), { rowSpan: newRowSpan }))
});
}
});
const blocksAfter = findBlocks(tableBlocks, 'rowIndex', rowIndex, '>=').map((block) => {
const blockData = block.getData().toJS();
return block.merge({
data: Immutable.Map(Object.assign(Object.assign({}, blockData), { rowIndex: blockData.rowIndex * 1 + 1 }))
});
});
const colCellLength = getCellCountForInsert(tableBlocks, rowIndex);
const rowBlocks = createRowBlocks(tableKey, rowIndex, colCellLength || cellCounts);
const focusCellKey = rowBlocks.first().getKey();
const nextTableBlocks = rebuildTableBlocks(blocksBefore.concat(rowBlocks, blocksAfter), addonBlockData);
const nextContentState = updateTableBlocks(contentState, editorState.getSelection(), focusCellKey, nextTableBlocks, tableKey);
return EditorState.push(editorState, nextContentState, 'insert-table-row');
};
// 从表格中移除指定的某一行单元格
export const removeRow = (editorState, tableKey, rowIndex, addonBlockData = {}) => {
const contentState = editorState.getCurrentContent();
const contentBlocks = contentState.getBlockMap().toSeq();
const tableBlocks = findBlocks(contentBlocks, 'tableKey', tableKey);
const blocksBefore = findBlocks(tableBlocks, 'rowIndex', rowIndex, '<').map((block) => {
const blockData = block.getData().toJS();
const { rowIndex: blockRowIndex, rowSpan: blockRowSpan } = blockData;
if (blockRowIndex > rowIndex) {
return block;
}
else {
const needUpdate = blockRowSpan && blockRowIndex + blockRowSpan > rowIndex;
const newRowSpan = needUpdate ? blockRowSpan * 1 - 1 : blockRowSpan;
return block.merge({
data: Immutable.Map(Object.assign(Object.assign({}, blockData), { rowSpan: newRowSpan }))
});
}
});
const blocksAfter = findBlocks(tableBlocks, 'rowIndex', rowIndex, '>').map((block) => {
const blockData = block.getData().toJS();
return block.merge({
data: Immutable.Map(Object.assign(Object.assign({}, blockData), { rowIndex: blockData.rowIndex * 1 - 1 }))
});
});
const cellsToBeAdded = findBlocks(tableBlocks, 'rowIndex', rowIndex).reduce((cellsToBeAdded, block) => {
const { colIndex, rowIndex, colSpan, rowSpan } = block.getData().toJS();
if (rowSpan > 1) {
cellsToBeAdded.push({
text: block.getText(),
tableKey: tableKey,
colIndex: colIndex,
rowIndex: rowIndex,
colSpan: colSpan,
rowSpan: rowSpan - 1
});
}
return cellsToBeAdded;
}, []);
const focusCellKey = (blocksAfter.first() ||
blocksBefore.last() ||
contentBlocks.first()).getKey();
const nextTableBlocks = rebuildTableBlocks(insertCells(blocksBefore.concat(blocksAfter), cellsToBeAdded), addonBlockData);
const nextContentState = updateTableBlocks(contentState, editorState.getSelection(), focusCellKey, nextTableBlocks, tableKey
// true
);
return EditorState.push(editorState, nextContentState, 'remove-table-row');
};
// 合并单元格
export const mergeCells = (editorState, tableKey, cellKeys) => {
const contentState = editorState.getCurrentContent();
const contentBlocks = contentState.getBlockMap();
const cellBlocksData = [];
let mergedText = '';
const tableBlocks = findBlocks(contentBlocks, 'tableKey', tableKey).filter((block) => {
if (~cellKeys.indexOf(block.getKey())) {
mergedText += block.getText();
cellBlocksData.push(Object.assign({ key: block.getKey() }, block.getData().toJS()));
return false;
}
else {
return true;
}
});
const sortedCellBlocksData = cellBlocksData.sort((prev, next) => next.colIndex + next.rowIndex - (prev.colIndex + prev.rowIndex));
const firstCellData = sortedCellBlocksData.slice(-1)[0];
const lastCellData = sortedCellBlocksData[0];
const mergedCell = contentState.getBlockForKey(firstCellData.key).merge({
text: mergedText,
data: Immutable.Map(Object.assign(Object.assign({}, firstCellData), { colSpan: lastCellData.colIndex - firstCellData.colIndex + 1, rowSpan: lastCellData.rowIndex - firstCellData.rowIndex + 1 })),
characterList: Immutable.List(Immutable.Repeat(CharacterMetadata.create(), mergedText.length))
});
const nextContentState = updateTableBlocks(contentState, editorState.getSelection(), firstCellData.key, insertCell(tableBlocks, mergedCell), tableKey);
return EditorState.push(editorState, nextContentState, 'merge-table-cell');
};
// 拆分单元格
export const splitCell = (editorState, tableKey, cellKey) => {
const contentState = editorState.getCurrentContent();
const contentBlocks = contentState.getBlockMap();
const cellsToBeAdded = [];
const tableBlocks = findBlocks(contentBlocks, 'tableKey', tableKey).map((block) => {
if (block.getKey() === cellKey) {
const blockData = block.getData().toJS();
const { colIndex, rowIndex, colSpan, rowSpan } = blockData;
if (colSpan === 1 && rowSpan === 1) {
return block;
}
for (let ii = colIndex; ii < colIndex + colSpan; ii++) {
for (let jj = rowIndex; jj < rowIndex + rowSpan; jj++) {
if (ii !== colIndex || jj !== rowIndex) {
cellsToBeAdded.push({
text: '',
tableKey: tableKey,
colIndex: ii,
rowIndex: jj,
colSpan: 1,
rowSpan: 1
});
}
}
}
return block.merge({
data: Immutable.Map(Object.assign(Object.assign({}, blockData), { colSpan: 1, rowSpan: 1 }))
});
}
else {
return block;
}
});
const nextContentState = updateTableBlocks(contentState, editorState.getSelection(), cellKey, insertCells(tableBlocks, cellsToBeAdded), tableKey);
return EditorState.push(editorState, nextContentState, 'merge-table-cell');
};
//# sourceMappingURL=utils.js.map