diff --git a/backport-changelog/6.8/7695.md b/backport-changelog/6.8/7695.md new file mode 100644 index 0000000000000..095c058e6fd10 --- /dev/null +++ b/backport-changelog/6.8/7695.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7695 + +* https://github.com/WordPress/gutenberg/pull/66631 diff --git a/backport-changelog/6.8/7697.md b/backport-changelog/6.8/7697.md new file mode 100644 index 0000000000000..e53fbf4bdb8ae --- /dev/null +++ b/backport-changelog/6.8/7697.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7697 + +* https://github.com/WordPress/gutenberg/pull/66656 diff --git a/docs/reference-guides/data/data-core-edit-post.md b/docs/reference-guides/data/data-core-edit-post.md index e3fb22a1a7afe..06fe5fc30420a 100644 --- a/docs/reference-guides/data/data-core-edit-post.md +++ b/docs/reference-guides/data/data-core-edit-post.md @@ -528,6 +528,10 @@ _Parameters_ - _feature_ `string`: Feature name. +### toggleFullscreenMode + +Action that toggles the Fullscreen Mode view option. + ### togglePinnedPluginItem Triggers an action object used to toggle a plugin name flag. diff --git a/docs/reference-guides/data/data-core-editor.md b/docs/reference-guides/data/data-core-editor.md index ac3413e694877..713a247d88be2 100644 --- a/docs/reference-guides/data/data-core-editor.md +++ b/docs/reference-guides/data/data-core-editor.md @@ -1585,6 +1585,11 @@ _Related_ Action that toggles Distraction free mode. Distraction free mode expects there are no sidebars, as due to the z-index values set, you can't close sidebars. +_Parameters_ + +- _options_ `[Object]`: Optional configuration object +- _options.createNotice_ `[boolean]`: Whether to create a notice + ### toggleEditorPanelEnabled Returns an action object used to enable or disable a panel in the editor. @@ -1619,6 +1624,14 @@ _Related_ - toggleSelection in core/block-editor store. +### toggleSpotlightMode + +Action that toggles the Spotlight Mode view option. + +### toggleTopToolbar + +Action that toggles the Top Toolbar view option. + ### trashPost Action for trashing the current post in the editor. diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 09f388e7d3b93..dafa8b25f278f 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -3297,6 +3297,10 @@ public function merge( $incoming ) { array(), array( 'include_node_paths_only' => true ) ); + + // Add top-level styles. + $style_nodes[] = array( 'path' => array( 'styles' ) ); + foreach ( $style_nodes as $style_node ) { $path = $style_node['path']; /* diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index 1a8e7f5741c0a..e739808000c5f 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -59,6 +59,16 @@ function gutenberg_add_format_query_vars_to_query_loop_block( $query, $block ) { return $query; } + // Return early if the query already contains a post format. This is to avoid duplication if the + // WordPress core filter is already applied. + if ( ! empty( $query['tax_query'] ) ) { + foreach ( $query['tax_query'] as $taxquery ) { + if ( isset( $taxquery['taxonomy'] ) && 'post_format' === $taxquery['taxonomy'] ) { + return $query; + } + } + } + $formats = $block->context['query']['format']; /* * Validate that the format is either `standard` or a supported post format. diff --git a/lib/compat/wordpress-6.8/preload.php b/lib/compat/wordpress-6.8/preload.php index 0805ace4233d5..aabe0d4fb574c 100644 --- a/lib/compat/wordpress-6.8/preload.php +++ b/lib/compat/wordpress-6.8/preload.php @@ -10,7 +10,13 @@ */ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) { if ( 'core/edit-site' === $context->name ) { - // Core already preloads both of these for `core/edit-post`. + if ( ! empty( $_GET['postId'] ) ) { + $route_for_post = rest_get_route_for_post( $_GET['postId'] ); + if ( $route_for_post ) { + $paths[] = add_query_arg( 'context', 'edit', $route_for_post ); + } + } + $paths[] = '/wp/v2/settings'; $paths[] = array( '/wp/v2/settings', 'OPTIONS' ); $paths[] = '/?_fields=' . implode( @@ -28,6 +34,8 @@ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) { 'url', ) ); + $paths[] = '/wp/v2/templates/lookup?slug=front-page'; + $paths[] = '/wp/v2/templates/lookup?slug=home'; } // Preload theme and global styles paths. diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index 0791dc909d5be..66a711454eb79 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -57,6 +57,19 @@ } } +// The black plus that shows up on the right side of an empty paragraph block, +// or the initial appender that exists only on empty documents. +.block-editor-block-list__empty-block-inserter.block-editor-block-list__empty-block-inserter { + position: absolute; + top: 0; + right: 0; + line-height: 0; + + &:disabled { + display: none; + } +} + // Sibling inserter / "inbetweenserter". .block-editor-block-list__empty-block-inserter, .block-editor-block-list__insertion-point-inserter { diff --git a/packages/block-editor/src/components/default-block-appender/content.scss b/packages/block-editor/src/components/default-block-appender/content.scss index 51e0b4381a15d..3a1bf9889d748 100644 --- a/packages/block-editor/src/components/default-block-appender/content.scss +++ b/packages/block-editor/src/components/default-block-appender/content.scss @@ -62,24 +62,12 @@ } } -// The black plus that shows up on the right side of an empty paragraph block, or the initial appender -// that exists only on empty documents. -.block-editor-block-list__empty-block-inserter.block-editor-block-list__empty-block-inserter, -.block-editor-default-block-appender .block-editor-inserter { - position: absolute; - top: 0; - right: 0; - line-height: 0; - - &:disabled { - display: none; - } -} - /** - * Fixed position appender. - * These styles apply to all in-canvas inserters that exist inside nesting containers. + * Fixed position appender (right bottom corner). + * + * These styles apply to all in-canvas inserters. All in-canvas inserters always + * exist within a block. */ .block-editor-block-list__block .block-list-appender { @@ -96,6 +84,10 @@ line-height: 0; } + .block-editor-inserter:disabled { + display: none; + } + .block-editor-default-block-appender { height: $button-size-small; } diff --git a/packages/block-editor/src/components/iframe/get-compatibility-styles.js b/packages/block-editor/src/components/iframe/get-compatibility-styles.js index 0eae82b11e01f..fd14e02a219bf 100644 --- a/packages/block-editor/src/components/iframe/get-compatibility-styles.js +++ b/packages/block-editor/src/components/iframe/get-compatibility-styles.js @@ -37,15 +37,10 @@ export function getCompatibilityStyles() { return accumulator; } - // Don't try to add the reset styles, which were removed as a dependency - // from `edit-blocks` for the iframe since we don't need to reset admin - // styles. - if ( - [ - 'wp-reset-editor-styles-css', - 'wp-reset-editor-styles-rtl-css', - ].includes( ownerNode.id ) - ) { + // Don't try to add core WP styles. We are responsible for adding + // them. This compatibility layer is only meant to add styles added + // by plugins or themes. + if ( ownerNode.id.startsWith( 'wp-' ) ) { return accumulator; } diff --git a/packages/block-library/src/comments/block.json b/packages/block-library/src/comments/block.json index cbdf5b3725b84..ceb8f750c3472 100644 --- a/packages/block-library/src/comments/block.json +++ b/packages/block-library/src/comments/block.json @@ -17,10 +17,7 @@ } }, "supports": { - "align": [ - "wide", - "full" - ], + "align": [ "wide", "full" ], "html": false, "color": { "gradients": true, @@ -63,8 +60,5 @@ } }, "editorStyle": "wp-block-comments-editor", - "usesContext": [ - "postId", - "postType" - ] -} \ No newline at end of file + "usesContext": [ "postId", "postType" ] +} diff --git a/packages/block-library/src/post-template/block.json b/packages/block-library/src/post-template/block.json index 6a57585558352..da576a83312a4 100644 --- a/packages/block-library/src/post-template/block.json +++ b/packages/block-library/src/post-template/block.json @@ -13,7 +13,8 @@ "displayLayout", "templateSlug", "previewPostType", - "enhancedPagination" + "enhancedPagination", + "postType" ], "supports": { "reusable": false, diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index a9e279ee2c305..b28c726acfc77 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -100,6 +100,7 @@ export default function PostTemplateEdit( { } = {}, templateSlug, previewPostType, + postType: postTypeFromContext, }, attributes: { layout }, __unstableLayoutClassNames, @@ -186,7 +187,10 @@ export default function PostTemplateEdit( { } // When we preview Query Loop blocks we should prefer the current // block's postType, which is passed through block context. - const usedPostType = previewPostType || postType; + const usedPostType = + postTypeFromContext && postTypeFromContext !== 'page' + ? postTypeFromContext + : previewPostType || postType; return { posts: getEntityRecords( 'postType', usedPostType, { ...query, diff --git a/packages/block-library/src/query/block.json b/packages/block-library/src/query/block.json index b2225192c6b21..2379a1d1da53c 100644 --- a/packages/block-library/src/query/block.json +++ b/packages/block-library/src/query/block.json @@ -42,7 +42,7 @@ "default": false } }, - "usesContext": [ "postType" ], + "usesContext": [ "postType", "templateSlug" ], "providesContext": { "queryId": "queryId", "query": "query", diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index 06105bef4e026..bc900c073f648 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -45,9 +45,15 @@ import { useToolsPanelDropdownMenuProps } from '../../../utils/hooks'; const { BlockInfo } = unlock( blockEditorPrivateApis ); export default function QueryInspectorControls( props ) { - const { attributes, setQuery, setDisplayLayout, isTemplate } = props; - const { query, displayLayout } = attributes; const { + attributes, + setQuery, + setDisplayLayout, + postTypeFromContext, + isSingular, + } = props; + const { query, displayLayout } = attributes; + let { order, orderBy, author: authorIds, @@ -61,6 +67,16 @@ export default function QueryInspectorControls( props ) { parents, format, } = query; + // If a post type is set in context, update `postType` to match it, + // unless the post type is `page`, as it usually doesn't make sense to loop + // through pages. + if ( + postTypeFromContext && + postTypeFromContext !== 'page' && + postTypeFromContext !== postType + ) { + postType = postTypeFromContext; + } const allowedControls = useAllowedControls( attributes ); const showSticky = postType === 'post'; const { @@ -118,7 +134,7 @@ export default function QueryInspectorControls( props ) { }, [ querySearch, onChangeDebounced ] ); const showInheritControl = - isTemplate && isControlAllowed( allowedControls, 'inherit' ); + ! isSingular && isControlAllowed( allowedControls, 'inherit' ); const showPostTypeControl = ! inherit && isControlAllowed( allowedControls, 'postType' ); const postTypeControlLabel = __( 'Post type' ); diff --git a/packages/block-library/src/query/edit/query-content.js b/packages/block-library/src/query/edit/query-content.js index 8b3ff09b17934..bdfcbe0498ec4 100644 --- a/packages/block-library/src/query/edit/query-content.js +++ b/packages/block-library/src/query/edit/query-content.js @@ -22,6 +22,7 @@ import EnhancedPaginationControl from './inspector-controls/enhanced-pagination- import QueryToolbar from './query-toolbar'; import QueryInspectorControls from './inspector-controls'; import EnhancedPaginationModal from './enhanced-pagination-modal'; +import { getQueryContextFromTemplate } from '../utils'; const DEFAULTS_POSTS_PER_PAGE = 3; @@ -42,7 +43,8 @@ export default function QueryContent( { tagName: TagName = 'div', query: { inherit } = {}, } = attributes; - const { postType } = context; + const { templateSlug, postType } = context; + const { isSingular } = getQueryContextFromTemplate( templateSlug ); const { __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore ); const instanceId = useInstanceId( QueryContent ); @@ -50,16 +52,6 @@ export default function QueryContent( { const innerBlocksProps = useInnerBlocksProps( blockProps, { template: TEMPLATE, } ); - const isTemplate = useSelect( - ( select ) => { - const currentTemplate = - select( coreStore ).__experimentalGetTemplateForLink()?.type; - const isInTemplate = 'wp_template' === currentTemplate; - const isInSingularContent = postType !== undefined; - return isInTemplate && ! isInSingularContent; - }, - [ postType ] - ); const { postsPerPage } = useSelect( ( select ) => { const { getSettings } = select( blockEditorStore ); const { getEntityRecord, getEntityRecordEdits, canUser } = @@ -106,9 +98,9 @@ export default function QueryContent( { } else if ( ! query.perPage && postsPerPage ) { newQuery.perPage = postsPerPage; } - // We need to reset the `inherit` value if not in a template, as queries - // are not inherited when outside a template (e.g. when in singular content). - if ( ! isTemplate && query.inherit ) { + // We need to reset the `inherit` value if in a singular template, as queries + // are not inherited when in singular content (e.g. post, page, 404, blank). + if ( isSingular && query.inherit ) { newQuery.inherit = false; } if ( !! Object.keys( newQuery ).length ) { @@ -117,10 +109,10 @@ export default function QueryContent( { } }, [ query.perPage, + query.inherit, postsPerPage, inherit, - isTemplate, - query.inherit, + isSingular, __unstableMarkNextChangeAsNotPersistent, updateQuery, ] ); @@ -167,7 +159,8 @@ export default function QueryContent( { setDisplayLayout={ updateDisplayLayout } setAttributes={ setAttributes } clientId={ clientId } - isTemplate={ isTemplate } + postTypeFromContext={ postType } + isSingular={ isSingular } /> diff --git a/packages/block-library/src/query/test/utils.js b/packages/block-library/src/query/test/utils.js index 1b71185008677..8c2d88ade8051 100644 --- a/packages/block-library/src/query/test/utils.js +++ b/packages/block-library/src/query/test/utils.js @@ -2,7 +2,11 @@ * Internal dependencies */ import { terms } from './fixtures'; -import { getEntitiesInfo, getValueFromObjectPath } from '../utils'; +import { + getEntitiesInfo, + getValueFromObjectPath, + getQueryContextFromTemplate, +} from '../utils'; describe( 'Query block utils', () => { describe( 'getEntitiesInfo', () => { @@ -61,4 +65,58 @@ describe( 'Query block utils', () => { expect( result ).toBe( 'test' ); } ); } ); + + describe( 'getQueryContextFromTemplate', () => { + it( 'should return the correct query context based on template slug', () => { + expect( getQueryContextFromTemplate() ).toStrictEqual( { + isSingular: true, + } ); + expect( getQueryContextFromTemplate( '404' ) ).toStrictEqual( { + isSingular: true, + templateType: '404', + } ); + expect( getQueryContextFromTemplate( 'blank' ) ).toStrictEqual( { + isSingular: true, + templateType: 'blank', + } ); + expect( getQueryContextFromTemplate( 'single' ) ).toStrictEqual( { + isSingular: true, + templateType: 'single', + } ); + expect( + getQueryContextFromTemplate( 'single-film' ) + ).toStrictEqual( { + isSingular: true, + templateType: 'single', + } ); + expect( getQueryContextFromTemplate( 'page' ) ).toStrictEqual( { + isSingular: true, + templateType: 'page', + } ); + expect( getQueryContextFromTemplate( 'wp' ) ).toStrictEqual( { + isSingular: true, + templateType: 'custom', + } ); + expect( getQueryContextFromTemplate( 'category' ) ).toStrictEqual( { + isSingular: false, + templateType: 'category', + } ); + expect( + getQueryContextFromTemplate( 'category-dog' ) + ).toStrictEqual( { + isSingular: false, + templateType: 'category', + } ); + expect( getQueryContextFromTemplate( 'archive' ) ).toStrictEqual( { + isSingular: false, + templateType: 'archive', + } ); + expect( + getQueryContextFromTemplate( 'archive-film' ) + ).toStrictEqual( { + isSingular: false, + templateType: 'archive', + } ); + } ); + } ); } ); diff --git a/packages/block-library/src/query/utils.js b/packages/block-library/src/query/utils.js index 68da2573bab0f..fc22ca46d471c 100644 --- a/packages/block-library/src/query/utils.js +++ b/packages/block-library/src/query/utils.js @@ -435,3 +435,32 @@ export const useUnsupportedBlocks = ( clientId ) => { [ clientId ] ); }; + +/** + * Helper function that returns the query context from the editor based on the + * available template slug. + * + * @param {string} templateSlug Current template slug based on context. + * @return {Object} An object with isSingular and templateType properties. + */ +export function getQueryContextFromTemplate( templateSlug ) { + // In the Post Editor, the template slug is not available. + if ( ! templateSlug ) { + return { isSingular: true }; + } + let isSingular = false; + let templateType = templateSlug === 'wp' ? 'custom' : templateSlug; + const singularTemplates = [ '404', 'blank', 'single', 'page', 'custom' ]; + const templateTypeFromSlug = templateSlug.includes( '-' ) + ? templateSlug.split( '-', 1 )[ 0 ] + : templateSlug; + const queryFromTemplateSlug = templateSlug.includes( '-' ) + ? templateSlug.split( '-' ).slice( 1 ).join( '-' ) + : ''; + if ( queryFromTemplateSlug ) { + templateType = templateTypeFromSlug; + } + isSingular = singularTemplates.includes( templateType ); + + return { isSingular, templateType }; +} diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 5fd48336f956c..4b0da1d669ece 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Deprecations + +- `Radio`: Deprecate 36px default size ([#66572](https://github.com/WordPress/gutenberg/pull/66572)). + ### Enhancements - `MenuItem`: Add 40px size prop on Button ([#66596](https://github.com/WordPress/gutenberg/pull/66596)). diff --git a/packages/components/src/radio-group/README.md b/packages/components/src/radio-group/README.md index 1c24d49f1169d..78cff48559727 100644 --- a/packages/components/src/radio-group/README.md +++ b/packages/components/src/radio-group/README.md @@ -57,10 +57,10 @@ const MyControlledRadioRadioGroup = () => { const [ checked, setChecked ] = useState( '25' ); return ( - 25% - 50% - 75% - 100% + 25% + 50% + 75% + 100% ); }; @@ -80,10 +80,10 @@ import { const MyUncontrolledRadioRadioGroup = () => { return ( - 25% - 50% - 75% - 100% + 25% + 50% + 75% + 100% ); }; diff --git a/packages/components/src/radio-group/radio.tsx b/packages/components/src/radio-group/radio.tsx index 50a5a2647b39d..782a737b6ba28 100644 --- a/packages/components/src/radio-group/radio.tsx +++ b/packages/components/src/radio-group/radio.tsx @@ -16,6 +16,7 @@ import Button from '../button'; import { RadioGroupContext } from './context'; import type { WordPressComponentProps } from '../context'; import type { RadioProps } from './types'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; function UnforwardedRadio( { @@ -30,6 +31,12 @@ function UnforwardedRadio( const selectedValue = useStoreState( store, 'value' ); const isChecked = selectedValue !== undefined && selectedValue === value; + maybeWarnDeprecated36pxSize( { + componentName: 'Radio', + size: undefined, + __next40pxDefaultSize: props.__next40pxDefaultSize, + } ); + return ( = { parameters: { actions: { argTypesRegex: '^on.*' }, controls: { expanded: true }, - docs: { canvas: { sourceState: 'shown' } }, + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'This component is deprecated. Use `RadioControl` or `ToggleGroupControl` instead.', + }, + }, }, }; export default meta; @@ -44,9 +50,15 @@ Default.args = { defaultChecked: 'option2', children: ( <> - Option 1 - Option 2 - Option 3 + + Option 1 + + + Option 2 + + + Option 3 + ), }; diff --git a/packages/components/src/radio-group/types.ts b/packages/components/src/radio-group/types.ts index 6422cf4b275f0..c72917a33faa4 100644 --- a/packages/components/src/radio-group/types.ts +++ b/packages/components/src/radio-group/types.ts @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import type { ButtonProps } from '../button/types'; + export type RadioGroupProps = { /** * Accessible label for the radio group @@ -27,7 +32,7 @@ export type RadioGroupProps = { children: React.ReactNode; }; -export type RadioProps = { +export type RadioProps = Pick< ButtonProps, '__next40pxDefaultSize' > & { /** * The actual value of the radio element. */ diff --git a/packages/components/src/utils/deprecated-36px-size.ts b/packages/components/src/utils/deprecated-36px-size.ts index e28e9eebb31a9..be5baa2515637 100644 --- a/packages/components/src/utils/deprecated-36px-size.ts +++ b/packages/components/src/utils/deprecated-36px-size.ts @@ -10,9 +10,12 @@ export function maybeWarnDeprecated36pxSize( { }: { componentName: string; __next40pxDefaultSize: boolean | undefined; - size: string; + size: string | undefined; } ) { - if ( __next40pxDefaultSize || size !== 'default' ) { + if ( + __next40pxDefaultSize || + ( size !== undefined && size !== 'default' ) + ) { return; } diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index d3980b89e8fc5..ae0c7f456e533 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -563,58 +563,6 @@ export const getAutosave = await resolveSelect.getAutosaves( postType, postId ); }; -/** - * Retrieve the frontend template used for a given link. - * - * @param {string} link Link. - */ -export const __experimentalGetTemplateForLink = - ( link ) => - async ( { dispatch, resolveSelect } ) => { - let template; - try { - // This is NOT calling a REST endpoint but rather ends up with a response from - // an Ajax function which has a different shape from a WP_REST_Response. - template = await apiFetch( { - url: addQueryArgs( link, { - '_wp-find-template': true, - } ), - } ).then( ( { data } ) => data ); - } catch ( e ) { - // For non-FSE themes, it is possible that this request returns an error. - } - - if ( ! template ) { - return; - } - - const record = await resolveSelect.getEntityRecord( - 'postType', - 'wp_template', - template.id - ); - - if ( record ) { - dispatch.receiveEntityRecords( - 'postType', - 'wp_template', - [ record ], - { - 'find-template': link, - } - ); - } - }; - -__experimentalGetTemplateForLink.shouldInvalidate = ( action ) => { - return ( - ( action.type === 'RECEIVE_ITEMS' || action.type === 'REMOVE_ITEMS' ) && - action.invalidateCache && - action.kind === 'postType' && - action.name === 'wp_template' - ); -}; - export const __experimentalGetCurrentGlobalStylesId = () => async ( { dispatch, resolveSelect } ) => { @@ -801,13 +749,27 @@ export const getNavigationFallbackId = export const getDefaultTemplateId = ( query ) => - async ( { dispatch } ) => { + async ( { dispatch, registry, resolveSelect } ) => { const template = await apiFetch( { path: addQueryArgs( '/wp/v2/templates/lookup', query ), } ); + // Wait for the the entities config to be loaded, otherwise receiving + // the template as an entity will not work. + await resolveSelect.getEntitiesConfig( 'postType' ); // Endpoint may return an empty object if no template is found. if ( template?.id ) { - dispatch.receiveDefaultTemplateId( query, template.id ); + registry.batch( () => { + dispatch.receiveDefaultTemplateId( query, template.id ); + dispatch.receiveEntityRecords( 'postType', 'wp_template', [ + template, + ] ); + // Avoid further network requests. + dispatch.finishResolution( 'getEntityRecord', [ + 'postType', + 'wp_template', + template.id, + ] ); + } ); } }; diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 9acadd5c0c0e7..7ea8c2f7f26d5 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -1284,38 +1284,6 @@ export function getReferenceByDistinctEdits( state ) { return state.editsReference; } -/** - * Retrieve the frontend template used for a given link. - * - * @param state Editor state. - * @param link Link. - * - * @return The template record. - */ -export function __experimentalGetTemplateForLink( - state: State, - link: string -): Optional< ET.Updatable< ET.WpTemplate > > | null | false { - const records = getEntityRecords< ET.WpTemplate >( - state, - 'postType', - 'wp_template', - { - 'find-template': link, - } - ); - - if ( records?.length ) { - return getEditedEntityRecord< ET.WpTemplate >( - state, - 'postType', - 'wp_template', - records[ 0 ].id - ); - } - return null; -} - /** * Retrieve the current theme's base global styles * diff --git a/packages/dataviews/src/components/dataform-combined-edit/style.scss b/packages/dataviews/src/components/dataform-combined-edit/style.scss index 0b59cbc9a4776..97e052ed89798 100644 --- a/packages/dataviews/src/components/dataform-combined-edit/style.scss +++ b/packages/dataviews/src/components/dataform-combined-edit/style.scss @@ -9,4 +9,8 @@ &__field { flex: 1 1 auto; } + + p.components-base-control__help:has(.components-checkbox-control__help) { + margin-top: $grid-unit-05; + } } diff --git a/packages/edit-post/src/components/keyboard-shortcuts/index.js b/packages/edit-post/src/components/keyboard-shortcuts/index.js index 3d1bc9d3350da..8567b541d03ab 100644 --- a/packages/edit-post/src/components/keyboard-shortcuts/index.js +++ b/packages/edit-post/src/components/keyboard-shortcuts/index.js @@ -15,7 +15,7 @@ import { __ } from '@wordpress/i18n'; import { store as editPostStore } from '../../store'; function KeyboardShortcuts() { - const { toggleFeature } = useDispatch( editPostStore ); + const { toggleFullscreenMode } = useDispatch( editPostStore ); const { registerShortcut } = useDispatch( keyboardShortcutsStore ); useEffect( () => { @@ -31,7 +31,7 @@ function KeyboardShortcuts() { }, [] ); useShortcut( 'core/edit-post/toggle-fullscreen', () => { - toggleFeature( 'fullscreenMode' ); + toggleFullscreenMode(); } ); return null; diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 7812280795aad..b50e17054fd3e 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -424,9 +424,7 @@ function Layout( { !! select( blockEditorStore ).getBlockSelectionStart(), showIconLabels: get( 'core', 'showIconLabels' ), isDistractionFree: get( 'core', 'distractionFree' ), - showMetaBoxes: - ! DESIGN_POST_TYPES.includes( currentPostType ) && - isRenderingPostOnly, + showMetaBoxes: ! DESIGN_POST_TYPES.includes( currentPostType ), isWelcomeGuideVisible: isFeatureActive( 'welcomeGuide' ), templateId: supportsTemplateMode && diff --git a/packages/edit-post/src/components/more-menu/index.js b/packages/edit-post/src/components/more-menu/index.js index 136742a19dddd..026354e681349 100644 --- a/packages/edit-post/src/components/more-menu/index.js +++ b/packages/edit-post/src/components/more-menu/index.js @@ -4,8 +4,8 @@ import { __ } from '@wordpress/i18n'; import { useViewportMatch } from '@wordpress/compose'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; -import { PreferenceToggleMenuItem } from '@wordpress/preferences'; import { displayShortcut } from '@wordpress/keycodes'; +import { PreferenceToggleMenuItem } from '@wordpress/preferences'; /** * Internal dependencies @@ -29,9 +29,9 @@ const MoreMenu = () => { name="fullscreenMode" label={ __( 'Fullscreen mode' ) } info={ __( 'Show and hide the admin user interface' ) } - messageActivated={ __( 'Fullscreen mode activated' ) } + messageActivated={ __( 'Fullscreen mode activated.' ) } messageDeactivated={ __( - 'Fullscreen mode deactivated' + 'Fullscreen mode deactivated.' ) } shortcut={ displayShortcut.secondary( 'f' ) } /> diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index 7ab0a965379be..85702ae162285 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -10,6 +10,8 @@ import { import deprecated from '@wordpress/deprecated'; import { addAction } from '@wordpress/hooks'; import { store as coreStore } from '@wordpress/core-data'; +import { store as noticesStore } from '@wordpress/notices'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -509,3 +511,44 @@ export const toggleDistractionFree = } ); registry.dispatch( editorStore ).toggleDistractionFree(); }; + +/** + * Action that toggles the Fullscreen Mode view option. + */ +export const toggleFullscreenMode = + () => + ( { registry } ) => { + const isFullscreen = registry + .select( preferencesStore ) + .get( 'core/edit-post', 'fullscreenMode' ); + + registry + .dispatch( preferencesStore ) + .toggle( 'core/edit-post', 'fullscreenMode' ); + + registry + .dispatch( noticesStore ) + .createInfoNotice( + isFullscreen + ? __( 'Fullscreen mode activated.' ) + : __( 'Fullscreen mode deactivated.' ), + { + id: 'core/edit-post/toggle-fullscreen-mode/notice', + type: 'snackbar', + actions: [ + { + label: __( 'Undo' ), + + onClick: () => { + registry + .dispatch( preferencesStore ) + .toggle( + 'core/edit-post', + 'fullscreenMode' + ); + }, + }, + ], + } + ); + }; diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js index a2b3007757e42..fbff29ed67afa 100644 --- a/packages/edit-site/src/components/post-edit/index.js +++ b/packages/edit-site/src/components/post-edit/index.js @@ -23,6 +23,14 @@ import { unlock } from '../../lock-unlock'; const { PostCardPanel } = unlock( editorPrivateApis ); +const fieldsWithBulkEditSupport = [ + 'title', + 'status', + 'date', + 'author', + 'comment_status', +]; + function PostEditForm( { postType, postId } ) { const ids = useMemo( () => postId.split( ',' ), [ postId ] ); const { record } = useSelect( @@ -58,27 +66,36 @@ function PostEditForm( { postType, postId } ) { } ), [ _fields ] ); - const form = { - type: 'panel', - fields: [ - 'featured_media', - 'title', - 'author', - 'date', - 'slug', - 'parent', - 'comment_status', - ], - }; - - const fieldsWithBulkEditSupport = [ - 'title', - 'status', - 'date', - 'author', - 'comment_status', - ]; + const form = useMemo( + () => ( { + type: 'panel', + fields: [ + 'featured_media', + 'title', + 'status_and_visibility', + 'author', + 'date', + 'slug', + 'parent', + 'comment_status', + ].filter( + ( field ) => + ids.length === 1 || + fieldsWithBulkEditSupport.includes( field ) + ), + combinedFields: [ + { + id: 'status_and_visibility', + label: __( 'Status & Visibility' ), + children: [ 'status', 'password' ], + direction: 'vertical', + render: ( { item } ) => item.status, + }, + ], + } ), + [ ids ] + ); const onChange = ( edits ) => { for ( const id of ids ) { if ( @@ -117,16 +134,7 @@ function PostEditForm( { postType, postId } ) { - fieldsWithBulkEditSupport.includes( field ) - ), - } - } + form={ form } onChange={ onChange } /> diff --git a/packages/edit-site/src/components/post-edit/style.scss b/packages/edit-site/src/components/post-edit/style.scss index 4eaa41b2e8ed7..5688d83df00c5 100644 --- a/packages/edit-site/src/components/post-edit/style.scss +++ b/packages/edit-site/src/components/post-edit/style.scss @@ -7,3 +7,10 @@ justify-content: center; } } + +.dataforms-layouts-panel__field-dropdown { + .fields-controls__password { + border-top: $border-width solid $gray-200; + padding-top: $grid-unit-20; + } +} diff --git a/packages/edit-site/src/components/post-fields/index.js b/packages/edit-site/src/components/post-fields/index.js index 7a52b05321794..e659a4f96f23f 100644 --- a/packages/edit-site/src/components/post-fields/index.js +++ b/packages/edit-site/src/components/post-fields/index.js @@ -8,7 +8,12 @@ import clsx from 'clsx'; */ import { __, sprintf } from '@wordpress/i18n'; import { decodeEntities } from '@wordpress/html-entities'; -import { featuredImageField, slugField, parentField } from '@wordpress/fields'; +import { + featuredImageField, + slugField, + parentField, + passwordField, +} from '@wordpress/fields'; import { createInterpolateElement, useMemo, @@ -348,6 +353,7 @@ function usePostFields( viewType ) { }, ], }, + passwordField, ], [ authors, viewType, frontPageId, postsPageId ] ); diff --git a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js index 35eeb5963fb54..3498bed4c99a5 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js @@ -31,47 +31,33 @@ const postTypesWithoutParentTemplate = [ const authorizedPostTypes = [ 'page', 'post' ]; function useResolveEditedEntityAndContext( { postId, postType } ) { - const { - hasLoadedAllDependencies, - homepageId, - postsPageId, - url, - frontPageTemplateId, - } = useSelect( ( select ) => { - const { getEntityRecord, getEntityRecords } = select( coreDataStore ); - const siteData = getEntityRecord( 'root', 'site' ); - const base = getEntityRecord( 'root', '__unstableBase' ); - const templates = getEntityRecords( 'postType', TEMPLATE_POST_TYPE, { - per_page: -1, - } ); - const _homepageId = - siteData?.show_on_front === 'page' && - [ 'number', 'string' ].includes( typeof siteData.page_on_front ) && - !! +siteData.page_on_front // We also need to check if it's not zero(`0`). - ? siteData.page_on_front.toString() - : null; - const _postsPageId = - siteData?.show_on_front === 'page' && - [ 'number', 'string' ].includes( typeof siteData.page_for_posts ) - ? siteData.page_for_posts.toString() - : null; - let _frontPageTemplateId; - if ( templates ) { - const frontPageTemplate = templates.find( - ( t ) => t.slug === 'front-page' - ); - _frontPageTemplateId = frontPageTemplate - ? frontPageTemplate.id - : false; - } - return { - hasLoadedAllDependencies: !! base && !! siteData, - homepageId: _homepageId, - postsPageId: _postsPageId, - url: base?.home, - frontPageTemplateId: _frontPageTemplateId, - }; - }, [] ); + const { hasLoadedAllDependencies, homepageId, postsPageId } = useSelect( + ( select ) => { + const { getEntityRecord } = select( coreDataStore ); + const siteData = getEntityRecord( 'root', 'site' ); + const _homepageId = + siteData?.show_on_front === 'page' && + [ 'number', 'string' ].includes( + typeof siteData.page_on_front + ) && + !! +siteData.page_on_front // We also need to check if it's not zero(`0`). + ? siteData.page_on_front.toString() + : null; + const _postsPageId = + siteData?.show_on_front === 'page' && + [ 'number', 'string' ].includes( + typeof siteData.page_for_posts + ) + ? siteData.page_for_posts.toString() + : null; + return { + hasLoadedAllDependencies: !! siteData, + homepageId: _homepageId, + postsPageId: _postsPageId, + }; + }, + [] + ); /** * This is a hook that recreates the logic to resolve a template for a given WordPress postID postTypeId @@ -99,7 +85,6 @@ function useResolveEditedEntityAndContext( { postId, postType } ) { getEditedEntityRecord, getEntityRecords, getDefaultTemplateId, - __experimentalGetTemplateForLink, } = select( coreDataStore ); function resolveTemplateForPostTypeAndId( @@ -111,15 +96,7 @@ function useResolveEditedEntityAndContext( { postId, postType } ) { postTypeToResolve === 'page' && homepageId === postIdToResolve ) { - // We're still checking whether the front page template exists. - // Don't resolve the template yet. - if ( frontPageTemplateId === undefined ) { - return undefined; - } - - if ( !! frontPageTemplateId ) { - return frontPageTemplateId; - } + return getDefaultTemplateId( { slug: 'front-page' } ); } const editedEntity = getEditedEntityRecord( @@ -135,8 +112,7 @@ function useResolveEditedEntityAndContext( { postId, postType } ) { postTypeToResolve === 'page' && postsPageId === postIdToResolve ) { - return __experimentalGetTemplateForLink( editedEntity.link ) - ?.id; + return getDefaultTemplateId( { slug: 'home' } ); } // First see if the post/page has an assigned template and fetch it. const currentTemplateSlug = editedEntity.template; @@ -194,20 +170,9 @@ function useResolveEditedEntityAndContext( { postId, postType } ) { } // If we're not rendering a specific page, use the front page template. - if ( url ) { - const template = __experimentalGetTemplateForLink( url ); - return template?.id; - } + return getDefaultTemplateId( { slug: 'front-page' } ); }, - [ - homepageId, - postsPageId, - hasLoadedAllDependencies, - url, - postId, - postType, - frontPageTemplateId, - ] + [ homepageId, postsPageId, hasLoadedAllDependencies, postId, postType ] ); const context = useMemo( () => { diff --git a/packages/editor/src/components/commands/index.js b/packages/editor/src/components/commands/index.js index 8eb88567bd0a3..b4b9c05db256d 100644 --- a/packages/editor/src/components/commands/index.js +++ b/packages/editor/src/components/commands/index.js @@ -36,7 +36,6 @@ function useEditorCommandLoader() { isListViewOpen, showBlockBreadcrumbs, isDistractionFree, - isTopToolbar, isFocusMode, isPreviewMode, isViewable, @@ -56,7 +55,6 @@ function useEditorCommandLoader() { showBlockBreadcrumbs: get( 'core', 'showBlockBreadcrumbs' ), isDistractionFree: get( 'core', 'distractionFree' ), isFocusMode: get( 'core', 'focusMode' ), - isTopToolbar: get( 'core', 'fixedToolbar' ), isPreviewMode: getSettings().isPreviewMode, isViewable: getPostType( getCurrentPostType() )?.viewable ?? false, isCodeEditingEnabled: getEditorSettings().codeEditingEnabled, @@ -73,6 +71,8 @@ function useEditorCommandLoader() { setIsListViewOpened, switchEditorMode, toggleDistractionFree, + toggleSpotlightMode, + toggleTopToolbar, } = useDispatch( editorStore ); const { openModal, enableComplementaryArea, disableComplementaryArea } = useDispatch( interfaceStore ); @@ -121,23 +121,8 @@ function useEditorCommandLoader() { ? __( 'Exit Spotlight mode' ) : __( 'Enter Spotlight mode' ), callback: ( { close } ) => { - toggle( 'core', 'focusMode' ); + toggleSpotlightMode(); close(); - createInfoNotice( - isFocusMode ? __( 'Spotlight off.' ) : __( 'Spotlight on.' ), - { - id: 'core/editor/toggle-spotlight-mode/notice', - type: 'snackbar', - actions: [ - { - label: __( 'Undo' ), - onClick: () => { - toggle( 'core', 'focusMode' ); - }, - }, - ], - } - ); }, } ); @@ -164,28 +149,8 @@ function useEditorCommandLoader() { name: 'core/toggle-top-toolbar', label: __( 'Top toolbar' ), callback: ( { close } ) => { - toggle( 'core', 'fixedToolbar' ); - if ( isDistractionFree ) { - toggleDistractionFree(); - } + toggleTopToolbar(); close(); - createInfoNotice( - isTopToolbar - ? __( 'Top toolbar off.' ) - : __( 'Top toolbar on.' ), - { - id: 'core/editor/toggle-top-toolbar/notice', - type: 'snackbar', - actions: [ - { - label: __( 'Undo' ), - onClick: () => { - toggle( 'core', 'fixedToolbar' ); - }, - }, - ], - } - ); }, } ); diff --git a/packages/editor/src/components/create-template-part-modal/index.js b/packages/editor/src/components/create-template-part-modal/index.js index 63e3830295c37..5d594cd646cc1 100644 --- a/packages/editor/src/components/create-template-part-modal/index.js +++ b/packages/editor/src/components/create-template-part-modal/index.js @@ -158,6 +158,7 @@ export function CreateTemplatePartModalContents( { { templatePartAreas.map( ( { icon, label, area: value, description } ) => ( { setPreference( 'core', 'distractionFree', false ); }; @@ -67,10 +68,10 @@ export default function MoreMenu() { 'Access all block and document tools in a single place' ) } messageActivated={ __( - 'Top toolbar activated' + 'Top toolbar activated.' ) } messageDeactivated={ __( - 'Top toolbar deactivated' + 'Top toolbar deactivated.' ) } /> + toggleDistractionFree( { + createNotice: false, + } ) + } messageActivated={ __( - 'Distraction free mode activated' + 'Distraction free mode activated.' ) } messageDeactivated={ __( - 'Distraction free mode deactivated' + 'Distraction free mode deactivated.' ) } shortcut={ displayShortcut.primaryShift( '\\' @@ -96,10 +101,10 @@ export default function MoreMenu() { label={ __( 'Spotlight mode' ) } info={ __( 'Focus on one block at a time' ) } messageActivated={ __( - 'Spotlight mode activated' + 'Spotlight mode activated.' ) } messageDeactivated={ __( - 'Spotlight mode deactivated' + 'Spotlight mode deactivated.' ) } /> diff --git a/packages/editor/src/components/sidebar/post-summary.js b/packages/editor/src/components/sidebar/post-summary.js index 72f1080770fd9..3539f7ba964ec 100644 --- a/packages/editor/src/components/sidebar/post-summary.js +++ b/packages/editor/src/components/sidebar/post-summary.js @@ -87,11 +87,11 @@ export default function PostSummary( { onActionPerformed } ) { + { fills } - { fills } ) } diff --git a/packages/editor/src/components/visual-editor/index.js b/packages/editor/src/components/visual-editor/index.js index 6a5466a265c3e..795a4f983f153 100644 --- a/packages/editor/src/components/visual-editor/index.js +++ b/packages/editor/src/components/visual-editor/index.js @@ -462,7 +462,13 @@ function VisualEditor( { renderingMode !== 'post-only' || isDesignPostType ? 'wp-site-blocks' - : `${ blockListLayoutClass } wp-block-post-content` // Ensure root level blocks receive default/flow blockGap styling rules. + : `${ blockListLayoutClass } wp-block-post-content`, // Ensure root level blocks receive default/flow blockGap styling rules. + { + 'has-global-padding': + renderingMode === 'post-only' && + ! isDesignPostType && + hasRootPaddingAwareAlignments, + } ) } layout={ blockListLayout } dropZoneElement={ diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index cc2c23bcffcf7..7ff23cb49f498 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -812,9 +812,12 @@ export function setIsListViewOpened( isOpen ) { * Action that toggles Distraction free mode. * Distraction free mode expects there are no sidebars, as due to the * z-index values set, you can't close sidebars. + * + * @param {Object} [options={}] Optional configuration object + * @param {boolean} [options.createNotice=true] Whether to create a notice */ export const toggleDistractionFree = - () => + ( { createNotice = true } = {} ) => ( { dispatch, registry } ) => { const isDistractionFree = registry .select( preferencesStore ) @@ -837,42 +840,116 @@ export const toggleDistractionFree = registry .dispatch( preferencesStore ) .set( 'core', 'distractionFree', ! isDistractionFree ); - registry - .dispatch( noticesStore ) - .createInfoNotice( - isDistractionFree - ? __( 'Distraction free mode deactivated.' ) - : __( 'Distraction free mode activated.' ), - { - id: 'core/editor/distraction-free-mode/notice', - type: 'snackbar', - actions: [ - { - label: __( 'Undo' ), - onClick: () => { - registry.batch( () => { - registry - .dispatch( preferencesStore ) - .set( - 'core', - 'fixedToolbar', - isDistractionFree ? true : false - ); - registry - .dispatch( preferencesStore ) - .toggle( - 'core', - 'distractionFree' - ); - } ); + + if ( createNotice ) { + registry + .dispatch( noticesStore ) + .createInfoNotice( + isDistractionFree + ? __( 'Distraction free mode deactivated.' ) + : __( 'Distraction free mode activated.' ), + { + id: 'core/editor/distraction-free-mode/notice', + type: 'snackbar', + actions: [ + { + label: __( 'Undo' ), + onClick: () => { + registry.batch( () => { + registry + .dispatch( preferencesStore ) + .set( + 'core', + 'fixedToolbar', + isDistractionFree + ); + registry + .dispatch( preferencesStore ) + .toggle( + 'core', + 'distractionFree' + ); + } ); + }, }, - }, - ], - } - ); + ], + } + ); + } } ); }; +/** + * Action that toggles the Spotlight Mode view option. + */ +export const toggleSpotlightMode = + () => + ( { registry } ) => { + registry.dispatch( preferencesStore ).toggle( 'core', 'focusMode' ); + + const isFocusMode = registry + .select( preferencesStore ) + .get( 'core', 'focusMode' ); + + registry + .dispatch( noticesStore ) + .createInfoNotice( + isFocusMode + ? __( 'Spotlight mode activated.' ) + : __( 'Spotlight mode deactivated.' ), + { + id: 'core/editor/toggle-spotlight-mode/notice', + type: 'snackbar', + actions: [ + { + label: __( 'Undo' ), + onClick: () => { + registry + .dispatch( preferencesStore ) + .toggle( 'core', 'focusMode' ); + }, + }, + ], + } + ); + }; + +/** + * Action that toggles the Top Toolbar view option. + */ +export const toggleTopToolbar = + () => + ( { registry } ) => { + registry.dispatch( preferencesStore ).toggle( 'core', 'fixedToolbar' ); + + const isTopToolbar = registry + .select( preferencesStore ) + .get( 'core', 'fixedToolbar' ); + + registry + .dispatch( noticesStore ) + .createInfoNotice( + isTopToolbar + ? __( 'Top toolbar activated.' ) + : __( 'Top toolbar deactivated.' ), + { + id: 'core/editor/toggle-top-toolbar/notice', + type: 'snackbar', + actions: [ + { + label: __( 'Undo' ), + + onClick: () => { + registry + .dispatch( preferencesStore ) + .toggle( 'core', 'fixedToolbar' ); + }, + }, + ], + } + ); + }; + /** * Triggers an action used to switch editor mode. * diff --git a/packages/fields/README.md b/packages/fields/README.md index 5fd1031b50fe2..214f3d6ee3a50 100644 --- a/packages/fields/README.md +++ b/packages/fields/README.md @@ -50,6 +50,10 @@ Undocumented declaration. This field is used to display the post parent. +### passwordField + +This field is used to display the post password. + ### permanentlyDeletePost Undocumented declaration. diff --git a/packages/fields/src/fields/index.ts b/packages/fields/src/fields/index.ts index 9f1abef0417f4..29cbdeb2a4ba6 100644 --- a/packages/fields/src/fields/index.ts +++ b/packages/fields/src/fields/index.ts @@ -3,3 +3,4 @@ export { default as titleField } from './title'; export { default as orderField } from './order'; export { default as featuredImageField } from './featured-image'; export { default as parentField } from './parent'; +export { default as passwordField } from './password'; diff --git a/packages/fields/src/fields/password/edit.tsx b/packages/fields/src/fields/password/edit.tsx new file mode 100644 index 0000000000000..75226c1f93f15 --- /dev/null +++ b/packages/fields/src/fields/password/edit.tsx @@ -0,0 +1,68 @@ +/** + * WordPress dependencies + */ +import { + CheckboxControl, + __experimentalVStack as VStack, + TextControl, +} from '@wordpress/components'; +import type { DataFormControlProps } from '@wordpress/dataviews'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import type { BasePost } from '../../types'; + +function PasswordEdit( { + data, + onChange, + field, +}: DataFormControlProps< BasePost > ) { + const [ showPassword, setShowPassword ] = useState( + !! field.getValue( { item: data } ) + ); + + const handleTogglePassword = ( value: boolean ) => { + setShowPassword( value ); + if ( ! value ) { + onChange( { password: '' } ); + } + }; + + return ( + + + { showPassword && ( +
+ + onChange( { + password: value, + } ) + } + value={ field.getValue( { item: data } ) || '' } + placeholder={ __( 'Use a secure password' ) } + type="text" + __next40pxDefaultSize + __nextHasNoMarginBottom + maxLength={ 255 } + /> +
+ ) } +
+ ); +} +export default PasswordEdit; diff --git a/packages/fields/src/fields/password/index.tsx b/packages/fields/src/fields/password/index.tsx new file mode 100644 index 0000000000000..aa7bc57e3f7ca --- /dev/null +++ b/packages/fields/src/fields/password/index.tsx @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import type { Field } from '@wordpress/dataviews'; + +/** + * Internal dependencies + */ +import type { BasePost } from '../../types'; +import PasswordEdit from './edit'; + +const passwordField: Field< BasePost > = { + id: 'password', + type: 'text', + getValue: ( { item } ) => item.password, + Edit: PasswordEdit, + enableSorting: false, + enableHiding: false, + isVisible: ( item ) => item.status !== 'private', +}; + +/** + * This field is used to display the post password. + */ +export default passwordField; diff --git a/packages/media-utils/src/components/media-upload/index.js b/packages/media-utils/src/components/media-upload/index.js index 91de011869ad3..ed0a59ddbc684 100644 --- a/packages/media-utils/src/components/media-upload/index.js +++ b/packages/media-utils/src/components/media-upload/index.js @@ -70,6 +70,46 @@ const getFeaturedImageMediaFrame = () => { } ); }; +/** + * Prepares the default frame for selecting a single media item. + * + * @return {window.wp.media.view.MediaFrame.Select} The default media workflow. + */ +const getSingleMediaFrame = () => { + const { wp } = window; + + // Extend the default Select frame, and use the same `createStates` method as in core, + // but with the addition of `filterable: 'uploaded'` to the Library state, so that + // the user can filter the media library by uploaded media. + return wp.media.view.MediaFrame.Select.extend( { + /** + * Create the default states on the frame. + */ + createStates() { + const options = this.options; + + if ( this.options.states ) { + return; + } + + // Add the default states. + this.states.add( [ + // Main states. + new wp.media.controller.Library( { + library: wp.media.query( options.library ), + multiple: options.multiple, + title: options.title, + priority: 20, + filterable: 'uploaded', // Allow filtering by uploaded images. + } ), + new wp.media.controller.EditImage( { + model: options.editImage, + } ), + ] ); + }, + } ); +}; + /** * Prepares the Gallery toolbars and frames. * @@ -324,6 +364,47 @@ class MediaUpload extends Component { }; } + /** + * Initializes the Media Library requirements for the single image flow. + * + * @return {void} + */ + buildAndSetSingleMediaFrame() { + const { wp } = window; + const { + allowedTypes, + multiple = false, + title = __( 'Select or Upload Media' ), + value, + } = this.props; + + const frameConfig = { + title, + multiple, + }; + if ( !! allowedTypes ) { + frameConfig.library = { type: allowedTypes }; + } + + // If a frame already exists, remove it. + if ( this.frame ) { + this.frame.remove(); + } + + const singleImageFrame = getSingleMediaFrame(); + const attachments = getAttachmentsCollection( value ); + const selection = new wp.media.model.Selection( attachments.models, { + props: attachments.props.toJSON(), + } ); + this.frame = new singleImageFrame( { + mimeType: allowedTypes, + multiple, + selection, + ...frameConfig, + } ); + wp.media.frame = this.frame; + } + componentWillUnmount() { this.frame?.remove(); } @@ -424,27 +505,15 @@ class MediaUpload extends Component { openModal() { const { - allowedTypes, gallery = false, unstableFeaturedImageFlow = false, modalClass, - multiple = false, - title = __( 'Select or Upload Media' ), } = this.props; - const { wp } = window; if ( gallery ) { this.buildAndSetGalleryFrame(); } else { - const frameConfig = { - title, - multiple, - }; - if ( !! allowedTypes ) { - frameConfig.library = { type: allowedTypes }; - } - - this.frame = wp.media( frameConfig ); + this.buildAndSetSingleMediaFrame(); } if ( modalClass ) { diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 02186fecdcea2..3c69271903005 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -424,8 +424,15 @@ public static function parse_block_styles( $block_styles, $options ) { continue; } - $parsed_styles['classnames'] = array_merge( $parsed_styles['classnames'], static::get_classnames( $style_value, $style_definition ) ); - $parsed_styles['declarations'] = array_merge( $parsed_styles['declarations'], static::get_css_declarations( $style_value, $style_definition, $options ) ); + $classnames = static::get_classnames( $style_value, $style_definition ); + if ( ! empty( $classnames ) ) { + $parsed_styles['classnames'] = array_merge( $parsed_styles['classnames'], $classnames ); + } + + $css_declarations = static::get_css_declarations( $style_value, $style_definition, $options ); + if ( ! empty( $css_declarations ) ) { + $parsed_styles['declarations'] = array_merge( $parsed_styles['declarations'], $css_declarations ); + } } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 29d454f6f4f0b..50a0af4f9b3ff 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -2301,7 +2301,9 @@ public function test_merge_incoming_background_styles() { 'styles' => array( 'background' => array( 'backgroundImage' => array( - 'url' => 'http://example.org/quote.png', + 'id' => 'uploaded', + 'source' => 'file', + 'url' => 'http://example.org/quote.png', ), 'backgroundSize' => 'cover', ), @@ -2333,7 +2335,10 @@ public function test_merge_incoming_background_styles() { 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'background' => array( - 'backgroundSize' => 'contain', + 'backgroundImage' => array( + 'url' => 'http://example.org/site.png', + ), + 'backgroundSize' => 'contain', ), 'blocks' => array( 'core/group' => array( @@ -2363,7 +2368,7 @@ public function test_merge_incoming_background_styles() { 'styles' => array( 'background' => array( 'backgroundImage' => array( - 'url' => 'http://example.org/quote.png', + 'url' => 'http://example.org/site.png', ), 'backgroundSize' => 'contain', ),