Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rich text: copy tag name on internal paste #48254

Merged
merged 10 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
pasteHandler,
findTransform,
getBlockTransforms,
htmlToBlocks,
hasBlockSupport,
getBlockContent,
} from '@wordpress/blocks';
import {
isEmpty,
Expand Down Expand Up @@ -44,6 +47,15 @@ function adjustLines( value, isMultiline ) {
return replace( value, new RegExp( LINE_SEPARATOR, 'g' ), '\n' );
}

function maybeInline( blocks, mode ) {
if ( mode !== 'AUTO' ) return blocks;
if ( blocks.length !== 1 ) return blocks;
const [ block ] = blocks;
if ( ! hasBlockSupport( block.name, '__unstablePasteTextInline', false ) )
return blocks;
return getBlockContent( block );
}

export function usePasteHandler( props ) {
const propsRef = useRef( props );
propsRef.current = props;
Expand Down Expand Up @@ -129,29 +141,6 @@ export function usePasteHandler( props ) {
}

const files = [ ...getFilesFromDataTransfer( clipboardData ) ];
const isInternal = clipboardData.getData( 'rich-text' ) === 'true';

// If the data comes from a rich text instance, we can directly use it
// without filtering the data. The filters are only meant for externally
// pasted content and remove inline styles.
if ( isInternal ) {
const pastedMultilineTag =
clipboardData.getData( 'rich-text-multi-line-tag' ) ||
undefined;
let pastedValue = create( {
html,
multilineTag: pastedMultilineTag,
multilineWrapperTags:
pastedMultilineTag === 'li'
? [ 'ul', 'ol' ]
: undefined,
preserveWhiteSpace,
} );
pastedValue = adjustLines( pastedValue, !! multilineTag );
addActiveFormats( pastedValue, value.activeFormats );
onChange( insert( value, pastedValue ) );
return;
}

if ( pastePlainText ) {
onChange( insert( value, create( { text: plainText } ) ) );
Expand Down Expand Up @@ -232,13 +221,19 @@ export function usePasteHandler( props ) {
mode = 'BLOCKS';
}

const content = pasteHandler( {
HTML: html,
plainText,
mode,
tagName,
preserveWhiteSpace,
} );
// If the data comes from a rich text instance, we can directly use it
// without filtering the data. The filters are only meant for externally
// pasted content and remove inline styles.
const isInternal = !! clipboardData.getData( 'rich-text' );
const content = isInternal
? maybeInline( htmlToBlocks( html ), mode )
: pasteHandler( {
HTML: html,
plainText,
mode,
tagName,
preserveWhiteSpace,
} );

if ( typeof content === 'string' ) {
let valueToInsert = create( { html: content } );
Expand Down
13 changes: 13 additions & 0 deletions packages/blocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,19 @@ _Returns_

- `boolean`: True if a block contains at least one child blocks with inserter support and false otherwise.

### htmlToBlocks

Converts HTML directly to blocks. Looks for a matching transform for each top-level tag. The HTML should be filtered to not have any text between top-level tags and formatted in a way that blocks can handle the HTML.

_Parameters_

- _html_ `string`: HTML to convert.
- _handler_ `Function`: The handler calling htmlToBlocks: either rawHandler or pasteHandler.

_Returns_

- `Array`: An array of blocks.

### isReusableBlock

Determines whether or not the given block is a reusable block. This is a special block type that is used to point to a global block stored via the API.
Expand Down
1 change: 1 addition & 0 deletions packages/blocks/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export {
export {
pasteHandler,
rawHandler,
htmlToBlocks,
deprecatedGetPhrasingContentSchema as getPhrasingContentSchema,
} from './raw-handling';

Expand Down
1 change: 1 addition & 0 deletions packages/blocks/src/api/raw-handling/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import shortcodeConverter from './shortcode-converter';
import { deepFilterHTML, getBlockContentSchema } from './utils';

export { pasteHandler } from './paste-handler';
export { htmlToBlocks } from './html-to-blocks';

export function deprecatedGetPhrasingContentSchema( context ) {
deprecated( 'wp.blocks.getPhrasingContentSchema', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ exports[`RichText should apply multiple formats when selection is collapsed 1`]
<!-- /wp:paragraph -->"
`;

exports[`RichText should copy/paste heading 1`] = `
"<!-- wp:heading -->
<h2 class=\\"wp-block-heading\\">Heading</h2>
<!-- /wp:heading -->

<!-- wp:heading -->
<h2 class=\\"wp-block-heading\\">Heading</h2>
<!-- /wp:heading -->"
`;

exports[`RichText should handle Home and End keys 1`] = `
"<!-- wp:paragraph -->
<p>-<strong>12</strong>+</p>
Expand Down
11 changes: 11 additions & 0 deletions packages/e2e-tests/specs/editor/various/rich-text.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@
await page.keyboard.press( 'ArrowLeft' );
await pressKeyWithModifier( 'primary', 'v' );

expect( await getEditedPostContent() ).toMatchSnapshot();

Check failure on line 350 in packages/e2e-tests/specs/editor/various/rich-text.test.js

View workflow job for this annotation

GitHub Actions / Puppeteer - 2

Error: expect(received).toMatchSnapshot() Snapshot name: `RichText should not split rich text on inline paste 1` - Snapshot - 1 + Received + 1 <!-- wp:paragraph --> - <p>123</p> + <p>1<p>2</p>3</p> <!-- /wp:paragraph --> at Object.toMatchSnapshot (/home/runner/work/gutenberg/gutenberg/packages/e2e-tests/specs/editor/various/rich-text.test.js:350:42) at runMicrotasks (<anonymous>) at processTicksAndRejections (internal/process/task_queues.js:95:5)
} );

it( 'should not split rich text on inline paste with formatting', async () => {
Expand All @@ -363,7 +363,7 @@
await page.keyboard.press( 'ArrowLeft' );
await pressKeyWithModifier( 'primary', 'v' );

expect( await getEditedPostContent() ).toMatchSnapshot();

Check failure on line 366 in packages/e2e-tests/specs/editor/various/rich-text.test.js

View workflow job for this annotation

GitHub Actions / Puppeteer - 2

Error: expect(received).toMatchSnapshot() Snapshot name: `RichText should not split rich text on inline paste with formatting 1` - Snapshot - 1 + Received + 1 <!-- wp:paragraph --> - <p>a1<strong>2</strong>3b</p> + <p>a<p>1<strong>2</strong>3</p>b</p> <!-- /wp:paragraph --> at Object.toMatchSnapshot (/home/runner/work/gutenberg/gutenberg/packages/e2e-tests/specs/editor/various/rich-text.test.js:366:42) at runMicrotasks (<anonymous>) at processTicksAndRejections (internal/process/task_queues.js:95:5)
} );

it( 'should make bold after split and merge', async () => {
Expand Down Expand Up @@ -391,7 +391,7 @@
await page.keyboard.press( 'ArrowLeft' );
await pressKeyWithModifier( 'primary', 'v' );

expect( await getEditedPostContent() ).toMatchSnapshot();

Check failure on line 394 in packages/e2e-tests/specs/editor/various/rich-text.test.js

View workflow job for this annotation

GitHub Actions / Puppeteer - 2

Error: expect(received).toMatchSnapshot() Snapshot name: `RichText should apply active formatting for inline paste 1` - Snapshot - 1 + Received + 1 <!-- wp:paragraph --> - <p><strong>132</strong>3</p> + <p><strong>1<p>3</p>2</strong>3</p> <!-- /wp:paragraph --> at Object.toMatchSnapshot (/home/runner/work/gutenberg/gutenberg/packages/e2e-tests/specs/editor/various/rich-text.test.js:394:42) at runMicrotasks (<anonymous>) at processTicksAndRejections (internal/process/task_queues.js:95:5)
} );

it( 'should preserve internal formatting', async () => {
Expand Down Expand Up @@ -442,7 +442,7 @@
// Paste the colored text.
await pressKeyWithModifier( 'primary', 'v' );

expect( await getEditedPostContent() ).toMatchSnapshot();

Check failure on line 445 in packages/e2e-tests/specs/editor/various/rich-text.test.js

View workflow job for this annotation

GitHub Actions / Puppeteer - 2

Error: expect(received).toMatchSnapshot() Snapshot name: `RichText should preserve internal formatting 2` - Snapshot - 1 + Received + 1 "<!-- wp:paragraph --> <p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-cyan-bluish-gray-color">1</mark></p> <!-- /wp:paragraph --> <!-- wp:paragraph --> - <p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-cyan-bluish-gray-color">1</mark></p> + <p><p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-cyan-bluish-gray-color">1</mark></p></p> <!-- /wp:paragraph -->" at Object.toMatchSnapshot (/home/runner/work/gutenberg/gutenberg/packages/e2e-tests/specs/editor/various/rich-text.test.js:445:42) at runMicrotasks (<anonymous>) at processTicksAndRejections (internal/process/task_queues.js:95:5)
} );

it( 'should paste paragraph contents into list', async () => {
Expand All @@ -467,7 +467,7 @@
// Paste paragraph contents.
await pressKeyWithModifier( 'primary', 'v' );

expect( await getEditedPostContent() ).toMatchSnapshot();

Check failure on line 470 in packages/e2e-tests/specs/editor/various/rich-text.test.js

View workflow job for this annotation

GitHub Actions / Puppeteer - 2

Error: expect(received).toMatchSnapshot() Snapshot name: `RichText should paste paragraph contents into list 1` - Snapshot - 1 + Received + 1 @@ -2,8 +2,8 @@ <p>1<br>2</p> <!-- /wp:paragraph --> <!-- wp:list --> <ul><!-- wp:list-item --> - <li>1<br>2</li> + <li><p>1<br>2</p></li> <!-- /wp:list-item --></ul> <!-- /wp:list --> at Object.toMatchSnapshot (/home/runner/work/gutenberg/gutenberg/packages/e2e-tests/specs/editor/various/rich-text.test.js:470:42) at runMicrotasks (<anonymous>) at processTicksAndRejections (internal/process/task_queues.js:95:5)
} );

it( 'should paste list contents into paragraph', async () => {
Expand Down Expand Up @@ -556,4 +556,15 @@
// Expect: <strong>1</strong>-<em>2</em>
expect( await getEditedPostContent() ).toMatchSnapshot();
} );

test( 'should copy/paste heading', async () => {
await insertBlock( 'Heading' );
await page.keyboard.type( 'Heading' );
await pressKeyWithModifier( 'primary', 'a' );
await pressKeyWithModifier( 'primary', 'c' );
await page.keyboard.press( 'ArrowRight' );
await page.keyboard.press( 'Enter' );
await pressKeyWithModifier( 'primary', 'v' );
expect( await getEditedPostContent() ).toMatchSnapshot();

Check failure on line 568 in packages/e2e-tests/specs/editor/various/rich-text.test.js

View workflow job for this annotation

GitHub Actions / Puppeteer - 2

Error: expect(received).toMatchSnapshot() Snapshot name: `RichText should copy/paste heading 1` - Snapshot - 4 + Received + 4 "<!-- wp:heading --> - <h2 class=\"wp-block-heading\">Heading</h2> + <h2 class="wp-block-heading">Heading</h2> <!-- /wp:heading --> - <!-- wp:heading --> + <!-- wp:paragraph --> - <h2 class=\"wp-block-heading\">Heading</h2> + <p><h2 class="wp-block-heading">Heading</h2></p> - <!-- /wp:heading -->" + <!-- /wp:paragraph -->" at Object.toMatchSnapshot (/home/runner/work/gutenberg/gutenberg/packages/e2e-tests/specs/editor/various/rich-text.test.js:568:42) at runMicrotasks (<anonymous>) at processTicksAndRejections (internal/process/task_queues.js:95:5)
} );
} );
8 changes: 4 additions & 4 deletions packages/rich-text/src/component/use-copy-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@ export function useCopyHandler( props ) {

const selectedRecord = slice( record.current );
const plainText = getTextContent( selectedRecord );
const tagName = element.tagName.toLowerCase();
const html = toHTMLString( {
value: selectedRecord,
multilineTag,
preserveWhiteSpace,
} );
event.clipboardData.setData( 'text/plain', plainText );
event.clipboardData.setData( 'text/html', html );
event.clipboardData.setData( 'rich-text', 'true' );
event.clipboardData.setData(
'rich-text-multi-line-tag',
multilineTag || ''
'text/html',
`<${ tagName }>${ html }</${ tagName }>`
);
event.clipboardData.setData( 'rich-text', 'true' );
event.preventDefault();

if ( event.type === 'cut' ) {
Expand Down
Loading