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

Adds 'nofollow' setting to inline links (rich text only) #53945

Merged
merged 12 commits into from
Aug 31, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -475,5 +475,6 @@ function LinkControl( {
}

LinkControl.ViewerFill = ViewerFill;
LinkControl.DEFAULT_LINK_SETTINGS = DEFAULT_LINK_SETTINGS;

export default LinkControl;
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const LinkControlSettings = ( { value, onChange = noop, settings } ) => {
label={ setting.title }
onChange={ handleSettingChange( setting ) }
checked={ value ? !! value[ setting.id ] : false }
help={ setting?.help }
/>
) );

Expand Down
10 changes: 5 additions & 5 deletions packages/block-editor/src/components/link-control/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -401,19 +401,19 @@ $preview-image-height: 140px;
}

.block-editor-link-control__setting {
margin-bottom: $grid-unit-20;
margin-bottom: 0;
flex: 1;
padding: $grid-unit-10 0 $grid-unit-10 $grid-unit-30;

.components-base-control__field {
display: flex; // don't allow label to wrap under checkbox.
}

// Cancel left margin inherited from WP Admin Forms CSS.
input {
margin-left: 0;
}

&.block-editor-link-control__setting:last-child {
margin-bottom: 0;
}

.is-preview & {
padding: 20px $grid-unit-10 $grid-unit-10 0;
}
Expand Down
1 change: 1 addition & 0 deletions packages/format-library/src/link/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export const link = {
type: 'data-type',
id: 'data-id',
target: 'target',
rel: 'rel',
},
__unstablePasteRule( value, { html, plainText } ) {
if ( isCollapsed( value ) ) {
Expand Down
15 changes: 14 additions & 1 deletion packages/format-library/src/link/inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ import { createLinkFormat, isValidHref, getFormatBoundary } from './utils';
import { link as settings } from './index';
import useLinkInstanceKey from './use-link-instance-key';

const LINK_SETTINGS = [
...LinkControl.DEFAULT_LINK_SETTINGS,
{
id: 'nofollow',
title: createInterpolateElement(
__( 'Mark as <code>nofollow</code>' ),
{ code: <code /> }
),
},
];

function InlineLinkUI( {
isActive,
activeAttributes,
Expand Down Expand Up @@ -60,6 +71,7 @@ function InlineLinkUI( {
type: activeAttributes.type,
id: activeAttributes.id,
opensInNewTab: activeAttributes.target === '_blank',
nofollow: activeAttributes.rel?.includes( 'nofollow' ),
title: richTextText,
};

Expand All @@ -77,7 +89,6 @@ function InlineLinkUI( {
const didToggleSetting =
linkValue.opensInNewTab !== nextValue.opensInNewTab &&
nextValue.url === undefined;

// Merge the next value with the current link value.
nextValue = {
...linkValue,
Expand All @@ -93,6 +104,7 @@ function InlineLinkUI( {
? String( nextValue.id )
: undefined,
opensInNewWindow: nextValue.opensInNewTab,
nofollow: nextValue.nofollow,
} );

const newText = nextValue.title || newUrl;
Expand Down Expand Up @@ -247,6 +259,7 @@ function InlineLinkUI( {
withCreateSuggestion={ userCanCreatePages }
createSuggestionButtonText={ createButtonText }
hasTextControl
settings={ LINK_SETTINGS }
/>
</Popover>
);
Expand Down
20 changes: 17 additions & 3 deletions packages/format-library/src/link/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,16 @@ export function isValidHref( href ) {
* @param {string} options.type The type of the link.
* @param {string} options.id The ID of the link.
* @param {boolean} options.opensInNewWindow Whether this link will open in a new window.
*
* @param {boolean} options.nofollow Whether this link is marked as no follow relationship.
getdave marked this conversation as resolved.
Show resolved Hide resolved
* @return {Object} The final format object.
*/
export function createLinkFormat( { url, type, id, opensInNewWindow } ) {
export function createLinkFormat( {
url,
type,
id,
opensInNewWindow,
nofollow,
} ) {
const format = {
type: 'core/link',
attributes: {
Expand All @@ -101,7 +107,15 @@ export function createLinkFormat( { url, type, id, opensInNewWindow } ) {

if ( opensInNewWindow ) {
format.attributes.target = '_blank';
format.attributes.rel = 'noreferrer noopener';
format.attributes.rel = format.attributes.rel
? format.attributes.rel + ' noreferrer noopener'
: 'noreferrer noopener';
}

if ( nofollow ) {
format.attributes.rel = format.attributes.rel
? format.attributes.rel + ' nofollow'
: 'nofollow';
getdave marked this conversation as resolved.
Show resolved Hide resolved
}

return format;
Expand Down
82 changes: 82 additions & 0 deletions test/e2e/specs/editor/blocks/links.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,86 @@ test.describe( 'Links', () => {
// This verifies that the editor preference was persisted.
await expect( page.getByLabel( 'Open in new tab' ) ).not.toBeVisible();
} );

test( 'can toggle link settings and save', async ( {
page,
editor,
pageUtils,
} ) => {
await editor.insertBlock( {
name: 'core/paragraph',
attributes: {
content:
'<a href="https://wordpress.org/gutenberg">Gutenberg</a>',
},
} );

// Move caret into the link.
await pageUtils.pressKeys( 'ArrowRight' );

// Switch Link UI to "edit" mode.
await page.getByRole( 'button', { name: 'Edit' } ).click();

// Open Advanced Settings
await page
.getByRole( 'region', {
name: 'Editor content',
} )
.getByRole( 'button', {
name: 'Advanced',
} )
.click();

// expect settings for `Open in new tab` and `No follow`
await expect( page.getByLabel( 'Open in new tab' ) ).not.toBeChecked();
await expect( page.getByLabel( 'nofollow' ) ).not.toBeChecked();

// Toggle both of the settings
await page.getByLabel( 'Open in new tab' ).click();
await page.getByLabel( 'nofollow' ).click();

// Save the link
await page
.locator( '.block-editor-link-control' )
.getByRole( 'button', { name: 'Save' } )
.click();

// Expect correct attributes to be set on the underlying link.
await expect.poll( editor.getBlocks ).toMatchObject( [
{
name: 'core/paragraph',
attributes: {
content: `<a href="https://wordpress.org/gutenberg" target="_blank" rel="noreferrer noopener nofollow">Gutenberg</a>`,
},
},
] );

// Move caret back into the link.
await page.keyboard.press( 'ArrowRight' );
await page.keyboard.press( 'ArrowRight' );

// Edit the link
await page.getByRole( 'button', { name: 'Edit' } ).click();

// Toggle both the settings to be off.
// Note: no need to toggle settings again because the open setting should be persisted.
await page.getByLabel( 'Open in new tab' ).click();
await page.getByLabel( 'nofollow' ).click();

// Save the link
await page
.locator( '.block-editor-link-control' )
.getByRole( 'button', { name: 'Save' } )
.click();

// Expect correct attributes to be set on the underlying link.
await expect.poll( editor.getBlocks ).toMatchObject( [
{
name: 'core/paragraph',
attributes: {
content: `<a href="https://wordpress.org/gutenberg">Gutenberg</a>`,
},
},
] );
} );
} );