diff --git a/docs/how-to-guides/platform/custom-block-editor.md b/docs/how-to-guides/platform/custom-block-editor.md new file mode 100644 index 0000000000000..bb119977e6986 --- /dev/null +++ b/docs/how-to-guides/platform/custom-block-editor.md @@ -0,0 +1,599 @@ +# Building a custom block editor + +The WordPress block editor is a powerful tool that allows you to create and format content in various ways. It is powered, in part, by the [`@wordpress/block-editor`](/packages/block-editor/README.md) package, which is a JavaScript library that provides the core functionality of the editor. + +This package can also be used to create custom block editors for virtually any other web application. This means that you can use the same blocks and block editing experience outside of WordPress. + +![alt text](https://developer.wordpress.org/files/2023/07/custom-block-editor.png 'The Standalone Editor instance populated with example Blocks within a custom WordPress admin page.') + +This flexibility and interoperability makes blocks a powerful tool for building and managing content across multiple applications. It also makes it simpler for developers to create content editors that work best for their users. + +This guide covers the basics of creating your first custom block editor. + +## Table of contents + +- [Introduction](#introduction) +- [Code Syntax](#code-syntax) +- [What you're going to be building](#what-youre-going-to-be-building) +- [Plugin setup and organization](#plugin-setup-and-organization) +- [The "Core" of the editor](#the-core-of-the-editor) +- [Creating the custom "Block Editor" page](#creating-the-custom-block-editor-page) +- [Registering and rendering the custom block editor](#registering-and-rendering-the-custom-block-editor) +- [Reviewing the `` component](#reviewing-the-editor-component) +- [The custom ``](#the-custom-blockeditor) +- [Reviewing the sidebar](#reviewing-the-sidebar) +- [Block persistence](#block-persistence) +- [Wrapping up](#wrapping-up) + +## Introduction + +With its many packages and components, the Gutenberg codebase can be daunting at first. But at its core, it's all about managing and editing blocks. So if you want to work on the editor, it's essential to understand how block editing works at a fundamental level. + +This guide will walk you through building a fully functioning, custom block editor "instance" within WordPress. Along the way, we'll introduce you to the key packages and components, so you can see how the block editor works under the hood. + +By the end of this article, you will have a solid understanding of the block editor's inner workings and be well on your way to creating your own block editor instances. + +
+ The code used throughout this guide is available for download in the accompanying WordPress plugin. The demo code in this plugin as an essential resource. +
+ +## Code syntax + +The code snippets in this guide use JSX syntax. However, you could use plain JavaScript if you prefer. However, once familiar with JSX, many developers find it easier to read and write, so most code examples in the Block Editor Handbook use this syntax. + +## What you're going to be building + +Throughout this guide, you will create an (almost) fully functioning block editor instance. The result will look something like this: + +![The Standalone Editor instance populated with example Blocks within a custom WordPress admin page](https://developer.wordpress.org/files/2023/07/custom-block-editor.png) + +While it looks similar, this editor will not be the same _Block Editor_ you are familiar with when creating posts and pages in WordPress. Instead, it will be an entirely custom instance that will live within a custom WordPress admin page called "Block Editor." + +The editor will have the following features: + +- Ability to add and edit all Core blocks. +- Familiar visual styles and main/sidebar layout. +- _Basic_ block persistence between page reloads. + +## Plugin setup and organization + +The custom editor is going to be built as a WordPress plugin. To keep things simple, the plugin will be named `Standalone Block Editor Demo` because that is what it does. + +The plugin file structure will look like this: + +![alt text](https://wordpress.org/gutenberg/files/2020/03/repo-files.png 'Screenshot showing file structure of the Plugin at https://github.com/getdave/standalone-block-editor.') + +Here is a brief summary of what's going on: + +- `plugin.php` – Standard plugin "entry" file with comment meta data, which requires `init.php`. +- `init.php` - Handles the initialization of the main plugin logic. +- `src/` (directory) - This is where the JavaScript and CSS source files will live. These files are _not_ directly enqueued by the plugin. +- `webpack.config.js` - A custom Webpack config extending the defaults provided by the [`@wordpress/scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) npm package to allow for custom CSS styles (via Sass). + +The only item not shown above is the `build/` directory, which is where the _compiled_ JS and CSS files are outputted by `@wordpress/scripts`. These files are enqueued by the plugin seperately. + +
+ Throughout this guide, filename references will be placed in a comment at the top of each code snippet so you can follow along. +
+ +With the basic file structure in place, let's look at what packages will be needed. + +## The "Core" of the editor + +While the WordPress Editor is comprised of many moving parts, at its core is the [`@wordpress/block-editor`](/packages/block-editor/README.md) package, which is best summarized by its own `README` file: + +> This module allows you to create and use standalone block editors. + +Perfect, this is the main package you will use to create the custom block editor instance. But first, you need to create a home for the editor. + +## Creating the custom "Block Editor" page + +Let's begin by creating a custom page within WordPress admin that will house the custom block editor instance. + +
+ If you're already comfortable with the process of creating custom admin pages in WordPress, you might want to skip ahead. +
+ +### Registering the page + +To do this, you need to [register a custom admin page](https://developer.wordpress.org/reference/functions/add_menu_page/) using the standard WordPress [`add_menu_page()`](https://developer.wordpress.org/reference/functions/add_menu_page/) helper: + +```php +// File: init.php + +add_menu_page( + 'Standalone Block Editor', // Visible page name + 'Block Editor', // Menu label + 'edit_posts', // Required capability + 'getdavesbe', // Hook/slug of page + 'getdave_sbe_render_block_editor', // Function to render the page + 'dashicons-welcome-widgets-menus' // Custom icon +); +``` + +The `getdave_sbe_render_block_editor` function will be used to render the contents of the admin page. As a reminder, the source code for each step is available in the [accompanying plugin](https://github.com/getdave/standalone-block-editor). + +### Adding the target HTML + +Since the block editor is a React-powered application, you need to output some HTML into the custom page where JavaScript can render the block editor. + +Let's use the `getdave_sbe_render_block_editor` function referenced in the step above. + +```php +// File: init.php + +function getdave_sbe_render_block_editor() { + ?> +
+ Loading Editor... +
+ ` component into the waiting `
` on the custom admin page. + +```jsx +domReady( function () { + const settings = window.getdaveSbeSettings || {}; + registerCoreBlocks(); + render( + , + document.getElementById( 'getdave-sbe-block-editor' ) + ); +} ); +``` + +
+ It is possible to render the editor from PHP without creating an unnecessary JS global. Check out the Edit Site package in the Gutenberg plugin for an example of this. +
+ +## Reviewing the `` component + +Let's take a closer look at the `` component that was used in the code above and lives in `src/editor.js` of the [companion plugin](https://github.com/getdave/standalone-block-editor). + +Despite its name, this is not the actual core of the block editor. Rather, it is a _wrapper_ component that will contain the components that form the custom editor's main body. + +### Dependencies + +The first thing to do inside `` is to pull in some dependencies. + +```jsx +// File: src/editor.js + +import Notices from 'components/notices'; +import Header from 'components/header'; +import Sidebar from 'components/sidebar'; +import BlockEditor from 'components/block-editor'; +``` + +The most important of these are the internal components `BlockEditor` and `Sidebar`, which will be covered shortly. + +The remaining components consist mostly of static elements that form the editor's layout and surrounding user interface (UI). These elements include the header and notice areas, among others. + +### Editor render + +With these components available, you can define the `` component. + +```jsx +// File: src/editor.js + +function Editor( { settings } ) { + return ( + + +
+ +
+ + +
+ +
+
+ ); +} +``` + +In this process, the core of the editor's layout is being scaffolded, along with a few specialized [context providers](https://reactjs.org/docs/context.html#contextprovider) that make specific functionality available throughout the component hierarchy. + +Let's examine these in more detail: + +- `` – Enables the use of the ["Slot/Fill" + pattern](/docs/reference-guides/slotfills/README.md) through the component tree +- `` – Enables the use of [dropzones for drag and drop functionality](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/components/src/drop-zone) +- `` – Provides a "snack bar" Notice that will be rendered if any messages are dispatched to the `core/notices` store +- `
` – Renders the static title "Standalone Block Editor" at the top of the editor UI +- `` – The custom block editor component +- `` – Renders a slot into which ``s can be rendered + using the Slot/Fill mechanic + +### Keyboard navigation + +With this basic component structure in place, the only remaining thing left to do +is wrap everything in the [`navigateRegions` HOC](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/components/src/higher-order/navigate-regions) to provide keyboard navigation between the different "regions" in the layout. + +```jsx +// File: src/editor.js + +export default navigateRegions( Editor ); +``` + +## The custom `` + +Now the core layouts and components are in place. It's time to explore the custom implementation of the block editor itself. + +The component for this is called ``, and this is where the magic happens. + +Opening `src/components/block-editor/index.js` reveals that it's the most complex component encountered thus far. A lot going on, so start by focusing on what is being rendered by the `` component: + +```js +// File: src/components/block-editor/index.js + +return ( +
+ + + + +
+ + + + + + +
+
+
+); +``` + +The key components are `` and ``. Let's examine these. + +### Understanding the `` component + +[``](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/provider) is one of the most important components in the hierarchy. It establishes a new block editing context for a new block editor. + +As a result, it is _fundamental_ to the entire goal of this project. + +The children of `` comprise the UI for the block editor. These components then have access to data (via `Context`), enabling them to _render_ and _manage_ the blocks and their behaviors within the editor. + +```jsx +// File: src/components/block-editor/index.js + + +``` + +#### `BlockEditor` props + +You can see that `` accepts an array of (parsed) block objects as its `value` prop and, when there's a change detected within the editor, calls the `onChange` and/or `onInput` handler prop (passing the new Blocks as an argument). + +Internally it does this by subscribing to the provided `registry` (via the [`withRegistryProvider` HOC](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/provider/index.js#L158)), listening to block change events, determining whether the block changing was persistent, and then calling the appropriate `onChange|Input` handler accordingly. + +For the purposes of this simple project, these features allow you to: + +- Store the array of current blocks in state as `blocks`. +- Update the `blocks` state in memory on `onInput` by calling the hook setter + `updateBlocks(blocks)`. +- Handle basic persistence of blocks into `localStorage` using `onChange`. This is [fired when block updates are considered "committed"](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/provider#onchange). + +It's also worth recalling that the component accepts a `settings` property. This is where you will add the editor settings inlined earlier as JSON within `init.php`. You can use these settings to configure features such as custom colors, available image sizes, and [much more](https://github.com/WordPress/gutenberg/tree/4c472c3443513d070a50ba1e96f3a476861447b3/packages/block-editor#SETTINGS_DEFAULTS). + +### Understanding the `` component + +Alongside `` the next most interesting component is [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/block-list/index.js). + +This is one of the most important components as it's role is to **render a list of blocks into the editor**. + +It does this in part thanks to being placed as a child of ``, which affords it full access to all information about the state of the current blocks in the editor. + +#### How does `BlockList` work? + +Under the hood, `` relies on several other lower-level components in order to render the list of blocks. + +The hierarchy of these components can be _approximated_ as follows: + +```jsx +// Pseudo code for example purposes only. + + + /* renders a list of blocks from the rootClientId. */ + + /* renders a single block from the BlockList. */ + + /* renders the standard editable area of a block. */ + /* renders the block UI as defined by its `edit()` implementation. + */ + + + +``` + +Here is roughly how this works together to render the list of blocks: + +- `` loops over all the block `clientIds` and + renders each via [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/block-list/block.js). +- ``, in turn, renders the individual block + using its own subcomponent [``](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-edit/index.js). +- Finally, the [block itself](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-edit/edit.js) is rendered using the `Component` placeholder component. + +The `@wordpress/block-editor` package components are among the most complex and involved. Understanding them is crucial if you want to grasp how the editor functions at a fundamental level. Studying these components is strongly advised. + +### Utility components in the custom block editor + +Jumping back to your custom `` component, it is also worth noting the following "utility" components: + +```js +// File: src/components/block-editor/index.js + +
+ /* 1. */ + + /* 2. */ + + /* 3. */ + + + +
+``` + +These provide other important elements of functionality for the editor instance. + +1. [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/keyboard-shortcuts/index.js) – Enables and usage of keyboard shortcuts within the editor +2. [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/writing-flow/index.js) – Handles selection, focus management, and navigation across blocks +3. [``](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/observe-typing)- Used to manage the editor's internal `isTyping` flag + +## Reviewing the sidebar + +Also within the render of the ``, is the `` component. + +```jsx +// File: src/components/block-editor/index.js + +return ( +
+ + /* <-- SIDEBAR */ + + +
+ // snip +
+
+
+); +``` + +This is used, in part, to display advanced block settings via the `` component. + +```jsx + + + +``` + +However, the keen-eyed readers amongst you will have already noted the presence of a `` component within the `` (`src/editor.js`) component's +layout: + +```jsx +// File: src/editor.js + +
+ // <-- What's this? + + +``` + +Opening the `src/components/sidebar/index.js` file, you can see that this is, in fact, the component rendered within `` above. However, the implementation utilises +Slot/Fill to expose a `Fill` (``), which is subsequently imported and rendered inside of the `` component (see above). + +With this in place, you then can render `` as a child of the `Sidebar.InspectorFill`. This has the result of allowing you to keep `` within the React context of `` whilst allowing it to be rendered into the DOM in a separate location (i.e. in the ``). + +This might seem overly complex, but it is required in order for `` to have access to information about the current block. Without Slot/Fill, this setup would be extremely difficult to achieve. + +And with that you have covered the render of you custom ``. + +
+<BlockInspector> +itself actually renders a Slot for <InspectorControls>. This is what allows you render a <InspectorControls>> component inside +the edit() definition for your block and have +it display within the editor's sidebar. Exploring this component in more detail is recommended. +
+ +## Block Persistence + +You have come a long way on your journey to create a custom block editor. But there is one major area left to touch upon - block persistence. In other words, having your +blocks saved and available _between_ page refreshes. + +![alt text](https://developer.wordpress.org/files/2023/07/custom-block-editor-persistance.gif 'Screencapture showing blocks being restored between page refreshes.') + +As this is only an _experiment_, this guide has opted to utilize the browser's `localStorage` API to handle saving block data. In a real-world scenario, you would likely choose a more reliable and robust system (e.g. a database). + +That said, let's take a closer look at how to handle save blocks. + +### Storing blocks in state + +Looking at the `src/components/block-editor/index.js` file, you will notice that some state has been created to store the blocks as an array: + +```jsx +// File: src/components/block-editor/index.js + +const [ blocks, updateBlocks ] = useState( [] ); +``` + +As mentioned earlier, `blocks` is passed to the "controlled" component `` as its `value` prop. This "hydrates" it with an initial set of blocks. Similarly, the `updateBlocks` setter is hooked up to the `onInput` callback on ``, which ensures that the block state is kept in sync with changes made to blocks within the editor. + +### Saving block data + +If you now turn your attention to the `onChange` handler, you will notice it is hooked up to a function `persistBlocks()` which is defined as follows: + +```js +// File: src/components/block-editor/index.js + +function persistBlocks( newBlocks ) { + updateBlocks( newBlocks ); + window.localStorage.setItem( 'getdavesbeBlocks', serialize( newBlocks ) ); +} +``` + +This function accepts an array of "committed" block changes and calls the state setter `updateBlocks`. It also stores the blocks within LocalStorage under the key `getdavesbeBlocks`. In order to achieve this, the block data is serialized into [Gutenberg "Block Grammar"](https://developer.wordpress.org/block-editor/principles/key-concepts/#blocks) format, meaning it can be safely stored as a string. + +If you open DeveloperTools and inspect the LocalStorage you will see serialized block data stored and updated as changes occur within the editor. Below is an example of the format: + +``` + +

An experiment with a standalone Block Editor in the WordPress admin

+ + + +

This is an experiment to discover how easy (or otherwise) it is to create a standalone instance of the Block Editor in the WordPress admin.

+ +``` + +### Retrieving previous block data + +Having persistence in place is all well and good, but it's only useful if that data is retrieved and _restored_ within the editor upon each full page reload. + +Accessing data is a side effect, so you must use the `useEffect` hook to handle this. + +```jsx +// File: src/components/block-editor/index.js + +useEffect( () => { + const storedBlocks = window.localStorage.getItem( 'getdavesbeBlocks' ); + + if ( storedBlocks && storedBlocks.length ) { + updateBlocks( () => parse( storedBlocks ) ); + createInfoNotice( 'Blocks loaded', { + type: 'snackbar', + isDismissible: true, + } ); + } +}, [] ); +``` + +This handler: + +- Grabs the serialized block data from local storage. +- Converts the serialized blocks back to JavaScript objects using the `parse()` utility. +- Calls the state setter `updateBlocks` causing the `blocks` value to be updated in state to reflect the blocks retrieved from LocalStorage. + +As a result of these operations, the controlled `` component is updated with the blocks restored from LocalStorage, causing the editor to show these blocks. + +Finally, you will want to generate a notice - which will display in the `` component as a "snackbar" notice - to indicate that the blocks have been restored. + +## Wrapping up + +Congratulations for completing this guide. You should now have a better understanding of how the block editor works under the hood. + +The full code for the custom block editor you have just built is [available on GitHub](https://github.com/getdave/standalone-block-editor). Download and try it out for yourself. Experiment, then and take things even further. diff --git a/docs/how-to-guides/platform/custom-block-editor/README.md b/docs/how-to-guides/platform/custom-block-editor/README.md deleted file mode 100644 index b21907577c150..0000000000000 --- a/docs/how-to-guides/platform/custom-block-editor/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Building a custom block editor - -The purpose of [this tutorial](/docs/how-to-guides/platform/custom-block-editor/tutorial.md) is to step through the fundamentals of creating a custom instance of a "block editor". - -![alt text](https://wordpress.org/gutenberg/files/2020/03/editor.png 'The Standalone Editor instance populated with example Blocks within a custom WP Admin page.') - -The editor you will see in this tutorial (as above) is **_not_ the same Block Editor you are familiar with when creating Posts** in with WordPress. Rather it is an entirely **custom block editor instance** built using the lower-level [`@wordpress/block-editor`](/packages/block-editor/README.md) package (and friends). - -## Following this tutorial - -To follow along with this tutorial, you can [download the accompanying WordPress plugin](https://github.com/getdave/standalone-block-editor) which includes all of the examples for you to try on your own site. - -## Code Syntax - -Code snippets are provided using JSX syntax. Note it is not required to use JSX to create blocks or extend the editor, you can use plain JavaScript. However, once familiar with JSX, many developers tend find it is easier to read and write, thus most code examples you'll find use that syntax. - -- [Start custom block editor tutorial](/docs/how-to-guides/platform/custom-block-editor/tutorial.md) diff --git a/docs/how-to-guides/platform/custom-block-editor/tutorial.md b/docs/how-to-guides/platform/custom-block-editor/tutorial.md deleted file mode 100644 index 1e49433da3e65..0000000000000 --- a/docs/how-to-guides/platform/custom-block-editor/tutorial.md +++ /dev/null @@ -1,623 +0,0 @@ -# Tutorial: building a custom block editor - -This tutorial will step through the fundamentals of creating a custom instance -of a "block editor" using the `@wordpress/block-editor` package. - -## Table of Contents - -- [Introduction](#introduction). -- [What we're going to be building](#what-were-going-to-be-building). -- [Plugin setup and organization](#plugin-setup-and-organization). -- [The "Core" of the Editor](#the-core-of-the-editor). -- [Creating the custom "Block Editor" page in WP Admin](#creating-the-custom-block-editor-page-in-wp-admin). -- [Registering and Rendering our custom block editor](#registering-and-rendering-our-custom-block-editor). -- [Reviewing the `` component](#reviewing-the-editor-component). -- [The custom ``](#the-custom-blockeditor). -- [Reviewing the Sidebar](#reviewing-the-sidebar). -- [Block Persistence](#block-persistence). -- [Wrapping up](#wrapping-up). - -## Introduction - -The Gutenberg codebase is complex, with many packages and components, but at its core it is a tool for managing and editing blocks. Therefore, when working on the editor it is important to gain a better understanding of how block editing works at a _fundamental_ level. - -To do this, this tutorial will walk you through building a **fully functioning, _custom_ block editor "instance"** within WordPress, introducing you to the key packages and components along the way. - -By the end of this article, you should have gained a good understanding of how the block editor works and some of the knowledge required to put together your own block editor instances. - -## What we're going to be building - -We're going to be creating an (almost) fully functioning Block Editor instance. - -![The Standalone Editor instance populated with example Blocks within a custom WP Admin page](https://wordpress.org/gutenberg/files/2020/03/editor.png) - -This block editor will not be the same _Block Editor_ you are familiar with when creating `Post`s in WP Admin. Rather it will be an entirely custom instance which will live within a custom WP Admin page called (imaginatively) "Block Editor". - -Our editor will have the following features: - -- Ability to add and edit all Core Blocks. -- Familiar visual styles and main/sidebar layout. -- _Basic_ block persistence between page reloads. - -With that in mind, let's start taking our first steps towards building this. - -## Plugin setup and organization - -Our custom editor is going to be built as a WordPress Plugin. To keep things simple. we'll call this `Standalone Block Editor Demo` because that is what it does. Nice! - -Let's take a look at our Plugin file structure: - -![alt text](https://wordpress.org/gutenberg/files/2020/03/repo-files.png 'Screenshot showing file structure of the Plugin at https://github.com/getdave/standalone-block-editor.') - -Here's a brief summary of what's going on: - -- `plugin.php` - standard Plugin "entry" file with comment meta data. Requires `init.php`. -- `init.php` - handles the initialization of the main Plugin logic. We'll be spending a lot of time here. -- `src/` (directory) - this is where our JavaScript (and CSS) source files will live. These files are _not_ directly enqueued by the Plugin. -- `webpack.config.js` - a custom Webpack config extending the defaults provided by the `@wordpress/scripts` npm package to allow for custom CSS styles (via Sass). - -The only item not shown above is the `build/` directory, which is where our _compiled_ JS and CSS files will be outputted by `@wordpress/scripts` ready to be enqueued by our Plugin. - -**Note:** throughout this tutorial, filename references will be placed in a comment at the top of each code snippet so you can follow along. - -With our basic file structure in place, we can now start looking at what package we're going to need. - -## The "Core" of the Editor - -Whilst the Gutenberg Editor is comprised of many moving parts, at it's core is the `@wordpress/block-editor` package. - -It's role is perhaps best summarized by its own `README` file: - -> This module allows you to create and use standalone block editors. - -This is great and exactly what we need! Indeed, it is the main package we'll be using to create our custom block editor instance. - -However, before we can get to working with this package in code, we're going to need to create a home for our editor within WP Admin. - -## Creating the custom "Block Editor" page in WP Admin - -As a first step, we need to create a custom page within WP Admin. - -**Note**: if you're already comfortable with the process of creating custom Admin pages in WordPress you might want to [skip ahead](#registering-and-rendering-our-custom-block-editor). - -### Registering the Page - -To do this we [register our custom admin page](https://developer.wordpress.org/reference/functions/add_menu_page/) using the standard WP `add_menu_page()` helper: - -```php -// init.php - -add_menu_page( - 'Standalone Block Editor', // visible page name - 'Block Editor', // menu label - 'edit_posts', // required capability - 'getdavesbe', // hook/slug of page - 'getdave_sbe_render_block_editor', // function to render the page - 'dashicons-welcome-widgets-menus' // custom icon -); -``` - -Note the reference to a function `getdave_sbe_render_block_editor` which is the function which we will use to render the contents of the admin page. - -### Adding the target HTML - -As the block editor is a React powered application, we now need to output some HTML into our custom page into which the JavaScript can render the block editor. - -To do this we need to look at our `getdave_sbe_render_block_editor` function referenced in the step above. - -```php -// init.php - -function getdave_sbe_render_block_editor() { - ?> -
- Loading Editor... -
- ` component into the waiting `
` on our custom Admin page. - -```jsx -domReady( function () { - const settings = window.getdaveSbeSettings || {}; - registerCoreBlocks(); - render( - , - document.getElementById( 'getdave-sbe-block-editor' ) - ); -} ); -``` - -**Note**: it is possible to render the editor from PHP without creating an unnecessary JS global. Check out [the Edit Site package in Gutenberg Core for an example of this](https://href.li/?https://github.com/WordPress/gutenberg/blob/c6821d7e64a54eb322583a35daedc6c192ece850/lib/edit-site-page.php#L135). - -## Reviewing the `` component - -Let's take a closer look at the `` component we saw being used above. - -Despite its name, this _is not_ the actual core of the block editor. Rather it is a _wrapper_ component we've created to contain the components which form the main body of our custom editor. - -### Dependencies - -The first thing we do inside `` is to pull in some dependencies. - -```jsx -// src/editor.js - -import Notices from 'components/notices'; -import Header from 'components/header'; -import Sidebar from 'components/sidebar'; -import BlockEditor from 'components/block-editor'; -``` - -The most important of these are the internal components `BlockEditor` and `Sidebar`, which we will explore in greater detail shortly. - -The remaining components are largely static elements which form the layout and surrounding UI of the editor (eg: header and notice areas). - -### Editor Render - -With these components available we can proceed to define our `` component. - -```jsx -// src/editor.js - -function Editor( { settings } ) { - return ( - - -
- -
- - -
- -
-
- ); -} -``` - -Here we are scaffolding the core of the editor's layout alongside a few specialised [context providers](https://reactjs.org/docs/context.html#contextprovider) which make particular functionality available throughout the component hierarchy. - -Let's examine these in more detail: - -- `` - enables the use of the ["Slot/Fill" - pattern](/docs/reference-guides/slotfills/README.md) through our component tree. -- `` - enables the use of [dropzones for drag and drop functionality](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/components/src/drop-zone). -- `` - custom component. Provides a "snack bar" Notice that will be rendered if any messages are dispatched to `core/notices` store. -- `
` - renders the static title "Standalone Block Editor" at the top of the - editor UI. -- `` - our custom block editor component. This is where things get - interesting. We'll focus a little more on this in a moment. -- `` - renders a slot into which ``s can be rendered - using the Slot/Fill mechanic. - -### Keyboard Navigation - -With this basic component structure in place the only remaining thing left to do -is wrap everything in [the `navigateRegions` HOC](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/components/src/higher-order/navigate-regions) to provide keyboard navigation between the different "regions" in the layout. - -```jsx -// src/editor.js - -export default navigateRegions( Editor ); -``` - -## The custom `` - -Now we have a our core layouts and components in place, it's time to explore our -custom implementation of the block editor itself. - -The component for this is called `` and this is where the magic happens. - -Opening `src/components/block-editor/index.js` we see that this is the most -complex of the components we have encountered thus far. - -There's a lot going on so let's break this down! - -### Understanding the render - -To start, let's focus on what is being rendered by the `` component: - -```js -// src/components/block-editor/index.js - -return ( -
- - - - -
- - - - - - -
-
-
-); -``` - -The key components to focus on here are `` and ``. Let's examine these. - -### Understanding the `` component - -[``](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/provider) is one of the most important components in the hierarchy. As we learnt earlier, it establishes a new block editing context for a new block editor. - -As a result, it is _fundamental_ to the entire goal of our project. - -The children of `` comprise the UI for the block -editor. These components then have access to data (via `Context`) which enables -them to _render_ and _manage_ the Blocks and their behaviors within the editor. - -```jsx -// src/components/block-editor/index.js - - -``` - -#### `BlockEditor` props - -We can see that `` accepts array of (parsed) block objects as its `value` prop and, when there's a change detected within the editor, calls the `onChange` and/or `onInput` handler prop (passing the new Blocks as a argument). - -Internally it does this by subscribing to the provided `registry` (via the [`withRegistryProvider` HOC](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/provider/index.js#L158)), listening to block change events, determining whether Block changing was persistent, and then calling the appropriate `onChange|Input` handler accordingly. - -For the purposes of our simple project these features allow us to: - -- Store the array of current blocks in state as `blocks`. -- Update the `blocks` state in memory on `onInput` by calling the hook setter - `updateBlocks(blocks)`. -- Handle basic persistence of blocks into `localStorage` using `onChange`. This is [fired when block updates are considered - "committed"](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/provider#onchange). - -It's also worth recalling that the component accepts a `settings` prop. This accepts the editor settings which we inlined as JSON within `init.php` earlier. This configures features such as custom colors, available image sizes and [much more](https://github.com/WordPress/gutenberg/tree/4c472c3443513d070a50ba1e96f3a476861447b3/packages/block-editor#SETTINGS_DEFAULTS). - -### Understanding the `` component - -Alongside `` the next most interesting component is [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/block-list/index.js). - -This is one of the most important components as it's role is to **render a list of Blocks into the editor**. - -It does this in part thanks to being placed as a child of `` which affords it full access to all information about the state of the current Blocks in the editor. - -#### How does `BlockList` work? - -Under the hood `` relies on several other lower-level components in order to render the list of Blocks. - -The hierarchy of these components can be _approximated_ as follows: - -```jsx -// Pseudo code - example purposes only - - - /* renders a list of Blocks from the rootClientId. */ - - /* renders a single "Block" from the BlockList. */ - - /* renders the standard editable area of a Block. */ - /* renders the Block UI as defined by its `edit()` implementation. - */ - - - -``` - -Here's roughly how this works together to render our list of blocks: - -- `` loops over all the Block clientIds and - renders each via [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/block-list/block.js). -- `` in turn renders the individual "Block" - via it's own subcomponent [``](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-edit/index.js). -- Finally [the Block itself](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-edit/edit.js) is rendered using the `Component` placeholder component. - -These are some of the most complex and involved components within the `@wordpress/block-editor` package. That said, if you want to have a strong grasp of how the editor works at a fundamental level, I strongly advise making a study of these components. I leave this as an exercise for the reader! - -### Utility components in our custom block editor - -Jumping back to our own custom `` component, it is also worth noting the following "utility" components: - -```js -// src/components/block-editor/index.js - -
- /* 1. */ - - /* 2. */ - - /* 3. */ - - - -
-``` - -These provide other important elements of functionality for our editor instance. - -1. [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/keyboard-shortcuts/index.js) - enables and usage of keyboard shortcuts within the editor. -2. [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/writing-flow/index.js) - handles selection, focus management and navigation across blocks. -3. [``](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/observe-typing)- used to manage the editor's internal `isTyping` flag. This is used in various places, most commonly to show/hide the Block toolbar in response to typing. - -## Reviewing the Sidebar - -Also within the render of our ``, is our `` component. - -```jsx -// src/components/block-editor/index.js - -return ( -
- - /* <-- SIDEBAR */ - - -
- // snip -
-
-
-); -``` - -This is used - alongside other things - to display advanced Block settings via the `` component. - -```jsx - - - -``` - -However, the keen-eyed readers amongst you will have already noted the presence -of a `` component within our `` (`src/editor.js`) component's -layout: - -```jsx -// src/editor.js - -
- // <-- eh!? - - -``` - -Opening `src/components/sidebar/index.js` we see that this is in fact the -component rendered within `` above. However, the implementation utilises -Slot/Fill to expose a `Fill` (``) which we subsequently -`import` and render inside of our `` component (see above). - -With this in place, we then render `` as a child of the -`Sidebar.InspectorFill`. This has the result of allowing us to keep -`` within the React context of `` whilst -allowing it to be rendered into the DOM in a separate location (ie: in the ``). - -This might seem overly complex, but it is required in order that -`` can have access to information about the current Block. -Without Slot/Fill this setup would be extremely difficult to achieve. - -Aside: -[``](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-inspector/index.js) -itself actually renders a `Slot` for [``](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/inspector-controls). This is what allows you [render a `` component inside -the `edit()` definition for your block](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-library/src/paragraph/edit.js#L127) and have -it display within Gutenberg's sidebar. I recommend looking into this component -in more detail. - -And with that we have covered the render of our custom ``! - -## Block Persistence - -We've come a long way on our journey to create a custom block editor. But there is -one major area left to touch upon - Block persistence; that is the act of having our -Blocks saved and **available _between_ page refreshes**. - -![alt text](https://wordpress.org/gutenberg/files/2020/03/block-persistance.gif 'Screencapture showing added Blocks being restored between page refreshes.') - -As this is only an _experiment_ we've opted to utilise the browser's -`localStorage` API to handle saving Block data. In a real-world scenario however -you'd like choose a more reliable and robust system (eg: a database). - -That said, let's take a closer look at how we're handling saving our Blocks. - -### Storing blocks in state - -Opening `src/components/block-editor/index.js` we will notice we have created -some state to store our Blocks as an array: - -```jsx -// src/components/block-editor/index.js - -const [ blocks, updateBlocks ] = useState( [] ); -``` - -As mentioned earlier, `blocks` is passed to the "controlled" component `` as its `value` prop. This "hydrates" it with an initial set of Blocks. Similarly, the `updateBlocks` setter is hooked up to the `onInput` callback on `` which ensures that our block state is kept in sync with changes made to blocks within the editor. - -### Saving Block data - -If we now turn our attention to the `onChange` handler, we will notice it is -hooked up to a function `persistBlocks()` which is defined as follows: - -```js -// src/components/block-editor/index.js - -function persistBlocks( newBlocks ) { - updateBlocks( newBlocks ); - window.localStorage.setItem( 'getdavesbeBlocks', serialize( newBlocks ) ); -} -``` - -This function accepts an array of "committed" block changes and calls the state -setter `updateBlocks`. In addition to this however, it also stores the blocks -within LocalStorage under the key `getdavesbeBlocks`. In order to achieve this -the Block data is serialized into [Gutenberg "Block Grammar"](https://developer.wordpress.org/block-editor/principles/key-concepts/#blocks) format, meaning it can be safely stored as a string. - -If we open DeveloperTools and inspect our LocalStorage we will see serialized -Block data stored and updated as changes occur within the editor. Below is an -example of the format: - -``` - -

An experiment with a standalone Block Editor in WPAdmin

- - - -

This is an experiment to discover how easy (or otherwise) it is to create a standalone instance of the Block Editor in WPAdmin.

- -``` - -### Retrieving previous block data - -Having persistence in place is all well and good, but it's useless unless that -data is retrieved and _restored_ within the editor upon each full page reload. - -Accessing data is a side effect, so naturally we reach for our old (new!?) -friend the `useEffect` hook to handle this. - -```jsx -// src/components/block-editor/index.js - -useEffect( () => { - const storedBlocks = window.localStorage.getItem( 'getdavesbeBlocks' ); - - if ( storedBlocks && storedBlocks.length ) { - updateBlocks( () => parse( storedBlocks ) ); - createInfoNotice( 'Blocks loaded', { - type: 'snackbar', - isDismissible: true, - } ); - } -}, [] ); -``` - -In this handler, we: - -- Grab the serialized block data from local storage. -- Convert the serialized blocks back to JavaScript objects using the `parse()` - utility. -- Call the state setter `updateBlocks` causing the `blocks` value to be updated - in state to reflect the blocks retrieved from LocalStorage. - -As a result of these operations the controlled `` component -is updated with the blocks restored from LocalStorage causing the editor to -show these blocks. - -Finally, for good measure we generate a notice - which will display in our `` component as a "snackbar" notice - to indicate that the blocks have been restored. - -## Wrapping up - -If you've made it this far then congratulations! I hope you now have a better understanding of how the block editor works under the hood. - -In addition, you've reviewed an working example of the code required to implement your own custom functioning block editor. This information should prove useful, especially as Gutenberg expands beyond editing just the `Post` and into Widgets, Full Site Editing and beyond! - -The full code for the custom functioning block editor we've just built is [available on GitHub](https://github.com/getdave/standalone-block-editor). I encourage you to download and try it out for yourself. Experiment, then and take things even further! diff --git a/docs/manifest.json b/docs/manifest.json index 5f75e49924f35..578d476222476 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -170,15 +170,9 @@ { "title": "Building a custom block editor", "slug": "custom-block-editor", - "markdown_source": "../docs/how-to-guides/platform/custom-block-editor/README.md", + "markdown_source": "../docs/how-to-guides/platform/custom-block-editor.md", "parent": "platform" }, - { - "title": "Tutorial: building a custom block editor", - "slug": "tutorial", - "markdown_source": "../docs/how-to-guides/platform/custom-block-editor/tutorial.md", - "parent": "custom-block-editor" - }, { "title": "Create your First App with Gutenberg Data", "slug": "data-basics", diff --git a/docs/toc.json b/docs/toc.json index 1660afdcc2949..b523fcedea416 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -80,11 +80,7 @@ { "docs/how-to-guides/platform/README.md": [ { - "docs/how-to-guides/platform/custom-block-editor/README.md": [ - { - "docs/how-to-guides/platform/custom-block-editor/tutorial.md": [] - } - ] + "docs/how-to-guides/platform/custom-block-editor.md": [] } ] },