From b0abc74391809c33675471070660ed2519f22c9b Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Fri, 13 Sep 2024 06:37:53 -0700 Subject: [PATCH] Split content view with meta boxes (#64351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use iframe even with metaboxes * Split content view when metaboxes are open * Fix styling issues Co-authored-by: t-hamano * Update e2e tests Co-authored-by: ellatrix * Make metaboxes a details element in short viewports * Make metabox area resizable when viewport isn’t short * Tweak details element usage * Add max-height when resized by user * Hide metabox area if all metaboxes are hidden * Persist toggle and height state in user preferences * Wrap contents and rename things The added wrapping element was due to Safari clipping the drop-shadow when `overflow: auto` was on the component root. * Make visual editor stacking context when iframed with metaboxes * Omit meta box area when empty instead of hiding it * Fix meta boxes content appearing in front of UI * Use split views only when canvas is iframed * Don’t omit main meta box area unless all locations aren’t visible --------- Co-authored-by: t-hamano Co-authored-by: ellatrix Co-authored-by: youknowriad Co-authored-by: ciampo Co-authored-by: torounit Co-authored-by: talldan Co-authored-by: arnaudbroes --- .../edit-post/src/components/layout/index.js | 128 +++++++++++++++++- .../src/components/layout/style.scss | 70 +++++++++- .../components/layout/use-should-iframe.js | 11 +- packages/edit-post/src/store/selectors.js | 1 - .../specs/editor/plugins/meta-boxes.spec.js | 18 ++- .../editor/plugins/wp-editor-meta-box.spec.js | 2 +- .../editor/various/publish-button.spec.js | 7 +- 7 files changed, 205 insertions(+), 32 deletions(-) diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 185f97ba45a56f..2cbf5c32e814b8 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -24,7 +24,12 @@ import { } from '@wordpress/block-editor'; import { PluginArea } from '@wordpress/plugins'; import { __, sprintf } from '@wordpress/i18n'; -import { useCallback, useMemo } from '@wordpress/element'; +import { + useCallback, + useLayoutEffect, + useMemo, + useRef, +} from '@wordpress/element'; import { store as noticesStore } from '@wordpress/notices'; import { store as preferencesStore } from '@wordpress/preferences'; import { @@ -36,8 +41,8 @@ import { privateApis as blockLibraryPrivateApis } from '@wordpress/block-library import { addQueryArgs } from '@wordpress/url'; import { decodeEntities } from '@wordpress/html-entities'; import { store as coreStore } from '@wordpress/core-data'; -import { SlotFillProvider } from '@wordpress/components'; -import { useViewportMatch } from '@wordpress/compose'; +import { ResizableBox, SlotFillProvider } from '@wordpress/components'; +import { useMediaQuery, useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies @@ -151,6 +156,118 @@ function useEditorStyles() { ] ); } +/** + * @param {Object} props + * @param {boolean} props.isLegacy True when the editor canvas is not in an iframe. + */ +function MetaBoxesMain( { isLegacy } ) { + const [ isOpen, openHeight, hasAnyVisible ] = useSelect( ( select ) => { + const { get } = select( preferencesStore ); + const { isMetaBoxLocationVisible } = select( editPostStore ); + return [ + get( 'core/edit-post', 'metaBoxesMainIsOpen' ), + get( 'core/edit-post', 'metaBoxesMainOpenHeight' ), + isMetaBoxLocationVisible( 'normal' ) || + isMetaBoxLocationVisible( 'advanced' ) || + isMetaBoxLocationVisible( 'side' ), + ]; + }, [] ); + const { set: setPreference } = useDispatch( preferencesStore ); + const resizableBoxRef = useRef(); + const isShort = useMediaQuery( '(max-height: 549px)' ); + + const isAutoHeight = openHeight === undefined; + // In case a user size is set stops the default max-height from applying. + useLayoutEffect( () => { + if ( ! isLegacy && hasAnyVisible && ! isShort && ! isAutoHeight ) { + resizableBoxRef.current.resizable.classList.add( 'has-user-size' ); + } + }, [ isAutoHeight, isShort, hasAnyVisible, isLegacy ] ); + + if ( ! hasAnyVisible ) { + return; + } + + const className = 'edit-post-meta-boxes-main'; + const contents = ( +
+ + +
+ ); + + if ( isLegacy ) { + return contents; + } + + if ( isShort ) { + return ( +
{ + setPreference( + 'core/edit-post', + 'metaBoxesMainIsOpen', + target.open + ); + } } + > + { __( 'Meta Boxes' ) } + { contents } +
+ ); + } + return ( + { + target.setPointerCapture( pointerId ); + } } + onResizeStart={ ( event, direction, elementRef ) => { + // Avoids height jumping in case it’s limited by max-height. + elementRef.style.height = `${ elementRef.offsetHeight }px`; + // Stops initial max-height from being applied. + elementRef.classList.add( 'has-user-size' ); + } } + onResizeStop={ () => { + setPreference( + 'core/edit-post', + 'metaBoxesMainOpenHeight', + resizableBoxRef.current.state.height + ); + } } + > + { contents } + + ); +} + function Layout( { postId: initialPostId, postType: initialPostType, @@ -355,10 +472,7 @@ function Layout( { extraContent={ ! isDistractionFree && showMetaBoxes && ( -
- - -
+ ) } > diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 5fdaceaa002be9..5392a7e9da4d95 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -1,6 +1,70 @@ -.edit-post-layout__metaboxes { - flex-shrink: 0; - clear: both; +.edit-post-meta-boxes-main { + filter: drop-shadow(0 -1px rgba($color: #000, $alpha: 0.133)); // 0.133 = $gray-200 but with alpha. + background-color: $white; + clear: both; // This is seemingly only needed in case the canvas is not iframe’d. + + &:not(details) { + padding-top: 23px; + max-height: 100%; + + &:not(.has-user-size) { + max-height: 50% !important; + } + } + + // The component renders as a details element in short viewports. + &:is(details) { + & > summary { + cursor: pointer; + color: $gray-900; + background-color: $white; + height: $button-size-compact; + line-height: $button-size-compact; + font-size: 13px; + padding-left: $grid-unit-30; + box-shadow: 0 $border-width $gray-300; + } + + &[open] > summary { + position: sticky; + top: 0; + z-index: 1; + } + } + + & .components-resizable-box__handle-top { + top: 0; + box-shadow: 0 $border-width $gray-300; + } + & .components-resizable-box__side-handle::before { + border-radius: 0; + top: 0; + height: $border-width; + } + & .components-resizable-box__handle::after { + background-color: $gray-300; + box-shadow: none; + border-radius: 4px; + height: $grid-unit-05; + top: calc(50% - #{$grid-unit-05} / 2); + width: 100px; + right: calc(50% - 50px); + } +} + +.edit-post-meta-boxes-main__liner { + overflow: auto; + max-height: 100%; + // Keep the contents behind the resize handle or details summary. + isolation: isolate; +} + +.has-metaboxes .editor-visual-editor { + flex: 1; + + &.is-iframed { + isolation: isolate; + } } // Adjust the position of the notices diff --git a/packages/edit-post/src/components/layout/use-should-iframe.js b/packages/edit-post/src/components/layout/use-should-iframe.js index 248ea53109f250..e36a4773c4a1fd 100644 --- a/packages/edit-post/src/components/layout/use-should-iframe.js +++ b/packages/edit-post/src/components/layout/use-should-iframe.js @@ -6,11 +6,6 @@ import { useSelect } from '@wordpress/data'; import { store as blocksStore } from '@wordpress/blocks'; import { store as blockEditorStore } from '@wordpress/block-editor'; -/** - * Internal dependencies - */ -import { store as editPostStore } from '../../store'; - const isGutenbergPlugin = globalThis.IS_GUTENBERG_PLUGIN ? true : false; export function useShouldIframe() { @@ -18,7 +13,6 @@ export function useShouldIframe() { isBlockBasedTheme, hasV3BlocksOnly, isEditingTemplate, - hasMetaBoxes, isZoomOutMode, } = useSelect( ( select ) => { const { getEditorSettings, getCurrentPostType } = select( editorStore ); @@ -31,14 +25,13 @@ export function useShouldIframe() { return type.apiVersion >= 3; } ), isEditingTemplate: getCurrentPostType() === 'wp_template', - hasMetaBoxes: select( editPostStore ).hasMetaBoxes(), isZoomOutMode: __unstableGetEditorMode() === 'zoom-out', }; }, [] ); return ( - ( ( hasV3BlocksOnly || ( isGutenbergPlugin && isBlockBasedTheme ) ) && - ! hasMetaBoxes ) || + hasV3BlocksOnly || + ( isGutenbergPlugin && isBlockBasedTheme ) || isEditingTemplate || isZoomOutMode ); diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index da1f9959e32e91..5bea6e7d35eb62 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -379,7 +379,6 @@ export const isMetaBoxLocationVisible = createRegistrySelector( isMetaBoxLocationActive( state, location ) && getMetaBoxesPerLocation( state, location )?.some( ( { id } ) => { return select( editorStore ).isEditorPanelEnabled( - state, `meta-box-${ id }` ); } ) diff --git a/test/e2e/specs/editor/plugins/meta-boxes.spec.js b/test/e2e/specs/editor/plugins/meta-boxes.spec.js index 1b7adc18760ff8..511b3837f80303 100644 --- a/test/e2e/specs/editor/plugins/meta-boxes.spec.js +++ b/test/e2e/specs/editor/plugins/meta-boxes.spec.js @@ -26,7 +26,7 @@ test.describe( 'Meta boxes', () => { await expect( saveDraft ).toBeDisabled(); // Add title to enable valid non-empty post save. - await page + await editor.canvas .getByRole( 'textbox', { name: 'Add title' } ) .fill( 'Hello Meta' ); @@ -44,7 +44,7 @@ test.describe( 'Meta boxes', () => { page, } ) => { // Publish a post so there's something for the latest posts dynamic block to render. - await page + await editor.canvas .getByRole( 'textbox', { name: 'Add title' } ) .fill( 'A published post' ); await page.keyboard.press( 'Enter' ); @@ -53,7 +53,7 @@ test.describe( 'Meta boxes', () => { // Publish a post with the latest posts dynamic block. await admin.createNewPost(); - await page + await editor.canvas .getByRole( 'textbox', { name: 'Add title' } ) .fill( 'Dynamic block test' ); await editor.insertBlock( { name: 'core/latest-posts' } ); @@ -70,10 +70,12 @@ test.describe( 'Meta boxes', () => { editor, page, } ) => { - await page + await editor.canvas .getByRole( 'textbox', { name: 'Add title' } ) .fill( 'A published post' ); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); + await editor.canvas + .getByRole( 'button', { name: 'Add default block' } ) + .click(); await page.keyboard.type( 'Excerpt from content.' ); const postId = await editor.publishPost(); @@ -89,9 +91,11 @@ test.describe( 'Meta boxes', () => { page, } ) => { await editor.openDocumentSettingsSidebar(); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); + await editor.canvas + .getByRole( 'button', { name: 'Add default block' } ) + .click(); await page.keyboard.type( 'Excerpt from content.' ); - await page + await editor.canvas .getByRole( 'textbox', { name: 'Add title' } ) .fill( 'A published post' ); diff --git a/test/e2e/specs/editor/plugins/wp-editor-meta-box.spec.js b/test/e2e/specs/editor/plugins/wp-editor-meta-box.spec.js index 710e06b35e124f..c5eafdafe918db 100644 --- a/test/e2e/specs/editor/plugins/wp-editor-meta-box.spec.js +++ b/test/e2e/specs/editor/plugins/wp-editor-meta-box.spec.js @@ -20,7 +20,7 @@ test.describe( 'WP Editor Meta Boxes', () => { await admin.createNewPost(); // Add title to enable valid non-empty post save. - await page + await editor.canvas .locator( 'role=textbox[name="Add title"i]' ) .type( 'Hello Meta' ); diff --git a/test/e2e/specs/editor/various/publish-button.spec.js b/test/e2e/specs/editor/various/publish-button.spec.js index 631ddcd0fe61ba..cdc9c1a9936361 100644 --- a/test/e2e/specs/editor/various/publish-button.spec.js +++ b/test/e2e/specs/editor/various/publish-button.spec.js @@ -70,13 +70,12 @@ test.describe( 'Post publish button', () => { admin, page, requestUtils, + editor, } ) => { await requestUtils.activatePlugin( 'gutenberg-test-plugin-meta-box' ); await admin.createNewPost(); - await page - .getByRole( 'textbox', { - name: 'Add title', - } ) + await editor.canvas + .getByRole( 'textbox', { name: 'Add title' } ) .fill( 'Test post' ); const topBar = page.getByRole( 'region', { name: 'Editor top bar' } );