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

Make Link UI inactive if selection extends beyond format bounds or encounters new link #35946

Merged
merged 5 commits into from
Oct 26, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
38 changes: 36 additions & 2 deletions packages/block-editor/src/components/rich-text/format-edit.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
/**
* WordPress dependencies
*/
import { getActiveFormat, getActiveObject } from '@wordpress/rich-text';
import {
getActiveFormat,
getActiveObject,
isCollapsed,
} from '@wordpress/rich-text';
/**
* External dependencies
*/
import { find } from 'lodash';

export default function FormatEdit( {
formatTypes,
Expand All @@ -18,11 +26,37 @@ export default function FormatEdit( {
}

const activeFormat = getActiveFormat( value, name );
const isActive = activeFormat !== undefined;
let isActive = activeFormat !== undefined;
const activeObject = getActiveObject( value );
const isObjectActive =
activeObject !== undefined && activeObject.type === name;

// Edge case: un-collapsed link formats.
// If there is a missing link format at either end of the selection
// then we shouldn't show the Edit UI because the selection has exceeded
// the bounds of the link format.
// Also if the format objects don't match then we're dealing with two separate
// links so we should not allow the link to be modified over the top.
if ( name === 'core/link' && ! isCollapsed( value ) ) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No need to handle collapsed selections because for these start and end will be identical and thus there is no "selection".

Copy link
Member

Choose a reason for hiding this comment

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

Could we remove the hard coding of a format name here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. Are you suggesting to replace with a reference instead? Or were you suggesting something else?

Copy link
Member

Choose a reason for hiding this comment

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

Why has this been merged while it's hardcoding link logic in the rich text component?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apologies. That must have been an oversight despite your comment here. Both Andrei and I must have missed it in the Github UI even though I responded 🤦

My question still stands though. How can we work around this?

Copy link
Member

Choose a reason for hiding this comment

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

Not sure I understand the question 😅
In any case, I've made a PR here: #48789

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure I understand the question 😅

Neither do I nearly a year down the line 😄 I think it was "do you have any thoughts on how we could proceed"...and I'm not surprised by the fact that you did (do!).

Thank you for following up here.

const formats = value.formats;

const linkFormatAtStart = find( formats[ value.start ], {
type: 'core/link',
} );

const linkFormatAtEnd = find( formats[ value.end - 1 ], {
type: 'core/link',
} );

if (
! linkFormatAtStart ||
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If no format here then we've exceeded the leftmost side of the link boundary.

! linkFormatAtEnd ||
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If no format here then we've exceeded the rightmost side of the link boundary.

linkFormatAtStart !== linkFormatAtEnd
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If they do not display referential equality then we're dealing with two separate links.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does this mean that if the links are the same the UI shows?

Copy link
Contributor Author

@getdave getdave Oct 26, 2021

Choose a reason for hiding this comment

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

Screen.Capture.on.2021-10-26.at.14-09-46.mov

Nope. Not in my testing. This is because the two link format objects are not referentially equal (i.e. they are not the same format object).

Note in the video above the two items I'm console.loging are linkFormatAtStart and linkFormatAtEnd

) {
isActive = false;
}
}

return (
<Edit
key={ name }
Expand Down
154 changes: 154 additions & 0 deletions packages/e2e-tests/specs/editor/various/links.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,4 +588,158 @@ describe( 'Links', () => {

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

describe( 'Disabling Link UI active state', () => {
it( 'should not show the Link UI when selection extends beyond link boundary', async () => {
const linkedText = `Gutenberg`;
const textBeyondLinkedText = ` and more text.`;

// Create a block with some text
await clickBlockAppender();
await page.keyboard.type(
`This is ${ linkedText }${ textBeyondLinkedText }`
);

// Move cursor next to end of `linkedText`
for (
let index = 0;
index < textBeyondLinkedText.length;
index++
) {
await page.keyboard.press( 'ArrowLeft' );
}

// Select the linkedText
await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' );

// Click on the Link button
await page.click( 'button[aria-label="Link"]' );

// Wait for the URL field to auto-focus
await waitForAutoFocus();

// Type a URL
await page.keyboard.type( 'https://wordpress.org/gutenberg' );

// Update the link
await page.keyboard.press( 'Enter' );

await page.keyboard.press( 'ArrowLeft' );
await page.keyboard.press( 'ArrowLeft' );

expect(
await page.$(
'.components-popover__content .block-editor-link-control'
)
).not.toBeNull();

// Make selection starting within the link and moving beyond boundary to the left.
for ( let index = 0; index < linkedText.length; index++ ) {
await pressKeyWithModifier( 'shift', 'ArrowLeft' );
}

// The Link UI should have disappeared (i.e. be inactive).
expect(
await page.$(
'.components-popover__content .block-editor-link-control'
)
).toBeNull();

// Cancel selection and move back within the Link.
await page.keyboard.press( 'ArrowRight' );

// We should see the Link UI displayed again.
expect(
await page.$(
'.components-popover__content .block-editor-link-control'
)
).not.toBeNull();

// Make selection starting within the link and moving beyond boundary to the right.
await pressKeyWithModifier( 'shift', 'ArrowRight' );
await pressKeyWithModifier( 'shift', 'ArrowRight' );
await pressKeyWithModifier( 'shift', 'ArrowRight' );

// The Link UI should have disappeared (i.e. be inactive).
expect(
await page.$(
'.components-popover__content .block-editor-link-control'
)
).toBeNull();
} );

it( 'should not show the Link UI when selection extends into another link', async () => {
const linkedTextOne = `Gutenberg`;
const linkedTextTwo = `Block Editor`;
const linkOneURL = 'https://wordpress.org';
const linkTwoURL = 'https://wordpress.org/gutenberg';

// Create a block with some text
await clickBlockAppender();
await page.keyboard.type(
`This is the ${ linkedTextOne }${ linkedTextTwo }`
);

// Select the linkedTextTwo
for ( let index = 0; index < linkedTextTwo.length; index++ ) {
await pressKeyWithModifier( 'shift', 'ArrowLeft' );
}

// Click on the Link button
await page.click( 'button[aria-label="Link"]' );

// Wait for the URL field to auto-focus
await waitForAutoFocus();

// Type a URL
await page.keyboard.type( linkTwoURL );

// Update the link
await page.keyboard.press( 'Enter' );

// Move cursor next to the **end** of `linkTextOne`
for ( let index = 0; index < linkedTextTwo.length + 2; index++ ) {
await page.keyboard.press( 'ArrowLeft' );
}

// Select `linkTextOne`
await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' );

// Click on the Link button
await page.click( 'button[aria-label="Link"]' );

// Wait for the URL field to auto-focus
await waitForAutoFocus();

// Type a URL
await page.keyboard.type( linkOneURL );

// Update the link
await page.keyboard.press( 'Enter' );

// Move cursor within `linkTextOne`
for ( let index = 0; index < 3; index++ ) {
await page.keyboard.press( 'ArrowLeft' );
}

// Link UI should activate for `linkTextOne`
expect(
await page.$(
'.components-popover__content .block-editor-link-control'
)
).not.toBeNull();

// Expand selection so that it overlaps with `linkTextTwo`
for ( let index = 0; index < 3; index++ ) {
await pressKeyWithModifier( 'shift', 'ArrowRight' );
}

// Link UI should be inactive.
expect(
await page.$(
'.components-popover__content .block-editor-link-control'
)
).toBeNull();
} );
} );
} );