Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
fab47d0
Boilerplate
talldan Sep 10, 2025
b9e18ae
v1
talldan Sep 10, 2025
de34b38
Fixes
talldan Sep 10, 2025
ad41662
Small improvements:
talldan Sep 10, 2025
615f532
Add block icon
talldan Sep 10, 2025
5cd8ebc
Refactor to a `controls` array in the block index
talldan Sep 16, 2025
bea3948
Stub out media and link controls
talldan Sep 16, 2025
58592e0
Add first draft of media control
talldan Sep 17, 2025
749e652
Move controls to separate files
talldan Sep 17, 2025
be3560c
Add styles for media control
talldan Sep 17, 2025
590d0cf
Add caption and alt to image
talldan Sep 17, 2025
e9dea9e
Make toolspanel dropdown open to the side
talldan Sep 17, 2025
4fad718
Add link control initial implementation
talldan Sep 17, 2025
da5a41f
Add image controls to cover
talldan Sep 17, 2025
d094b8e
Add social link, button support
talldan Sep 17, 2025
b349819
Improve thumbnail
talldan Sep 17, 2025
b455dc5
Add support to audio block
talldan Sep 17, 2025
43bf8b7
Add support for code
talldan Sep 17, 2025
805feaf
Add media-text support
talldan Sep 17, 2025
bd84bec
Add support for More and Preformatted
talldan Sep 17, 2025
9a49fa7
Add support to pullquote
talldan Sep 17, 2025
c6bae84
Add support to search
talldan Sep 17, 2025
76c0f8c
Add support for verse
talldan Sep 17, 2025
b7425ed
Better support for video in cover block
talldan Sep 17, 2025
5e23b27
Show controls when for root block when it is a content block
talldan Sep 17, 2025
5d62731
Switch to MediaReplaceFlow
talldan Sep 17, 2025
59b21d2
Support featured image in cover block
talldan Sep 17, 2025
ed501df
Remove unset buttons
talldan Sep 17, 2025
66b5ced
Semi functioning rich text control
talldan Sep 17, 2025
41761c7
More rich text stuff
talldan Sep 17, 2025
4057ba1
Improve richtext control styles (hacky)
talldan Sep 17, 2025
1eb93d4
Kitchen sink to get formats working
talldan Sep 17, 2025
be274f6
Fix formats showing on toolbar twice
talldan Sep 17, 2025
40b3d53
Fix tagName
talldan Sep 17, 2025
f0edd58
Match border color
talldan Sep 17, 2025
4302be9
Add plain text control
talldan Sep 17, 2025
8f831c6
Add tagName back again to fix formatting
talldan Sep 17, 2025
37508b3
Remove old configuration
talldan Sep 18, 2025
d658f2f
Show media thumbnail and title when possible
talldan Sep 18, 2025
cfc0590
Fix component name
talldan Sep 18, 2025
825b2f8
Use actual buttons
talldan Sep 18, 2025
80bad4a
Improve fallback to URL
talldan Sep 18, 2025
a37780e
More button style iterations
talldan Sep 18, 2025
ab796a3
Nesting
talldan Sep 19, 2025
63f4ae0
Improved styles
talldan Sep 19, 2025
31b6f8d
Improved nesting
talldan Sep 19, 2025
b84a759
Add list item support
talldan Sep 19, 2025
8956f28
Condense the tools panels
talldan Sep 19, 2025
1290679
Avoid orphaned drilldowns
talldan Sep 19, 2025
b97c8db
Make navigator button font weights match tools panel headings (hacky)
talldan Sep 19, 2025
7ce41bf
Support details
talldan Sep 19, 2025
4ce6327
Update comments
talldan Sep 19, 2025
61d47e6
Add file support
talldan Sep 19, 2025
9853d8b
Add basic nav block support
talldan Sep 19, 2025
fcc7b60
Fix showing the editable fields in the content tab
talldan Sep 30, 2025
eaac840
Add explicit scss imports
talldan Nov 6, 2025
d1648d3
Rename `controls` to `fields`
talldan Nov 10, 2025
2b7004d
Remove navigation duplicate content role
talldan Nov 10, 2025
e8400bf
Fix navigation screen padding
andrewserong Nov 11, 2025
5cb023e
Ensure consistency with what this branch was previously doing for lin…
andrewserong Nov 13, 2025
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
Prev Previous commit
Next Next commit
Add link control initial implementation
  • Loading branch information
talldan authored and andrewserong committed Nov 13, 2025
commit 4fad718546dd40c9ed3f13751add9ae0a4e46696
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { store as blockEditorStore } from '../../store';
import BlockIcon from '../block-icon';
import useBlockDisplayTitle from '../block-title/use-block-display-title';
import useBlockDisplayInformation from '../use-block-display-information';
import { useToolsPanelDropdownMenuProps } from '../global-styles/utils';
import { useInspectorPopoverPlacement } from './use-inspector-popover-placement';

// controls
import RichText from './rich-text';
Expand Down Expand Up @@ -74,7 +74,7 @@ function BlockControls( { clientId } ) {
context: 'list-view',
} );
const blockInformation = useBlockDisplayInformation( clientId );
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
const popoverPlacementProps = useInspectorPopoverPlacement();

if ( ! blockType?.controls?.length ) {
// TODO - we might still want to show a placeholder for blocks with no controls.
Expand All @@ -91,7 +91,7 @@ function BlockControls( { clientId } ) {
</HStack>
}
panelId={ clientId }
dropdownMenuProps={ dropdownMenuProps }
dropdownMenuProps={ popoverPlacementProps }
>
{ blockType?.controls?.map( ( control, index ) => (
<BlockAttributeToolsPanelItem
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,72 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

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

This whole content only controls feel like we're recreating DataForm, shouldn't we take the time to make sure DataForm works properly instead of reinventing the same thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree, I'll get to it, I've been working on a few other things first so I haven't pushed to this for a while.

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense, but I'm wondering if it can be a follow up? To see if something works properly sounds like an investigation task, which I'd wager would create a list of compatibility sub tasks

Or if folks think it's required right now, let's make sure everyone is on board with the extra time required to investigate.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it could be worth a (time-boxed) bit of investigation, partly because this PR introduces a shape of an API for fields in block settings. Not necessarily a blocker as this PR is guarded behind an experiment, but it sounds like it'd be good for the fields set in a block to be effectively the same (or similar) API as DataForm fields?

Or another way to look at it, is that I think there's two things we might want to achieve:

  1. Validate the UI/UX approach of this PR — does it create the ideal experience?
  2. Code quality / API design — can we make this consistent with DataForm and tease apart which pieces need to be hard-coded and which bits can become part of DataForm / generic field controls (i.e. rich text controls, generic media control, etc)

So long as things are guarded behind an experiment, we can continue to iterate, but overall I like the idea of consolidating with DataForm and using the work here to help inform improvements to the DataForm and controls.

* WordPress dependencies
*/
import { __experimentalToolsPanelItem as ToolsPanelItem } from '@wordpress/components';
import {
Button,
Icon,
__experimentalToolsPanelItem as ToolsPanelItem,
__experimentalGrid as Grid,
Popover,
} from '@wordpress/components';
import { useMemo, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { lineSolid, link } from '@wordpress/icons';
import { prependHTTP } from '@wordpress/url';

/**
* Internal dependencies
*/
import LinkControl from '../../link-control';
import { useInspectorPopoverPlacement } from '../use-inspector-popover-placement';

export const NEW_TAB_REL = 'noreferrer noopener';
export const NEW_TAB_TARGET = '_blank';
export const NOFOLLOW_REL = 'nofollow';

/**
* Updates the link attributes.
*
* @param {Object} attributes The current block attributes.
* @param {string} attributes.rel The current link rel attribute.
* @param {string} attributes.url The current link url.
* @param {boolean} attributes.opensInNewTab Whether the link should open in a new window.
* @param {boolean} attributes.nofollow Whether the link should be marked as nofollow.
*/
export function getUpdatedLinkAttributes( {
rel = '',
url = '',
opensInNewTab,
nofollow,
} ) {
let newLinkTarget;
// Since `rel` is editable attribute, we need to check for existing values and proceed accordingly.
let updatedRel = rel;

if ( opensInNewTab ) {
newLinkTarget = NEW_TAB_TARGET;
updatedRel = updatedRel?.includes( NEW_TAB_REL )
? updatedRel
: updatedRel + ` ${ NEW_TAB_REL }`;
} else {
const relRegex = new RegExp( `\\b${ NEW_TAB_REL }\\s*`, 'g' );
updatedRel = updatedRel?.replace( relRegex, '' ).trim();
}

if ( nofollow ) {
updatedRel = updatedRel?.includes( NOFOLLOW_REL )
? updatedRel
: ( updatedRel + ` ${ NOFOLLOW_REL }` ).trim();
} else {
const relRegex = new RegExp( `\\b${ NOFOLLOW_REL }\\s*`, 'g' );
updatedRel = updatedRel?.replace( relRegex, '' ).trim();
}

return {
url: prependHTTP( url ),
linkTarget: newLinkTarget,
rel: updatedRel || undefined,
};
}

export default function Link( {
clientId,
Expand All @@ -10,17 +75,143 @@ export default function Link( {
attributeValues,
updateAttributes,
} ) {
const [ isLinkControlOpen, setIsLinkControlOpen ] = useState( false );
const { popoverProps } = useInspectorPopoverPlacement( {
isControl: true,
} );
const hrefKey = control.mapping.href;
const relKey = control.mapping.rel;
const targetKey = control.mapping.target;
const destinationKey = control.mapping.destination;

const href = attributeValues[ hrefKey ];
const rel = attributeValues[ relKey ];
const target = attributeValues[ targetKey ];
const destination = attributeValues[ destinationKey ];

const hrefDefaultValue =
blockType.attributes[ href ]?.defaultValue ?? undefined;
const relDefaultValue =
blockType.attributes[ rel ]?.defaultValue ?? undefined;
const targetDefaultValue =
blockType.attributes[ target ]?.defaultValue ?? undefined;
const destinationDefaultValue =
blockType.attributes[ destination ]?.defaultValue ?? undefined;

const opensInNewTab = target === NEW_TAB_TARGET;
const nofollow = rel === NOFOLLOW_REL;

// Memoize link value to avoid overriding the LinkControl's internal state.
// This is a temporary fix. See https://github.com/WordPress/gutenberg/issues/51256.
const linkValue = useMemo(
() => ( { url: href, opensInNewTab, nofollow } ),
[ href, opensInNewTab, nofollow ]
);

return (
<ToolsPanelItem
panelId={ clientId }
label={ control.label }
hasValue={ () => false } // TODO.
hasValue={ () => !! href }
onDeselect={ () => {
// TODO.
updateAttributes( {
[ hrefKey ]: hrefDefaultValue,
[ relKey ]: relDefaultValue,
[ targetKey ]: targetDefaultValue,
[ destinationKey ]: destinationDefaultValue,
} );
} }
isShownByDefault={ control.shownByDefault }
>
Link
<div className="block-editor-content-only-controls__media">
<div
role="button"
tabIndex={ -1 }
onClick={ () => {
setIsLinkControlOpen( true );
} }
onKeyDown={ () => setIsLinkControlOpen( true ) }
>
<Grid
rowGap={ 0 }
columnGap={ 8 }
templateColumns="24px 1fr 24px"
className="block-editor-content-only-controls__media-row"
>
{ href && (
<>
<Icon icon={ link } size={ 24 } />
<span className="block-editor-content-only-controls__media-title">
{ href }
</span>
</>
) }
{ ! href && (
<>
<Icon
icon={ link }
size={ 24 }
style={ { opacity: 0.3 } }
/>
<span className="block-editor-content-only-controls__media-title">
{ __( 'Link' ) }
</span>
</>
) }
{ href && (
<>
<Button
size="small"
className="block-editor-content-only-controls__remove-button"
icon={ lineSolid }
onClick={ ( event ) => {
event.stopPropagation();
updateAttributes( {
[ hrefKey ]: hrefDefaultValue,
[ relKey ]: relDefaultValue,
[ targetKey ]: targetDefaultValue,
[ destinationKey ]:
destinationDefaultValue,
} );
} }
/>
</>
) }
</Grid>
</div>
</div>
{ isLinkControlOpen && (
<Popover
onClose={ () => {
setIsLinkControlOpen( false );
} }
{ ...( popoverProps ?? {} ) }
>
<LinkControl
value={ linkValue }
onChange={ ( newValues ) => {
const updatedAttrs = getUpdatedLinkAttributes( {
rel,
...newValues,
} );

updateAttributes( {
[ hrefKey ]: updatedAttrs.url,
[ relKey ]: updatedAttrs.rel,
[ targetKey ]: updatedAttrs.linkTarget,
} );
} }
onRemove={ () => {
updateAttributes( {
[ hrefKey ]: hrefDefaultValue,
[ relKey ]: relDefaultValue,
[ targetKey ]: targetDefaultValue,
[ destinationKey ]: destinationDefaultValue,
} );
} }
/>
</Popover>
) }
</ToolsPanelItem>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* WordPress dependencies
*/
import { useViewportMatch } from '@wordpress/compose';

export function useInspectorPopoverPlacement(
{ isControl } = { isControl: false }
) {
const isMobile = useViewportMatch( 'medium', '<' );
return ! isMobile
? {
popoverProps: {
placement: 'left-start',
// For non-mobile, inner sidebar width (248px) - button width (24px) - border (1px) + padding (16px) + spacing (20px)
offset: isControl ? 35 : 259,
},
}
: {};
}