diff --git a/packages/block-editor/src/components/link-control/settings.js b/packages/block-editor/src/components/link-control/settings.js index e63ef926358fe9..bbec936d1641e1 100644 --- a/packages/block-editor/src/components/link-control/settings.js +++ b/packages/block-editor/src/components/link-control/settings.js @@ -18,17 +18,26 @@ const LinkControlSettings = ( { value, onChange = noop, settings } ) => { } ); }; - const theSettings = settings.map( ( setting ) => ( - - ) ); + const theSettings = settings.map( ( setting ) => + setting.render ? ( +
+ { setting.render( setting, value, onChange ) } +
+ ) : ( + + ) + ); return (
diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 16493e1a5aa7f0..a8afa9e2ffc12a 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -346,11 +346,15 @@ $block-editor-link-control-number-of-actions: 1; .block-editor-link-control__setting { margin-bottom: 0; flex: 1; - padding: $grid-unit-10 0 $grid-unit-10 $grid-unit-30; + padding: $grid-unit-10 $grid-unit-30; - .components-base-control__field { - display: flex; // don't allow label to wrap under checkbox. + .components-base-control:not(.components-input-control) { + .components-base-control__field { + display: flex; // don't allow label to wrap under checkbox. + } + } + .components-base-control__field { .components-checkbox-control__label { color: $gray-900; } diff --git a/packages/format-library/src/link/index.js b/packages/format-library/src/link/index.js index 508ed4655a6695..7225514b219782 100644 --- a/packages/format-library/src/link/index.js +++ b/packages/format-library/src/link/index.js @@ -227,6 +227,7 @@ export const link = { _id: 'id', target: 'target', rel: 'rel', + class: 'class', }, __unstablePasteRule( value, { html, plainText } ) { const pastedText = ( html || plainText ) diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index 964e9a4271dda9..b9f38327463094 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -1,10 +1,18 @@ /** * WordPress dependencies */ -import { useMemo, createInterpolateElement } from '@wordpress/element'; +import { + useState, + useMemo, + createInterpolateElement, +} from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; -import { Popover } from '@wordpress/components'; +import { + Popover, + __experimentalInputControl as InputControl, + CheckboxControl, +} from '@wordpress/components'; import { prependHTTP } from '@wordpress/url'; import { create, @@ -30,12 +38,71 @@ import { useDispatch, useSelect } from '@wordpress/data'; import { createLinkFormat, isValidHref, getFormatBoundary } from './utils'; import { link as settings } from './index'; +const TogglableSettingComponent = ( { setting, value, onChange } ) => { + const hasValue = value ? value?.cssClasses?.length > 0 : false; + const [ inputVisible, setInputVisible ] = useState( hasValue ); + + const handleSettingChange = ( newValue ) => { + onChange( { + ...value, + [ setting.id ]: newValue, + } ); + }; + + const handleCheckboxChange = () => { + if ( inputVisible ) { + if ( hasValue ) { + // Reset the value. + handleSettingChange( '' ); + } + setInputVisible( false ); + } else { + setInputVisible( true ); + } + }; + + return ( +
+ + { inputVisible && ( + + ) } +
+ ); +}; + const LINK_SETTINGS = [ ...LinkControl.DEFAULT_LINK_SETTINGS, { id: 'nofollow', title: __( 'Mark as nofollow' ), }, + { + id: 'cssClasses', + title: __( 'Additional CSS class(es)' ), + render: ( setting, value, onChange ) => { + return ( + + ); + }, + }, ]; function InlineLinkUI( { @@ -78,8 +145,10 @@ function InlineLinkUI( { opensInNewTab: activeAttributes.target === '_blank', nofollow: activeAttributes.rel?.includes( 'nofollow' ), title: richTextText, + cssClasses: activeAttributes.class, } ), [ + activeAttributes.class, activeAttributes.id, activeAttributes.rel, activeAttributes.target, @@ -116,6 +185,7 @@ function InlineLinkUI( { : undefined, opensInNewWindow: nextValue.opensInNewTab, nofollow: nextValue.nofollow, + cssClasses: nextValue.cssClasses, } ); const newText = nextValue.title || newUrl; diff --git a/packages/format-library/src/link/style.scss b/packages/format-library/src/link/style.scss index 05eb576fe26e27..32c24b75eb4532 100644 --- a/packages/format-library/src/link/style.scss +++ b/packages/format-library/src/link/style.scss @@ -17,3 +17,9 @@ color: $alert-red; } } + +.block-editor-link-control__toggleable-setting { + display: flex; + flex-direction: column; + gap: $grid-unit-20 0; +} diff --git a/packages/format-library/src/link/utils.js b/packages/format-library/src/link/utils.js index 314c8118713a43..4f64950efd457d 100644 --- a/packages/format-library/src/link/utils.js +++ b/packages/format-library/src/link/utils.js @@ -86,6 +86,7 @@ export function isValidHref( href ) { * @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. + * @param {string} options.cssClasses The CSS classes to apply to the link. * @return {Object} The final format object. */ export function createLinkFormat( { @@ -94,6 +95,7 @@ export function createLinkFormat( { id, opensInNewWindow, nofollow, + cssClasses, } ) { const format = { type: 'core/link', @@ -122,6 +124,12 @@ export function createLinkFormat( { : 'nofollow'; } + const trimmedCssClasses = cssClasses?.trim(); + + if ( trimmedCssClasses?.length ) { + format.attributes.class = trimmedCssClasses; + } + return format; }