Skip to content

Commit

Permalink
Merge branch 'WordPress:trunk' into trunk
Browse files Browse the repository at this point in the history
  • Loading branch information
SavPhill authored May 20, 2022
2 parents 7f9101f + 0da4e25 commit ccecb76
Show file tree
Hide file tree
Showing 361 changed files with 3,706 additions and 4,125 deletions.
37 changes: 32 additions & 5 deletions .github/workflows/publish-npm-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@ name: Publish npm packages
on:
workflow_dispatch:
inputs:
wp_version:
description: 'WordPress major version'
release_type:
description: 'Release type'
required: true
type: choice
default: 'development'
options:
- development
- bugfix
- wp
wp_version:
description: 'WordPress major version (`wp` only)'
type: string

# Cancels all previous workflow runs for pull requests that have not completed.
Expand All @@ -16,8 +24,8 @@ concurrency:
cancel-in-progress: true

jobs:
wp-release:
name: WordPress major bugfix release
release:
name: Release - ${{ github.event.inputs.release_type }}
runs-on: ubuntu-latest
environment: WordPress packages
steps:
Expand Down Expand Up @@ -47,7 +55,26 @@ jobs:
node-version-file: 'main/.nvmrc'
registry-url: 'https://registry.npmjs.org'

- name: Publish packages to npm ("wp/${{ github.event.inputs.wp_version }}" dist-tag)
- name: Publish development packages to npm ("next" dist-tag)
if: ${{ github.event.inputs.release_type == 'development' }}
run: |
cd main
npm ci
./bin/plugin/cli.js npm-next --ci --repository-path ../publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Publish packages to npm with bug fixes ("latest" dist-tag)
if: ${{ github.event.inputs.release_type == 'bugfix' }}
run: |
cd main
npm ci
./bin/plugin/cli.js npm-bugfix --ci --repository-path ../publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Publish packages to npm for WP major ("wp/${{ github.event.inputs.wp_version || 'X.Y' }}" dist-tag)
if: ${{ github.event.inputs.release_type == 'wp' && github.event.inputs.wp_version }}
run: |
cd main
npm ci
Expand Down
298 changes: 298 additions & 0 deletions changelog.txt

Large diffs are not rendered by default.

84 changes: 25 additions & 59 deletions docs/how-to-guides/data-basics/2-building-a-list-of-pages.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Building a list of pages

In this part, we will build a filterable list of all WordPress pages. This is what the app will look like at the end of
this section:
In this part, we will build a filterable list of all WordPress pages. This is what the app will look like at the end of this section:

![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/part1-finished.jpg)

Expand Down Expand Up @@ -30,42 +29,31 @@ function PagesList( { pages } ) {
}
```
Note that this component does not fetch any data yet, only presents the hardcoded list of pages. When you refresh the page,
you should see the following:
Note that this component does not fetch any data yet, only presents the hardcoded list of pages. When you refresh the page, you should see the following:
![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/simple-list.jpg)
## Step 2: Fetch the data
The hard-coded sample page isn’t very useful. We want to display your actual WordPress pages so let’s fetch the actual
list of pages from the [WordPress REST API](https://developer.wordpress.org/rest-api/).
The hard-coded sample page isn’t very useful. We want to display your actual WordPress pages so let’s fetch the actual list of pages from the [WordPress REST API](https://developer.wordpress.org/rest-api/).
Before we start, let’s confirm we actually have some pages to fetch. Within WPAdmin, Navigate to Pages using the sidebar menu and
ensure it shows at least four or five Pages:
Before we start, let’s confirm we actually have some pages to fetch. Within WPAdmin, Navigate to Pages using the sidebar menu and ensure it shows at least four or five Pages:
![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/pages-list.jpg)
If it doesn’t, go ahead and create a few pages – you can use the same titles as on the screenshot above. Be sure to _
publish_ and not just _save_ them.
If it doesn’t, go ahead and create a few pages – you can use the same titles as on the screenshot above. Be sure to _publish_ and not just _save_ them.
Now that we have the data to work with, let’s dive into the code. We will take advantage of the [`@wordpress/core-data` package](https://github.com/WordPress/gutenberg/tree/trunk/packages/core-data)
package which provides resolvers, selectors, and actions to work with the WordPress core API. `@wordpress/core-data` builds on top
of the [`@wordpress/data` package](https://github.com/WordPress/gutenberg/tree/trunk/packages/data).
Now that we have the data to work with, let’s dive into the code. We will take advantage of the [`@wordpress/core-data` package](https://github.com/WordPress/gutenberg/tree/trunk/packages/core-data) package which provides resolvers, selectors, and actions to work with the WordPress core API. `@wordpress/core-data` builds on top of the [`@wordpress/data` package](https://github.com/WordPress/gutenberg/tree/trunk/packages/data).
To fetch the list of pages, we will use
the [`getEntityRecords`](/docs/reference-guides/data/data-core/#getentityrecords) selector. In broad strokes, it will
issue the correct API request, cache the results, and return the list of the records we need. Here’s how to use it:
To fetch the list of pages, we will use the [`getEntityRecords`](/docs/reference-guides/data/data-core/#getentityrecords) selector. In broad strokes, it will issue the correct API request, cache the results, and return the list of the records we need. Here’s how to use it:
```js
wp.data.select( 'core' ).getEntityRecords( 'postType', 'page' )
```
If you run that following snippet in your browser’s dev tools, you will see it returns `null`. Why? The pages are only
requested by the `getEntityRecords` resolver after first running the _selector_. If you wait a moment and re-run it, it
will return the list of all pages.
If you run that following snippet in your browser’s dev tools, you will see it returns `null`. Why? The pages are only requested by the `getEntityRecords` resolver after first running the _selector_. If you wait a moment and re-run it, it will return the list of all pages.
Similarly, the `MyFirstApp` component needs to re-run the selector once the data is available. That’s exactly what
the `useSelect` hook does:
Similarly, the `MyFirstApp` component needs to re-run the selector once the data is available. That’s exactly what the `useSelect` hook does:
```js
import { useSelect } from '@wordpress/data';
Expand All @@ -83,9 +71,7 @@ function MyFirstApp() {
Note that we use an `import` statement inside index.js. This enables the plugin to automatically load the dependencies using `wp_enqueue_script`. Any references to `coreDataStore` are compiled to the same `wp.data` reference we use in browser's devtools.
`useSelect` takes two arguments: a callback and dependencies. In broad strokes, it re-runs the callback whenever either
the dependencies or the underlying data store changes. You can learn more about [useSelect](/packages/data/README.md#useselect) in
the [data module documentation](/packages/data/README.md#useselect).
`useSelect` takes two arguments: a callback and dependencies. In broad strokes, it re-runs the callback whenever either the dependencies or the underlying data store changes. You can learn more about [useSelect](/packages/data/README.md#useselect) in the [data module documentation](/packages/data/README.md#useselect).
Putting it together, we get the following code:
Expand Down Expand Up @@ -149,8 +135,7 @@ function PagesList( { pages } ) {
## Step 4: Add a search box
The list of pages is short for now; however, the longer it grows, the harder it is to work with. WordPress admins
typically solves this problem with a search box – let’s implement one too!
The list of pages is short for now; however, the longer it grows, the harder it is to work with. WordPress admins typically solves this problem with a search box – let’s implement one too!
Let’s start by adding a search field:
Expand All @@ -173,29 +158,21 @@ function MyFirstApp() {
}
```
Note that instead of using an `input` tag, we took advantage of
the [SearchControl](https://developer.wordpress.org/block-editor/reference-guides/components/search-control/) component.
This is what it looks like:
Note that instead of using an `input` tag, we took advantage of the [SearchControl](https://developer.wordpress.org/block-editor/reference-guides/components/search-control/) component. This is what it looks like:
![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/filter-field.jpg)
The field starts empty, and the contents are stored in the `searchTerm` state value. If you aren’t familiar with
the [useState](https://reactjs.org/docs/hooks-state.html) hook, you can learn more
in [React’s documentation](https://reactjs.org/docs/hooks-state.html).
The field starts empty, and the contents are stored in the `searchTerm` state value. If you aren’t familiar with the [useState](https://reactjs.org/docs/hooks-state.html) hook, you can learn more in [React’s documentation](https://reactjs.org/docs/hooks-state.html).
We can now request only the pages matching the `searchTerm`.
After checking with the [WordPress API documentation](https://developer.wordpress.org/rest-api/reference/pages/), we
see that the [/wp/v2/pages](https://developer.wordpress.org/rest-api/reference/pages/) endpoint accepts a `search`
query parameter and uses it to _limit results to those matching a string_. But how can we use it? We can pass custom query
parameters as the third argument to `getEntityRecords` as below:
After checking with the [WordPress API documentation](https://developer.wordpress.org/rest-api/reference/pages/), we see that the [/wp/v2/pages](https://developer.wordpress.org/rest-api/reference/pages/) endpoint accepts a `search` query parameter and uses it to _limit results to those matching a string_. But how can we use it? We can pass custom query parameters as the third argument to `getEntityRecords` as below:
```js
wp.data.select( 'core' ).getEntityRecords( 'postType', 'page', { search: 'home' } )
```
Running that snippet in your browser’s dev tools will trigger a request to `/wp/v2/pages?search=home` instead of
just `/wp/v2/pages`.
Running that snippet in your browser’s dev tools will trigger a request to `/wp/v2/pages?search=home` instead of just `/wp/v2/pages`.
Let’s mirror this in our `useSelect` call as follows:
Expand All @@ -219,8 +196,7 @@ function MyFirstApp() {
}
```
The `searchTerm` is now used as a `search` query parameter when provided. Note that `searchTerm` is also specified
inside the list of `useSelect` dependencies to make sure `getEntityRecords` is re-run when the `searchTerm` changes.
The `searchTerm` is now used as a `search` query parameter when provided. Note that `searchTerm` is also specified inside the list of `useSelect` dependencies to make sure `getEntityRecords` is re-run when the `searchTerm` changes.
Finally, here’s how `MyFirstApp` looks once we wire it all together:
Expand Down Expand Up @@ -276,30 +252,23 @@ function MyFirstApp() {
Working outside of core-data, we would need to solve two problems here.
Firstly, out-of-order updates. Searching for „About” would trigger five API requests filtering for `A`, `Ab`, `Abo`, `Abou`, and
`About`. Theese requests could finish in a different order than they started. It is possible that _search=A_ would resolve after _
search=About_ and thus we’d display the wrong data.
Firstly, out-of-order updates. Searching for „About” would trigger five API requests filtering for `A`, `Ab`, `Abo`, `Abou`, and `About`. Theese requests could finish in a different order than they started. It is possible that _search=A_ would resolve after _ search=About_ and thus we’d display the wrong data.
Gutenberg data helps by handling the asynchronous part behind the scenes. `useSelect` remembers the most recent call and
returns only the data we expect.
Gutenberg data helps by handling the asynchronous part behind the scenes. `useSelect` remembers the most recent call and returns only the data we expect.
Secondly, every keystroke would trigger an API request. If you typed `About`, deleted it, and retyped it, it would
issue 10 requests in total even though we could reuse the data.
Secondly, every keystroke would trigger an API request. If you typed `About`, deleted it, and retyped it, it would issue 10 requests in total even though we could reuse the data.
Gutenberg data helps by caching the responses to API requests triggered by `getEntityRecords()` and reuses them on
subsequent calls. This is especially important when other components rely on the same entity records.
Gutenberg data helps by caching the responses to API requests triggered by `getEntityRecords()` and reuses them on subsequent calls. This is especially important when other components rely on the same entity records.
All in all, the utilities built into core-data are designed to solve the typical problems so that you can focus on your application
instead.
All in all, the utilities built into core-data are designed to solve the typical problems so that you can focus on your application instead.
## Step 5: Loading Indicator
There is one problem with our search feature. We can’t be quite sure whether it’s still searching or showing no results:
![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/unclear-status.jpg)
A few messages like _Loading…_ or _No results_ would clear it up. Let’s implement them! First, `PagesList` has to be
aware of the current status:
A few messages like _Loading…_ or _No results_ would clear it up. Let’s implement them! First, `PagesList` has to be aware of the current status:
```js
import { SearchControl, Spinner } from '@wordpress/components';
Expand All @@ -325,16 +294,13 @@ function MyFirstApp() {
}
```
Note that instead of building a custom loading indicator, we took advantage of
the [Spinner](https://developer.wordpress.org/block-editor/reference-guides/components/spinner/) component.
Note that instead of building a custom loading indicator, we took advantage of the [Spinner](https://developer.wordpress.org/block-editor/reference-guides/components/spinner/) component.
We still need to know whether the pages selector `hasResolved` or not. We can find out using
the `hasFinishedResolution` selector:
We still need to know whether the pages selector `hasResolved` or not. We can find out using the `hasFinishedResolution` selector:
`wp.data.select('core').hasFinishedResolution( 'getEntityRecords', [ 'postType', 'page', { search: 'home' } ] )`
It takes the name of the selector and the _exact same arguments you passed to that selector_ and returns either `true` if the data was already loaded or `false`
if we’re still waiting. Let’s add it to `useSelect`:
It takes the name of the selector and the _exact same arguments you passed to that selector_ and returns either `true` if the data was already loaded or `false` if we’re still waiting. Let’s add it to `useSelect`:
```js
import { useSelect } from '@wordpress/data';
Expand Down
31 changes: 27 additions & 4 deletions docs/how-to-guides/data-basics/3-building-an-edit-form.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,15 +420,29 @@ export function EditPageForm( { pageId, onSaveFinished } ) {
// ...
<div className="form-buttons">
<Button onClick={ handleSave } variant="primary" disabled={ ! hasEdits || isSaving }>
{ isSaving ? <Spinner/> : 'Save' }
{ isSaving ? (
<>
<Spinner/>
Saving
</>
) : 'Save' }
</Button>
<Button
onClick={ onCancel }
variant="tertiary"
disabled={ isSaving }
>
Cancel
</Button>
</div>
// ...
);
}
```
Note that we disable the save button when there are no edits and when the page is currently being saved. This is to prevent the user from accidentally pressing the button twice.
Note that we disable the _save_ button when there are no edits and when the page is currently being saved. This is to prevent the user from accidentally pressing the button twice.
Also, interrupting a *save* in progress is not supported by `@wordpress/data` so we also conditionally disabled the _cancel_ button.
Here's what it looks like in action:
Expand Down Expand Up @@ -503,9 +517,18 @@ export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
variant="primary"
disabled={ ! hasEdits || isSaving }
>
{ isSaving ? <Spinner /> : 'Save' }
{ isSaving ? (
<>
<Spinner/>
Saving
</>
) : 'Save' }
</Button>
<Button onClick={ onCancel } variant="tertiary">
<Button
onClick={ onCancel }
variant="tertiary"
disabled={ isSaving }
>
Cancel
</Button>
</div>
Expand Down
Loading

0 comments on commit ccecb76

Please sign in to comment.