Repository navigation
Patterns: Let users load synced patterns they can't edit, without "Edit original" #84641
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
Changes from 4 commits
8a7d9b5
aa343bc
d1842de
8e87bb8
1c141ce
7d12fa3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { createSlotFill } from '@wordpress/components'; | ||
|
|
||
| /** | ||
| * Where the block inspector shows how to edit the entity a synced pattern or | ||
| * template part block renders. Those entities are WordPress-specific, so the | ||
| * editor package fills it; the fill receives the block's `clientId`. | ||
| */ | ||
| export const PrivateBlockInspectorEditOriginal = createSlotFill( | ||
| Symbol( 'PrivateBlockInspectorEditOriginal' ) | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,7 +25,7 @@ import { | |
| InnerBlocks, | ||
| } from '@wordpress/block-editor'; | ||
| import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; | ||
| import { getBlockBindingsSource } from '@wordpress/blocks'; | ||
| import { getBlockBindingsSource, parse } from '@wordpress/blocks'; | ||
| import { unlock } from '../lock-unlock'; | ||
|
|
||
| const { useLayoutClasses } = unlock( blockEditorPrivateApis ); | ||
|
|
@@ -139,6 +139,7 @@ function ReusableBlockControl( { | |
| } | ||
|
|
||
| const EMPTY_OBJECT = {}; | ||
| const EMPTY_ARRAY = []; | ||
|
|
||
| function ReusableBlockEdit( { | ||
| name, | ||
|
|
@@ -151,10 +152,40 @@ function ReusableBlockEdit( { | |
| 'wp_block', | ||
| ref | ||
| ); | ||
| const [ blocks ] = useEntityBlockEditor( 'postType', 'wp_block', { | ||
| const [ editableBlocks ] = useEntityBlockEditor( 'postType', 'wp_block', { | ||
| id: ref, | ||
| } ); | ||
| const isMissing = hasResolved && ! record; | ||
|
|
||
| // A user who can read the pattern but not edit it can't load it in the | ||
| // `edit` context. Its `view` context has the content too, so render that. | ||
| const isUnavailableToEdit = hasResolved && ! record; | ||
|
Comment on lines
+161
to
+163
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the code check It would imply a situation where
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's avoid Maybe split rendering into two branches. I think Template Parts is already doing something similar. Cons: An extra
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kept the edit-context result as the signal. Checking
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! I tried the split locally (checking For users who can edit the pattern, it made the pattern about 0.9s slower to render (2.2s → 3.1s median from the first pattern request). Since users who can edit are the common case, I'd keep the current approach. The refused request only happens for read-only users, and only once per pattern. Happy to revisit if you'd rather avoid the 403 anyway. |
||
| const { readOnlyRecord, hasResolvedReadOnly } = useSelect( | ||
| ( select ) => { | ||
| if ( ! isUnavailableToEdit ) { | ||
| return {}; | ||
| } | ||
| const args = [ 'postType', 'wp_block', ref, { context: 'view' } ]; | ||
| const { getEntityRecord, hasFinishedResolution } = | ||
| select( coreStore ); | ||
| return { | ||
| readOnlyRecord: getEntityRecord( ...args ), | ||
| hasResolvedReadOnly: hasFinishedResolution( | ||
| 'getEntityRecord', | ||
| args | ||
| ), | ||
| }; | ||
| }, | ||
| [ isUnavailableToEdit, ref ] | ||
| ); | ||
|
fabiankaegy marked this conversation as resolved.
Resolved
|
||
| const readOnlyContent = readOnlyRecord?.content?.raw; | ||
| const readOnlyBlocks = useMemo( | ||
| () => ( readOnlyContent ? parse( readOnlyContent ) : EMPTY_ARRAY ), | ||
| [ readOnlyContent ] | ||
| ); | ||
| const blocks = readOnlyRecord ? readOnlyBlocks : editableBlocks; | ||
|
fabiankaegy marked this conversation as resolved.
Resolved
|
||
| const isResolving = | ||
| ! hasResolved || ( isUnavailableToEdit && ! hasResolvedReadOnly ); | ||
|
fabiankaegy marked this conversation as resolved.
Resolved
|
||
| const isMissing = isUnavailableToEdit && ! isResolving && ! readOnlyRecord; | ||
|
fabiankaegy marked this conversation as resolved.
Resolved
|
||
|
|
||
| const { __unstableMarkLastChangeAsPersistent } = | ||
| useDispatch( blockEditorStore ); | ||
|
|
@@ -238,7 +269,7 @@ function ReusableBlockEdit( { | |
| ); | ||
| } | ||
|
|
||
| if ( ! hasResolved ) { | ||
| if ( isResolving ) { | ||
| children = ( | ||
| <Placeholder> | ||
| <Spinner /> | ||
|
|
@@ -248,7 +279,7 @@ function ReusableBlockEdit( { | |
|
|
||
| return ( | ||
| <> | ||
| { hasResolved && ! isMissing && ( | ||
| { ! isResolving && ! isMissing && ( | ||
| <ReusableBlockControl | ||
| recordId={ ref } | ||
| canOverrideBlocks={ canOverrideBlocks } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| import { Button } from '@wordpress/components'; | ||
| import { Stack } from '@wordpress/ui'; | ||
| import { __ } from '@wordpress/i18n'; | ||
| import { useSelect } from '@wordpress/data'; | ||
| import { isTemplatePart } from '@wordpress/blocks'; | ||
| import { | ||
| privateApis as blockEditorPrivateApis, | ||
| store as blockEditorStore, | ||
| // @ts-expect-error No exported types | ||
| } from '@wordpress/block-editor'; | ||
| import { store as coreStore } from '@wordpress/core-data'; | ||
| import { store as editorStore } from '../../store'; | ||
| import { unlock } from '../../lock-unlock'; | ||
|
|
||
| const { PrivateBlockInspectorEditOriginal } = unlock( blockEditorPrivateApis ); | ||
|
|
||
| interface EditorSettings { | ||
| onNavigateToEntityRecord?: ( entity: { | ||
| postId: number | string; | ||
| postType: string; | ||
| } ) => void; | ||
| } | ||
|
|
||
| interface EditOriginalButtonProps { | ||
| /** Client ID of the synced pattern or template part block. */ | ||
| clientId: string; | ||
| } | ||
|
|
||
| /** | ||
| * The block inspector's "Edit original" button for synced patterns and | ||
| * template parts, which opens the entity the block renders. | ||
| */ | ||
| export default function EditOriginalInspectorControls() { | ||
| return ( | ||
| <PrivateBlockInspectorEditOriginal.Fill> | ||
| { ( { clientId }: EditOriginalButtonProps ) => ( | ||
| <EditOriginalButton clientId={ clientId } /> | ||
| ) } | ||
| </PrivateBlockInspectorEditOriginal.Fill> | ||
| ); | ||
| } | ||
|
|
||
| function EditOriginalButton( { clientId }: EditOriginalButtonProps ) { | ||
| const { postType, postId, canUserEdit, onNavigateToEntityRecord } = | ||
| useSelect( | ||
| ( select ) => { | ||
| const block = select( blockEditorStore ).getBlock( clientId ); | ||
| // Editor settings are typed as a bare `Object`. | ||
| const editorSettings = select( | ||
| editorStore | ||
| ).getEditorSettings() as EditorSettings; | ||
| const { ref, theme, slug } = block?.attributes ?? {}; | ||
| const isTemplatePartBlock = !! block && isTemplatePart( block ); | ||
| const _postType = isTemplatePartBlock | ||
| ? 'wp_template_part' | ||
| : 'wp_block'; | ||
| const _postId = isTemplatePartBlock | ||
| ? theme && slug && `${ theme }//${ slug }` | ||
| : ref; | ||
|
|
||
| return { | ||
| postType: _postType, | ||
| postId: _postId, | ||
| // Matches the block toolbar's "Edit original" button. | ||
| canUserEdit: | ||
| ! _postId || | ||
| !! select( coreStore ).canUser( 'update', { | ||
| kind: 'postType', | ||
| name: _postType, | ||
| id: _postId, | ||
| } ), | ||
| onNavigateToEntityRecord: | ||
| editorSettings.onNavigateToEntityRecord, | ||
| }; | ||
| }, | ||
| [ clientId ] | ||
| ); | ||
|
|
||
| if ( ! canUserEdit || ! onNavigateToEntityRecord ) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <Stack | ||
| direction="column" | ||
| className="block-editor-block-inspector-edit-contents" | ||
| > | ||
| <Button | ||
| className="block-editor-block-inspector-edit-contents__button" | ||
| __next40pxDefaultSize | ||
| variant="secondary" | ||
| onClick={ () => | ||
| onNavigateToEntityRecord( { postId, postType } ) | ||
| } | ||
| accessibleWhenDisabled | ||
| disabled={ ! postId } | ||
| > | ||
| { __( 'Edit original' ) } | ||
| </Button> | ||
| </Stack> | ||
| ); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.