|
@@ -9,6 +9,7 @@
|
|
|
|
|
|
|
|
import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
|
|
import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
|
|
|
import ViewRange from '@ckeditor/ckeditor5-engine/src/view/range';
|
|
import ViewRange from '@ckeditor/ckeditor5-engine/src/view/range';
|
|
|
|
|
+import CellSpans from '../cellspans';
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Model table element to view table element conversion helper.
|
|
* Model table element to view table element conversion helper.
|
|
@@ -36,7 +37,7 @@ export default function downcastTable() {
|
|
|
const headingRows = getNumericAttribute( table, 'headingRows', 0 );
|
|
const headingRows = getNumericAttribute( table, 'headingRows', 0 );
|
|
|
const tableRows = Array.from( table.getChildren() );
|
|
const tableRows = Array.from( table.getChildren() );
|
|
|
|
|
|
|
|
- const cellSpans = createPreviousCellSpans( table, 0 );
|
|
|
|
|
|
|
+ const cellSpans = getCellSpansWithPreviousFilled( table, 0 );
|
|
|
|
|
|
|
|
for ( const tableRow of tableRows ) {
|
|
for ( const tableRow of tableRows ) {
|
|
|
const rowIndex = tableRows.indexOf( tableRow );
|
|
const rowIndex = tableRows.indexOf( tableRow );
|
|
@@ -84,7 +85,7 @@ export function downcastInsertRow() {
|
|
|
const tableSection = Array.from( tableElement.getChildren() )
|
|
const tableSection = Array.from( tableElement.getChildren() )
|
|
|
.filter( child => child.name === ( isHeadingRow ? 'thead' : 'tbody' ) )[ 0 ];
|
|
.filter( child => child.name === ( isHeadingRow ? 'thead' : 'tbody' ) )[ 0 ];
|
|
|
|
|
|
|
|
- const cellSpans = createPreviousCellSpans( table, rowIndex );
|
|
|
|
|
|
|
+ const cellSpans = getCellSpansWithPreviousFilled( table, rowIndex );
|
|
|
|
|
|
|
|
downcastTableRow( tableRow, rowIndex, tableSection, cellSpans, conversionApi );
|
|
downcastTableRow( tableRow, rowIndex, tableSection, cellSpans, conversionApi );
|
|
|
}, { priority: 'normal' } );
|
|
}, { priority: 'normal' } );
|
|
@@ -119,13 +120,13 @@ export function downcastInsertCell() {
|
|
|
|
|
|
|
|
let columnIndex = 0;
|
|
let columnIndex = 0;
|
|
|
|
|
|
|
|
- const cellSpans = createPreviousCellSpans( table, rowIndex, columnIndex );
|
|
|
|
|
|
|
+ const cellSpans = getCellSpansWithPreviousFilled( table, rowIndex, columnIndex );
|
|
|
|
|
|
|
|
// check last row up to
|
|
// check last row up to
|
|
|
columnIndex = getColumnIndex( tableRow, columnIndex, cellSpans, rowIndex, tableCell );
|
|
columnIndex = getColumnIndex( tableRow, columnIndex, cellSpans, rowIndex, tableCell );
|
|
|
|
|
|
|
|
// Check whether current columnIndex is overlapped by table cells from previous rows.
|
|
// Check whether current columnIndex is overlapped by table cells from previous rows.
|
|
|
- columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
|
|
|
|
|
|
|
+ columnIndex = cellSpans.getAdjustedColumnIndex( rowIndex, columnIndex );
|
|
|
|
|
|
|
|
const cellElementName = getCellElementName( rowIndex, columnIndex, headingRows, headingColumns );
|
|
const cellElementName = getCellElementName( rowIndex, columnIndex, headingRows, headingColumns );
|
|
|
|
|
|
|
@@ -157,7 +158,7 @@ export function downcastAttributeChange( attribute ) {
|
|
|
|
|
|
|
|
const tableRows = Array.from( table.getChildren() );
|
|
const tableRows = Array.from( table.getChildren() );
|
|
|
|
|
|
|
|
- const cellSpans = createPreviousCellSpans( table, 0 );
|
|
|
|
|
|
|
+ const cellSpans = getCellSpansWithPreviousFilled( table, 0 );
|
|
|
|
|
|
|
|
const cachedTableSections = {};
|
|
const cachedTableSections = {};
|
|
|
|
|
|
|
@@ -194,7 +195,7 @@ export function downcastAttributeChange( attribute ) {
|
|
|
|
|
|
|
|
for ( const tableCell of Array.from( tableRow.getChildren() ) ) {
|
|
for ( const tableCell of Array.from( tableRow.getChildren() ) ) {
|
|
|
// Check whether current columnIndex is overlapped by table cells from previous rows.
|
|
// Check whether current columnIndex is overlapped by table cells from previous rows.
|
|
|
- columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
|
|
|
|
|
|
|
+ columnIndex = cellSpans.getAdjustedColumnIndex( rowIndex, columnIndex );
|
|
|
|
|
|
|
|
const cellElementName = getCellElementName( rowIndex, columnIndex, headingRows, headingColumns );
|
|
const cellElementName = getCellElementName( rowIndex, columnIndex, headingRows, headingColumns );
|
|
|
|
|
|
|
@@ -226,7 +227,7 @@ export function downcastAttributeChange( attribute ) {
|
|
|
//
|
|
//
|
|
|
// @param {module:engine/model/element~Element} tableRow
|
|
// @param {module:engine/model/element~Element} tableRow
|
|
|
// @param {Number} rowIndex
|
|
// @param {Number} rowIndex
|
|
|
-// @param {CellSpans} cellSpans
|
|
|
|
|
|
|
+// @param {module:table/cellspans~CellSpans} cellSpans
|
|
|
// @param {module:engine/view/containerelement~ContainerElement} tableSection
|
|
// @param {module:engine/view/containerelement~ContainerElement} tableSection
|
|
|
function downcastTableCell( tableCell, rowIndex, columnIndex, cellSpans, cellElementName, trElement, conversionApi, offset = 'end' ) {
|
|
function downcastTableCell( tableCell, rowIndex, columnIndex, cellSpans, cellElementName, trElement, conversionApi, offset = 'end' ) {
|
|
|
const colspan = getNumericAttribute( tableCell, 'colspan', 1 );
|
|
const colspan = getNumericAttribute( tableCell, 'colspan', 1 );
|
|
@@ -272,7 +273,7 @@ function downcastTableRow( tableRow, rowIndex, tableSection, cellSpans, conversi
|
|
|
|
|
|
|
|
for ( const tableCell of Array.from( tableRow.getChildren() ) ) {
|
|
for ( const tableCell of Array.from( tableRow.getChildren() ) ) {
|
|
|
// Check whether current columnIndex is overlapped by table cells from previous rows.
|
|
// Check whether current columnIndex is overlapped by table cells from previous rows.
|
|
|
- columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
|
|
|
|
|
|
|
+ columnIndex = cellSpans.getAdjustedColumnIndex( rowIndex, columnIndex );
|
|
|
|
|
|
|
|
const cellElementName = getCellElementName( rowIndex, columnIndex, headingRows, headingColumns );
|
|
const cellElementName = getCellElementName( rowIndex, columnIndex, headingRows, headingColumns );
|
|
|
|
|
|
|
@@ -376,7 +377,7 @@ export function getNumericAttribute( element, attribute, defaultValue ) {
|
|
|
function getColumnIndex( tableRow, columnIndex, cellSpans, rowIndex, tableCell ) {
|
|
function getColumnIndex( tableRow, columnIndex, cellSpans, rowIndex, tableCell ) {
|
|
|
for ( const tableCellA of Array.from( tableRow.getChildren() ) ) {
|
|
for ( const tableCellA of Array.from( tableRow.getChildren() ) ) {
|
|
|
// Check whether current columnIndex is overlapped by table cells from previous rows.
|
|
// Check whether current columnIndex is overlapped by table cells from previous rows.
|
|
|
- columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
|
|
|
|
|
|
|
+ columnIndex = cellSpans.getAdjustedColumnIndex( rowIndex, columnIndex );
|
|
|
|
|
|
|
|
// Up to here only!
|
|
// Up to here only!
|
|
|
if ( tableCellA === tableCell ) {
|
|
if ( tableCellA === tableCell ) {
|
|
@@ -393,7 +394,12 @@ function getColumnIndex( tableRow, columnIndex, cellSpans, rowIndex, tableCell )
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function createPreviousCellSpans( table, currentRowIndex ) {
|
|
|
|
|
|
|
+// Helper method for creating a pre-filled cell span instance. Useful when converting part of a table.
|
|
|
|
|
+//
|
|
|
|
|
+// @param {module:engine/model/element~Element} table Model table instance.
|
|
|
|
|
+// @param currentRowIndex Current row index - all table rows up to this index will be analyzed (excluding provided row index).
|
|
|
|
|
+// @returns {module:table/cellspans~CellSpans}
|
|
|
|
|
+function getCellSpansWithPreviousFilled( table, currentRowIndex ) {
|
|
|
const cellSpans = new CellSpans();
|
|
const cellSpans = new CellSpans();
|
|
|
|
|
|
|
|
for ( let rowIndex = 0; rowIndex <= currentRowIndex; rowIndex++ ) {
|
|
for ( let rowIndex = 0; rowIndex <= currentRowIndex; rowIndex++ ) {
|
|
@@ -401,8 +407,9 @@ function createPreviousCellSpans( table, currentRowIndex ) {
|
|
|
|
|
|
|
|
let columnIndex = 0;
|
|
let columnIndex = 0;
|
|
|
|
|
|
|
|
|
|
+ // TODO: make this an iterator?
|
|
|
for ( const tableCell of Array.from( row.getChildren() ) ) {
|
|
for ( const tableCell of Array.from( row.getChildren() ) ) {
|
|
|
- columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
|
|
|
|
|
|
|
+ columnIndex = cellSpans.getAdjustedColumnIndex( rowIndex, columnIndex );
|
|
|
|
|
|
|
|
const colspan = getNumericAttribute( tableCell, 'colspan', 1 );
|
|
const colspan = getNumericAttribute( tableCell, 'colspan', 1 );
|
|
|
const rowspan = getNumericAttribute( tableCell, 'rowspan', 1 );
|
|
const rowspan = getNumericAttribute( tableCell, 'rowspan', 1 );
|
|
@@ -416,120 +423,3 @@ function createPreviousCellSpans( table, currentRowIndex ) {
|
|
|
|
|
|
|
|
return cellSpans;
|
|
return cellSpans;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Holds information about spanned table cells.
|
|
|
|
|
- *
|
|
|
|
|
- * @private
|
|
|
|
|
- */
|
|
|
|
|
-export class CellSpans {
|
|
|
|
|
- /**
|
|
|
|
|
- * Creates CellSpans instance.
|
|
|
|
|
- */
|
|
|
|
|
- constructor() {
|
|
|
|
|
- /**
|
|
|
|
|
- * Holds table cell spans mapping.
|
|
|
|
|
- *
|
|
|
|
|
- * @type {Map<Number, Number>}
|
|
|
|
|
- * @private
|
|
|
|
|
- */
|
|
|
|
|
- this._spans = new Map();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Returns proper column index if a current cell index is overlapped by other (has a span defined).
|
|
|
|
|
- *
|
|
|
|
|
- * @param {Number} row
|
|
|
|
|
- * @param {Number} column
|
|
|
|
|
- * @return {Number} Returns current column or updated column index.
|
|
|
|
|
- */
|
|
|
|
|
- getNextFreeColumnIndex( row, column ) {
|
|
|
|
|
- let span = this._check( row, column ) || 0;
|
|
|
|
|
-
|
|
|
|
|
- // Offset current table cell columnIndex by spanning cells from rows above.
|
|
|
|
|
- while ( span ) {
|
|
|
|
|
- column += span;
|
|
|
|
|
- span = this._check( row, column );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return column;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Updates spans based on current table cell height & width. Spans with height <= 1 will not be recorded.
|
|
|
|
|
- *
|
|
|
|
|
- * For instance if a table cell at row 0 and column 0 has height of 3 and width of 2 we're setting spans:
|
|
|
|
|
- *
|
|
|
|
|
- * 0 1 2 3 4 5
|
|
|
|
|
- * 0:
|
|
|
|
|
- * 1: 2
|
|
|
|
|
- * 2: 2
|
|
|
|
|
- * 3:
|
|
|
|
|
- *
|
|
|
|
|
- * Adding another spans for a table cell at row 2 and column 1 that has height of 2 and width of 4 will update above to:
|
|
|
|
|
- *
|
|
|
|
|
- * 0 1 2 3 4 5
|
|
|
|
|
- * 0:
|
|
|
|
|
- * 1: 2
|
|
|
|
|
- * 2: 2
|
|
|
|
|
- * 3: 4
|
|
|
|
|
- *
|
|
|
|
|
- * The above span mapping was calculated from a table below (cells 03 & 12 were not added as their height is 1):
|
|
|
|
|
- *
|
|
|
|
|
- * +----+----+----+----+----+----+
|
|
|
|
|
- * | 00 | 02 | 03 | 05 |
|
|
|
|
|
- * | +--- +----+----+----+
|
|
|
|
|
- * | | 12 | 24 | 25 |
|
|
|
|
|
- * | +----+----+----+----+
|
|
|
|
|
- * | | 22 |
|
|
|
|
|
- * |----+----+ +
|
|
|
|
|
- * | 31 | 32 | |
|
|
|
|
|
- * +----+----+----+----+----+----+
|
|
|
|
|
- *
|
|
|
|
|
- * @param {Number} rowIndex
|
|
|
|
|
- * @param {Number} columnIndex
|
|
|
|
|
- * @param {Number} height
|
|
|
|
|
- * @param {Number} width
|
|
|
|
|
- */
|
|
|
|
|
- recordSpans( rowIndex, columnIndex, height, width ) {
|
|
|
|
|
- // This will update all rows below up to row height with value of span width.
|
|
|
|
|
- for ( let rowToUpdate = rowIndex + 1; rowToUpdate < rowIndex + height; rowToUpdate++ ) {
|
|
|
|
|
- if ( !this._spans.has( rowToUpdate ) ) {
|
|
|
|
|
- this._spans.set( rowToUpdate, new Map() );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const rowSpans = this._spans.get( rowToUpdate );
|
|
|
|
|
-
|
|
|
|
|
- rowSpans.set( columnIndex, width );
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Removes row from mapping.
|
|
|
|
|
- *
|
|
|
|
|
- * @param {Number} rowIndex
|
|
|
|
|
- */
|
|
|
|
|
- drop( rowIndex ) {
|
|
|
|
|
- if ( this._spans.has( rowIndex ) ) {
|
|
|
|
|
- this._spans.delete( rowIndex );
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Checks if given table cell is spanned by other.
|
|
|
|
|
- *
|
|
|
|
|
- * @param {Number} rowIndex
|
|
|
|
|
- * @param {Number} columnIndex
|
|
|
|
|
- * @return {Boolean|Number} Returns false or width of a span.
|
|
|
|
|
- * @private
|
|
|
|
|
- */
|
|
|
|
|
- _check( rowIndex, columnIndex ) {
|
|
|
|
|
- if ( !this._spans.has( rowIndex ) ) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const rowSpans = this._spans.get( rowIndex );
|
|
|
|
|
-
|
|
|
|
|
- return rowSpans.has( columnIndex ) ? rowSpans.get( columnIndex ) : false;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|