Skip to content

Commit

Permalink
task: Expand hidden title robustness (#97)
Browse files Browse the repository at this point in the history
* style: Format code

* feat: Apply opinionated default content styles

In the WordPress admin context, the block list receives "post content"
attributes that dictate the layout and alignment styles applied to the
blocks within the list. Currently, we cannot retrieve the post content
attributes via the REST API, as it does not include the current post
context.

Instead, we apply the "constrained" layout to improve the mobile editing
experience. However, this approach may ultimately introduce subtle
styling bugs--namely, classic vs block themes and editing posts/pages
that use non-traditional template layouts (e.g., full-width content).

* fix: Inject editor styles

Ensure various editor styles are injected. These styles are determined
by editor and theme settings. Unfortunately, we do not have all the
necessary context in the REST API to ensure all the styles are present.
Therefore, these changes also include some manual style additions that
are notably absent.

This manual approach is brittle. Ideally, the web editor relies upon the
same REST API endpoints as the mobile app rather than relying upon
server rendering context. This would push both platforms to ensure all
of the necessary settings are exposed.

* refactor: Pass renamed hideTitle setting as prop

Renamed for consistency with other settings. Passed as a prop to avoid
redundant bridge communication and unnecessary re-renders. Added option
to the text editor.

* docs: Document TextEditor

* refactor: Remove unused settings fetch

* docs: Document VisualEditor

* fix: Apply missing post title alignment styles

Apply similar constraints as found on the post content.

* fix: Address remote editor errors

Reinstate the layout component to simplify remote editor support.

* fix: Repair root level alignment conditional

The `themeStyles` setting is not utilized in the web editor and may be
unnecessary in this context.

* feat: Ensure reasonable defaults when theme styles are absent

This might be used when classic themes are active or in development
environments lack site/theme context.

* refactor: Hoist editor ready logic

Align with the web editor. Begin a path towards avoiding portions of the
editor UI appearing at different times.
  • Loading branch information
dcalhoun authored and kean committed Feb 18, 2025
1 parent 41e3615 commit c98af19
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 51 deletions.
2 changes: 1 addition & 1 deletion ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public struct EditorConfiguration {
public var postType: String?
public var themeStyles = false
public var plugins = false
public var isTitleHidden = false
public var hideTitle = false
public var siteURL = ""
public var siteApiRoot = ""
public var siteApiNamespace = ""
Expand Down
4 changes: 1 addition & 3 deletions ios/Sources/GutenbergKit/Sources/EditorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,12 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro
siteApiNamespace: '\(configuration.siteApiNamespace)',
authHeader: '\(configuration.authHeader)',
themeStyles: \(configuration.themeStyles),
hideTitle: \(configuration.hideTitle),
post: {
id: \(configuration.postID ?? -1),
title: '\(escapedTitle)',
content: '\(escapedContent)'
},
settings: {
isTitleHidden: \(configuration.isTitleHidden)
},
};
localStorage.setItem('GBKit', JSON.stringify(window.GBKit));
"done";
Expand Down
38 changes: 22 additions & 16 deletions src/components/editor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { useRef } from '@wordpress/element';
* Internal dependencies
*/
import VisualEditor from '../visual-editor';
import EditorLoadNotice from '../editor-load-notice';
import './style.scss';
import { useSyncHistoryControls } from './use-sync-history-controls';
import { useHostBridge } from './use-host-bridge';
Expand All @@ -33,13 +32,14 @@ const { ExperimentalBlockEditorProvider: BlockEditorProvider } = unlock(
/**
* Entry component rendering the editor and surrounding UI.
*
* @param {Object} props Component props.
* @param {Post} props.post Post object containing post details.
* @param {import('@wordpress/element').Element} props.children The children to render in the editor.
* @param {Object} props Component props.
* @param {Post} props.post Post object containing post details.
* @param {boolean} props.hideTitle Whether to hide the title input.
* @param {import('@wordpress/element').Element} props.children The children to render in the editor.
*
* @return {JSX.Element} The rendered App component.
*/
export default function Editor({ post, children }) {
export default function Editor({ post, children, hideTitle }) {
const editorRef = useRef(null);
useHostBridge(post, editorRef);
useSyncHistoryControls();
Expand All @@ -54,22 +54,31 @@ export default function Editor({ post, children }) {
);

const settings = useGBKitSettings(post);
const useRootPaddingAwareAlignments =
settings.themeStyles &&
settings.__experimentalFeatures?.useRootPaddingAwareAlignments;

const { mode, isRichEditingEnabled } = useSelect((select) => {
const { getEditorSettings, getEditorMode } = select(editorStore);
const { isReady, mode, isRichEditingEnabled } = useSelect((select) => {
const { __unstableIsEditorReady, getEditorSettings, getEditorMode } =
select(editorStore);
const editorSettings = getEditorSettings();

return {
// TODO(Perf): The `__unstableIsEditorReady` selector is insufficient as
// it does not account for post type loading, which is first referenced
// within the post title component render. This results in the post title
// popping in after the editor mounted. The web editor does not experience
// this issue because the post type is loaded for "mode" selection before
// the editor is mounted.
isReady: __unstableIsEditorReady(),
mode: getEditorMode(),
isRichEditingEnabled: editorSettings.richEditingEnabled,
};
}, []);

if (!isReady) {
return null;
}

return (
<div className="gutenberg-kit-editor" ref={editorRef}>
<EditorLoadNotice className="gutenberg-kit-editor__load-notice" />
<BlockEditorProvider
value={postBlocks}
onInput={onInput}
Expand All @@ -78,18 +87,15 @@ export default function Editor({ post, children }) {
useSubRegistry={false}
>
{mode === 'visual' && isRichEditingEnabled && (
<VisualEditor
useRootPaddingAwareAlignments={
useRootPaddingAwareAlignments
}
/>
<VisualEditor hideTitle={hideTitle} />
)}

{(mode === 'text' || !isRichEditingEnabled) && (
<TextEditor
// We should auto-focus the canvas (title) on load.
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true}
hideTitle={hideTitle}
/>
)}

Expand Down
2 changes: 2 additions & 0 deletions src/components/layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { EditorSnackbars, ErrorBoundary } from '@wordpress/editor';
* Internal dependencies
*/
import Editor from './editor';
import EditorLoadNotice from './editor-load-notice';

/**
* Top-level layout, including the Editor component wrapped in an ErrorBoundary.
Expand All @@ -21,6 +22,7 @@ export default function Layout(props) {
<Editor {...props}>
<EditorSnackbars />
</Editor>
<EditorLoadNotice className="gutenberg-kit-editor__load-notice" />
</ErrorBoundary>
);
}
12 changes: 10 additions & 2 deletions src/components/text-editor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@ import { PostTitleRaw, PostTextEditor } from '@wordpress/editor';
*/
import './style.scss';

export default function TextEditor() {
/**
* TextEditor component renders a text editor with an optional title.
*
* @param {Object} props Component props.
* @param {boolean} props.hideTitle Whether to hide the title input.
*
* @return {JSX.Element} The rendered text editor component.
*/
export default function TextEditor({ hideTitle }) {
return (
<div className="gutenberg-kit-text-editor">
<PostTitleRaw />
{!hideTitle && <PostTitleRaw />}
<PostTextEditor />
</div>
);
Expand Down
105 changes: 85 additions & 20 deletions src/components/visual-editor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useRef } from '@wordpress/element';
import {
BlockList,
privateApis as blockEditorPrivateApis,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { Popover } from '@wordpress/components';
import { store as editorStore, PostTitle } from '@wordpress/editor';
Expand All @@ -36,58 +37,122 @@ import { unlock } from '../../lock-unlock';
import DefaultBlockAppender from '../default-block-appender';
import { getGBKit } from '../../utils/bridge';

const { ExperimentalBlockCanvas: BlockCanvas } = unlock(blockEditorPrivateApis);
const {
ExperimentalBlockCanvas: BlockCanvas,
LayoutStyle,
useLayoutClasses,
useLayoutStyles,
} = unlock(blockEditorPrivateApis);

// Add some styles for alignwide/alignfull Post Content and its children.
const alignCSS = `.is-root-container.alignwide { max-width: var(--wp--style--global--wide-size); margin-left: auto; margin-right: auto;}
.is-root-container.alignwide:where(.is-layout-flow) > :not(.alignleft):not(.alignright) { max-width: var(--wp--style--global--wide-size);}
.is-root-container.alignfull { max-width: none; margin-left: auto; margin-right: auto;}
.is-root-container.alignfull:where(.is-layout-flow) > :not(.alignleft):not(.alignright) { max-width: none;}`;

/**
* Editor component for managing and editing post content.
*
* @param {Object} props Component props.
* @param {boolean} props.useRootPaddingAwareAlignments Apply root padding.
* @param {Object} props Component props.
* @param {boolean} props.hideTitle Whether to hide the title input.
*
* @return {JSX.Element} The rendered Editor component.
*/
function VisualEditor({ useRootPaddingAwareAlignments }) {
function VisualEditor({ hideTitle }) {
const editorPostTitleRef = useRef();
const { settings } = getGBKit();
const isTitleHidden = settings?.isTitleHidden || false;

const { isEditorReady } = useSelect((select) => {
const { __unstableIsEditorReady } = select(editorStore);
const {
renderingMode,
themeHasDisabledLayoutStyles,
themeSupportsLayout,
hasRootPaddingAwareAlignments,
} = useSelect((select) => {
const { getRenderingMode } = select(editorStore);
const _renderingMode = getRenderingMode();
const { getSettings } = unlock(select(blockEditorStore));
const _settings = getSettings();

return {
isEditorReady: __unstableIsEditorReady(),
renderingMode: _renderingMode,
themeSupportsLayout: _settings.supportsLayout,
themeHasDisabledLayoutStyles: _settings.disableLayoutStyles,
hasRootPaddingAwareAlignments:
_settings.__experimentalFeatures?.useRootPaddingAwareAlignments,
};
}, []);

const styles = useEditorStyles();

const editorClasses = clsx('gutenberg-kit-visual-editor', {
'has-root-padding': !useRootPaddingAwareAlignments,
'has-root-padding': !hasRootPaddingAwareAlignments,
});

const titleClasses = clsx(
'gutenberg-kit-visual-editor__post-title-wrapper',
'editor-visual-editor__post-title-wrapper',
{ 'has-global-padding': hasRootPaddingAwareAlignments }
);

// An opinionated default, as we currently cannot retrievew the post content
// attributes from the REST API, as it does not include the current post
// context.
const postContentAttributes = {
align: 'full',
layout: { type: 'constrained' },
};
const { layout = {}, align = '' } = postContentAttributes;
const postContentLayoutClasses = useLayoutClasses(
postContentAttributes,
'core/post-content'
);
const postContentLayoutStyles = useLayoutStyles(
postContentAttributes,
'core/post-content',
'.block-editor-block-list__layout.is-root-container'
);
const blockListClasses = clsx(
themeSupportsLayout && postContentLayoutClasses,
align && `align${align}`,
{
'has-global-padding': useRootPaddingAwareAlignments,
'is-layout-flow': !themeSupportsLayout,
'has-global-padding': hasRootPaddingAwareAlignments,
}
);
const blockListClasses = clsx({
'has-global-padding': useRootPaddingAwareAlignments,
});

return (
<div className={editorClasses}>
<BlockCanvas shouldIframe={false} height="100%" styles={styles}>
{!isTitleHidden && (
{themeSupportsLayout &&
!themeHasDisabledLayoutStyles &&
renderingMode === 'post-only' && (
<>
<LayoutStyle
selector=".editor-visual-editor__post-title-wrapper"
layout={layout}
/>
<LayoutStyle
selector=".block-editor-block-list__layout.is-root-container"
layout={layout}
/>
{align && <LayoutStyle css={alignCSS} />}
{postContentLayoutStyles && (
<LayoutStyle
layout={layout}
css={postContentLayoutStyles}
/>
)}
</>
)}
{!hideTitle && (
<div className={titleClasses}>
{isEditorReady && <PostTitle ref={editorPostTitleRef} />}
<PostTitle ref={editorPostTitleRef} />
</div>
)}
<BlockList className={blockListClasses} />
<BlockList className={blockListClasses} layout={layout} />
<DefaultBlockAppender />
</BlockCanvas>

{isEditorReady && (
<EditorToolbar className="gutenberg-kit-visual-editor__toolbar" />
)}
<EditorToolbar className="gutenberg-kit-visual-editor__toolbar" />

<Popover.Slot />
</div>
Expand Down
7 changes: 6 additions & 1 deletion src/components/visual-editor/use-editor-styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { unlock } from '../../lock-unlock';
// The Vite query parameter breaks the linter's import resolution
// eslint-disable-next-line import/no-unresolved
import defaultThemeStyles from './default-theme-styles.scss?inline';
// eslint-disable-next-line import/no-unresolved
import commonStyles from './wp-common-styles.scss?inline';

const { getLayoutStyles } = unlock(blockEditorPrivateApis);

Expand Down Expand Up @@ -60,7 +62,10 @@ export function useEditorStyles() {
? (editorSettings.styles ?? [])
: defaultEditorStyles;

return baseStyles;
// `commonStyles` represent manually added notable styles that are missing.
// The styles likely absent due to them being injected by the WP Admin
// context.
return [{ css: commonStyles }, ...baseStyles];
}, [
editorSettings.defaultEditorStyles,
editorSettings.disableLayoutStyles,
Expand Down
46 changes: 46 additions & 0 deletions src/components/visual-editor/wp-common-styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Manually added styles that are missing from the editor settings, likely
// because the styles are injected by the WP Admin context, which is not present
// in the REST API endpoint.

// Constraints for the post title.
.editor-visual-editor__post-title-wrapper
> :where(:not(.alignleft):not(.alignright):not(.alignfull)) {
max-width: var(--wp--style--global--content-size);
margin-left: auto !important;
margin-right: auto !important;
}

.editor-visual-editor__post-title-wrapper > .alignwide {
max-width: 1340px;
}

.editor-visual-editor__post-title-wrapper > .alignfull {
max-width: none;
}

// Remove margin from the first and last child of a layout flow container.
:root :where(.is-layout-flow) > :first-child {
margin-block-start: 0;
}

:root :where(.is-layout-flow) > :last-child {
margin-block-end: 0;
}

:root :where(.is-layout-flow) > * {
margin-block-start: 1.2rem;
margin-block-end: 0;
}

:root :where(.is-layout-constrained) > :first-child {
margin-block-start: 0;
}

:root :where(.is-layout-constrained) > :last-child {
margin-block-end: 0;
}

:root :where(.is-layout-constrained) > * {
margin-block-start: 1.2rem;
margin-block-end: 0;
}
7 changes: 2 additions & 5 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ initializeEditor();
* Configure editor settings and styles, and render the editor.
*/
function initializeEditor() {
const { themeStyles } = getGBKit();
const { themeStyles, hideTitle } = getGBKit();

// TEMP: This should be fetched from the host apps.
apiFetch({ path: `/wp-block-editor/v1/settings` })
Expand All @@ -43,13 +43,10 @@ function initializeEditor() {
});

const post = getPost();
const settings = {
post,
};

createRoot(document.getElementById('root')).render(
<StrictMode>
<Layout {...settings} />
<Layout post={post} hideTitle={hideTitle} />
</StrictMode>
);
}
Loading

0 comments on commit c98af19

Please sign in to comment.