diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index a4c491d9cd3218..3c5c15cc8d602d 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -29,7 +29,6 @@ import PositionControls from '../inspector-controls-tabs/position-controls-panel import useBlockInspectorAnimationSettings from './useBlockInspectorAnimationSettings'; import { useBorderPanelLabel } from '../../hooks/border'; import ContentTab from '../inspector-controls-tabs/content-tab'; - import { unlock } from '../../lock-unlock'; function BlockStylesPanel( { clientId } ) { @@ -368,7 +367,10 @@ const BlockInspectorSingleBlock = ( { { hasBlockStyles && ( ) } - + { ! isSectionBlock && ( + 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 ( + + +
{ blockTitle }
+ + } + panelId={ clientId } + dropdownMenuProps={ popoverPlacementProps } + > + { blockType?.fields?.map( ( field, index ) => ( + + ) ) } +
+ ); +} + +function DrillDownButton( { clientId } ) { + const blockTitle = useBlockDisplayTitle( { + clientId, + context: 'list-view', + } ); + const blockInformation = useBlockDisplayInformation( clientId ); + return ( +
+ + + + +
{ blockTitle }
+
+ +
+
+
+ ); +} + +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 && ( +
+ + + +
{ __( 'Back' ) }
+
+
+
+ ) } + { isRootContentBlock && } + { contentClientIds.map( ( clientId ) => { + if ( parentClientIds?.[ clientId ] ) { + return ( + + ); + } + + return ; + } ) } + + ); +} + +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 ( + + + + + { Object.keys( nestedContentClientIds ).map( ( clientId ) => ( + + + + ) ) } + + ); +} diff --git a/packages/block-editor/src/components/content-only-controls/link/index.js b/packages/block-editor/src/components/content-only-controls/link/index.js new file mode 100644 index 00000000000000..356f1102bbc867 --- /dev/null +++ b/packages/block-editor/src/components/content-only-controls/link/index.js @@ -0,0 +1,195 @@ +/** + * WordPress dependencies + */ +import { + Button, + Icon, + __experimentalToolsPanelItem as ToolsPanelItem, + __experimentalGrid as Grid, + Popover, +} from '@wordpress/components'; +import { useMemo, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { 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, + control, + blockType, + 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 ( + !! href } + onDeselect={ () => { + updateAttributes( { + [ hrefKey ]: hrefDefaultValue, + [ relKey ]: relDefaultValue, + [ targetKey ]: targetDefaultValue, + [ destinationKey ]: destinationDefaultValue, + } ); + } } + isShownByDefault={ control.shownByDefault } + > + + { isLinkControlOpen && ( + { + setIsLinkControlOpen( false ); + } } + { ...( popoverProps ?? {} ) } + > + { + 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, + } ); + } } + /> + + ) } + + ); +} diff --git a/packages/block-editor/src/components/content-only-controls/link/styles.scss b/packages/block-editor/src/components/content-only-controls/link/styles.scss new file mode 100644 index 00000000000000..56767be7705fa9 --- /dev/null +++ b/packages/block-editor/src/components/content-only-controls/link/styles.scss @@ -0,0 +1,23 @@ +@use "@wordpress/base-styles/colors" as *; +@use "@wordpress/base-styles/variables" as *; + +.block-editor-content-only-controls__link { + width: 100%; + box-shadow: inset 0 0 0 $border-width $gray-400; + + &:focus:not(:disabled) { + // Allow smooth transition between focused and unfocused box-shadow states. + box-shadow: 0 0 0 currentColor inset, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + } +} + +.block-editor-content-only-controls__link-row { + align-items: center; +} + +.block-editor-content-only-controls__link-title { + width: 100%; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} diff --git a/packages/block-editor/src/components/content-only-controls/media/index.js b/packages/block-editor/src/components/content-only-controls/media/index.js new file mode 100644 index 00000000000000..d7b4b836d3e5f3 --- /dev/null +++ b/packages/block-editor/src/components/content-only-controls/media/index.js @@ -0,0 +1,285 @@ +/** + * WordPress dependencies + */ +import { + Button, + Icon, + __experimentalToolsPanelItem as ToolsPanelItem, + __experimentalGrid as Grid, +} from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { + audio as audioIcon, + image as imageIcon, + media as mediaIcon, + video as videoIcon, +} from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import MediaReplaceFlow from '../../media-replace-flow'; +import MediaUploadCheck from '../../media-upload/check'; +import { useInspectorPopoverPlacement } from '../use-inspector-popover-placement'; +import { getMediaSelectKey } from '../../../store/private-keys'; +import { store as blockEditorStore } from '../../../store'; + +function MediaThumbnail( { control, attributeValues, attachment } ) { + const { allowedTypes, multiple } = control.args; + const mapping = control.mapping; + if ( multiple ) { + return 'todo multiple'; + } + + if ( attachment?.media_type === 'image' || attachment?.poster ) { + return ( + + ); + } + + if ( allowedTypes.length === 1 ) { + let src; + if ( + allowedTypes[ 0 ] === 'image' && + mapping.src && + attributeValues[ mapping.src ] + ) { + src = attributeValues[ mapping.src ]; + } else if ( + allowedTypes[ 0 ] === 'video' && + mapping.poster && + attributeValues[ mapping.poster ] + ) { + src = attributeValues[ mapping.poster ]; + } + + if ( src ) { + return ( + + ); + } + + let icon; + if ( allowedTypes[ 0 ] === 'image' ) { + icon = imageIcon; + } else if ( allowedTypes[ 0 ] === 'video' ) { + icon = videoIcon; + } else if ( allowedTypes[ 0 ] === 'audio' ) { + icon = audioIcon; + } else { + icon = mediaIcon; + } + + if ( icon ) { + return ; + } + } + + return ; +} + +export default function Media( { + clientId, + control, + blockType, + attributeValues, + updateAttributes, +} ) { + const { popoverProps } = useInspectorPopoverPlacement( { + isControl: true, + } ); + const typeKey = control.mapping.type; + const idKey = control.mapping.id; + const srcKey = control.mapping.src; + const captionKey = control.mapping.caption; + const altKey = control.mapping.alt; + const posterKey = control.mapping.poster; + const featuredImageKey = control.mapping.featuredImage; + + const id = attributeValues[ idKey ]; + const src = attributeValues[ srcKey ]; + const caption = attributeValues[ captionKey ]; + const alt = attributeValues[ altKey ]; + const useFeaturedImage = attributeValues[ featuredImageKey ]; + + const attachment = useSelect( + ( select ) => { + if ( ! id ) { + return; + } + + const settings = select( blockEditorStore ).getSettings(); + const getMedia = settings[ getMediaSelectKey ]; + + if ( ! getMedia ) { + return; + } + + return getMedia( select, id ); + }, + [ id ] + ); + + // TODO - pluralize when multiple. + let chooseItemLabel; + if ( control.args.allowedTypes.length === 1 ) { + const allowedType = control.args.allowedTypes[ 0 ]; + if ( allowedType === 'image' ) { + chooseItemLabel = __( 'Choose an image…' ); + } else if ( allowedType === 'video' ) { + chooseItemLabel = __( 'Choose a video…' ); + } else if ( allowedType === 'application' ) { + chooseItemLabel = __( 'Choose a file…' ); + } else { + chooseItemLabel = __( 'Choose a media item…' ); + } + } else { + chooseItemLabel = __( 'Choose a media item…' ); + } + + const defaultValues = useMemo( () => { + return Object.fromEntries( + Object.entries( control.mapping ).map( ( [ , attributeKey ] ) => { + return [ + attributeKey, + blockType.attributes[ attributeKey ]?.defaultValue ?? + undefined, + ]; + } ) + ); + }, [ blockType.attributes, control.mapping ] ); + + return ( + + !! src } + onDeselect={ () => { + updateAttributes( defaultValues ); + } } + isShownByDefault={ control.shownByDefault } + > + { + updateAttributes( defaultValues ); + } } + useFeaturedImage={ !! useFeaturedImage } + onToggleFeaturedImage={ + !! featuredImageKey && + ( () => { + updateAttributes( { + ...defaultValues, + [ featuredImageKey ]: ! useFeaturedImage, + } ); + } ) + } + onSelect={ ( selectedMedia ) => { + if ( selectedMedia.id && selectedMedia.url ) { + const optionalAttributes = {}; + + if ( typeKey && selectedMedia.type ) { + optionalAttributes[ typeKey ] = + selectedMedia.type; + } + + if ( + captionKey && + ! caption && + selectedMedia.caption + ) { + optionalAttributes[ captionKey ] = + selectedMedia.caption; + } + if ( altKey && ! alt && selectedMedia.alt ) { + optionalAttributes[ altKey ] = + selectedMedia.alt; + } + if ( posterKey && selectedMedia.poster ) { + optionalAttributes[ posterKey ] = + selectedMedia.poster; + } + + updateAttributes( { + [ idKey ]: selectedMedia.id, + [ srcKey ]: selectedMedia.url, + ...optionalAttributes, + } ); + } + } } + renderToggle={ ( buttonProps ) => ( + + ) } + /> + + + ); +} diff --git a/packages/block-editor/src/components/content-only-controls/media/styles.scss b/packages/block-editor/src/components/content-only-controls/media/styles.scss new file mode 100644 index 00000000000000..c1e9e6fb066401 --- /dev/null +++ b/packages/block-editor/src/components/content-only-controls/media/styles.scss @@ -0,0 +1,47 @@ +@use "@wordpress/base-styles/colors" as *; +@use "@wordpress/base-styles/variables" as *; + +.block-editor-content-only-controls__media { + width: 100%; + box-shadow: inset 0 0 0 $border-width $gray-400; + + &:focus:not(:disabled) { + // Allow smooth transition between focused and unfocused box-shadow states. + box-shadow: 0 0 0 currentColor inset, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + } +} + +.block-editor-content-only-controls__media-replace-flow { + // Required, otherwise the width collapses. + display: block; +} + +.block-editor-content-only-controls__media-row { + align-items: center; +} + +.block-editor-content-only-controls__media-placeholder { + width: 24px; + height: 24px; + border-radius: $radius-small; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2); + display: inline-block; + padding: 0; + background: + $white + linear-gradient(-45deg, transparent 48%, $gray-300 48%, $gray-300 52%, transparent 52%); +} + +.block-editor-content-only-controls__media-title { + width: 100%; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +block-editor-content-only-controls__media-thumbnail { + width: 100%; + height: 100%; + border-radius: $radius-small; + align-self: center; +} diff --git a/packages/block-editor/src/components/content-only-controls/plain-text/index.js b/packages/block-editor/src/components/content-only-controls/plain-text/index.js new file mode 100644 index 00000000000000..8281c4a8e5f153 --- /dev/null +++ b/packages/block-editor/src/components/content-only-controls/plain-text/index.js @@ -0,0 +1,49 @@ +/** + * WordPress dependencies + */ +import { + __experimentalToolsPanelItem as ToolsPanelItem, + TextControl, +} from '@wordpress/components'; +import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; + +export default function PlainText( { + clientId, + control, + blockType, + attributeValues, + updateAttributes, +} ) { + const valueKey = control.mapping.value; + const value = attributeValues[ valueKey ]; + const defaultValue = + blockType.attributes[ valueKey ]?.defaultValue ?? undefined; + + return ( + { + return ( + value !== defaultValue && stripHTML( value )?.length !== 0 + ); + } } + onDeselect={ () => { + updateAttributes( { [ valueKey ]: defaultValue } ); + } } + isShownByDefault={ control.shownByDefault } + > + { + updateAttributes( { [ valueKey ]: newValue } ); + } } + autoComplete="off" + hideLabelFromVision={ control.shownByDefault } + /> + + ); +} diff --git a/packages/block-editor/src/components/content-only-controls/rich-text/index.js b/packages/block-editor/src/components/content-only-controls/rich-text/index.js new file mode 100644 index 00000000000000..559641874c2e09 --- /dev/null +++ b/packages/block-editor/src/components/content-only-controls/rich-text/index.js @@ -0,0 +1,193 @@ +/** + * WordPress dependencies + */ +import { + BaseControl, + useBaseControlProps, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; +import { useMergeRefs } from '@wordpress/compose'; +import { useRegistry } from '@wordpress/data'; +import { useRef, useState } from '@wordpress/element'; +import { + __unstableUseRichText as useRichText, + isEmpty, + removeFormat, +} from '@wordpress/rich-text'; + +/** + * Internal dependencies + */ +import { useFormatTypes } from '../../rich-text/use-format-types'; +import { getAllowedFormats } from '../../rich-text/utils'; +import { useEventListeners } from '../../rich-text/event-listeners'; +import FormatEdit from '../../rich-text/format-edit'; +import { keyboardShortcutContext, inputEventContext } from '../../rich-text'; + +export default function RichTextControl( { + clientId, + control, + blockType, + attributeValues, + updateAttributes, +} ) { + const registry = useRegistry(); + const valueKey = control.mapping.value; + const attrValue = attributeValues[ valueKey ]; + const defaultValue = + blockType.attributes[ valueKey ]?.defaultValue ?? undefined; + const [ selection, setSelection ] = useState( { + start: undefined, + end: undefined, + } ); + const [ isSelected, setIsSelected ] = useState( false ); + const anchorRef = useRef(); + const inputEvents = useRef( new Set() ); + const keyboardShortcuts = useRef( new Set() ); + + const adjustedAllowedFormats = getAllowedFormats( { + allowedFormats: control.args?.allowedFormats, + disableFormats: control.args?.disableFormats, + } ); + + const { + formatTypes, + prepareHandlers, + valueHandlers, + changeHandlers, + dependencies, + } = useFormatTypes( { + clientId, + identifier: valueKey, + allowedFormats: adjustedAllowedFormats, + withoutInteractiveFormatting: + control.args?.withoutInteractiveFormatting, + disableNoneEssentialFormatting: true, + } ); + + function addEditorOnlyFormats( value ) { + return valueHandlers.reduce( + ( accumulator, fn ) => fn( accumulator, value.text ), + value.formats + ); + } + + function removeEditorOnlyFormats( value ) { + formatTypes.forEach( ( formatType ) => { + // Remove formats created by prepareEditableTree, because they are editor only. + if ( formatType.__experimentalCreatePrepareEditableTree ) { + value = removeFormat( + value, + formatType.name, + 0, + value.text.length + ); + } + } ); + + return value.formats; + } + + function addInvisibleFormats( value ) { + return prepareHandlers.reduce( + ( accumulator, fn ) => fn( accumulator, value.text ), + value.formats + ); + } + + function onFocus() { + anchorRef.current?.focus(); + } + + const { + value, + getValue, + onChange, + ref: richTextRef, + } = useRichText( { + value: attrValue, + onChange( html, { __unstableFormats, __unstableText } ) { + updateAttributes( { [ valueKey ]: html } ); + Object.values( changeHandlers ).forEach( ( changeHandler ) => { + changeHandler( __unstableFormats, __unstableText ); + } ); + }, + selectionStart: selection.start, + selectionEnd: selection.end, + onSelectionChange: ( start, end ) => setSelection( { start, end } ), + __unstableIsSelected: isSelected, + preserveWhiteSpace: !! control.args?.preserveWhiteSpace, + placeholder: control.args?.placeholder, + __unstableDisableFormats: control.args?.disableFormats, + __unstableDependencies: dependencies, + __unstableAfterParse: addEditorOnlyFormats, + __unstableBeforeSerialize: removeEditorOnlyFormats, + __unstableAddInvisibleFormats: addInvisibleFormats, + } ); + + const { baseControlProps, controlProps } = useBaseControlProps( { + hideLabelFromVision: control.shownByDefault, + label: control.label, + } ); + + return ( + { + return value?.text && ! isEmpty( value ); + } } + onDeselect={ () => + updateAttributes( { [ valueKey ]: defaultValue } ) + } + isShownByDefault={ control.shownByDefault } + > + { isSelected && ( + + +
+ +
+
+
+ ) } + +
setIsSelected( true ) } + onBlur={ () => setIsSelected( false ) } + contentEditable + { ...controlProps } + /> + + + ); +} diff --git a/packages/block-editor/src/components/content-only-controls/rich-text/styles.scss b/packages/block-editor/src/components/content-only-controls/rich-text/styles.scss new file mode 100644 index 00000000000000..a02d9a98e1aa4e --- /dev/null +++ b/packages/block-editor/src/components/content-only-controls/rich-text/styles.scss @@ -0,0 +1,24 @@ +@use "@wordpress/base-styles/colors" as *; +@use "@wordpress/base-styles/mixins" as *; +@use "@wordpress/base-styles/variables" as *; + +.block-editor-content-only-controls__rich-text { + width: 100%; + // Override input style margin in WP forms.css. + margin: 0; + background: $white; + color: $gray-900; + @include input-control( var(--wp-admin-theme-color, #3858e9) ); + border-color: $gray-600; + + &::placeholder { + color: color-mix(in srgb, $gray-900, transparent 38%); + } + + min-height: $grid-unit-50; + + // Subtract 1px to account for the border, which isn't included on the element + // on newer components like InputControl, SelectControl, etc. + // These values should be shared with the `controlPaddingX` in ./utils/config-values.js + padding: $grid-unit-15; +} diff --git a/packages/block-editor/src/components/content-only-controls/styles.scss b/packages/block-editor/src/components/content-only-controls/styles.scss new file mode 100644 index 00000000000000..71d76cdc25a8aa --- /dev/null +++ b/packages/block-editor/src/components/content-only-controls/styles.scss @@ -0,0 +1,35 @@ +@use "@wordpress/base-styles/colors" as *; +@use "@wordpress/base-styles/variables" as *; +@use "./link/styles.scss" as *; +@use "./media/styles.scss" as *; +@use "./rich-text/styles.scss" as *; + +.block-editor-content-only-controls__screen { + &.components-navigator-screen { + padding: $grid-unit-10 0 0 0; + } + + // Add border for the entire content controls and remove the similar border + // for tools panel. + border-top: $border-width solid $gray-200; + + + .components-tools-panel { + border-top: none; + + // Condense the tools panels more than normal. + padding-top: $grid-unit-10; + } +} + +.block-editor-content-only-controls__button-panel { + padding: 4px; + + // Match heading font weights for the tools panels. + font-weight: 500 !important; +} + +.block-editor-content-only-controls__back-button, +.block-editor-content-only-controls__drill-down-button { + width: 100%; +} diff --git a/packages/block-editor/src/components/content-only-controls/use-inspector-popover-placement.js b/packages/block-editor/src/components/content-only-controls/use-inspector-popover-placement.js new file mode 100644 index 00000000000000..4aac7451c7a2fb --- /dev/null +++ b/packages/block-editor/src/components/content-only-controls/use-inspector-popover-placement.js @@ -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, + }, + } + : {}; +} diff --git a/packages/block-editor/src/components/inspector-controls-tabs/content-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/content-tab.js index 5d03e5637be8c1..0b553ccda34957 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/content-tab.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/content-tab.js @@ -8,16 +8,24 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import BlockQuickNavigation from '../block-quick-navigation'; +import ContentOnlyControls from '../content-only-controls'; -const ContentTab = ( { contentClientIds } ) => { +const ContentTab = ( { rootClientId, contentClientIds } ) => { if ( ! contentClientIds || contentClientIds.length === 0 ) { return null; } return ( - - - + <> + { ! window?.__experimentalContentOnlyPatternInsertion && ( + + + + ) } + { window?.__experimentalContentOnlyPatternInsertion && ( + + ) } + ); }; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/index.js b/packages/block-editor/src/components/inspector-controls-tabs/index.js index ed24d56a08f13f..2f61027a232061 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/index.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/index.js @@ -109,7 +109,10 @@ export default function InspectorControlsTabs( { /> - + diff --git a/packages/block-editor/src/components/media-replace-flow/index.js b/packages/block-editor/src/components/media-replace-flow/index.js index eb4dcd0165d3c2..64280dde597d7c 100644 --- a/packages/block-editor/src/components/media-replace-flow/index.js +++ b/packages/block-editor/src/components/media-replace-flow/index.js @@ -93,6 +93,7 @@ const MediaReplaceFlow = ( { handleUpload = true, popoverProps, renderToggle, + className, } ) => { const { getSettings } = useSelect( blockEditorStore ); const errorNoticeID = `block-editor/media-replace-flow/error-notice/${ ++uniqueId }`; @@ -169,6 +170,7 @@ const MediaReplaceFlow = ( { return ( { if ( renderToggle ) { diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index 47c704f6d7b394..7502b2cf9dc207 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -40,6 +40,7 @@ import { globalStylesLinksDataKey, sectionRootClientIdKey, mediaEditKey, + getMediaSelectKey, essentialFormatKey, } from './store/private-keys'; import { requiresWrapperOnCopy } from './components/writing-flow/utils'; @@ -108,6 +109,7 @@ lock( privateApis, { CommentIconSlotFill, CommentIconToolbarSlotFill, mediaEditKey, + getMediaSelectKey, essentialFormatKey, useBlockElement, useBlockElementRef, diff --git a/packages/block-editor/src/store/private-keys.js b/packages/block-editor/src/store/private-keys.js index 8fde1a53aaa523..35ca6b1dd4d09c 100644 --- a/packages/block-editor/src/store/private-keys.js +++ b/packages/block-editor/src/store/private-keys.js @@ -4,4 +4,5 @@ export const selectBlockPatternsKey = Symbol( 'selectBlockPatternsKey' ); export const reusableBlocksSelectKey = Symbol( 'reusableBlocksSelect' ); export const sectionRootClientIdKey = Symbol( 'sectionRootClientIdKey' ); export const mediaEditKey = Symbol( 'mediaEditKey' ); +export const getMediaSelectKey = Symbol( 'getMediaSelect' ); export const essentialFormatKey = Symbol( 'essentialFormat' ); diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index aa303c90b0aa3c..e7a5f2c341e4c0 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -29,6 +29,7 @@ @use "./components/block-variation-transforms/style.scss" as *; @use "./components/border-radius-control/style.scss" as *; @use "./components/colors-gradients/style.scss" as *; +@use "./components/content-only-controls/styles.scss" as *; @use "./components/date-format-picker/style.scss" as *; @use "./components/duotone-control/style.scss" as *; @use "./components/font-appearance-control/style.scss" as *; diff --git a/packages/block-library/src/audio/index.js b/packages/block-library/src/audio/index.js index aa1bba1c341d2f..e37123683d9104 100644 --- a/packages/block-library/src/audio/index.js +++ b/packages/block-library/src/audio/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { audio as icon } from '@wordpress/icons'; /** @@ -31,4 +32,30 @@ export const settings = { save, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Audio' ), + type: 'Media', + shownByDefault: true, + mapping: { + id: 'id', + src: 'src', + }, + args: { + allowedTypes: [ 'audio' ], + multiple: false, + }, + }, + { + label: __( 'Caption' ), + type: 'RichText', + shownByDefault: false, + mapping: { + value: 'caption', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/button/index.js b/packages/block-library/src/button/index.js index 32212afc2eb706..4765fe5b089d66 100644 --- a/packages/block-library/src/button/index.js +++ b/packages/block-library/src/button/index.js @@ -34,4 +34,27 @@ export const settings = { } ), }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Content' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'text', + }, + }, + { + label: __( 'Link' ), + type: 'Link', + shownByDefault: false, + mapping: { + href: 'url', + rel: 'rel', + target: 'linkTarget', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/code/index.js b/packages/block-library/src/code/index.js index c602045256d6e9..e3a928461f82a1 100644 --- a/packages/block-library/src/code/index.js +++ b/packages/block-library/src/code/index.js @@ -39,4 +39,17 @@ export const settings = { save, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Code' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'content', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/cover/index.js b/packages/block-library/src/cover/index.js index 6dcac170b063b9..88777cc137524f 100644 --- a/packages/block-library/src/cover/index.js +++ b/packages/block-library/src/cover/index.js @@ -52,4 +52,27 @@ export const settings = { variations, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Background' ), + type: 'Media', + shownByDefault: true, + mapping: { + type: 'backgroundType', + id: 'id', + src: 'url', + alt: 'alt', + featuredImage: 'useFeaturedImage', + }, + args: { + // TODO - How to support custom gradient? + // Build it into Media, or use a custom control? + allowedTypes: [ 'image', 'video' ], + multiple: false, + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/details/index.js b/packages/block-library/src/details/index.js index 5785faec3932ed..8d33963fd9486b 100644 --- a/packages/block-library/src/details/index.js +++ b/packages/block-library/src/details/index.js @@ -61,4 +61,17 @@ export const settings = { transforms, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Summary' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'summary', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/file/index.js b/packages/block-library/src/file/index.js index 46b9691ea88a7a..c33cd03338be49 100644 --- a/packages/block-library/src/file/index.js +++ b/packages/block-library/src/file/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { _x } from '@wordpress/i18n'; +import { _x, __ } from '@wordpress/i18n'; import { file as icon } from '@wordpress/icons'; /** @@ -32,4 +32,38 @@ export const settings = { save, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'File' ), + type: 'Media', + shownByDefault: true, + mapping: { + id: 'id', + src: 'href', + }, + args: { + allowedTypes: [], + multiple: false, + }, + }, + { + label: __( 'Filename' ), + type: 'RichText', + shownByDefault: false, + mapping: { + value: 'fileName', + }, + }, + { + label: __( 'Button Text' ), + type: 'RichText', + shownByDefault: false, + mapping: { + value: 'downloadButtonText', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/heading/index.js b/packages/block-library/src/heading/index.js index 2ab252c6c9c18f..aec0fa016e3028 100644 --- a/packages/block-library/src/heading/index.js +++ b/packages/block-library/src/heading/index.js @@ -69,4 +69,17 @@ export const settings = { variations, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Content' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'content', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js index 68625bd77cc873..f389d26a1b0cd8 100644 --- a/packages/block-library/src/image/index.js +++ b/packages/block-library/src/image/index.js @@ -62,4 +62,51 @@ export const settings = { deprecated, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Image' ), + type: 'Media', + shownByDefault: true, + mapping: { + id: 'id', + src: 'url', + caption: 'caption', + alt: 'alt', + }, + args: { + allowedTypes: [ 'image' ], + multiple: false, + }, + }, + { + label: __( 'Link' ), + type: 'Link', + shownByDefault: false, + mapping: { + href: 'href', + rel: 'rel', + target: 'linkTarget', + destination: 'linkDestination', + }, + }, + { + label: __( 'Caption' ), + type: 'RichText', + shownByDefault: false, + mapping: { + value: 'caption', + }, + }, + { + label: __( 'Alt text' ), + type: 'PlainText', + shownByDefault: false, + mapping: { + value: 'alt', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/list-item/index.js b/packages/block-library/src/list-item/index.js index 07c5bb7fda9015..582f7c1db0218d 100644 --- a/packages/block-library/src/list-item/index.js +++ b/packages/block-library/src/list-item/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { listItem as icon } from '@wordpress/icons'; import { privateApis } from '@wordpress/block-editor'; @@ -32,4 +33,17 @@ export const settings = { [ unlock( privateApis ).requiresWrapperOnCopy ]: true, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Content' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'content', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/media-text/index.js b/packages/block-library/src/media-text/index.js index 373050cb77fd56..6bbc7a613b4523 100644 --- a/packages/block-library/src/media-text/index.js +++ b/packages/block-library/src/media-text/index.js @@ -50,4 +50,33 @@ export const settings = { deprecated, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Media' ), + type: 'Media', + shownByDefault: true, + mapping: { + id: 'mediaId', + type: 'mediaType', + src: 'mediaUrl', + }, + args: { + allowedTypes: [ 'image', 'video' ], + multiple: false, + }, + }, + { + label: __( 'Link' ), + type: 'Link', + shownByDefault: false, + mapping: { + href: 'href', + rel: 'rel', + target: 'linkTarget', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/more/index.js b/packages/block-library/src/more/index.js index b40bb2123bc727..2022d0db00ee02 100644 --- a/packages/block-library/src/more/index.js +++ b/packages/block-library/src/more/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { more as icon } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -35,4 +36,17 @@ export const settings = { save, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Content' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'customText', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/navigation-link/block.json b/packages/block-library/src/navigation-link/block.json index 5f2d10b97dabe8..99d893120cea1f 100644 --- a/packages/block-library/src/navigation-link/block.json +++ b/packages/block-library/src/navigation-link/block.json @@ -34,7 +34,8 @@ "default": false }, "url": { - "type": "string" + "type": "string", + "role": "content" }, "title": { "type": "string" diff --git a/packages/block-library/src/navigation-link/index.js b/packages/block-library/src/navigation-link/index.js index 3d1f0207ff955a..72c9cc67178aa9 100644 --- a/packages/block-library/src/navigation-link/index.js +++ b/packages/block-library/src/navigation-link/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { _x } from '@wordpress/i18n'; +import { _x, __ } from '@wordpress/i18n'; import { customLink as linkIcon } from '@wordpress/icons'; import { InnerBlocks } from '@wordpress/block-editor'; import { addFilter } from '@wordpress/hooks'; @@ -89,6 +89,29 @@ export const settings = { transforms, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Label' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'label', + }, + }, + { + label: __( 'Link' ), + type: 'Link', + shownByDefault: false, + mapping: { + href: 'url', + rel: 'rel', + // TODO - opens in new tab? id? + }, + }, + ]; +} + export const init = () => { addFilter( 'blocks.registerBlockType', diff --git a/packages/block-library/src/navigation-submenu/block.json b/packages/block-library/src/navigation-submenu/block.json index be56628e6142eb..33af205689b9a6 100644 --- a/packages/block-library/src/navigation-submenu/block.json +++ b/packages/block-library/src/navigation-submenu/block.json @@ -29,7 +29,8 @@ "default": false }, "url": { - "type": "string" + "type": "string", + "role": "content" }, "title": { "type": "string" diff --git a/packages/block-library/src/navigation-submenu/index.js b/packages/block-library/src/navigation-submenu/index.js index f5eb1e2346d76e..c72c891ac09dae 100644 --- a/packages/block-library/src/navigation-submenu/index.js +++ b/packages/block-library/src/navigation-submenu/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { page, addSubmenu } from '@wordpress/icons'; -import { _x } from '@wordpress/i18n'; +import { _x, __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -48,4 +48,27 @@ export const settings = { transforms, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Label' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'label', + }, + }, + { + label: __( 'Link' ), + type: 'Link', + shownByDefault: false, + mapping: { + href: 'url', + rel: 'rel', + // TODO - opens in new tab? id? + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index 249193e1cc234a..1315cce9318fbb 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -105,6 +105,7 @@ "supports": { "align": [ "wide", "full" ], "ariaLabel": true, + "contentRole": true, "html": false, "inserter": true, "typography": { @@ -138,8 +139,7 @@ } }, "interactivity": true, - "renaming": false, - "contentRole": true + "renaming": false }, "editorStyle": "wp-block-navigation-editor", "style": "wp-block-navigation" diff --git a/packages/block-library/src/paragraph/index.js b/packages/block-library/src/paragraph/index.js index a2328b1ecdf82d..a040f8d2273019 100644 --- a/packages/block-library/src/paragraph/index.js +++ b/packages/block-library/src/paragraph/index.js @@ -58,4 +58,17 @@ export const settings = { variations, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Content' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'content', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/preformatted/index.js b/packages/block-library/src/preformatted/index.js index a2e1fecc185528..ec41dc0e8e1f0a 100644 --- a/packages/block-library/src/preformatted/index.js +++ b/packages/block-library/src/preformatted/index.js @@ -39,4 +39,17 @@ export const settings = { }, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Content' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'content', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/pullquote/index.js b/packages/block-library/src/pullquote/index.js index 9d2b42f7698ab5..efdfdebdf5ed24 100644 --- a/packages/block-library/src/pullquote/index.js +++ b/packages/block-library/src/pullquote/index.js @@ -36,4 +36,25 @@ export const settings = { deprecated, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Content' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'value', + }, + }, + { + label: __( 'Citation' ), + type: 'RichText', + shownByDefault: false, + mapping: { + value: 'citation', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/search/index.js b/packages/block-library/src/search/index.js index 85770a23268cba..10ce7c6732ad16 100644 --- a/packages/block-library/src/search/index.js +++ b/packages/block-library/src/search/index.js @@ -26,4 +26,33 @@ export const settings = { edit, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Label' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'label', + }, + }, + { + label: __( 'Button text' ), + type: 'RichText', + shownByDefault: false, + mapping: { + value: 'buttonText', + }, + }, + { + label: __( 'Placeholder' ), + type: 'RichText', + shownByDefault: false, + mapping: { + value: 'placeholder', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/social-link/index.js b/packages/block-library/src/social-link/index.js index 161e6f2697acca..d601e36876df18 100644 --- a/packages/block-library/src/social-link/index.js +++ b/packages/block-library/src/social-link/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { share as icon } from '@wordpress/icons'; /** @@ -21,4 +22,26 @@ export const settings = { variations, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Link' ), + type: 'Link', + shownByDefault: true, + mapping: { + href: 'url', + rel: 'rel', + }, + }, + { + label: __( 'Label' ), + type: 'RichText', + shownByDefault: false, + mapping: { + value: 'label', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/verse/index.js b/packages/block-library/src/verse/index.js index 5d80b63a4ca84c..35a5b4dec27475 100644 --- a/packages/block-library/src/verse/index.js +++ b/packages/block-library/src/verse/index.js @@ -41,4 +41,17 @@ export const settings = { save, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Content' ), + type: 'RichText', + shownByDefault: true, + mapping: { + value: 'content', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/video/index.js b/packages/block-library/src/video/index.js index 57201ef7125658..79cc5bb52657a5 100644 --- a/packages/block-library/src/video/index.js +++ b/packages/block-library/src/video/index.js @@ -33,4 +33,32 @@ export const settings = { save, }; +if ( window.__experimentalContentOnlyPatternInsertion ) { + settings.fields = [ + { + label: __( 'Video' ), + type: 'Media', + shownByDefault: true, + mapping: { + id: 'id', + src: 'src', + caption: 'caption', + poster: 'poster', + }, + args: { + allowedTypes: [ 'video' ], + multiple: false, + }, + }, + { + label: __( 'Caption' ), + type: 'RichText', + shownByDefault: false, + mapping: { + value: 'caption', + }, + }, + ]; +} + export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 136bb88dd10fae..0a3ebb6df33d62 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -96,6 +96,7 @@ const { reusableBlocksSelectKey, sectionRootClientIdKey, mediaEditKey, + getMediaSelectKey, } = unlock( privateApis ); /** @@ -293,6 +294,13 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { hasFixedToolbar, isDistractionFree, keepCaretInsideBlock, + [ getMediaSelectKey ]: ( select, attachmentId ) => { + return select( coreStore ).getEntityRecord( + 'postType', + 'attachment', + attachmentId + ); + }, [ mediaEditKey ]: hasUploadPermissions ? editMediaEntity : undefined, diff --git a/packages/format-library/src/link/index.js b/packages/format-library/src/link/index.js index ec8eb1f0d32675..0c96dc6a0591aa 100644 --- a/packages/format-library/src/link/index.js +++ b/packages/format-library/src/link/index.js @@ -206,7 +206,7 @@ function Edit( { aria-expanded={ addingLink } /> ) } - { addingLink && ( + { isVisible && addingLink && (