@ckeditor/ckeditor5-table
Version:
Table feature for CKEditor 5.
130 lines (129 loc) • 4.64 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
*/
/**
* @module table/plaintableoutput
*/
import { Plugin } from 'ckeditor5/src/core.js';
import Table from './table.js';
/**
* The plain table output feature.
*/
export default class PlainTableOutput extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'PlainTableOutput';
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/
static get requires() {
return [Table];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Override default table data downcast converter.
editor.conversion.for('dataDowncast').elementToStructure({
model: 'table',
view: downcastTableElement,
converterPriority: 'high'
});
// Make sure table <caption> is downcasted into <caption> in the data pipeline when necessary.
if (editor.plugins.has('TableCaption')) {
editor.conversion.for('dataDowncast').elementToElement({
model: 'caption',
view: (modelElement, { writer }) => {
if (modelElement.parent.name === 'table') {
return writer.createContainerElement('caption');
}
},
converterPriority: 'high'
});
}
// Handle border-style, border-color, border-width and background-color table attributes.
if (editor.plugins.has('TableProperties')) {
downcastTableBorderAndBackgroundAttributes(editor);
}
}
}
/**
* The plain table downcast converter callback.
*
* @param table Table model element.
* @param conversionApi The conversion API object.
* @returns Created element.
*/
function downcastTableElement(table, { writer }) {
const headingRows = table.getAttribute('headingRows') || 0;
// Table head rows slot.
const headRowsSlot = writer.createSlot((element) => element.is('element', 'tableRow') && element.index < headingRows);
// Table body rows slot.
const bodyRowsSlot = writer.createSlot((element) => element.is('element', 'tableRow') && element.index >= headingRows);
// Table children slot.
const childrenSlot = writer.createSlot((element) => !element.is('element', 'tableRow'));
// Table <thead> element with all the heading rows.
const theadElement = writer.createContainerElement('thead', null, headRowsSlot);
// Table <tbody> element with all the body rows.
const tbodyElement = writer.createContainerElement('tbody', null, bodyRowsSlot);
// Table contents element containing <thead> and <tbody> when necessary.
const tableContentElements = [];
if (headingRows) {
tableContentElements.push(theadElement);
}
if (headingRows < table.childCount) {
tableContentElements.push(tbodyElement);
}
// Create table structure.
//
// <table>
// {children-slot-like-caption}
// <thead>
// {table-head-rows-slot}
// </thead>
// <tbody>
// {table-body-rows-slot}
// </tbody>
// </table>
return writer.createContainerElement('table', null, [childrenSlot, ...tableContentElements]);
}
/**
* Register table border and background attributes converters.
*/
function downcastTableBorderAndBackgroundAttributes(editor) {
const modelAttributes = {
'border-width': 'tableBorderWidth',
'border-color': 'tableBorderColor',
'border-style': 'tableBorderStyle',
'background-color': 'tableBackgroundColor'
};
for (const [styleName, modelAttribute] of Object.entries(modelAttributes)) {
editor.conversion.for('dataDowncast').add(dispatcher => {
return dispatcher.on(`attribute:${modelAttribute}:table`, (evt, data, conversionApi) => {
const { item, attributeNewValue } = data;
const { mapper, writer } = conversionApi;
if (!conversionApi.consumable.consume(item, evt.name)) {
return;
}
const table = mapper.toViewElement(item);
if (attributeNewValue) {
writer.setStyle(styleName, attributeNewValue, table);
}
else {
writer.removeStyle(styleName, table);
}
}, { priority: 'high' });
});
}
}