UNPKG

8.41 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/tableediting
7 */
8import { Plugin } from 'ckeditor5/src/core';
9import upcastTable, { ensureParagraphInTableCell, skipEmptyTableRow, upcastTableFigure } from './converters/upcasttable';
10import { convertParagraphInTableCell, downcastCell, downcastRow, downcastTable } from './converters/downcast';
11import InsertTableCommand from './commands/inserttablecommand';
12import InsertRowCommand from './commands/insertrowcommand';
13import InsertColumnCommand from './commands/insertcolumncommand';
14import SplitCellCommand from './commands/splitcellcommand';
15import MergeCellCommand from './commands/mergecellcommand';
16import RemoveRowCommand from './commands/removerowcommand';
17import RemoveColumnCommand from './commands/removecolumncommand';
18import SetHeaderRowCommand from './commands/setheaderrowcommand';
19import SetHeaderColumnCommand from './commands/setheadercolumncommand';
20import MergeCellsCommand from './commands/mergecellscommand';
21import SelectRowCommand from './commands/selectrowcommand';
22import SelectColumnCommand from './commands/selectcolumncommand';
23import TableUtils from '../src/tableutils';
24import injectTableLayoutPostFixer from './converters/table-layout-post-fixer';
25import injectTableCellParagraphPostFixer from './converters/table-cell-paragraph-post-fixer';
26import tableHeadingsRefreshHandler from './converters/table-headings-refresh-handler';
27import tableCellRefreshHandler from './converters/table-cell-refresh-handler';
28import '../theme/tableediting.css';
29/**
30 * The table editing feature.
31 */
32export default class TableEditing extends Plugin {
33 /**
34 * @inheritDoc
35 */
36 static get pluginName() {
37 return 'TableEditing';
38 }
39 /**
40 * @inheritDoc
41 */
42 static get requires() {
43 return [TableUtils];
44 }
45 /**
46 * @inheritDoc
47 */
48 constructor(editor) {
49 super(editor);
50 this._additionalSlots = [];
51 }
52 /**
53 * @inheritDoc
54 */
55 init() {
56 const editor = this.editor;
57 const model = editor.model;
58 const schema = model.schema;
59 const conversion = editor.conversion;
60 const tableUtils = editor.plugins.get(TableUtils);
61 schema.register('table', {
62 inheritAllFrom: '$blockObject',
63 allowAttributes: ['headingRows', 'headingColumns']
64 });
65 schema.register('tableRow', {
66 allowIn: 'table',
67 isLimit: true
68 });
69 schema.register('tableCell', {
70 allowContentOf: '$container',
71 allowIn: 'tableRow',
72 allowAttributes: ['colspan', 'rowspan'],
73 isLimit: true,
74 isSelectable: true
75 });
76 // Figure conversion.
77 conversion.for('upcast').add(upcastTableFigure());
78 // Table conversion.
79 conversion.for('upcast').add(upcastTable());
80 conversion.for('editingDowncast').elementToStructure({
81 model: {
82 name: 'table',
83 attributes: ['headingRows']
84 },
85 view: downcastTable(tableUtils, {
86 asWidget: true,
87 additionalSlots: this._additionalSlots
88 })
89 });
90 conversion.for('dataDowncast').elementToStructure({
91 model: {
92 name: 'table',
93 attributes: ['headingRows']
94 },
95 view: downcastTable(tableUtils, {
96 additionalSlots: this._additionalSlots
97 })
98 });
99 // Table row conversion.
100 conversion.for('upcast').elementToElement({ model: 'tableRow', view: 'tr' });
101 conversion.for('upcast').add(skipEmptyTableRow());
102 conversion.for('downcast').elementToElement({
103 model: 'tableRow',
104 view: downcastRow()
105 });
106 // Table cell conversion.
107 conversion.for('upcast').elementToElement({ model: 'tableCell', view: 'td' });
108 conversion.for('upcast').elementToElement({ model: 'tableCell', view: 'th' });
109 conversion.for('upcast').add(ensureParagraphInTableCell('td'));
110 conversion.for('upcast').add(ensureParagraphInTableCell('th'));
111 conversion.for('editingDowncast').elementToElement({
112 model: 'tableCell',
113 view: downcastCell({ asWidget: true })
114 });
115 conversion.for('dataDowncast').elementToElement({
116 model: 'tableCell',
117 view: downcastCell()
118 });
119 // Duplicates code - needed to properly refresh paragraph inside a table cell.
120 conversion.for('editingDowncast').elementToElement({
121 model: 'paragraph',
122 view: convertParagraphInTableCell({ asWidget: true }),
123 converterPriority: 'high'
124 });
125 conversion.for('dataDowncast').elementToElement({
126 model: 'paragraph',
127 view: convertParagraphInTableCell(),
128 converterPriority: 'high'
129 });
130 // Table attributes conversion.
131 conversion.for('downcast').attributeToAttribute({ model: 'colspan', view: 'colspan' });
132 conversion.for('upcast').attributeToAttribute({
133 model: { key: 'colspan', value: upcastCellSpan('colspan') },
134 view: 'colspan'
135 });
136 conversion.for('downcast').attributeToAttribute({ model: 'rowspan', view: 'rowspan' });
137 conversion.for('upcast').attributeToAttribute({
138 model: { key: 'rowspan', value: upcastCellSpan('rowspan') },
139 view: 'rowspan'
140 });
141 // Define the config.
142 editor.config.define('table.defaultHeadings.rows', 0);
143 editor.config.define('table.defaultHeadings.columns', 0);
144 // Define all the commands.
145 editor.commands.add('insertTable', new InsertTableCommand(editor));
146 editor.commands.add('insertTableRowAbove', new InsertRowCommand(editor, { order: 'above' }));
147 editor.commands.add('insertTableRowBelow', new InsertRowCommand(editor, { order: 'below' }));
148 editor.commands.add('insertTableColumnLeft', new InsertColumnCommand(editor, { order: 'left' }));
149 editor.commands.add('insertTableColumnRight', new InsertColumnCommand(editor, { order: 'right' }));
150 editor.commands.add('removeTableRow', new RemoveRowCommand(editor));
151 editor.commands.add('removeTableColumn', new RemoveColumnCommand(editor));
152 editor.commands.add('splitTableCellVertically', new SplitCellCommand(editor, { direction: 'vertically' }));
153 editor.commands.add('splitTableCellHorizontally', new SplitCellCommand(editor, { direction: 'horizontally' }));
154 editor.commands.add('mergeTableCells', new MergeCellsCommand(editor));
155 editor.commands.add('mergeTableCellRight', new MergeCellCommand(editor, { direction: 'right' }));
156 editor.commands.add('mergeTableCellLeft', new MergeCellCommand(editor, { direction: 'left' }));
157 editor.commands.add('mergeTableCellDown', new MergeCellCommand(editor, { direction: 'down' }));
158 editor.commands.add('mergeTableCellUp', new MergeCellCommand(editor, { direction: 'up' }));
159 editor.commands.add('setTableColumnHeader', new SetHeaderColumnCommand(editor));
160 editor.commands.add('setTableRowHeader', new SetHeaderRowCommand(editor));
161 editor.commands.add('selectTableRow', new SelectRowCommand(editor));
162 editor.commands.add('selectTableColumn', new SelectColumnCommand(editor));
163 injectTableLayoutPostFixer(model);
164 injectTableCellParagraphPostFixer(model);
165 this.listenTo(model.document, 'change:data', () => {
166 tableHeadingsRefreshHandler(model, editor.editing);
167 tableCellRefreshHandler(model, editor.editing);
168 });
169 }
170 /**
171 * Registers downcast handler for the additional table slot.
172 */
173 registerAdditionalSlot(slotHandler) {
174 this._additionalSlots.push(slotHandler);
175 }
176}
177/**
178 * Returns fixed colspan and rowspan attrbutes values.
179 *
180 * @param type colspan or rowspan.
181 * @returns conversion value function.
182 */
183function upcastCellSpan(type) {
184 return (cell) => {
185 const span = parseInt(cell.getAttribute(type));
186 if (Number.isNaN(span) || span <= 0) {
187 return null;
188 }
189 return span;
190 };
191}