UNPKG

6.48 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4 */
5/**
6 * @module table/converters/downcast
7 */
8import { toWidget, toWidgetEditable } from 'ckeditor5/src/widget';
9import TableWalker from './../tablewalker';
10/**
11 * Model table element to view table element conversion helper.
12 */
13export function downcastTable(tableUtils, options) {
14 return (table, { writer }) => {
15 const headingRows = table.getAttribute('headingRows') || 0;
16 const tableElement = writer.createContainerElement('table', null, []);
17 const figureElement = writer.createContainerElement('figure', { class: 'table' }, tableElement);
18 // Table head slot.
19 if (headingRows > 0) {
20 writer.insert(writer.createPositionAt(tableElement, 'end'), writer.createContainerElement('thead', null, writer.createSlot(element => element.is('element', 'tableRow') && element.index < headingRows)));
21 }
22 // Table body slot.
23 if (headingRows < tableUtils.getRows(table)) {
24 writer.insert(writer.createPositionAt(tableElement, 'end'), writer.createContainerElement('tbody', null, writer.createSlot(element => element.is('element', 'tableRow') && element.index >= headingRows)));
25 }
26 // Dynamic slots.
27 for (const { positionOffset, filter } of options.additionalSlots) {
28 writer.insert(writer.createPositionAt(tableElement, positionOffset), writer.createSlot(filter));
29 }
30 // Create a slot with items that don't fit into the table.
31 writer.insert(writer.createPositionAt(tableElement, 'after'), writer.createSlot(element => {
32 if (element.is('element', 'tableRow')) {
33 return false;
34 }
35 return !options.additionalSlots.some(({ filter }) => filter(element));
36 }));
37 return options.asWidget ? toTableWidget(figureElement, writer) : figureElement;
38 };
39}
40/**
41 * Model table row element to view `<tr>` element conversion helper.
42 *
43 * @returns Element creator.
44 */
45export function downcastRow() {
46 return (tableRow, { writer }) => {
47 return tableRow.isEmpty ?
48 writer.createEmptyElement('tr') :
49 writer.createContainerElement('tr');
50 };
51}
52/**
53 * Model table cell element to view `<td>` or `<th>` element conversion helper.
54 *
55 * This conversion helper will create proper `<th>` elements for table cells that are in the heading section (heading row or column)
56 * and `<td>` otherwise.
57 *
58 * @param options.asWidget If set to `true`, the downcast conversion will produce a widget.
59 * @returns Element creator.
60 */
61export function downcastCell(options = {}) {
62 return (tableCell, { writer }) => {
63 const tableRow = tableCell.parent;
64 const table = tableRow.parent;
65 const rowIndex = table.getChildIndex(tableRow);
66 const tableWalker = new TableWalker(table, { row: rowIndex });
67 const headingRows = table.getAttribute('headingRows') || 0;
68 const headingColumns = table.getAttribute('headingColumns') || 0;
69 let result = null;
70 // We need to iterate over a table in order to get proper row & column values from a walker.
71 for (const tableSlot of tableWalker) {
72 if (tableSlot.cell == tableCell) {
73 const isHeading = tableSlot.row < headingRows || tableSlot.column < headingColumns;
74 const cellElementName = isHeading ? 'th' : 'td';
75 result = options.asWidget ?
76 toWidgetEditable(writer.createEditableElement(cellElementName), writer) :
77 writer.createContainerElement(cellElementName);
78 break;
79 }
80 }
81 return result;
82 };
83}
84/**
85 * Overrides paragraph inside table cell conversion.
86 *
87 * This converter:
88 * * should be used to override default paragraph conversion.
89 * * It will only convert `<paragraph>` placed directly inside `<tableCell>`.
90 * * For a single paragraph without attributes it returns `<span>` to simulate data table.
91 * * For all other cases it returns `<p>` element.
92 *
93 * @param options.asWidget If set to `true`, the downcast conversion will produce a widget.
94 * @returns Element creator.
95 */
96export function convertParagraphInTableCell(options = {}) {
97 return (modelElement, { writer }) => {
98 if (!modelElement.parent.is('element', 'tableCell')) {
99 return null;
100 }
101 if (!isSingleParagraphWithoutAttributes(modelElement)) {
102 return null;
103 }
104 if (options.asWidget) {
105 return writer.createContainerElement('span', { class: 'ck-table-bogus-paragraph' });
106 }
107 else {
108 // Using `<p>` in case there are some markers on it and transparentRendering will render it anyway.
109 const viewElement = writer.createContainerElement('p');
110 writer.setCustomProperty('dataPipeline:transparentRendering', true, viewElement);
111 return viewElement;
112 }
113 };
114}
115/**
116 * Checks if given model `<paragraph>` is an only child of a parent (`<tableCell>`) and if it has any attribute set.
117 *
118 * The paragraph should be converted in the editing view to:
119 *
120 * * If returned `true` - to a `<span class="ck-table-bogus-paragraph">`
121 * * If returned `false` - to a `<p>`
122 */
123export function isSingleParagraphWithoutAttributes(modelElement) {
124 const tableCell = modelElement.parent;
125 const isSingleParagraph = tableCell.childCount == 1;
126 return isSingleParagraph && !hasAnyAttribute(modelElement);
127}
128/**
129 * Converts a given {@link module:engine/view/element~Element} to a table widget:
130 * * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the table widget element.
131 * * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
132 *
133 * @param writer An instance of the view writer.
134 * @param label The element's label. It will be concatenated with the table `alt` attribute if one is present.
135 */
136function toTableWidget(viewElement, writer) {
137 writer.setCustomProperty('table', true, viewElement);
138 return toWidget(viewElement, writer, { hasSelectionHandle: true });
139}
140/**
141 * Checks if an element has any attributes set.
142 */
143function hasAnyAttribute(element) {
144 const iteratorItem = element.getAttributeKeys().next();
145 return !iteratorItem.done;
146}