@ckeditor/ckeditor5-table
Version:
Table feature for CKEditor 5.
108 lines (107 loc) • 3.79 kB
JavaScript
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* Injects a table cell post-fixer into the model which inserts a `paragraph` element into empty table cells.
*
* A table cell must contain at least one block element as a child. An empty table cell will have an empty `paragraph` as a child.
*
* ```xml
* <table>
* <tableRow>
* <tableCell></tableCell>
* </tableRow>
* </table>
* ```
*
* Will be fixed to:
*
* ```xml
* <table>
* <tableRow>
* <tableCell><paragraph></paragraph></tableCell>
* </tableRow>
* </table>
* ```
*/
export default function injectTableCellParagraphPostFixer(model) {
model.document.registerPostFixer(writer => tableCellContentsPostFixer(writer, model));
}
/**
* The table cell contents post-fixer.
*/
function tableCellContentsPostFixer(writer, model) {
const changes = model.document.differ.getChanges();
let wasFixed = false;
for (const entry of changes) {
if (entry.type == 'insert' && entry.name == 'table') {
wasFixed = fixTable(entry.position.nodeAfter, writer) || wasFixed;
}
if (entry.type == 'insert' && entry.name == 'tableRow') {
wasFixed = fixTableRow(entry.position.nodeAfter, writer) || wasFixed;
}
if (entry.type == 'insert' && entry.name == 'tableCell') {
wasFixed = fixTableCellContent(entry.position.nodeAfter, writer) || wasFixed;
}
if ((entry.type == 'remove' || entry.type == 'insert') && checkTableCellChange(entry)) {
wasFixed = fixTableCellContent(entry.position.parent, writer) || wasFixed;
}
}
return wasFixed;
}
/**
* Fixes all table cells in a table.
*/
function fixTable(table, writer) {
let wasFixed = false;
for (const row of table.getChildren()) {
if (row.is('element', 'tableRow')) {
wasFixed = fixTableRow(row, writer) || wasFixed;
}
}
return wasFixed;
}
/**
* Fixes all table cells in a table row.
*/
function fixTableRow(tableRow, writer) {
let wasFixed = false;
for (const tableCell of tableRow.getChildren()) {
wasFixed = fixTableCellContent(tableCell, writer) || wasFixed;
}
return wasFixed;
}
/**
* Fixes all table cell content by:
* - Adding a paragraph to a table cell without any child.
* - Wrapping direct $text in a `<paragraph>`.
*/
function fixTableCellContent(tableCell, writer) {
// Insert paragraph to an empty table cell.
if (tableCell.childCount == 0) {
// @if CK_DEBUG_TABLE // console.log( 'Post-fixing table: insert paragraph in empty cell.' );
writer.insertElement('paragraph', tableCell);
return true;
}
// Check table cell children for directly placed text nodes.
// Temporary solution. See https://github.com/ckeditor/ckeditor5/issues/1464.
const textNodes = Array.from(tableCell.getChildren()).filter(child => child.is('$text'));
// @if CK_DEBUG_TABLE // textNodes.length && console.log( 'Post-fixing table: wrap cell content with paragraph.' );
for (const child of textNodes) {
writer.wrap(writer.createRangeOn(child), 'paragraph');
}
// Return true when there were text nodes to fix.
return !!textNodes.length;
}
/**
* Checks if a differ change should fix the table cell. This happens on:
* - Removing content from the table cell (i.e. `tableCell` can be left empty).
* - Adding a text node directly into a table cell.
*/
function checkTableCellChange(entry) {
if (!entry.position.parent.is('element', 'tableCell')) {
return false;
}
return entry.type == 'insert' && entry.name == '$text' || entry.type == 'remove';
}