diff --git a/packages/ckeditor5-table/docs/_snippets/features/table-default-properties.html b/packages/ckeditor5-table/docs/_snippets/features/table-default-properties.html new file mode 100644 index 00000000000..8d2f6837ee2 --- /dev/null +++ b/packages/ckeditor5-table/docs/_snippets/features/table-default-properties.html @@ -0,0 +1 @@ +
diff --git a/packages/ckeditor5-table/docs/_snippets/features/table-default-properties.js b/packages/ckeditor5-table/docs/_snippets/features/table-default-properties.js new file mode 100644 index 00000000000..c8805ba8cd0 --- /dev/null +++ b/packages/ckeditor5-table/docs/_snippets/features/table-default-properties.js @@ -0,0 +1,55 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals ClassicEditor, CKEditorPlugins, console, window, document */ + +ClassicEditor + .create( document.querySelector( '#snippet-default-properties' ), { + extraPlugins: [ + CKEditorPlugins.TableProperties, + CKEditorPlugins.TableCellProperties + ], + table: { + contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties' ], + tableProperties: { + defaultProperties: { + borderStyle: 'dashed', + borderColor: 'hsl(0, 0%, 60%)', + borderWidth: '3px', + alignment: 'left' + } + }, + tableCellProperties: { + defaultProperties: { + borderStyle: 'dotted', + borderColor: 'hsl(120, 75%, 60%)', + borderWidth: '2px', + horizontalAlignment: 'right', + verticalAlignment: 'bottom' + } + } + }, + image: { + toolbar: [ + 'imageStyle:full', + 'imageStyle:side', + '|', + 'imageTextAlternative' + ] + }, + placeholder: 'Insert the new table with applied the default styles.' + } ) + .then( editor => { + window.editorDefaultStyles = editor; + + window.attachTourBalloon( { + target: window.findToolbarItem( editor.ui.view.toolbar, 0 ), + text: 'Click to insert the new table.', + editor + } ); + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/packages/ckeditor5-table/docs/features/table.md b/packages/ckeditor5-table/docs/features/table.md index be1827558a9..666a679be9f 100644 --- a/packages/ckeditor5-table/docs/features/table.md +++ b/packages/ckeditor5-table/docs/features/table.md @@ -205,6 +205,45 @@ ClassicEditor .catch( ... ); ``` +### Default table and cell styles + +You can specify the default styles for the table and cells that will be applied while creating a new table. To configure default table styles, pass the `defaultProperties` object to {@link module:table/table~TableConfig#tableProperties} configuration option. Similarly, cell styles can be configured by setting `defaultProperties` on {@link module:table/table~TableConfig#tableCellProperties} option. + +```js +const tableConfig = { + table: { + tableProperties: { + // The default styles for new tables. + defaultProperties: { + borderStyle: 'dashed', + borderColor: 'hsl(0, 0%, 60%)', + borderWidth: '3px', + alignment: 'left' + } + }, + + tableCellProperties: { + // The default styles for each cell inside the created table. + defaultProperties: { + borderStyle: 'dotted', + borderColor: 'hsl(120, 75%, 60%)', + borderWidth: '2px', + horizontalAlignment: 'right', + verticalAlignment: 'bottom' + } + } + } +}; +``` + +Read more about supported values in {@link module:table/table~TableConfig}. + +{@snippet features/table-default-properties} + + + The default table and cell styles **do not** impact the {@link builds/guides/integration/basic-api#setting-the-editor-data data loaded into the editor}. They are used only when creating the new table. + + ## Block vs inline content in table cells The table feature allows creating block content (like paragraphs, lists, headings, etc.) in table cells. However, if a table cell contains just one paragraph and this paragraph has no special attributes (like text alignment), the cell content is considered "inline" and the paragraph is not rendered. diff --git a/packages/ckeditor5-table/src/commands/insertcolumncommand.js b/packages/ckeditor5-table/src/commands/insertcolumncommand.js index b33cc8106d1..05c06a12be0 100644 --- a/packages/ckeditor5-table/src/commands/insertcolumncommand.js +++ b/packages/ckeditor5-table/src/commands/insertcolumncommand.js @@ -78,6 +78,10 @@ export default class InsertColumnCommand extends Command { const column = insertBefore ? columnIndexes.first : columnIndexes.last; const table = affectedTableCells[ 0 ].findAncestor( 'table' ); - tableUtils.insertColumns( table, { columns: 1, at: insertBefore ? column : column + 1 } ); + // The `TableUtils` plugin fires the `#insertColumns` event. In order to having a single undo step, + // we need to wrap the code in the `editor.model.change()` block. + editor.model.change( () => { + tableUtils.insertColumns( table, { columns: 1, at: insertBefore ? column : column + 1 } ); + } ); } } diff --git a/packages/ckeditor5-table/src/commands/insertrowcommand.js b/packages/ckeditor5-table/src/commands/insertrowcommand.js index 14f95699e62..4c5be76e54d 100644 --- a/packages/ckeditor5-table/src/commands/insertrowcommand.js +++ b/packages/ckeditor5-table/src/commands/insertrowcommand.js @@ -77,6 +77,10 @@ export default class InsertRowCommand extends Command { const row = insertAbove ? rowIndexes.first : rowIndexes.last; const table = affectedTableCells[ 0 ].findAncestor( 'table' ); - tableUtils.insertRows( table, { at: insertAbove ? row : row + 1, copyStructureFromAbove: !insertAbove } ); + // The `TableUtils` plugin fires the `#insertRows` event. In order to having a single undo step, + // we need to wrap the code in the `editor.model.change()` block. + editor.model.change( () => { + tableUtils.insertRows( table, { at: insertAbove ? row : row + 1, copyStructureFromAbove: !insertAbove } ); + } ); } } diff --git a/packages/ckeditor5-table/src/converters/downcast.js b/packages/ckeditor5-table/src/converters/downcast.js index 540b3ecf6e6..e35e45caa7a 100644 --- a/packages/ckeditor5-table/src/converters/downcast.js +++ b/packages/ckeditor5-table/src/converters/downcast.js @@ -7,7 +7,7 @@ * @module table/converters/downcast */ -import TableWalker from './../tablewalker'; +import TableWalker from '../tablewalker'; import { setHighlightHandling, toWidget, toWidgetEditable } from 'ckeditor5/src/widget'; import { toArray } from 'ckeditor5/src/utils'; diff --git a/packages/ckeditor5-table/src/converters/table-cell-default-properties-post-fixer.js b/packages/ckeditor5-table/src/converters/table-cell-default-properties-post-fixer.js new file mode 100644 index 00000000000..0c52fe06d7d --- /dev/null +++ b/packages/ckeditor5-table/src/converters/table-cell-default-properties-post-fixer.js @@ -0,0 +1,96 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module table/converters/table-layout-post-fixer + */ + +const TABLE_CELL_PROPERTIES = [ + 'borderStyle', + 'borderColor', + 'borderWidth', + 'backgroundColor', + 'padding', + 'horizontalAlignment', + 'verticalAlignment', + 'width', + 'height' +]; + +/** + * Injects a table cell default properties post-fixer into the model. + * + * A table cell should have specified the default properties when a new cell was added into the table. + * + * @param {module:core/editor/editor~Editor} editor + */ +export default function injectTableCellDefaultPropertiesPostFixer( editor ) { + editor.model.document.registerPostFixer( writer => tableCellDefaultPropertiesPostFixer( writer, editor ) ); +} + +// The table cell default properties post-fixer. +// +// @param {module:engine/model/writer~Writer} writer +// @param {module:core/editor/editor~Editor} editor +function tableCellDefaultPropertiesPostFixer( writer, editor ) { + const model = editor.model; + const changes = model.document.differ.getChanges(); + const cellProperties = editor.config.get( 'table.tableCellProperties.defaultProperties' ); + + // Do not check anything if the default cell properties are not specified. + if ( Object.keys( cellProperties ).length === 0 ) { + return false; + } + + let wasFixed = false; + + for ( const entry of changes ) { + if ( entry.type != 'insert' ) { + continue; + } + + // Fix table on adding/removing table cells and rows. + if ( entry.name == 'tableRow' ) { + const tableRow = entry.position.nodeAfter; + + // For each cell in the table row... + for ( const tableCell of tableRow.getChildren() ) { + // ...check its cell properties... + if ( shouldApplyDefaultCellProperties( tableCell ) ) { + // ...and if the cell has no properties, apply the default. + writer.setAttributes( cellProperties, tableCell ); + + wasFixed = true; + } + } + } + + // Fix table cell on adding/removing table cells and rows. + if ( entry.name == 'tableCell' ) { + const tableCell = entry.position.nodeAfter; + + if ( shouldApplyDefaultCellProperties( tableCell ) ) { + // ...and if the cell has no properties, apply the default. + writer.setAttributes( cellProperties, tableCell ); + + wasFixed = true; + } + } + } + + return wasFixed; +} + +// Checks whether the default properties should be applied for the specified cell. +// +// The default properties will be applied only if the cell does not contain any "visual" properties. +// +// @param {module:engine/model/element~Element} tableCell +// @returns {Boolean} +function shouldApplyDefaultCellProperties( tableCell ) { + const attrs = [ ...tableCell.getAttributeKeys() ]; + + return attrs.some( attributeName => TABLE_CELL_PROPERTIES.includes( attributeName ) ) === false; +} diff --git a/packages/ckeditor5-table/src/converters/table-layout-post-fixer.js b/packages/ckeditor5-table/src/converters/table-layout-post-fixer.js index 7eb5fda0f52..5e62ed7606e 100644 --- a/packages/ckeditor5-table/src/converters/table-layout-post-fixer.js +++ b/packages/ckeditor5-table/src/converters/table-layout-post-fixer.js @@ -7,7 +7,7 @@ * @module table/converters/table-layout-post-fixer */ -import TableWalker from './../tablewalker'; +import TableWalker from '../tablewalker'; import { createEmptyTableCell, updateNumericAttribute } from '../utils/common'; /** diff --git a/packages/ckeditor5-table/src/tablecellproperties.js b/packages/ckeditor5-table/src/tablecellproperties.js index daffcb7d022..9de6f39f0c5 100644 --- a/packages/ckeditor5-table/src/tablecellproperties.js +++ b/packages/ckeditor5-table/src/tablecellproperties.js @@ -64,6 +64,32 @@ export default class TableCellProperties extends Plugin { * } * }; * + * * The default styles for the cell while creating a new table (`tableCellProperties.defaultProperties`): + * + * const tableConfig = { + * tableCellProperties: { + * defaultProperties: { + * borderStyle: 'dotted', + * borderColor: 'hsl(120, 75%, 60%)', + * borderWidth: '2px', + * horizontalAlignment: 'right', + * verticalAlignment: 'bottom' + * } + * } + * } + * + * The following properties are supported: + * + * * `backgroundColor` – sets the cell background color + * * `borderColor` – sets the border color + * * `borderStyle` – sets the border style + * * `borderWidth` – sets the border width + * * `height` – sets the cell height + * * `horizontalAlignment` – sets the cell horizontal alignment + * * `padding` – sets the cell padding + * * `verticalAlignment` – sets the cell vertical alignment + * * `width` – sets the cell width + * * **Note**: The configurations do not impact the data loaded into the editor, * i.e. they do not limit or filter the colors in the data. They are used only in the user interface * allowing users to pick colors in a more convenient way. diff --git a/packages/ckeditor5-table/src/tablecellproperties/tablecellpropertiesediting.js b/packages/ckeditor5-table/src/tablecellproperties/tablecellpropertiesediting.js index 9944dacf349..16674b6224d 100644 --- a/packages/ckeditor5-table/src/tablecellproperties/tablecellpropertiesediting.js +++ b/packages/ckeditor5-table/src/tablecellproperties/tablecellpropertiesediting.js @@ -21,6 +21,9 @@ import TableCellHorizontalAlignmentCommand from './commands/tablecellhorizontala import TableCellBorderStyleCommand from './commands/tablecellborderstylecommand'; import TableCellBorderColorCommand from './commands/tablecellbordercolorcommand'; import TableCellBorderWidthCommand from './commands/tablecellborderwidthcommand'; +import TableWalker from '../tablewalker'; +import TableUtils from '../tableutils'; +import injectTableCellDefaultPropertiesPostFixer from '../converters/table-cell-default-properties-post-fixer'; const VALIGN_VALUES_REG_EXP = /^(top|bottom)$/; @@ -57,7 +60,7 @@ export default class TableCellPropertiesEditing extends Plugin { * @inheritDoc */ static get requires() { - return [ TableEditing ]; + return [ TableEditing, TableUtils ]; } /** @@ -69,6 +72,8 @@ export default class TableCellPropertiesEditing extends Plugin { const conversion = editor.conversion; const locale = editor.locale; + editor.config.define( 'table.tableCellProperties.defaultProperties', {} ); + editor.data.addStyleProcessorRules( addBorderRules ); enableBorderProperties( schema, conversion ); editor.commands.add( 'tableCellBorderStyle', new TableCellBorderStyleCommand( editor ) ); @@ -94,6 +99,36 @@ export default class TableCellPropertiesEditing extends Plugin { enableVerticalAlignmentProperty( schema, conversion ); editor.commands.add( 'tableCellVerticalAlignment', new TableCellVerticalAlignmentCommand( editor ) ); + + this._enableDefaultCellProperties(); + + injectTableCellDefaultPropertiesPostFixer( editor ); + } + + /** + * Enables applying the default cell properties. + * + * @private + */ + _enableDefaultCellProperties() { + const editor = this.editor; + const tableCellProperties = editor.config.get( 'table.tableCellProperties.defaultProperties' ); + const tableUtils = editor.plugins.get( TableUtils ); + + // Apply default cell properties while creating a new table. + this.listenTo( tableUtils, 'createTable', ( evt, [ writer ] ) => { + const tableElement = evt.return; + + for ( const item of new TableWalker( tableElement ) ) { + writer.setAttributes( tableCellProperties, item.cell ); + } + }, { priority: 'low' } ); + + // Apply default cell properties while inserting new rows into the table. + this.listenTo( tableUtils, 'insertRows', applyDefaultCellProperties( editor, tableCellProperties ), { priority: 'low' } ); + + // Apply default cell properties while inserting new rows into the table. + this.listenTo( tableUtils, 'insertColumns', applyDefaultCellProperties( editor, tableCellProperties ), { priority: 'low' } ); } } @@ -202,3 +237,20 @@ function enableProperty( schema, conversion, modelAttribute, styleName ) { upcastStyleToAttribute( conversion, 'tableCell', modelAttribute, styleName ); downcastAttributeToStyle( conversion, 'tableCell', modelAttribute, styleName ); } + +// A factory function that returns a handler which applies default cell properties for created cells. +// +// @param {module:core/editor/editor~Editor} editor +// @param {module:table/table~TableConfig#tableCellProperties} tableCellProperties +// @return {Function} +function applyDefaultCellProperties( editor, tableCellProperties ) { + return evt => { + const createdCells = evt.return; + + editor.model.change( writer => { + for ( const tableCell of createdCells ) { + writer.setAttributes( tableCellProperties, tableCell ); + } + } ); + }; +} diff --git a/packages/ckeditor5-table/src/tableproperties.js b/packages/ckeditor5-table/src/tableproperties.js index 25d23f8ffac..1ded0939aa7 100644 --- a/packages/ckeditor5-table/src/tableproperties.js +++ b/packages/ckeditor5-table/src/tableproperties.js @@ -65,6 +65,29 @@ export default class TableProperties extends Plugin { * } * }; * + * * The default styles for the new created table (`tableProperties.defaultProperties`): + * + * const tableConfig = { + * tableProperties: { + * defaultProperties: { + * borderStyle: 'dashed', + * borderColor: 'hsl(0, 0%, 90%)', + * borderWidth: '3px', + * alignment: 'left' + * } + * } + * } + * + * The following properties are supported: + * + * * `alignment` – sets the table alignment + * * `backgroundColor` – sets the table background color + * * `borderColor` – sets the border color + * * `borderStyle` – sets the border style + * * `borderWidth` – sets the border width + * * `height` – sets the table height + * * `width` – sets the table width + * * **Note**: The configurations do not impact the data loaded into the editor, * i.e. they do not limit or filter the colors in the data. They are used only in the user interface * allowing users to pick colors in a more convenient way. diff --git a/packages/ckeditor5-table/src/tableproperties/tablepropertiesediting.js b/packages/ckeditor5-table/src/tableproperties/tablepropertiesediting.js index c0648cff5bd..e0576cbf11d 100644 --- a/packages/ckeditor5-table/src/tableproperties/tablepropertiesediting.js +++ b/packages/ckeditor5-table/src/tableproperties/tablepropertiesediting.js @@ -24,6 +24,7 @@ import TableBorderWidthCommand from './commands/tableborderwidthcommand'; import TableWidthCommand from './commands/tablewidthcommand'; import TableHeightCommand from './commands/tableheightcommand'; import TableAlignmentCommand from './commands/tablealignmentcommand'; +import TableUtils from '../tableutils'; const ALIGN_VALUES_REG_EXP = /^(left|right)$/; @@ -58,7 +59,7 @@ export default class TablePropertiesEditing extends Plugin { * @inheritDoc */ static get requires() { - return [ TableEditing ]; + return [ TableEditing, TableUtils ]; } /** @@ -69,6 +70,8 @@ export default class TablePropertiesEditing extends Plugin { const schema = editor.model.schema; const conversion = editor.conversion; + editor.config.define( 'table.tableProperties.defaultProperties', {} ); + editor.data.addStyleProcessorRules( addBorderRules ); enableBorderProperties( schema, conversion ); editor.commands.add( 'tableBorderColor', new TableBorderColorCommand( editor ) ); @@ -87,6 +90,26 @@ export default class TablePropertiesEditing extends Plugin { editor.data.addStyleProcessorRules( addBackgroundRules ); enableProperty( schema, conversion, 'backgroundColor', 'background-color' ); editor.commands.add( 'tableBackgroundColor', new TableBackgroundColorCommand( editor ) ); + + this._enableDefaultTableProperties(); + } + + /** + * Enables applying the default table properties. + * + * @private + */ + _enableDefaultTableProperties() { + const editor = this.editor; + const tableProperties = editor.config.get( 'table.tableProperties.defaultProperties' ); + const tableUtils = editor.plugins.get( TableUtils ); + + // Apply default table properties while creating a new table. + this.listenTo( tableUtils, 'createTable', ( evt, [ writer ] ) => { + const tableElement = evt.return; + + writer.setAttributes( tableProperties, tableElement ); + }, { priority: 'low' } ); } } diff --git a/packages/ckeditor5-table/src/tableutils.js b/packages/ckeditor5-table/src/tableutils.js index 23b7ea9d8ca..c6fcd60b8b0 100644 --- a/packages/ckeditor5-table/src/tableutils.js +++ b/packages/ckeditor5-table/src/tableutils.js @@ -32,6 +32,7 @@ export default class TableUtils extends Plugin { init() { this.decorate( 'insertColumns' ); this.decorate( 'insertRows' ); + this.decorate( 'createTable' ); } /** @@ -141,6 +142,7 @@ export default class TableUtils extends Plugin { * @param {Number} [options.rows=1] The number of rows to insert. * @param {Boolean|undefined} [options.copyStructureFromAbove] The flag for copying row structure. Note that * the row structure will not be copied if this option is not provided. + * @returns {Array.} Created table cells. */ insertRows( table, options = {} ) { const model = this.editor.model; @@ -153,7 +155,7 @@ export default class TableUtils extends Plugin { const rows = this.getRows( table ); const columns = this.getColumns( table ); - model.change( writer => { + return model.change( writer => { const headingRows = table.getAttribute( 'headingRows' ) || 0; // Inserting rows inside heading section requires to update `headingRows` attribute as the heading section will grow. @@ -163,9 +165,7 @@ export default class TableUtils extends Plugin { // Inserting at the end or at the beginning of a table doesn't require to calculate anything special. if ( !isCopyStructure && ( insertAt === 0 || insertAt === rows ) ) { - createEmptyRows( writer, table, insertAt, rowsToInsert, columns ); - - return; + return createEmptyRows( writer, table, insertAt, rowsToInsert, columns ); } // Iterate over all the rows above the inserted rows in order to check for the row-spanned cells. @@ -195,6 +195,8 @@ export default class TableUtils extends Plugin { } } + const createdCells = []; + for ( let rowIndex = 0; rowIndex < rowsToInsert; rowIndex++ ) { const tableRow = writer.createElement( 'tableRow' ); @@ -206,13 +208,17 @@ export default class TableUtils extends Plugin { // Insert the empty cell only if this slot is not row-spanned from any other cell. if ( colspan > 0 ) { - createEmptyTableCell( writer, insertPosition, colspan > 1 ? { colspan } : null ); + const tableCell = createEmptyTableCell( writer, insertPosition, colspan > 1 ? { colspan } : null ); + + createdCells.push( tableCell ); } // Skip the col-spanned slots, there won't be any cells. cellIndex += Math.abs( colspan ) - 1; } } + + return createdCells; } ); } @@ -241,6 +247,7 @@ export default class TableUtils extends Plugin { * @param {Object} options * @param {Number} [options.at=0] The column index at which the columns will be inserted. * @param {Number} [options.columns=1] The number of columns to insert. + * @returns {Array.} Created table cells. */ insertColumns( table, options = {} ) { const model = this.editor.model; @@ -248,8 +255,9 @@ export default class TableUtils extends Plugin { const insertAt = options.at || 0; const columnsToInsert = options.columns || 1; - model.change( writer => { + return model.change( writer => { const headingColumns = table.getAttribute( 'headingColumns' ); + const createdCells = []; // Inserting columns inside heading section requires to update `headingColumns` attribute as the heading section will grow. if ( insertAt < headingColumns ) { @@ -261,10 +269,12 @@ export default class TableUtils extends Plugin { // Inserting at the end and at the beginning of a table doesn't require to calculate anything special. if ( insertAt === 0 || tableColumns === insertAt ) { for ( const tableRow of table.getChildren() ) { - createCells( columnsToInsert, writer, writer.createPositionAt( tableRow, insertAt ? 'end' : 0 ) ); + createdCells.push( + ...createCells( columnsToInsert, writer, writer.createPositionAt( tableRow, insertAt ? 'end' : 0 ) ) + ); } - return; + return createdCells; } const tableWalker = new TableWalker( table, { column: insertAt, includeAllSlots: true } ); @@ -291,9 +301,13 @@ export default class TableUtils extends Plugin { } else { // It's either cell at this column index or spanned cell by a row-spanned cell from row above. // In table above it's cell "e" and a spanned position from row below (empty cell between cells "g" and "h") - createCells( columnsToInsert, writer, tableSlot.getPositionBefore() ); + createdCells.push( + ...createCells( columnsToInsert, writer, tableSlot.getPositionBefore() ) + ); } } + + return createdCells; } ); } @@ -748,14 +762,21 @@ export default class TableUtils extends Plugin { // @param {Number} insertAt The row index of row insertion. // @param {Number} rows The number of rows to create. // @param {Number} tableCellToInsert The number of cells to insert in each row. +// @returns {Array.} Created table cells. function createEmptyRows( writer, table, insertAt, rows, tableCellToInsert, attributes = {} ) { + const createdCells = []; + for ( let i = 0; i < rows; i++ ) { const tableRow = writer.createElement( 'tableRow' ); writer.insert( tableRow, table, insertAt ); - createCells( tableCellToInsert, writer, writer.createPositionAt( tableRow, 'end' ), attributes ); + createdCells.push( + ...createCells( tableCellToInsert, writer, writer.createPositionAt( tableRow, 'end' ), attributes ) + ); } + + return createdCells; } // Creates cells at a given position. @@ -763,10 +784,15 @@ function createEmptyRows( writer, table, insertAt, rows, tableCellToInsert, attr // @param {Number} columns The number of columns to create // @param {module:engine/model/writer~Writer} writer // @param {module:engine/model/position~Position} insertPosition +// @returns {Array.} Created table cells. function createCells( cells, writer, insertPosition, attributes = {} ) { + const createdCells = []; + for ( let i = 0; i < cells; i++ ) { - createEmptyTableCell( writer, insertPosition, attributes ); + createdCells.push( createEmptyTableCell( writer, insertPosition, attributes ) ); } + + return createdCells; } // Evenly distributes the span of a cell to a number of provided cells. diff --git a/packages/ckeditor5-table/src/utils/common.js b/packages/ckeditor5-table/src/utils/common.js index 26ac1a071f5..c304d9cf1d2 100644 --- a/packages/ckeditor5-table/src/utils/common.js +++ b/packages/ckeditor5-table/src/utils/common.js @@ -29,7 +29,7 @@ export function updateNumericAttribute( key, value, item, writer, defaultValue = * * @param {module:engine/model/writer~Writer} writer The model writer. * @param {module:engine/model/position~Position} insertPosition The position at which the table cell should be inserted. - * @param {Object} attributes The element attributes. + * @param {Object} [attributes={}] The element attributes. * @returns {module:engine/model/element~Element} Created table cell. */ export function createEmptyTableCell( writer, insertPosition, attributes = {} ) { diff --git a/packages/ckeditor5-table/tests/_utils/utils.js b/packages/ckeditor5-table/tests/_utils/utils.js index 36bd57d89db..500935825c1 100644 --- a/packages/ckeditor5-table/tests/_utils/utils.js +++ b/packages/ckeditor5-table/tests/_utils/utils.js @@ -32,20 +32,26 @@ const WIDGET_TABLE_CELL_CLASS = 'ck-editor__editable ck-editor__nested-editable' * }; * * @param {Array.|Object>} tableData - * @param {Object} [attributes] Optional table attributes: `headingRows` and `headingColumns`. + * @param {Object} [options={}] Optional table attributes. Supports {@link module:table/table~TableConfig#tableProperties}. + * @param {Number} options.headingRows Number of heading rows. + * @param {Number} options.headingColumns Number of heading columns. + * @param {module:table/table~TableConfig#tableCellProperties} options.cellProperties Properties for all created cells. * * @returns {String} */ -export function modelTable( tableData, attributes ) { +export function modelTable( tableData, options = {} ) { const tableRows = makeRows( tableData, { cellElement: 'tableCell', rowElement: 'tableRow', headingElement: 'tableCell', wrappingElement: 'paragraph', - enforceWrapping: true + enforceWrapping: true, + cellProperties: options.cellProperties } ); - return `${ tableRows }`; + delete options.cellProperties; + + return `${ tableRows }`; } /** @@ -343,7 +349,7 @@ function formatAttributes( attributes ) { // Formats passed table data to a set of table rows. function makeRows( tableData, options ) { - const { cellElement, rowElement, headingElement, wrappingElement, enforceWrapping, asWidget } = options; + const { cellElement, rowElement, headingElement, wrappingElement, enforceWrapping, asWidget, cellProperties } = options; return tableData .reduce( ( previousRowsString, tableRow ) => { @@ -367,7 +373,8 @@ function makeRows( tableData, options ) { delete tableCellData.isSelected; } - let attributes = {}; + // Copy the default properties to avoid modifying references. + let attributes = Object.assign( {}, cellProperties ); if ( asWidget ) { attributes.class = getClassToSet( attributes ); diff --git a/packages/ckeditor5-table/tests/commands/insertcolumncommand.js b/packages/ckeditor5-table/tests/commands/insertcolumncommand.js index 8e25b0cd8ea..a043dcc6662 100644 --- a/packages/ckeditor5-table/tests/commands/insertcolumncommand.js +++ b/packages/ckeditor5-table/tests/commands/insertcolumncommand.js @@ -14,6 +14,7 @@ import TableEditing from '../../src/tableediting'; import { assertSelectedCells, modelTable } from '../_utils/utils'; import InsertColumnCommand from '../../src/commands/insertcolumncommand'; +import { UndoEditing } from '@ckeditor/ckeditor5-undo'; describe( 'InsertColumnCommand', () => { let editor, model, command; @@ -21,7 +22,7 @@ describe( 'InsertColumnCommand', () => { beforeEach( () => { return ModelTestEditor .create( { - plugins: [ Paragraph, TableEditing, TableSelection, HorizontalLineEditing ] + plugins: [ Paragraph, TableEditing, TableSelection, HorizontalLineEditing, UndoEditing ] } ) .then( newEditor => { editor = newEditor; @@ -213,6 +214,43 @@ describe( 'InsertColumnCommand', () => { [ '31', '', '' ] ] ) ); } ); + + it( 'should create a single undo step when inserting a column', () => { + setData( model, modelTable( [ + [ '11[]', '12' ], + [ '21', '22' ] + ] ) ); + + const tableUtils = editor.plugins.get( 'TableUtils' ); + const spy = sinon.spy(); + + tableUtils.on( 'insertColumns', () => { + model.change( writer => { + const table = model.document.getRoot().getNodeByPath( [ 0 ] ); + const paragraph = writer.createElement( 'paragraph' ); + + model.insertContent( paragraph, writer.createPositionBefore( table ) ); + + assertEqualMarkup( getData( model ), '' + modelTable( [ + [ '11[]', '', '12' ], + [ '21', '', '22' ] + ] ) ); + } ); + + spy(); + }, { priority: 'low' } ); + + command.execute(); + + expect( spy.calledOnce ).to.equal( true ); + + editor.execute( 'undo' ); + + assertEqualMarkup( getData( model ), modelTable( [ + [ '11[]', '12' ], + [ '21', '22' ] + ] ) ); + } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/commands/insertrowcommand.js b/packages/ckeditor5-table/tests/commands/insertrowcommand.js index c7c8b3c442b..ad470858f61 100644 --- a/packages/ckeditor5-table/tests/commands/insertrowcommand.js +++ b/packages/ckeditor5-table/tests/commands/insertrowcommand.js @@ -14,6 +14,7 @@ import TableSelection from '../../src/tableselection'; import { assertSelectedCells, modelTable } from '../_utils/utils'; import InsertRowCommand from '../../src/commands/insertrowcommand'; +import { UndoEditing } from '@ckeditor/ckeditor5-undo'; describe( 'InsertRowCommand', () => { let editor, model, command; @@ -21,7 +22,7 @@ describe( 'InsertRowCommand', () => { beforeEach( () => { return ModelTestEditor .create( { - plugins: [ Paragraph, TableEditing, TableSelection, HorizontalLineEditing ] + plugins: [ Paragraph, TableEditing, TableSelection, HorizontalLineEditing, UndoEditing ] } ) .then( newEditor => { editor = newEditor; @@ -308,6 +309,44 @@ describe( 'InsertRowCommand', () => { [ '10', '11', '12' ] ] ) ); } ); + + it( 'should create a single undo step when inserting a column', () => { + setData( model, modelTable( [ + [ '11[]', '12' ], + [ '21', '22' ] + ] ) ); + + const tableUtils = editor.plugins.get( 'TableUtils' ); + const spy = sinon.spy(); + + tableUtils.on( 'insertRows', () => { + model.change( writer => { + const table = model.document.getRoot().getNodeByPath( [ 0 ] ); + const paragraph = writer.createElement( 'paragraph' ); + + model.insertContent( paragraph, writer.createPositionBefore( table ) ); + + assertEqualMarkup( getData( model ), '' + modelTable( [ + [ '11[]', '12' ], + [ '', '' ], + [ '21', '22' ] + ] ) ); + } ); + + spy(); + }, { priority: 'low' } ); + + command.execute(); + + expect( spy.calledOnce ).to.equal( true ); + + editor.execute( 'undo' ); + + assertEqualMarkup( getData( model ), modelTable( [ + [ '11[]', '12' ], + [ '21', '22' ] + ] ) ); + } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/converters/table-cell-default-properties-post-fixer.js b/packages/ckeditor5-table/tests/converters/table-cell-default-properties-post-fixer.js new file mode 100644 index 00000000000..e049e1c6e71 --- /dev/null +++ b/packages/ckeditor5-table/tests/converters/table-cell-default-properties-post-fixer.js @@ -0,0 +1,245 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; +import { getData as getModelData, parse, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; + +import TableEditing from '../../src/tableediting'; +import { modelTable } from './../_utils/utils'; +import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting'; +import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; +import TableCellPropertiesEditing from '../../src/tablecellproperties/tablecellpropertiesediting'; + +describe( 'Table cell default properties post-fixer', () => { + let editor, model, root; + + const defaultProperties = { + borderStyle: 'solid', + borderWidth: '2px', + borderColor: '#f00', + horizontalAlignment: 'right', + verticalAlignment: 'bottom' + }; + + beforeEach( () => { + return VirtualTestEditor + .create( { + plugins: [ TableEditing, Paragraph, UndoEditing, TableCellPropertiesEditing ], + table: { + tableCellProperties: { + defaultProperties + } + } + } ) + .then( newEditor => { + editor = newEditor; + model = editor.model; + root = model.document.getRoot(); + } ); + } ); + + afterEach( () => { + editor.destroy(); + } ); + + describe( 'on collaboration', () => { + it( 'should add missing cells to columns (remove column vs insert row)', () => { + _testExternal( + modelTable( [ + [ '00[]', '01' ], + [ '10', '11' ] + ], { cellProperties: defaultProperties } ), + writer => _removeColumn( writer, 1, [ 0, 1 ] ), + writer => _insertRow( writer, 1, [ 'a', 'b' ] ), + // Table should have added empty cells. + modelTable( [ + [ '00', '' ], + [ 'a', 'b' ], + [ '10', '' ] + ], { cellProperties: defaultProperties } ), + // Table will have empty column after undo. + modelTable( [ + [ '00', '01', '' ], + [ 'a', 'b', '' ], + [ '10', '11', '' ] + ], { cellProperties: defaultProperties } ) ); + } ); + + it( 'should add missing cells to columns (insert row vs remove column)', () => { + _testExternal( + modelTable( [ + [ '00[]', '01' ], + [ '10', '11' ] + ], { cellProperties: defaultProperties } ), + writer => _insertRow( writer, 1, [ 'a', 'b' ] ), + writer => _removeColumn( writer, 1, [ 0, 2 ] ), + // There should be empty cells added. + modelTable( [ + [ '00', '' ], + [ 'a', 'b' ], + [ '10', '' ] + ], { cellProperties: defaultProperties } ), + // Table will have empty column after undo. + modelTable( [ + [ '00', '' ], + [ '10', '' ] + ], { cellProperties: defaultProperties } ) ); + } ); + + it( 'should add empty cell to an added row (insert row vs insert column)', () => { + _testExternal( + modelTable( [ + [ '00[]', '01' ], + [ '10', '11' ] + ], { cellProperties: defaultProperties } ), + writer => _insertRow( writer, 1, [ 'a', 'b' ] ), + writer => _insertColumn( writer, 1, [ 0, 2 ] ), + // There should be empty cells added. + modelTable( [ + [ '00', '', '01' ], + [ 'a', 'b', '' ], + [ '10', '', '11' ] + ], { cellProperties: defaultProperties } ), + // Table will have empty column after undo. + modelTable( [ + [ '00', '', '01' ], + [ '10', '', '11' ] + ], { cellProperties: defaultProperties } ) ); + } ); + + it( 'should add empty cell to an added row (insert column vs insert row)', () => { + _testExternal( + modelTable( [ + [ '00[]', '01' ], + [ '10', '11' ] + ], { cellProperties: defaultProperties } ), + writer => _insertColumn( writer, 1, [ 0, 1 ] ), + writer => _insertRow( writer, 1, [ 'a', 'b' ] ), + // There should be empty cells added. + modelTable( [ + [ '00', '', '01' ], + [ 'a', 'b', '' ], + [ '10', '', '11' ] + ], { cellProperties: defaultProperties } ), + // Table will have empty column after undo. + modelTable( [ + [ '00', '01', '' ], + [ 'a', 'b', '' ], + [ '10', '11', '' ] + ], { cellProperties: defaultProperties } ) ); + } ); + + it( 'should add empty cell when inserting column over a colspanned cell (insert column vs insert column)', () => { + _testExternal( + modelTable( [ + [ { colspan: 3, contents: '00' } ], + [ '10', '11', '12' ] + ], { cellProperties: defaultProperties } ), + writer => { + _setAttribute( writer, 'colspan', 4, [ 0, 0, 0 ] ); + _insertColumn( writer, 2, [ 1 ] ); + }, + writer => { + _setAttribute( writer, 'colspan', 4, [ 0, 0, 0 ] ); + _insertColumn( writer, 1, [ 1 ] ); + }, + // There should be empty cells added. + modelTable( [ + [ { colspan: 4, contents: '00' }, '' ], + [ '10', '', '11', '', '12' ] + ], { cellProperties: defaultProperties } ), + // Table will have empty column after undo. + modelTable( [ + [ { colspan: 3, contents: '00' }, '' ], + [ '10', '', '11', '12' ] + ], { cellProperties: defaultProperties } ) ); + } ); + + it( 'should add empty cell when inserting column over a colspanned cell (insert column vs insert column) - inverted', () => { + _testExternal( + modelTable( [ + [ { colspan: 3, contents: '00' } ], + [ '10', '11', '12' ] + ], { cellProperties: defaultProperties } ), + writer => { + _setAttribute( writer, 'colspan', 4, [ 0, 0, 0 ] ); + _insertColumn( writer, 1, [ 1 ] ); + }, + writer => { + _setAttribute( writer, 'colspan', 4, [ 0, 0, 0 ] ); + _insertColumn( writer, 3, [ 1 ] ); + }, + // There should be empty cells added. + modelTable( [ + [ { colspan: 4, contents: '00' }, '' ], + [ '10', '', '11', '', '12' ] + ], { cellProperties: defaultProperties } ), + // Table will have empty column after undo. + modelTable( [ + [ { colspan: 3, contents: '00' }, '' ], + [ '10', '11', '', '12' ] + ], { cellProperties: defaultProperties } ) ); + } ); + + function _testExternal( initialData, localCallback, externalCallback, modelAfter, modelAfterUndo ) { + setModelData( model, initialData ); + + model.change( localCallback ); + + model.enqueueChange( 'transparent', externalCallback ); + + assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelAfter ); + + editor.execute( 'undo' ); + + assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelAfterUndo ); + + editor.execute( 'redo' ); + + assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelAfter ); + } + + function _removeColumn( writer, columnIndex, rows ) { + const table = root.getChild( 0 ); + + for ( const index of rows ) { + const tableRow = table.getChild( index ); + const tableCell = tableRow.getChild( columnIndex ); + + writer.remove( tableCell ); + } + } + + function _insertRow( writer, rowIndex, rowData ) { + const table = root.getChild( 0 ); + + const parsedTable = parse( + modelTable( [ rowData ] ), + model.schema + ); + + writer.insert( parsedTable.getChild( 0 ), table, rowIndex ); + } + + function _setAttribute( writer, attributeKey, attributeValue, path ) { + const node = root.getNodeByPath( path ); + + writer.setAttribute( attributeKey, attributeValue, node ); + } + + function _insertColumn( writer, columnIndex, rows ) { + const table = root.getChild( 0 ); + + for ( const index of rows ) { + const tableRow = table.getChild( index ); + + const tableCell = writer.createElement( 'tableCell' ); + writer.insert( tableCell, tableRow, columnIndex ); + writer.insertElement( 'paragraph', tableCell ); + } + } + } ); +} ); diff --git a/packages/ckeditor5-table/tests/manual/tableproperties.js b/packages/ckeditor5-table/tests/manual/tableproperties.js index 79b0d202ab5..fd5464f5eb5 100644 --- a/packages/ckeditor5-table/tests/manual/tableproperties.js +++ b/packages/ckeditor5-table/tests/manual/tableproperties.js @@ -27,7 +27,24 @@ ClassicEditor ], table: { contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties' ], - tableToolbar: [ 'bold', 'italic' ] + tableToolbar: [ 'bold', 'italic' ], + tableProperties: { + defaultProperties: { + borderStyle: 'dashed', + borderColor: 'hsl(0, 0%, 60%)', + borderWidth: '3px', + alignment: 'left' + } + }, + tableCellProperties: { + defaultProperties: { + borderStyle: 'dotted', + borderColor: 'hsl(120, 75%, 60%)', + borderWidth: '2px', + horizontalAlignment: 'right', + verticalAlignment: 'bottom' + } + } } } ) .then( editor => { @@ -36,3 +53,4 @@ ClassicEditor .catch( err => { console.error( err.stack ); } ); + diff --git a/packages/ckeditor5-table/tests/manual/tableproperties.md b/packages/ckeditor5-table/tests/manual/tableproperties.md index fb5ab40b322..d19f0fd61b9 100644 --- a/packages/ckeditor5-table/tests/manual/tableproperties.md +++ b/packages/ckeditor5-table/tests/manual/tableproperties.md @@ -10,3 +10,36 @@ The editor should be loaded with tables: Compare their visual styles with the ones beside the editor. Most of the styles should be preserved. **Note**: Not all styles are preserved (i.e. the column height/width in the second GDocs table). Also note that the original table might have a different cell padding. + +After all, clear the editor before starting checking the default table and cell properties. + +### Default table properties + +After inserting a new table, the default styles (printed below) should be applied to the table automatically: + +```json +{ + "borderStyle": "dashed", + "borderColor": "hsl(0, 0%, 60%)", + "borderWidth": "3px", + "alignment": "left" +} +``` + +Use the `Table properties` icon in the table toolbar and compare values. + +### Default cells properties + +After inserting a new table, the default styles (printed below) should be applied to all cells automatically: + +```json +{ + "borderStyle": "dotted", + "borderColor": "hsl(120, 75%, 60%)", + "borderWidth": "2px", + "horizontalAlignment": "right", + "verticalAlignment": "bottom" +} +``` + +Use the `Cell properties` icon in the table toolbar and compare values. diff --git a/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesediting.js b/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesediting.js index 24fd7657336..c4a589528c7 100644 --- a/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesediting.js +++ b/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesediting.js @@ -19,9 +19,11 @@ import TableCellVerticalAlignmentCommand from '../../src/tablecellproperties/com import TableCellPaddingCommand from '../../src/tablecellproperties/commands/tablecellpaddingcommand'; import TableCellBackgroundColorCommand from '../../src/tablecellproperties/commands/tablecellbackgroundcolorcommand'; -import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; -import { assertTableCellStyle, assertTRBLAttribute } from '../_utils/utils'; +import { assertTableCellStyle, assertTRBLAttribute, modelTable } from '../_utils/utils'; +import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor'; +import TableUtils from '../../src/tableutils'; describe( 'table cell properties', () => { describe( 'TableCellPropertiesEditing', () => { @@ -43,6 +45,10 @@ describe( 'table cell properties', () => { expect( TableCellPropertiesEditing.pluginName ).to.equal( 'TableCellPropertiesEditing' ); } ); + it( 'should require TableUtils', () => { + expect( TableCellPropertiesEditing.requires ).to.include( TableUtils ); + } ); + it( 'adds tableCellBorderColor command', () => { expect( editor.commands.get( 'tableCellBorderColor' ) ).to.be.instanceOf( TableCellBorderColorCommand ); } ); @@ -79,6 +85,18 @@ describe( 'table cell properties', () => { expect( editor.commands.get( 'tableCellHeight' ) ).to.be.instanceOf( TableCellHeightCommand ); } ); + describe( 'config', () => { + let tableCellProperties; + + beforeEach( () => { + tableCellProperties = editor.config.get( 'table.tableCellProperties' ); + } ); + + it( 'should define the default properties for a table', () => { + expect( tableCellProperties.defaultProperties ).to.deep.equal( {} ); + } ); + } ); + describe( 'border', () => { it( 'should set proper schema rules', () => { expect( model.schema.checkAttribute( [ '$root', 'tableCell' ], 'borderColor' ) ).to.be.true; @@ -1244,5 +1262,99 @@ describe( 'table cell properties', () => { } ); } ); } ); + + describe( 'default cell properties', () => { + let editor, model; + + const defaultProperties = { + borderStyle: 'solid', + borderWidth: '2px', + borderColor: '#f00', + horizontalAlignment: 'right', + verticalAlignment: 'bottom' + }; + + beforeEach( () => { + return ModelTestEditor + .create( { + plugins: [ Paragraph, TableCellPropertiesEditing ], + table: { + tableCellProperties: { + defaultProperties + } + } + } ) + .then( newEditor => { + editor = newEditor; + model = editor.model; + setModelData( model, '[]' ); + } ); + } ); + + afterEach( () => { + return editor.destroy(); + } ); + + it( 'should create a table and all cells should have applied the default cell properties', () => { + editor.execute( 'insertTable' ); + + assertEqualMarkup( getModelData( model ), + modelTable( [ + [ '[]', '' ], + [ '', '' ] + ], { cellProperties: defaultProperties } ) + ); + } ); + + it( 'should apply default cell properties when inserting a new column (command=insertTableColumnRight)', () => { + editor.execute( 'insertTable' ); + editor.execute( 'insertTableColumnRight' ); + + assertEqualMarkup( getModelData( model ), + modelTable( [ + [ '[]', '', '' ], + [ '', '', '' ] + ], { cellProperties: defaultProperties } ) + ); + } ); + + it( 'should apply default cell properties when inserting a new column (command=insertTableColumnLeft)', () => { + editor.execute( 'insertTable' ); + editor.execute( 'insertTableColumnLeft' ); + + assertEqualMarkup( getModelData( model ), + modelTable( [ + [ '', '[]', '' ], + [ '', '', '' ] + ], { cellProperties: defaultProperties } ) + ); + } ); + + it( 'should apply default cell properties when inserting a new row (command=insertTableRowAbove)', () => { + editor.execute( 'insertTable' ); + editor.execute( 'insertTableRowAbove' ); + + assertEqualMarkup( getModelData( model ), + modelTable( [ + [ '', '' ], + [ '[]', '' ], + [ '', '' ] + ], { cellProperties: defaultProperties } ) + ); + } ); + + it( 'should apply default cell properties when inserting a new row (command=insertTableRowBelow)', () => { + editor.execute( 'insertTable' ); + editor.execute( 'insertTableRowBelow' ); + + assertEqualMarkup( getModelData( model ), + modelTable( [ + [ '[]', '' ], + [ '', '' ], + [ '', '' ] + ], { cellProperties: defaultProperties } ) + ); + } ); + } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesui.js b/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesui.js index f5538646802..b95be8a5d0d 100644 --- a/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesui.js +++ b/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesui.js @@ -74,7 +74,8 @@ describe( 'table cell properties', () => { it( 'should define table.tableCellProperties config', () => { expect( editor.config.get( 'table.tableCellProperties' ) ).to.deep.equal( { borderColors: defaultColors, - backgroundColors: defaultColors + backgroundColors: defaultColors, + defaultProperties: {} } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js index 98646113738..445e65c6ffd 100644 --- a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js +++ b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js @@ -19,7 +19,9 @@ import TableBackgroundColorCommand from '../../src/tableproperties/commands/tabl import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; -import { assertTableStyle, assertTRBLAttribute } from '../_utils/utils'; +import { assertTableStyle, assertTRBLAttribute, modelTable } from '../_utils/utils'; +import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor'; +import TableUtils from '../../src/tableutils'; describe( 'table properties', () => { describe( 'TablePropertiesEditing', () => { @@ -45,6 +47,10 @@ describe( 'table properties', () => { expect( TablePropertiesEditing.pluginName ).to.equal( 'TablePropertiesEditing' ); } ); + it( 'should require TableUtils', () => { + expect( TablePropertiesEditing.requires ).to.include( TableUtils ); + } ); + it( 'adds tableBorderColor command', () => { expect( editor.commands.get( 'tableBorderColor' ) ).to.be.instanceOf( TableBorderColorCommand ); } ); @@ -73,6 +79,18 @@ describe( 'table properties', () => { expect( editor.commands.get( 'tableBackgroundColor' ) ).to.be.instanceOf( TableBackgroundColorCommand ); } ); + describe( 'config', () => { + let tableProperties; + + beforeEach( () => { + tableProperties = editor.config.get( 'table.tableProperties' ); + } ); + + it( 'should define the default properties for a table', () => { + expect( tableProperties.defaultProperties ).to.deep.equal( {} ); + } ); + } ); + describe( 'border', () => { it( 'should set proper schema rules', () => { expect( model.schema.checkAttribute( [ '$root', 'table' ], 'borderColor' ) ).to.be.true; @@ -1238,15 +1256,58 @@ describe( 'table properties', () => { } ); } ); + describe( 'default table properties', () => { + let editor, model; + + const defaultProperties = { + borderStyle: 'solid', + borderWidth: '2px', + borderColor: '#f00', + alignment: 'right' + }; + + beforeEach( () => { + return ModelTestEditor + .create( { + plugins: [ Paragraph, TablePropertiesEditing ], + table: { + tableProperties: { + defaultProperties + } + } + } ) + .then( newEditor => { + editor = newEditor; + model = editor.model; + setModelData( model, '[]' ); + } ); + } ); + + afterEach( () => { + return editor.destroy(); + } ); + + it( 'should create a table with applied the default properties', () => { + editor.execute( 'insertTable' ); + + assertEqualMarkup( getModelData( model ), + modelTable( [ + [ '[]', '' ], + [ '', '' ] + ], { ...defaultProperties } ) + ); + } ); + } ); + function createEmptyTable() { setModelData( model, '' + - '' + - '' + - 'foo' + - '' + - '' + + '' + + '' + + 'foo' + + '' + + '' + '
' ); diff --git a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js index e0129f80e63..57e88ac03ed 100644 --- a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js +++ b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js @@ -73,7 +73,8 @@ describe( 'table properties', () => { it( 'should define table.tableProperties config', () => { expect( editor.config.get( 'table.tableProperties' ) ).to.deep.equal( { borderColors: defaultColors, - backgroundColors: defaultColors + backgroundColors: defaultColors, + defaultProperties: {} } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/tableutils.js b/packages/ckeditor5-table/tests/tableutils.js index cfc03d379fa..693eaa1262f 100644 --- a/packages/ckeditor5-table/tests/tableutils.js +++ b/packages/ckeditor5-table/tests/tableutils.js @@ -275,6 +275,47 @@ describe( 'TableUtils', () => { ] ) ); } ); + it( 'should return created tableCell elements (insert in the middle)', () => { + setData( model, modelTable( [ + [ '11[]', '12' ], + [ '21', '22' ] + ] ) ); + + const insertedCells = tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 1 } ); + + expect( insertedCells.length ).to.equal( 2 ); + expect( root.getNodeByPath( [ 0, 1, 0 ] ) ).to.equal( insertedCells[ 0 ] ); + expect( root.getNodeByPath( [ 0, 1, 1 ] ) ).to.equal( insertedCells[ 1 ] ); + } ); + + it( 'should return created tableCell elements (insert in the the beginning)', () => { + setData( model, modelTable( [ + [ '11[]', '12' ], + [ '21', '22' ] + ] ) ); + + const insertedCells = tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { rows: 2 } ); + + expect( insertedCells.length ).to.equal( 4 ); + expect( root.getNodeByPath( [ 0, 1, 1 ] ) ).to.equal( insertedCells[ 0 ] ); + expect( root.getNodeByPath( [ 0, 1, 0 ] ) ).to.equal( insertedCells[ 1 ] ); + expect( root.getNodeByPath( [ 0, 0, 1 ] ) ).to.equal( insertedCells[ 2 ] ); + expect( root.getNodeByPath( [ 0, 0, 0 ] ) ).to.equal( insertedCells[ 3 ] ); + } ); + + it( 'should return created tableCell elements (insert in the the end)', () => { + setData( model, modelTable( [ + [ '11[]', '12' ], + [ '21', '22' ] + ] ) ); + + const insertedCells = tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 2 } ); + + expect( insertedCells.length ).to.equal( 2 ); + expect( root.getNodeByPath( [ 0, 2, 1 ] ) ).to.equal( insertedCells[ 0 ] ); + expect( root.getNodeByPath( [ 0, 2, 0 ] ) ).to.equal( insertedCells[ 1 ] ); + } ); + describe( 'with copyStructureFrom enabled', () => { beforeEach( () => { // +----+----+----+----+----+----+ @@ -597,6 +638,47 @@ describe( 'TableUtils', () => { [ '', '32' ] ], { headingColumns: 3 } ) ); } ); + + it( 'should return created tableCell elements (insert in the middle)', () => { + setData( model, modelTable( [ + [ '11[]', '12' ], + [ '21', '22' ] + ] ) ); + + const insertedCells = tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 1 } ); + + expect( insertedCells.length ).to.equal( 2 ); + expect( root.getNodeByPath( [ 0, 0, 1 ] ) ).to.equal( insertedCells[ 0 ] ); + expect( root.getNodeByPath( [ 0, 1, 1 ] ) ).to.equal( insertedCells[ 1 ] ); + } ); + + it( 'should return created tableCell elements (insert in the the beginning)', () => { + setData( model, modelTable( [ + [ '11[]', '12' ], + [ '21', '22' ] + ] ) ); + + const insertedCells = tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { columns: 2 } ); + + expect( insertedCells.length ).to.equal( 4 ); + expect( root.getNodeByPath( [ 0, 0, 1 ] ) ).to.equal( insertedCells[ 0 ] ); + expect( root.getNodeByPath( [ 0, 0, 0 ] ) ).to.equal( insertedCells[ 1 ] ); + expect( root.getNodeByPath( [ 0, 1, 1 ] ) ).to.equal( insertedCells[ 2 ] ); + expect( root.getNodeByPath( [ 0, 1, 0 ] ) ).to.equal( insertedCells[ 3 ] ); + } ); + + it( 'should return created tableCell elements (insert in the the end)', () => { + setData( model, modelTable( [ + [ '11[]', '12' ], + [ '21', '22' ] + ] ) ); + + const insertedCells = tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { columns: 1, at: 2 } ); + + expect( insertedCells.length ).to.equal( 2 ); + expect( root.getNodeByPath( [ 0, 0, 2 ] ) ).to.equal( insertedCells[ 0 ] ); + expect( root.getNodeByPath( [ 0, 1, 2 ] ) ).to.equal( insertedCells[ 1 ] ); + } ); } ); describe( 'splitCellVertically()', () => { @@ -1572,6 +1654,25 @@ describe( 'TableUtils', () => { } ); describe( 'createTable()', () => { + it( 'should be decorated', () => { + const spy = sinon.spy(); + + setData( model, '[]' ); + + tableUtils.on( 'createTable', spy ); + + model.change( writer => { + const table = tableUtils.createTable( writer, { + rows: 2, + columns: 2 + } ); + + model.insertContent( table ); + } ); + + expect( spy.calledOnce ).to.be.true; + } ); + it( 'should create table', () => { setData( model, '[]' );