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

Add a UI to allow saving patterns #49607

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1883,6 +1883,12 @@
"markdown_source": "../packages/url/README.md",
"parent": "packages"
},
{
"title": "@wordpress/user-patterns",
"slug": "packages-user-patterns",
"markdown_source": "../packages/user-patterns/README.md",
"parent": "packages"
},
{
"title": "@wordpress/viewport",
"slug": "packages-viewport",
Expand Down
12 changes: 11 additions & 1 deletion lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ function gutenberg_register_packages_styles( $styles ) {
$styles,
'wp-editor',
gutenberg_url( 'build/editor/style.css' ),
array( 'wp-components', 'wp-block-editor', 'wp-reusable-blocks' ),
array( 'wp-components', 'wp-block-editor', 'wp-reusable-blocks', 'wp-user-patterns' ),
$version
);
$styles->add_data( 'wp-editor', 'rtl', 'replace' );
Expand Down Expand Up @@ -327,6 +327,7 @@ function gutenberg_register_packages_styles( $styles ) {
'wp-reset-editor-styles',
'wp-block-library',
'wp-reusable-blocks',
'wp-user-patterns',
// Until #37466, we can't specifically add them as editor styles yet,
// so we must hard-code it here as a dependency.
'wp-block-editor-content',
Expand Down Expand Up @@ -442,6 +443,15 @@ function gutenberg_register_packages_styles( $styles ) {
);
$styles->add_data( 'wp-reusable-blocks', 'rtl', 'replace' );

gutenberg_override_style(
$styles,
'wp-user-patterns',
gutenberg_url( 'build/user-patterns/style.css' ),
array( 'wp-components' ),
$version
);
$styles->add_data( 'wp-user-patterns', 'rtl', 'replace' );

gutenberg_override_style(
$styles,
'wp-widgets',
Expand Down
82 changes: 82 additions & 0 deletions lib/experimental/class-wp-rest-user-patterns-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php
/**
* User-created patterns REST API: WP_REST_User_Patterns_Controller class
*
* @package Gutenberg
*/

/**
* Controller which provides a REST endpoint for the editor to read, create,
* edit and delete patterns. Patterns are stored as posts with the wp_block_pattern
* post type.
*
* @see WP_REST_Posts_Controller
* @see WP_REST_Controller
*/
class WP_REST_User_Patterns_Controller extends WP_REST_Posts_Controller {

/**
* Checks if a pattern can be read.
*
* @param WP_Post $post Post object that backs the pattern.
* @return bool Whether the pattern can be read.
*/
public function check_read_permission( $post ) {
// By default the read_post capability is mapped to edit_posts.
if ( ! current_user_can( 'read_post', $post->ID ) ) {
return false;
}

return parent::check_read_permission( $post );
}

/**
* Filters a response based on the context defined in the schema.
*
* @param array $data Response data to filter.
* @param string $context Context defined in the schema.
* @return array Filtered response.
*/
public function filter_response_by_context( $data, $context ) {
$data = parent::filter_response_by_context( $data, $context );

/*
* Remove `title.rendered` and `content.rendered` from the response. It
* doesn't make sense for a pattern to have rendered content on its
* own, since rendering a pattern requires it to be inside a post or a page.
*/
unset( $data['title']['rendered'] );
unset( $data['content']['rendered'] );

return $data;
}

/**
* Retrieves the pattern's schema, conforming to JSON Schema.
*
* @return array Item schema data.
*/
public function get_item_schema() {
// Do not cache this schema because all properties are derived from parent controller.
$schema = parent::get_item_schema();

/*
* Allow all contexts to access `title.raw` and `content.raw`. Clients always
* need the raw markup of a pattern to do anything useful, e.g. parse
* it or display it in an editor.
*/
$schema['properties']['title']['properties']['raw']['context'] = array( 'view', 'edit' );
$schema['properties']['content']['properties']['raw']['context'] = array( 'view', 'edit' );

/*
* Remove `title.rendered` and `content.rendered` from the schema. It doesn’t
* make sense for a pattern to have rendered content on its own, since
* rendering a pattern requires it to be inside a post or a page.
*/
unset( $schema['properties']['title']['properties']['rendered'] );
unset( $schema['properties']['content']['properties']['rendered'] );

return $schema;
}

}
107 changes: 107 additions & 0 deletions lib/experimental/user-block-patterns.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php
/**
* Handles user-created block patterns.
*
* @package gutenberg
*/

/**
* Creates the `wp_block_pattern` post type.
*/
function gutenberg_create_wp_block_pattern_post_type() {
register_post_type(
'wp_block_pattern',
array(
'labels' => array(
'name' => _x( 'Patterns', 'post type general name', 'gutenberg' ),
'singular_name' => _x( 'Pattern', 'post type singular name', 'gutenberg' ),
'add_new' => _x( 'Add New', 'Pattern', 'gutenberg' ),
'add_new_item' => __( 'Add new Pattern', 'gutenberg' ),
'new_item' => __( 'New Pattern', 'gutenberg' ),
'edit_item' => __( 'Edit Pattern', 'gutenberg' ),
'view_item' => __( 'View Pattern', 'gutenberg' ),
'all_items' => __( 'All Patterns', 'gutenberg' ),
'search_items' => __( 'Search Patterns', 'gutenberg' ),
'not_found' => __( 'No pattern found.', 'gutenberg' ),
'not_found_in_trash' => __( 'No pattern found in Trash.', 'gutenberg' ),
'filter_items_list' => __( 'Filter pattern list', 'gutenberg' ),
'items_list_navigation' => __( 'Patterns list navigation', 'gutenberg' ),
'items_list' => __( 'Patterns list', 'gutenberg' ),
'item_published' => __( 'Pattern published.', 'gutenberg' ),
'item_published_privately' => __( 'Pattern published privately.', 'gutenberg' ),
'item_reverted_to_draft' => __( 'Pattern reverted to draft.', 'gutenberg' ),
'item_scheduled' => __( 'Pattern scheduled.', 'gutenberg' ),
'item_updated' => __( 'Pattern updated.', 'gutenberg' ),
),
'public' => false,
'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
'show_ui' => true,
'show_in_menu' => false,
'rewrite' => false,
'show_in_rest' => true,
'rest_base' => 'patterns',
'rest_controller_class' => 'WP_REST_User_Patterns_Controller',
'capability_type' => 'block',
'capabilities' => array(
// You need to be able to edit posts, in order to read blocks in their raw form.
'read' => 'edit_posts',
// You need to be able to publish posts, in order to create blocks.
'create_posts' => 'publish_posts',
'edit_posts' => 'edit_posts',
'edit_published_posts' => 'edit_published_posts',
'delete_published_posts' => 'delete_published_posts',
'edit_others_posts' => 'edit_others_posts',
'delete_others_posts' => 'delete_others_posts',
),
'map_meta_cap' => true,
'supports' => array(
'title',
'editor',
'revisions',
),
)
);
}
add_action( 'init', 'gutenberg_create_wp_block_pattern_post_type' );

/**
* Registers user-created block patterns.
*/
function gutenberg_register_user_block_patterns() {
// Get posts from the wp_block_pattern post type.
$posts = get_posts(
array(
'post_type' => 'wp_block_pattern',
'post_status' => 'publish',
'posts_per_page' => -1,
)
);

// Bail if there are no posts.
if ( empty( $posts ) ) {
return;
}

// Register the user category.
register_block_pattern_category(
'user',
array(
'label' => _x( 'User patterns', 'Block pattern category', 'gutenberg' ),
'description' => __( 'Patterns that were created by users on this site.', 'gutenberg' ),
)
);

// Register each post as a block pattern.
foreach ( $posts as $post ) {
register_block_pattern(
'wp-block-pattern/' . $post->post_name,
array(
'title' => $post->post_title,
'description' => $post->post_excerpt,
'content' => $post->post_content,
'categories' => array( 'user' ),
)
);
}
}
add_action( 'init', 'gutenberg_register_user_block_patterns' );
5 changes: 5 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ function gutenberg_is_experiment_enabled( $name ) {

require_once __DIR__ . '/experimental/class-gutenberg-rest-global-styles-revisions-controller.php';
require_once __DIR__ . '/experimental/class-wp-rest-navigation-fallback-controller.php';
if ( ! class_exists( 'WP_REST_User_Patterns_Controller' ) ) {
require_once __DIR__ . '/experimental/class-wp-rest-user-patterns-controller.php';
}

require_once __DIR__ . '/experimental/rest-api.php';
}

Expand Down Expand Up @@ -101,6 +105,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/experimental/navigation-theme-opt-in.php';
require __DIR__ . '/experimental/kses.php';
require __DIR__ . '/experimental/l10n.php';
require __DIR__ . '/experimental/user-block-patterns.php';

// Fonts API.
if ( ! class_exists( 'WP_Fonts' ) ) {
Expand Down
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@wordpress/style-engine": "file:packages/style-engine",
"@wordpress/token-list": "file:packages/token-list",
"@wordpress/url": "file:packages/url",
"@wordpress/user-patterns": "file:packages/user-patterns",
"@wordpress/viewport": "file:packages/viewport",
"@wordpress/warning": "file:packages/warning",
"@wordpress/widgets": "file:packages/widgets",
Expand Down
1 change: 1 addition & 0 deletions packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ $z-layers: (

// Should be above the popover (dropdown)
".reusable-blocks-menu-items__convert-modal": 1000001,
".user-patterns-menu-items__convert-modal": 1000001,
".edit-site-create-template-part-modal": 1000001,
".block-editor-block-lock-modal": 1000001,
".block-editor-template-part__selection-modal": 1000001,
Expand Down
8 changes: 8 additions & 0 deletions packages/edit-post/src/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ registerPlugin( 'edit-post', {
>
{ __( 'Manage Reusable blocks' ) }
</MenuItem>
<MenuItem
role="menuitem"
href={ addQueryArgs( 'edit.php', {
post_type: 'wp_block_pattern',
} ) }
>
{ __( 'Manage Patterns' ) }
</MenuItem>
<KeyboardShortcutsHelpMenuItem
onSelect={ onClose }
/>
Expand Down
1 change: 1 addition & 0 deletions packages/edit-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@wordpress/router": "file:../router",
"@wordpress/style-engine": "file:../style-engine",
"@wordpress/url": "file:../url",
"@wordpress/user-patterns": "file:../user-patterns",
"@wordpress/viewport": "file:../viewport",
"@wordpress/widgets": "file:../widgets",
"classnames": "^2.3.1",
Expand Down
2 changes: 2 additions & 0 deletions packages/edit-site/src/components/block-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
useResizeObserver,
} from '@wordpress/compose';
import { ReusableBlocksMenuItems } from '@wordpress/reusable-blocks';
import { UserPatternsMenuItems } from '@wordpress/user-patterns';

/**
* Internal dependencies
Expand Down Expand Up @@ -213,6 +214,7 @@ export default function BlockEditor() {
}
</EditorCanvasContainer.Slot>
<ReusableBlocksMenuItems />
<UserPatternsMenuItems />
</ExperimentalBlockEditorProvider>
);
}
1 change: 1 addition & 0 deletions packages/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@wordpress/rich-text": "file:../rich-text",
"@wordpress/server-side-render": "file:../server-side-render",
"@wordpress/url": "file:../url",
"@wordpress/user-patterns": "file:../user-patterns",
"@wordpress/wordcount": "file:../wordcount",
"classnames": "^2.3.1",
"date-fns": "^2.28.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/editor/src/components/provider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
import { ReusableBlocksMenuItems } from '@wordpress/reusable-blocks';
import { UserPatternsMenuItems } from '@wordpress/user-patterns';
import { store as noticesStore } from '@wordpress/notices';

/**
Expand Down Expand Up @@ -131,6 +132,7 @@ export const ExperimentalEditorProvider = withRegistryProvider(
>
{ children }
<ReusableBlocksMenuItems />
<UserPatternsMenuItems />
</BlockEditorProviderComponent>
</BlockContextProvider>
</EntityProvider>
Expand Down
1 change: 1 addition & 0 deletions packages/user-patterns/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
21 changes: 21 additions & 0 deletions packages/user-patterns/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# User patterns

User patterns components and logic.

## Installation

Install the module

```bash
npm install @wordpress/user-patterns --save
```

_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for such language features and APIs, you should include [the polyfill shipped in `@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default#polyfill) in your code._

## Contributing to this package

This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects.

To find out more about contributing to this package or Gutenberg as a whole, please read the project's main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md).

<br /><br /><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>
Loading