-
Notifications
You must be signed in to change notification settings - Fork 4.6k
ContentOnly Patterns experiment: Add content only inspector fields #71730
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
Merged
Merged
Changes from all commits
Commits
Show all changes
60 commits
Select commit
Hold shift + click to select a range
fab47d0
Boilerplate
talldan b9e18ae
v1
talldan de34b38
Fixes
talldan ad41662
Small improvements:
talldan 615f532
Add block icon
talldan 5cd8ebc
Refactor to a `controls` array in the block index
talldan bea3948
Stub out media and link controls
talldan 58592e0
Add first draft of media control
talldan 749e652
Move controls to separate files
talldan be3560c
Add styles for media control
talldan 590d0cf
Add caption and alt to image
talldan e9dea9e
Make toolspanel dropdown open to the side
talldan 4fad718
Add link control initial implementation
talldan da5a41f
Add image controls to cover
talldan d094b8e
Add social link, button support
talldan b349819
Improve thumbnail
talldan b455dc5
Add support to audio block
talldan 43bf8b7
Add support for code
talldan 805feaf
Add media-text support
talldan bd84bec
Add support for More and Preformatted
talldan 9a49fa7
Add support to pullquote
talldan c6bae84
Add support to search
talldan 76c0f8c
Add support for verse
talldan b7425ed
Better support for video in cover block
talldan 5e23b27
Show controls when for root block when it is a content block
talldan 5d62731
Switch to MediaReplaceFlow
talldan 59b21d2
Support featured image in cover block
talldan ed501df
Remove unset buttons
talldan 66b5ced
Semi functioning rich text control
talldan 41761c7
More rich text stuff
talldan 4057ba1
Improve richtext control styles (hacky)
talldan 1eb93d4
Kitchen sink to get formats working
talldan be274f6
Fix formats showing on toolbar twice
talldan 40b3d53
Fix tagName
talldan f0edd58
Match border color
talldan 4302be9
Add plain text control
talldan 8f831c6
Add tagName back again to fix formatting
talldan 37508b3
Remove old configuration
talldan d658f2f
Show media thumbnail and title when possible
talldan cfc0590
Fix component name
talldan 825b2f8
Use actual buttons
talldan 80bad4a
Improve fallback to URL
talldan a37780e
More button style iterations
talldan ab796a3
Nesting
talldan 63f4ae0
Improved styles
talldan 31b6f8d
Improved nesting
talldan b84a759
Add list item support
talldan 8956f28
Condense the tools panels
talldan 1290679
Avoid orphaned drilldowns
talldan b97c8db
Make navigator button font weights match tools panel headings (hacky)
talldan 7ce41bf
Support details
talldan 4ce6327
Update comments
talldan 61d47e6
Add file support
talldan 9853d8b
Add basic nav block support
talldan fcc7b60
Fix showing the editable fields in the content tab
talldan eaac840
Add explicit scss imports
talldan d1648d3
Rename `controls` to `fields`
talldan 2b7004d
Remove navigation duplicate content role
talldan e8400bf
Fix navigation screen padding
andrewserong 5cb023e
Ensure consistency with what this branch was previously doing for lin…
andrewserong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
296 changes: 296 additions & 0 deletions
296
packages/block-editor/src/components/content-only-controls/index.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,296 @@ | ||
| /** | ||
| * WordPress dependencies | ||
| */ | ||
| import { store as blocksStore } from '@wordpress/blocks'; | ||
| import { | ||
| __experimentalToolsPanel as ToolsPanel, | ||
| __experimentalHStack as HStack, | ||
| Icon, | ||
| Navigator, | ||
| } from '@wordpress/components'; | ||
| import { useDispatch, useSelect } from '@wordpress/data'; | ||
| import { __ } from '@wordpress/i18n'; | ||
| import { arrowLeft, arrowRight } from '@wordpress/icons'; | ||
|
|
||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import { unlock } from '../../lock-unlock'; | ||
| 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 { useInspectorPopoverPlacement } from './use-inspector-popover-placement'; | ||
|
|
||
| // controls | ||
| import PlainText from './plain-text'; | ||
| import RichText from './rich-text'; | ||
| import Media from './media'; | ||
| import Link from './link'; | ||
|
|
||
| const controls = { | ||
| PlainText, | ||
| RichText, | ||
| Media, | ||
| Link, | ||
| }; | ||
|
|
||
| function BlockAttributeToolsPanelItem( { | ||
| clientId, | ||
| control, | ||
| blockType, | ||
| attributeValues, | ||
| } ) { | ||
| const { updateBlockAttributes } = useDispatch( blockEditorStore ); | ||
| const ControlComponent = controls[ control.type ]; | ||
|
|
||
| if ( ! ControlComponent ) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <ControlComponent | ||
| clientId={ clientId } | ||
| control={ control } | ||
| blockType={ blockType } | ||
| attributeValues={ attributeValues } | ||
| updateAttributes={ ( attributes ) => | ||
| updateBlockAttributes( clientId, attributes ) | ||
| } | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| function BlockFields( { clientId } ) { | ||
| const { attributes, blockType } = useSelect( | ||
| ( select ) => { | ||
| const { getBlockAttributes, getBlockName } = | ||
| select( blockEditorStore ); | ||
| const { getBlockType } = select( blocksStore ); | ||
| const blockName = getBlockName( clientId ); | ||
| return { | ||
| attributes: getBlockAttributes( clientId ), | ||
| blockType: getBlockType( blockName ), | ||
| }; | ||
| }, | ||
| [ clientId ] | ||
| ); | ||
|
|
||
| const blockTitle = useBlockDisplayTitle( { | ||
| clientId, | ||
| context: 'list-view', | ||
| } ); | ||
| const blockInformation = useBlockDisplayInformation( clientId ); | ||
| const popoverPlacementProps = useInspectorPopoverPlacement(); | ||
|
|
||
| if ( ! blockType?.fields?.length ) { | ||
| // TODO - we might still want to show a placeholder for blocks with no fields. | ||
| // for example, a way to select the block. | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <ToolsPanel | ||
| label={ | ||
| <HStack spacing={ 1 }> | ||
| <BlockIcon icon={ blockInformation?.icon } /> | ||
| <div>{ blockTitle }</div> | ||
| </HStack> | ||
| } | ||
| panelId={ clientId } | ||
| dropdownMenuProps={ popoverPlacementProps } | ||
| > | ||
| { blockType?.fields?.map( ( field, index ) => ( | ||
| <BlockAttributeToolsPanelItem | ||
| key={ `${ clientId }/${ index }` } | ||
| clientId={ clientId } | ||
| control={ field } | ||
| blockType={ blockType } | ||
| attributeValues={ attributes } | ||
| /> | ||
| ) ) } | ||
| </ToolsPanel> | ||
| ); | ||
| } | ||
|
|
||
| function DrillDownButton( { clientId } ) { | ||
| const blockTitle = useBlockDisplayTitle( { | ||
| clientId, | ||
| context: 'list-view', | ||
| } ); | ||
| const blockInformation = useBlockDisplayInformation( clientId ); | ||
| return ( | ||
| <div className="block-editor-content-only-controls__button-panel"> | ||
| <Navigator.Button | ||
| path={ `/${ clientId }` } | ||
| className="block-editor-content-only-controls__drill-down-button" | ||
| > | ||
| <HStack expanded justify="space-between"> | ||
| <HStack justify="flex-start" spacing={ 1 }> | ||
| <BlockIcon icon={ blockInformation?.icon } /> | ||
| <div>{ blockTitle }</div> | ||
| </HStack> | ||
| <Icon icon={ arrowRight } /> | ||
| </HStack> | ||
| </Navigator.Button> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| function ContentOnlyControlsScreen( { | ||
| rootClientId, | ||
| contentClientIds, | ||
| parentClientIds, | ||
| isNested, | ||
| } ) { | ||
| const isRootContentBlock = useSelect( | ||
| ( select ) => { | ||
| const { getBlockName } = select( blockEditorStore ); | ||
| const blockName = getBlockName( rootClientId ); | ||
| const { hasContentRoleAttribute } = unlock( select( blocksStore ) ); | ||
| return hasContentRoleAttribute( blockName ); | ||
| }, | ||
| [ rootClientId ] | ||
| ); | ||
|
|
||
| if ( ! isRootContentBlock && ! contentClientIds.length ) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| { isNested && ( | ||
| <div className="block-editor-content-only-controls__button-panel"> | ||
| <Navigator.BackButton className="block-editor-content-only-controls__back-button"> | ||
| <HStack expanded spacing={ 1 } justify="flex-start"> | ||
| <Icon icon={ arrowLeft } /> | ||
| <div>{ __( 'Back' ) }</div> | ||
| </HStack> | ||
| </Navigator.BackButton> | ||
| </div> | ||
| ) } | ||
| { isRootContentBlock && <BlockFields clientId={ rootClientId } /> } | ||
| { contentClientIds.map( ( clientId ) => { | ||
| if ( parentClientIds?.[ clientId ] ) { | ||
| return ( | ||
| <DrillDownButton | ||
| key={ clientId } | ||
| clientId={ clientId } | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| return <BlockFields key={ clientId } clientId={ clientId } />; | ||
| } ) } | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default function ContentOnlyControls( { rootClientId } ) { | ||
| const { updatedRootClientId, nestedContentClientIds, contentClientIds } = | ||
| useSelect( | ||
| ( select ) => { | ||
| const { getClientIdsOfDescendants, getBlockEditingMode } = | ||
| select( blockEditorStore ); | ||
|
|
||
| // _nestedContentClientIds is for content blocks within 'drilldowns'. | ||
| // It's an object where the key is the parent clientId, and the element is | ||
| // an array of child clientIds whose controls are shown within the drilldown. | ||
| const _nestedContentClientIds = {}; | ||
|
|
||
| // _contentClientIds is the list of contentClientIds for blocks being | ||
| // shown at the root level. Includes parent blocks that might have a drilldown, | ||
| // but not the children of those blocks. | ||
| const _contentClientIds = []; | ||
|
|
||
| // An array of all nested client ids. Used for ensuring blocks within drilldowns | ||
| // don't appear at the root level. | ||
| let allNestedClientIds = []; | ||
|
|
||
| // A flattened list of all content clientIds to arrange into the | ||
| // groups above. | ||
| const allContentClientIds = getClientIdsOfDescendants( | ||
| rootClientId | ||
| ).filter( | ||
| ( clientId ) => | ||
| getBlockEditingMode( clientId ) === 'contentOnly' | ||
| ); | ||
|
|
||
| for ( const clientId of allContentClientIds ) { | ||
| const childClientIds = getClientIdsOfDescendants( | ||
| clientId | ||
| ).filter( | ||
| ( childClientId ) => | ||
| getBlockEditingMode( childClientId ) === | ||
| 'contentOnly' | ||
| ); | ||
|
|
||
| // If there's more than one child block, use a drilldown. | ||
| if ( | ||
| childClientIds.length > 1 && | ||
| ! allNestedClientIds.includes( clientId ) | ||
| ) { | ||
| _nestedContentClientIds[ clientId ] = childClientIds; | ||
| allNestedClientIds = [ | ||
| allNestedClientIds, | ||
| ...childClientIds, | ||
| ]; | ||
| } | ||
|
|
||
| if ( ! allNestedClientIds.includes( clientId ) ) { | ||
| _contentClientIds.push( clientId ); | ||
| } | ||
| } | ||
|
|
||
| // Avoid showing only one drilldown block at the root. | ||
| if ( | ||
| _contentClientIds.length === 1 && | ||
| Object.keys( _nestedContentClientIds ).length === 1 | ||
| ) { | ||
| const onlyParentClientId = Object.keys( | ||
| _nestedContentClientIds | ||
| )[ 0 ]; | ||
| return { | ||
| updatedRootClientId: onlyParentClientId, | ||
| contentClientIds: | ||
| _nestedContentClientIds[ onlyParentClientId ], | ||
| nestedContentClientIds: {}, | ||
| }; | ||
| } | ||
|
|
||
| return { | ||
| nestedContentClientIds: _nestedContentClientIds, | ||
| contentClientIds: _contentClientIds, | ||
| }; | ||
| }, | ||
| [ rootClientId ] | ||
| ); | ||
|
|
||
| return ( | ||
| <Navigator initialPath="/"> | ||
| <Navigator.Screen | ||
| path="/" | ||
| className="block-editor-content-only-controls__screen" | ||
| > | ||
| <ContentOnlyControlsScreen | ||
| rootClientId={ updatedRootClientId ?? rootClientId } | ||
| contentClientIds={ contentClientIds } | ||
| parentClientIds={ nestedContentClientIds } | ||
| /> | ||
| </Navigator.Screen> | ||
| { Object.keys( nestedContentClientIds ).map( ( clientId ) => ( | ||
| <Navigator.Screen | ||
| key={ clientId } | ||
| path={ `/${ clientId }` } | ||
| className="block-editor-content-only-controls__screen" | ||
| > | ||
| <ContentOnlyControlsScreen | ||
| isNested | ||
| rootClientId={ clientId } | ||
| contentClientIds={ nestedContentClientIds[ clientId ] } | ||
| /> | ||
| </Navigator.Screen> | ||
| ) ) } | ||
| </Navigator> | ||
| ); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the PR description one of the TODOs is
Is that referring to these components that render
settings.fields?I see one of the settings keys is now private, are there any other public API items that need to be made private 🤔