Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full Site Editing: Introduce a template editing mode inside the post editor #26355

Merged
merged 21 commits into from Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/designers-developers/developers/data/data-core-edit-post.md
Expand Up @@ -110,6 +110,18 @@ _Returns_

- `boolean`: Whether there are metaboxes or not.

<a name="isEditingTemplate" href="#isEditingTemplate">#</a> **isEditingTemplate**

Returns true if the template editing mode is enabled.

_Parameters_

- _state_ `Object`: Global application state.

_Returns_

- `boolean`: Whether we're editing the template.

<a name="isEditorPanelEnabled" href="#isEditorPanelEnabled">#</a> **isEditorPanelEnabled**

Returns true if the given panel is enabled, or false otherwise. Panels are
Expand Down Expand Up @@ -381,6 +393,18 @@ _Parameters_

- _metaBoxesPerLocation_ `Object`: Meta boxes per location.

<a name="setIsEditingTemplate" href="#setIsEditingTemplate">#</a> **setIsEditingTemplate**

Returns an action object used to switch to template editing.

_Parameters_

- _value_ `boolean`: Is editing template.

_Returns_

- `Object`: Action object.

<a name="setIsInserterOpened" href="#setIsInserterOpened">#</a> **setIsInserterOpened**

Returns an action object used to open/close the inserter.
Expand Down
13 changes: 13 additions & 0 deletions lib/client-assets.php
Expand Up @@ -649,3 +649,16 @@ function gutenberg_extend_block_editor_settings_with_default_editor_styles( $set
return $settings;
}
add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_settings_with_default_editor_styles' );

/**
* Adds a flag to the editor settings to know whether we're in FSE theme or not.
*
* @param array $settings Default editor settings.
*
* @return array Filtered editor settings.
*/
function gutenberg_extend_block_editor_settings_with_fse_theme_flag( $settings ) {
$settings['isFSETheme'] = gutenberg_is_fse_theme();
return $settings;
}
add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_settings_with_fse_theme_flag' );
14 changes: 1 addition & 13 deletions packages/block-library/src/post-content/edit.js
Expand Up @@ -2,7 +2,6 @@
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
import { useBlockProps } from '@wordpress/block-editor';

/**
Expand All @@ -13,19 +12,8 @@ import PostContentInnerBlocks from './inner-blocks';
export default function PostContentEdit( {
context: { postId: contextPostId, postType: contextPostType },
} ) {
const { id: currentPostId, type: currentPostType } = useSelect(
( select ) => select( 'core/editor' ).getCurrentPost() ?? {}
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reverts #24010 cc @Addison-Stavlo

It seems the checks were not good enough IMO. It relies on something that doesn't give us semantic information about the context but it was more an implementation detail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree that these original checks were not great, but there simply wasn't another way to do it with the information available at the time. There didn't seem to be a super clear way to add the necessary information to the context either... The problem is really, given a global context with a postID, how do you prevent nested context consumers from rendering recursively infinitely?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we need a context value to indicate the post content id we're currently rendering (and not just the "post id").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not fixed entirely, but the important breakage in the template editor is fixed by 46724a8

const blockProps = useBlockProps();

// Only render InnerBlocks if the context is different from the active post
// to avoid infinite recursion of post content.
if (
contextPostId &&
contextPostType &&
contextPostId !== currentPostId &&
contextPostType !== currentPostType
) {
if ( contextPostId && contextPostType ) {
return (
<div { ...blockProps }>
<PostContentInnerBlocks
Expand Down
17 changes: 17 additions & 0 deletions packages/core-data/src/controls.js
@@ -0,0 +1,17 @@
export function regularFetch( url ) {
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
return {
type: 'REGULAR_FETCH',
url,
};
}
const controls = {
async REGULAR_FETCH( { url } ) {
const { data } = await window
.fetch( url )
.then( ( res ) => res.json() );

return data;
},
};

export default controls;
3 changes: 2 additions & 1 deletion packages/core-data/src/index.js
Expand Up @@ -13,6 +13,7 @@ import * as actions from './actions';
import * as resolvers from './resolvers';
import * as locksSelectors from './locks/selectors';
import * as locksActions from './locks/actions';
import customControls from './controls';
import { defaultEntities, getMethodName } from './entities';
import { STORE_NAME } from './name';

Expand Down Expand Up @@ -58,7 +59,7 @@ const entityActions = defaultEntities.reduce( ( result, entity ) => {

const storeConfig = {
reducer,
controls,
controls: { ...customControls, ...controls },
actions: { ...actions, ...entityActions, ...locksActions },
selectors: { ...selectors, ...entitySelectors, ...locksSelectors },
resolvers: { ...resolvers, ...entityResolvers },
Expand Down
39 changes: 39 additions & 0 deletions packages/core-data/src/resolvers.js
Expand Up @@ -10,6 +10,10 @@ import { addQueryArgs } from '@wordpress/url';
import deprecated from '@wordpress/deprecated';
import { controls } from '@wordpress/data';
import { apiFetch } from '@wordpress/data-controls';
/**
* Internal dependencies
*/
import { regularFetch } from './controls';

/**
* Internal dependencies
Expand Down Expand Up @@ -382,3 +386,38 @@ export function* getAutosaves( postType, postId ) {
export function* getAutosave( postType, postId ) {
yield controls.resolveSelect( 'core', 'getAutosaves', postType, postId );
}

/**
* Retrieve the frontend template used for a given link.
*
* @param {string} link Link.
*/
export function* __experimentalGetTemplateForLink( link ) {
gziolo marked this conversation as resolved.
Show resolved Hide resolved
// Ideally this should be using an apiFetch call
// We could potentially do so by adding a "filter" to the `wp_template` end point.
// Also it seems the returned object is not a regular REST API post type.
const template = yield regularFetch(
addQueryArgs( link, {
'_wp-find-template': true,
} )
);

if ( template === null ) {
return;
}

yield getEntityRecord( 'postType', 'wp_template', template.ID );
const record = yield controls.select(
'core',
'getEntityRecord',
'postType',
'wp_template',
template.ID
);

if ( record ) {
yield receiveEntityRecords( 'postType', 'wp_template', [ record ], {
'find-template': link,
} );
}
}
16 changes: 16 additions & 0 deletions packages/core-data/src/selectors.js
Expand Up @@ -740,3 +740,19 @@ export const getReferenceByDistinctEdits = createSelector(
state.undo.flattenedUndo,
]
);

/**
* Retrieve the frontend template used for a given link.
*
* @param {Object} state Editor state.
* @param {string} link Link.
*
* @return {Object?} The template record.
*/
export function __experimentalGetTemplateForLink( state, link ) {
const records = getEntityRecords( state, 'postType', 'wp_template', {
'find-template': link,
} );

return records?.length ? records[ 0 ] : null;
}
12 changes: 12 additions & 0 deletions packages/e2e-test-utils/README.md
Expand Up @@ -446,6 +446,18 @@ Clicks on the button in the header which opens Document Settings sidebar when it

Opens the global block inserter.

<a name="openPreviewPage" href="#openPreviewPage">#</a> **openPreviewPage**

Opens the preview page of an edited post.

_Parameters_

- _editorPage_ `Page`: puppeteer editor page.

_Returns_

- `Page`: preview page.

<a name="openPublishPanel" href="#openPublishPanel">#</a> **openPublishPanel**

Opens the publish panel.
Expand Down
1 change: 1 addition & 0 deletions packages/e2e-test-utils/src/index.js
Expand Up @@ -74,5 +74,6 @@ export { uninstallPlugin } from './uninstall-plugin';
export { visitAdminPage } from './visit-admin-page';
export { waitForWindowDimensions } from './wait-for-window-dimensions';
export { showBlockToolbar } from './show-block-toolbar';
export { openPreviewPage } from './preview';

export * from './mocks';
29 changes: 29 additions & 0 deletions packages/e2e-test-utils/src/preview.js
@@ -0,0 +1,29 @@
/**
* External dependencies
*/
import { last } from 'lodash';

/** @typedef {import('puppeteer').Page} Page */

/**
* Opens the preview page of an edited post.
*
* @param {Page} editorPage puppeteer editor page.
* @return {Page} preview page.
*/
export async function openPreviewPage( editorPage = page ) {
let openTabs = await browser.pages();
const expectedTabsCount = openTabs.length + 1;
await editorPage.click( '.block-editor-post-preview__button-toggle' );
await editorPage.waitFor( '.edit-post-header-preview__button-external' );
await editorPage.click( '.edit-post-header-preview__button-external' );

// Wait for the new tab to open.
while ( openTabs.length < expectedTabsCount ) {
await editorPage.waitFor( 1 );
openTabs = await browser.pages();
}

const previewPage = last( openTabs );
return previewPage;
}
28 changes: 2 additions & 26 deletions packages/e2e-tests/specs/editor/plugins/block-context.test.js
@@ -1,8 +1,3 @@
/**
* External dependencies
*/
import { last } from 'lodash';

/**
* WordPress dependencies
*/
Expand All @@ -12,29 +7,9 @@ import {
deactivatePlugin,
insertBlock,
saveDraft,
openPreviewPage,
} from '@wordpress/e2e-test-utils';

async function openPreviewPage( editorPage ) {
let openTabs = await browser.pages();
const expectedTabsCount = openTabs.length + 1;
await editorPage.click( '.block-editor-post-preview__button-toggle' );
await editorPage.waitFor( '.edit-post-header-preview__button-external' );
await editorPage.click( '.edit-post-header-preview__button-external' );

// Wait for the new tab to open.
while ( openTabs.length < expectedTabsCount ) {
await editorPage.waitFor( 1 );
openTabs = await browser.pages();
}

const previewPage = last( openTabs );
// Wait for the preview to load. We can't do interstitial detection here,
// because it might load too quickly for us to pick up, so we wait for
// the preview to load by waiting for the content to appear.
await previewPage.waitForSelector( '.entry-content' );
return previewPage;
}

describe( 'Block context', () => {
beforeAll( async () => {
await activatePlugin( 'gutenberg-test-block-context' );
Expand Down Expand Up @@ -74,6 +49,7 @@ describe( 'Block context', () => {
await insertBlock( 'Test Context Provider' );
const editorPage = page;
const previewPage = await openPreviewPage( editorPage );
await previewPage.waitForSelector( '.entry-content' );

// Check default context values are populated.
let content = await previewPage.$eval(
Expand Down
30 changes: 4 additions & 26 deletions packages/e2e-tests/specs/editor/various/preview.test.js
@@ -1,8 +1,3 @@
/**
* External dependencies
*/
import { last } from 'lodash';

/**
* WordPress dependencies
*/
Expand All @@ -15,31 +10,11 @@ import {
deactivatePlugin,
publishPost,
saveDraft,
openPreviewPage,
} from '@wordpress/e2e-test-utils';

/** @typedef {import('puppeteer').Page} Page */

async function openPreviewPage( editorPage ) {
let openTabs = await browser.pages();
const expectedTabsCount = openTabs.length + 1;
await editorPage.click( '.block-editor-post-preview__button-toggle' );
await editorPage.waitFor( '.edit-post-header-preview__button-external' );
await editorPage.click( '.edit-post-header-preview__button-external' );

// Wait for the new tab to open.
while ( openTabs.length < expectedTabsCount ) {
await editorPage.waitFor( 1 );
openTabs = await browser.pages();
}

const previewPage = last( openTabs );
// Wait for the preview to load. We can't do interstitial detection here,
// because it might load too quickly for us to pick up, so we wait for
// the preview to load by waiting for the title to appear.
await previewPage.waitForSelector( '.entry-title' );
return previewPage;
}

/**
* Given the Page instance for the editor, opens preview drodpdown, and
* awaits the presence of the external preview selector.
Expand Down Expand Up @@ -123,6 +98,7 @@ describe( 'Preview', () => {
await editorPage.type( '.editor-post-title__input', 'Hello World' );

const previewPage = await openPreviewPage( editorPage );
await previewPage.waitForSelector( '.entry-title' );

// When autosave completes for a new post, the URL of the editor should
// update to include the ID. Use this to assert on preview URL.
Expand Down Expand Up @@ -222,6 +198,7 @@ describe( 'Preview', () => {

// Open the preview page.
const previewPage = await openPreviewPage( editorPage );
await previewPage.waitForSelector( '.entry-title' );

// Title in preview should match input.
let previewTitle = await previewPage.$eval(
Expand Down Expand Up @@ -282,6 +259,7 @@ describe( 'Preview with Custom Fields enabled', () => {

// Open the preview page.
const previewPage = await openPreviewPage( editorPage );
await previewPage.waitForSelector( '.entry-title' );

// Check the title and preview match.
let previewTitle = await previewPage.$eval(
Expand Down