Skip to content

Commit

Permalink
Add layout styles from Post Content block to post editor (#44258)
Browse files Browse the repository at this point in the history
* Add layout styles from Post Content block to post editor

* Fix post content finding logic.

* Remove extra styles when Post Content block exists.

* templateContent should always be a string

* Memoise post content block.

* Change function signature and mark experimental

* _Really_ make sure templateContent is a string

* Update layout type for legacy layouts.

* Fix selector specificity and legacy layout logic.

* Fix 404s on fetch and only parse content if needed

* Fix acces to layout when no Post Content block

* Add doc comment to findPostContent

* Increase specificity

* Remove unneeded LayoutStyle
  • Loading branch information
tellthemachines authored Sep 27, 2022
1 parent 6ae3319 commit cf0b87a
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 43 deletions.
1 change: 1 addition & 0 deletions packages/block-editor/src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import './metadata';
import './metadata-name';

export { useCustomSides } from './dimensions';
export { useLayoutClasses, useLayoutStyles } from './layout';
export { getBorderClassesAndStyles, useBorderProps } from './use-border-props';
export { getColorClassesAndStyles, useColorProps } from './use-color-props';
export { getSpacingClassesAndStyles } from './use-spacing-props';
Expand Down
85 changes: 64 additions & 21 deletions packages/block-editor/src/hooks/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,58 +37,101 @@ import { getLayoutType, getLayoutTypes } from '../layouts';
const layoutBlockSupportKey = '__experimentalLayout';

/**
* Generates the utility classnames for the given blocks layout attributes.
* This method was primarily added to reintroduce classnames that were removed
* in the 5.9 release (https://github.com/WordPress/gutenberg/issues/38719), rather
* than providing an extensive list of all possible layout classes. The plan is to
* have the style engine generate a more extensive list of utility classnames which
* will then replace this method.
* Generates the utility classnames for the given block's layout attributes.
*
* @param { Object } layout Layout object.
* @param { Object } layoutDefinitions An object containing layout definitions, stored in theme.json.
* @param { Object } block Block object.
*
* @return { Array } Array of CSS classname strings.
*/
function useLayoutClasses( layout, layoutDefinitions ) {
export function useLayoutClasses( block = {} ) {
const rootPaddingAlignment = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
return getSettings().__experimentalFeatures
?.useRootPaddingAwareAlignments;
}, [] );
const globalLayoutSettings = useSetting( 'layout' ) || {};

const { attributes = {}, name } = block;
const { layout } = attributes;

const { default: defaultBlockLayout } =
getBlockSupport( name, layoutBlockSupportKey ) || {};
const usedLayout =
layout?.inherit || layout?.contentSize || layout?.wideSize
? { ...layout, type: 'constrained' }
: layout || defaultBlockLayout || {};

const layoutClassnames = [];

if ( layoutDefinitions?.[ layout?.type || 'default' ]?.className ) {
if (
globalLayoutSettings?.definitions?.[ usedLayout?.type || 'default' ]
?.className
) {
layoutClassnames.push(
layoutDefinitions?.[ layout?.type || 'default' ]?.className
globalLayoutSettings?.definitions?.[ usedLayout?.type || 'default' ]
?.className
);
}

if (
( layout?.inherit ||
layout?.contentSize ||
layout?.type === 'constrained' ) &&
( usedLayout?.inherit ||
usedLayout?.contentSize ||
usedLayout?.type === 'constrained' ) &&
rootPaddingAlignment
) {
layoutClassnames.push( 'has-global-padding' );
}

if ( layout?.orientation ) {
layoutClassnames.push( `is-${ kebabCase( layout.orientation ) }` );
if ( usedLayout?.orientation ) {
layoutClassnames.push( `is-${ kebabCase( usedLayout.orientation ) }` );
}

if ( layout?.justifyContent ) {
if ( usedLayout?.justifyContent ) {
layoutClassnames.push(
`is-content-justification-${ kebabCase( layout.justifyContent ) }`
`is-content-justification-${ kebabCase(
usedLayout.justifyContent
) }`
);
}

if ( layout?.flexWrap && layout.flexWrap === 'nowrap' ) {
if ( usedLayout?.flexWrap && usedLayout.flexWrap === 'nowrap' ) {
layoutClassnames.push( 'is-nowrap' );
}

return layoutClassnames;
}

/**
* Generates a CSS rule with the given block's layout styles.
*
* @param { Object } block Block object.
* @param { string } selector A selector to use in generating the CSS rule.
*
* @return { string } CSS rule.
*/
export function useLayoutStyles( block = {}, selector ) {
const { attributes = {}, name } = block;
const { layout = {}, style = {} } = attributes;
// Update type for blocks using legacy layouts.
const usedLayout =
layout?.inherit || layout?.contentSize || layout?.wideSize
? { ...layout, type: 'constrained' }
: layout || {};
const fullLayoutType = getLayoutType( usedLayout?.type || 'default' );
const globalLayoutSettings = useSetting( 'layout' ) || {};
const blockGapSupport = useSetting( 'spacing.blockGap' );
const hasBlockGapSupport = blockGapSupport !== null;
const css = fullLayoutType?.getLayoutStyle?.( {
blockName: name,
selector,
layout,
layoutDefinitions: globalLayoutSettings?.definitions,
style,
hasBlockGapSupport,
} );
return css;
}

function LayoutPanel( { setAttributes, attributes, name: blockName } ) {
const { layout } = attributes;
const defaultThemeLayout = useSetting( 'layout' );
Expand Down Expand Up @@ -299,7 +342,7 @@ export const withInspectorControls = createHigherOrderComponent(
*/
export const withLayoutStyles = createHigherOrderComponent(
( BlockListBlock ) => ( props ) => {
const { name, attributes } = props;
const { name, attributes, block } = props;
const hasLayoutBlockSupport = hasBlockSupport(
name,
layoutBlockSupportKey
Expand All @@ -321,7 +364,7 @@ export const withLayoutStyles = createHigherOrderComponent(
? { ...layout, type: 'constrained' }
: layout || defaultBlockLayout || {};
const layoutClasses = hasLayoutBlockSupport
? useLayoutClasses( usedLayout, defaultThemeLayout?.definitions )
? useLayoutClasses( block )
: null;
const selector = `.${ getBlockDefaultClassName(
name
Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export {
getSpacingClassesAndStyles as __experimentalGetSpacingClassesAndStyles,
getGapCSSValue as __experimentalGetGapCSSValue,
useCachedTruthy,
useLayoutClasses as __experimentaluseLayoutClasses,
useLayoutStyles as __experimentaluseLayoutStyles,
} from './hooks';
export * from './components';
export * from './elements';
Expand Down
4 changes: 3 additions & 1 deletion packages/block-library/src/post-content/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ function EditableContent( { layout, context = {} } ) {
return getSettings()?.supportsLayout;
}, [] );
const defaultLayout = useSetting( 'layout' ) || {};
const usedLayout = !! layout && layout.inherit ? defaultLayout : layout;
const usedLayout = ! layout?.type
? { ...defaultLayout, ...layout, type: 'default' }
: { ...defaultLayout, ...layout };
const [ blocks, onInput, onChange ] = useEntityBlockEditor(
'postType',
postType,
Expand Down
131 changes: 110 additions & 21 deletions packages/edit-post/src/components/visual-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@ import {
__unstableUseMouseMoveTypingReset as useMouseMoveTypingReset,
__unstableIframe as Iframe,
__experimentalRecursionProvider as RecursionProvider,
__experimentaluseLayoutClasses as useLayoutClasses,
__experimentaluseLayoutStyles as useLayoutStyles,
} from '@wordpress/block-editor';
import { useEffect, useRef, useMemo } from '@wordpress/element';
import { Button, __unstableMotion as motion } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
import { useMergeRefs } from '@wordpress/compose';
import { arrowLeft } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';
import { parse } from '@wordpress/blocks';

/**
* Internal dependencies
Expand Down Expand Up @@ -82,20 +85,48 @@ function MaybeIframe( {
);
}

/**
* Given an array of nested blocks, find the first Post Content
* block inside it, recursing through any nesting levels.
*
* @param {Array} blocks A list of blocks.
*
* @return {Object} The Post Content block.
*/
function findPostContent( blocks ) {
for ( let i = 0; i < blocks.length; i++ ) {
if ( blocks[ i ].name === 'core/post-content' ) {
return blocks[ i ];
}
if ( blocks[ i ].innerBlocks.length ) {
const nestedPostContent = findPostContent(
blocks[ i ].innerBlocks
);

if ( nestedPostContent ) {
return nestedPostContent;
}
}
}
}

export default function VisualEditor( { styles } ) {
const {
deviceType,
isWelcomeGuideVisible,
isTemplateMode,
editedPostTemplate = {},
wrapperBlockName,
wrapperUniqueId,
} = useSelect( ( select ) => {
const {
isFeatureActive,
isEditingTemplate,
__experimentalGetPreviewDeviceType,
getEditedPostTemplate,
} = select( editPostStore );
const { getCurrentPostId, getCurrentPostType } = select( editorStore );
const { getCurrentPostId, getCurrentPostType, getEditorSettings } =
select( editorStore );
const _isTemplateMode = isEditingTemplate();
let _wrapperBlockName;

Expand All @@ -105,10 +136,17 @@ export default function VisualEditor( { styles } ) {
_wrapperBlockName = 'core/post-content';
}

const supportsTemplateMode = getEditorSettings().supportsTemplateMode;

return {
deviceType: __experimentalGetPreviewDeviceType(),
isWelcomeGuideVisible: isFeatureActive( 'welcomeGuide' ),
isTemplateMode: _isTemplateMode,
// Post template fetch returns a 404 on classic themes, which
// messes with e2e tests, so we check it's a block theme first.
editedPostTemplate: supportsTemplateMode
? getEditedPostTemplate()
: {},
wrapperBlockName: _wrapperBlockName,
wrapperUniqueId: getCurrentPostId(),
};
Expand All @@ -122,16 +160,13 @@ export default function VisualEditor( { styles } ) {
themeHasDisabledLayoutStyles,
themeSupportsLayout,
assets,
useRootPaddingAwareAlignments,
isFocusMode,
} = useSelect( ( select ) => {
const _settings = select( blockEditorStore ).getSettings();
return {
themeHasDisabledLayoutStyles: _settings.disableLayoutStyles,
themeSupportsLayout: _settings.supportsLayout,
assets: _settings.__unstableResolvedAssets,
useRootPaddingAwareAlignments:
_settings.__experimentalFeatures?.useRootPaddingAwareAlignments,
isFocusMode: _settings.focusMode,
};
}, [] );
Expand All @@ -154,7 +189,7 @@ export default function VisualEditor( { styles } ) {
borderBottom: 0,
};
const resizedCanvasStyles = useResizeCanvas( deviceType, isTemplateMode );
const defaultLayout = useSetting( 'layout' );
const globalLayoutSettings = useSetting( 'layout' );
const previewMode = 'is-' + deviceType.toLowerCase() + '-preview';

let animatedStyles = isTemplateMode
Expand Down Expand Up @@ -183,25 +218,68 @@ export default function VisualEditor( { styles } ) {

const blockSelectionClearerRef = useBlockSelectionClearer();

const layout = useMemo( () => {
// fallbackLayout is used if there is no Post Content,
// and for Post Title.
const fallbackLayout = useMemo( () => {
if ( isTemplateMode ) {
return { type: 'default' };
}

if ( themeSupportsLayout ) {
// We need to ensure support for wide and full alignments,
// so we add the constrained type.
return { ...defaultLayout, type: 'constrained' };
return { ...globalLayoutSettings, type: 'constrained' };
}
// Set default layout for classic themes so all alignments are supported.
return { type: 'default' };
}, [ isTemplateMode, themeSupportsLayout, defaultLayout ] );
}, [ isTemplateMode, themeSupportsLayout, globalLayoutSettings ] );

const postContentBlock = useMemo( () => {
// When in template editing mode, we can access the blocks directly.
if ( editedPostTemplate?.blocks ) {
return findPostContent( editedPostTemplate?.blocks );
}
// If there are no blocks, we have to parse the content string.
// Best double-check it's a string otherwise the parse function gets unhappy.
const parseableContent =
typeof editedPostTemplate?.content === 'string'
? editedPostTemplate?.content
: '';

return findPostContent( parse( parseableContent ) ) || {};
}, [ editedPostTemplate?.content, editedPostTemplate?.blocks ] );

const postContentLayoutClasses = useLayoutClasses( postContentBlock );

const blockListLayoutClass = classnames(
{
'is-layout-flow': ! themeSupportsLayout,
},
themeSupportsLayout && postContentLayoutClasses
);

const blockListLayoutClass = classnames( {
'is-layout-constrained': themeSupportsLayout,
'is-layout-flow': ! themeSupportsLayout,
'has-global-padding': useRootPaddingAwareAlignments,
} );
const postContentLayoutStyles = useLayoutStyles(
postContentBlock,
'.block-editor-block-list__layout.is-root-container'
);

const layout = postContentBlock?.attributes?.layout || {};

// Update type for blocks using legacy layouts.
const postContentLayout =
layout &&
( layout?.type === 'constrained' ||
layout?.inherit ||
layout?.contentSize ||
layout?.wideSize )
? { ...globalLayoutSettings, ...layout, type: 'constrained' }
: { ...globalLayoutSettings, ...layout, type: 'default' };

// If there is a Post Content block we use its layout for the block list;
// if not, this must be a classic theme, in which case we use the fallback layout.
const blockListLayout = postContentBlock
? postContentLayout
: fallbackLayout;

const titleRef = useRef();
useEffect( () => {
Expand Down Expand Up @@ -257,13 +335,24 @@ export default function VisualEditor( { styles } ) {
{ themeSupportsLayout &&
! themeHasDisabledLayoutStyles &&
! isTemplateMode && (
<LayoutStyle
selector=".edit-post-visual-editor__post-title-wrapper, .block-editor-block-list__layout.is-root-container"
layout={ layout }
layoutDefinitions={
defaultLayout?.definitions
}
/>
<>
<LayoutStyle
selector=".edit-post-visual-editor__post-title-wrapper, .block-editor-block-list__layout.is-root-container"
layout={ fallbackLayout }
layoutDefinitions={
globalLayoutSettings?.definitions
}
/>
{ postContentLayoutStyles && (
<LayoutStyle
layout={ postContentLayout }
css={ postContentLayoutStyles }
layoutDefinitions={
globalLayoutSettings?.definitions
}
/>
) }
</>
) }
{ ! isTemplateMode && (
<div
Expand All @@ -288,7 +377,7 @@ export default function VisualEditor( { styles } ) {
? 'wp-site-blocks'
: `${ blockListLayoutClass } wp-block-post-content` // Ensure root level blocks receive default/flow blockGap styling rules.
}
__experimentalLayout={ layout }
__experimentalLayout={ blockListLayout }
/>
</RecursionProvider>
</MaybeIframe>
Expand Down

0 comments on commit cf0b87a

Please sign in to comment.