Skip to content

Commit

Permalink
Table Block: Support rowspan attribute in table HTML, including when …
Browse files Browse the repository at this point in the history
…pasting (#46629)

* Table Block: Support rowspan attribute in table HTML, including when pasting

* Updated normalize function and add unit tests

* Move colspan test to integration tests

* Revert unit test for colspan

* Add unit test for rowspan

* Add integration test for rowspan

* Update integration test file name

* remove space

* Update cell text on unit test

* Normalize rowspan/colspan in transforms

* Fix unit test and utils

* fix unit test
  • Loading branch information
t-hamano authored Feb 1, 2023
1 parent 6d87e01 commit aa64669
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 56 deletions.
15 changes: 15 additions & 0 deletions packages/block-library/src/table/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
"type": "string",
"source": "attribute",
"attribute": "colspan"
},
"rowspan": {
"type": "string",
"source": "attribute",
"attribute": "rowspan"
}
}
}
Expand Down Expand Up @@ -92,6 +97,11 @@
"type": "string",
"source": "attribute",
"attribute": "colspan"
},
"rowspan": {
"type": "string",
"source": "attribute",
"attribute": "rowspan"
}
}
}
Expand Down Expand Up @@ -132,6 +142,11 @@
"type": "string",
"source": "attribute",
"attribute": "colspan"
},
"rowspan": {
"type": "string",
"source": "attribute",
"attribute": "rowspan"
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion packages/block-library/src/table/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,14 @@ function TableEdit( {
<tr key={ rowIndex }>
{ cells.map(
(
{ content, tag: CellTag, scope, align, colspan },
{
content,
tag: CellTag,
scope,
align,
colspan,
rowspan,
},
columnIndex
) => (
<RichText
Expand All @@ -418,6 +425,7 @@ function TableEdit( {
) }
scope={ CellTag === 'th' ? scope : undefined }
colSpan={ colspan }
rowSpan={ rowspan }
value={ content }
onChange={ onChange }
unstableOnFocus={ () => {
Expand Down
10 changes: 9 additions & 1 deletion packages/block-library/src/table/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,14 @@ export default function save( { attributes } ) {
<tr key={ rowIndex }>
{ cells.map(
(
{ content, tag, scope, align, colspan },
{
content,
tag,
scope,
align,
colspan,
rowspan,
},
cellIndex
) => {
const cellClasses = classnames( {
Expand All @@ -66,6 +73,7 @@ export default function save( { attributes } ) {
tag === 'th' ? scope : undefined
}
colSpan={ colspan }
rowSpan={ rowspan }
/>
);
}
Expand Down
20 changes: 20 additions & 0 deletions packages/block-library/src/table/test/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Internal dependencies
*/
import { normalizeRowColSpan } from '../utils';

describe( 'normalizeRowColSpan', () => {
it( 'should convert a value to a string', () => {
expect( normalizeRowColSpan( 2 ) ).toBe( '2' );
expect( normalizeRowColSpan( '2' ) ).toBe( '2' );
expect( normalizeRowColSpan( 2.55 ) ).toBe( '2' );
expect( normalizeRowColSpan( '2.55' ) ).toBe( '2' );
} );

it( 'should return undefined for values not allowed as the rowspan/colspan attributes', () => {
expect( normalizeRowColSpan( -2 ) ).toBe( undefined );
expect( normalizeRowColSpan( '-2' ) ).toBe( undefined );
expect( normalizeRowColSpan( 1 ) ).toBe( undefined );
expect( normalizeRowColSpan( '1' ) ).toBe( undefined );
} );
} );
67 changes: 65 additions & 2 deletions packages/block-library/src/table/transforms.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
/**
* WordPress dependencies
*/
import { createBlock } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import { normalizeRowColSpan } from './utils';

const tableContentPasteSchema = ( { phrasingContentSchema } ) => ( {
tr: {
allowEmpty: true,
children: {
th: {
allowEmpty: true,
children: phrasingContentSchema,
attributes: [ 'scope', 'colspan' ],
attributes: [ 'scope', 'colspan', 'rowspan' ],
},
td: {
allowEmpty: true,
children: phrasingContentSchema,
attributes: [ 'colspan' ],
attributes: [ 'colspan', 'rowspan' ],
},
},
},
Expand Down Expand Up @@ -41,6 +51,59 @@ const transforms = {
type: 'raw',
selector: 'table',
schema: tablePasteSchema,
transform: ( node ) => {
const attributes = Array.from( node.children ).reduce(
( sectionAcc, section ) => {
if ( ! section.children.length ) {
return sectionAcc;
}

const sectionName = section.nodeName
.toLowerCase()
.slice( 1 );

const sectionAttributes = Array.from(
section.children
).reduce( ( rowAcc, row ) => {
if ( ! row.children.length ) {
return rowAcc;
}

const rowAttributes = Array.from(
row.children
).reduce( ( colAcc, col ) => {
const rowspan = normalizeRowColSpan(
col.getAttribute( 'rowspan' )
);
const colspan = normalizeRowColSpan(
col.getAttribute( 'colspan' )
);

colAcc.push( {
tag: col.nodeName.toLowerCase(),
content: col.innerHTML,
rowspan,
colspan,
} );

return colAcc;
}, [] );

rowAcc.push( {
cells: rowAttributes,
} );

return rowAcc;
}, [] );

sectionAcc[ sectionName ] = sectionAttributes;
return sectionAcc;
},
{}
);

return createBlock( 'core/table', attributes );
},
},
],
};
Expand Down
18 changes: 18 additions & 0 deletions packages/block-library/src/table/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Normalize the rowspan/colspan value.
* Returns undefined if the parameter is not a positive number
* or the default value (1) for rowspan/colspan.
*
* @param {number|undefined} rowColSpan rowspan/colspan value.
*
* @return {string|undefined} normalized rowspan/colspan value.
*/
export function normalizeRowColSpan( rowColSpan ) {
const parsedValue = parseInt( rowColSpan, 10 );
if ( ! Number.isInteger( parsedValue ) ) {
return undefined;
}
return parsedValue < 0 || parsedValue === 1
? undefined
: parsedValue.toString();
}
131 changes: 79 additions & 52 deletions packages/blocks/src/api/raw-handling/test/paste-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,55 @@ import { init as initAndRegisterTableBlock } from '../../../../../block-library/
const tableWithHeaderFooterAndBodyUsingColspan = `
<table>
<thead>
<tr>
<th colspan="2">Colspan 2</th>
<th>Header Cell</th>
</tr>
</thead>
<tfoot>
<tr>
<th colspan="2">Footer Cell</th>
<th>Footer Cell</th>
</tr>
</tfoot>
<tbody>
<tr>
<td colspan="2">Colspan 2</td>
<td>Cell Data</td>
</tr>
</tbody>
<tr>
<th colspan="2">Colspan 2</th>
<th>Header Cell</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2">Colspan 2</td>
<td>Cell Data</td>
</tr>
</tbody>
<tfoot>
<tr>
<th colspan="2">Colspan 2</th>
<th>Footer Cell</th>
</tr>
</tfoot>
</table>`;

const googleDocsTableWithColspan = `
<meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-b0f68bdd-7fff-a054-94d1-43c2fdedca2a">
<div dir="ltr" style="margin-left:0pt;" align="left">
<table style="border:none;border-collapse:collapse;">
<colgroup>
<col width="185"/>
<col width="439"/>
</colgroup>
<tbody>
<tr style="height:21pt">
<td colspan="2"
style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;">
<p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span
style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Test colspan</span>
</p></td>
</tr>
</tbody>
</table>
</div>
<br/><br/></b>
`;
const tableWithHeaderFooterAndBodyUsingRowspan = `
<table>
<thead>
<tr>
<th rowspan="2">Rowspan 2</th>
<th>Header Cell</th>
</tr>
<tr>
<th>Header Cell</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="2">Rowspan 2</td>
<td>Cell Data</td>
</tr>
<tr>
<td>Cell Data</td>
</tr>
</tbody>
<tfoot>
<tr>
<td rowspan="2">Rowspan 2</td>
<td>Footer Cell</td>
</tr>
<tr>
<td>Footer Cell</td>
</tr>
</tfoot>
</table>`;

describe( 'pasteHandler', () => {
beforeAll( () => {
Expand Down Expand Up @@ -87,7 +96,7 @@ describe( 'pasteHandler', () => {
foot: [
{
cells: [
{ content: 'Footer Cell', tag: 'th', colspan: '2' },
{ content: 'Colspan 2', tag: 'th', colspan: '2' },
{ content: 'Footer Cell', tag: 'th' },
],
},
Expand All @@ -97,33 +106,51 @@ describe( 'pasteHandler', () => {
expect( result.isValid ).toBeTruthy();
} );

it( 'can handle a google docs table with colspan', () => {
it( 'can handle a table with thead, tbody and tfoot using rowspan', () => {
const [ result ] = pasteHandler( {
HTML: googleDocsTableWithColspan,
HTML: tableWithHeaderFooterAndBodyUsingRowspan,
tagName: 'p',
preserveWhiteSpace: false,
} );

expect( console ).toHaveLogged();

expect( result.attributes ).toEqual( {
hasFixedLayout: false,
caption: '',
head: [
{
cells: [
{ content: 'Rowspan 2', tag: 'th', rowspan: '2' },
{ content: 'Header Cell', tag: 'th' },
],
},
{
cells: [ { content: 'Header Cell', tag: 'th' } ],
},
],
body: [
{
cells: [
{
align: undefined,
colspan: '2',
content: 'Test colspan',
scope: undefined,
tag: 'td',
},
{ content: 'Rowspan 2', tag: 'td', rowspan: '2' },
{ content: 'Cell Data', tag: 'td' },
],
},
{
cells: [ { content: 'Cell Data', tag: 'td' } ],
},
],
foot: [
{
cells: [
{ content: 'Rowspan 2', tag: 'td', rowspan: '2' },
{ content: 'Footer Cell', tag: 'td' },
],
},
{
cells: [ { content: 'Footer Cell', tag: 'td' } ],
},
],
caption: '',
foot: [],
hasFixedLayout: false,
head: [],
} );
expect( result.name ).toEqual( 'core/table' );
expect( result.isValid ).toBeTruthy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ exports[`Blocks raw handling pasteHandler google-docs-list-only 1`] = `"My first
exports[`Blocks raw handling pasteHandler google-docs-table 1`] = `"<br><br><br><br>One<br>Two<br>Three<br>1<br>2<br>3<br>I<br>II<br>III"`;
exports[`Blocks raw handling pasteHandler google-docs-table-with-colspan 1`] = `"<br><br>Test colspan<br><br>"`;
exports[`Blocks raw handling pasteHandler google-docs-table-with-rowspan 1`] = `"<br><br>Test rowspan<br><br>"`;
exports[`Blocks raw handling pasteHandler google-docs-table-with-comments 1`] = `"<br><br><br><br>One<br>Two<br>Three<br>1<br>2<br>3<br>I<br>II<br>III"`;
exports[`Blocks raw handling pasteHandler google-docs-with-comments 1`] = `"This is a <strong>title</strong><br><br>This is a <em>heading</em><br><br>Formatting test: <strong>bold</strong>, <em>italic</em>, <a href=\\"https://w.org/\\">link</a>, <s>strikethrough</s>, <sup>superscript</sup>, <sub>subscript</sub>, <strong><em>nested</em></strong>.<br><br>A<br>Bulleted<br>Indented<br>List<br><br>One<br>Two<br>Three<br><br><br><br><br>One<br>Two<br>Three<br>1<br>2<br>3<br>I<br>II<br>III<br><br><br><br><br><br>An image:<br><br><img src=\\"https://lh4.googleusercontent.com/ID\\" width=\\"544\\" height=\\"184\\"><br><br>"`;
Expand Down
Loading

1 comment on commit aa64669

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in aa64669.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/4063026587
📝 Reported issues:

Please sign in to comment.