From 320f460db91e1f0acbf91d45d6a85509e3ca5d85 Mon Sep 17 00:00:00 2001 From: Enrique Piqueras Date: Thu, 20 Feb 2020 17:23:51 -0800 Subject: [PATCH] Edit Site: Add multiple template loading (#19141) * Template Loader: Don't capitalize template post titles. * Templates: Bypass slug deduplication. * Lib: Load all available templates in the site editor. * Edit Site: Make sure template title is set when publishing. * Edit Site: Implement template switcher component. * Edit Site: Add a sidebar slot/fill for templates. * Edit Site: Implement template switching. * Edit Site: Add component for adding templates. * Edit Site: Implement template adding in the sidebar. * Edit Site: Change save text from "Customize" to "Update Design". * Edit Site: Keep track of template part IDs and load them in the site editor. * Edit Site: Refactor template switcher to be a dropdown menu and allow switching to template parts. * Edit Site: Move template switcher to the header. * Edit Site: Show active choice in template switcher. * Edit Site: Move template creation to the template switcher. * Edit Site: Switch to new template after creation. * Edit Site: Move template switcher to the left. * Edit Site: Fix header position. * Edit Site: Fix template creation padding. * Edit Site: Remove unnecessary class name. * Edit Site: Remove template switcher padding. * Edit Site: Add theme exporter. * Edit Site: Mark customized templates and template parts. * Edit Site: Move theme exporter into a header dropdown. * Edit Site: Rotate more-menu icon. * Edit Site: Update theme exporter label. * Edit Site Page: Fix comment typo. * Template Loader: Add API for finding the current template for a given path. * Edit Site: Enable link viewer template navigation. * Edit Site: Add block navigation and tool selector. * Edit Site: Fix template part placeholder preview style conflicts. * Template Loader: Fix template part post ID reference. * Edit Site: Use `useEntityBlockEditor`. * Template Loader: Dedupe resolved templates and template parts. * Edit Site: Remove theme exporter. * Fix linting and snapshots. --- lib/demo-block-template-parts/header.html | 3 + lib/demo-block-templates/front-page.html | 1 + lib/edit-site-page.php | 53 ++++++- lib/template-loader.php | 67 ++++++--- lib/templates.php | 20 +++ package-lock.json | 55 +++++-- .../src/components/url-popover/link-viewer.js | 14 +- .../components/src/dropdown-menu/index.js | 22 ++- packages/edit-site/package.json | 4 +- .../src/components/add-template/index.js | 91 +++++++++++ .../src/components/block-editor/index.js | 60 +++++--- .../src/components/block-editor/style.scss | 2 +- .../edit-site/src/components/editor/index.js | 37 +++-- .../edit-site/src/components/header/index.js | 50 +++++- .../src/components/header/style.scss | 33 +++- .../src/components/navigate-to-link/index.js | 64 ++++++++ .../src/components/save-button/index.js | 21 ++- .../src/components/template-switcher/index.js | 142 ++++++++++++++++++ .../components/template-switcher/style.scss | 15 ++ packages/edit-site/src/style.scss | 1 + 20 files changed, 667 insertions(+), 88 deletions(-) create mode 100644 lib/demo-block-template-parts/header.html create mode 100644 packages/edit-site/src/components/add-template/index.js create mode 100644 packages/edit-site/src/components/navigate-to-link/index.js create mode 100644 packages/edit-site/src/components/template-switcher/index.js create mode 100644 packages/edit-site/src/components/template-switcher/style.scss diff --git a/lib/demo-block-template-parts/header.html b/lib/demo-block-template-parts/header.html new file mode 100644 index 00000000000000..11a9e3adf8beca --- /dev/null +++ b/lib/demo-block-template-parts/header.html @@ -0,0 +1,3 @@ + +

Header Template Part

+ \ No newline at end of file diff --git a/lib/demo-block-templates/front-page.html b/lib/demo-block-templates/front-page.html index 627b7ad76aecf8..da11edd68e8754 100644 --- a/lib/demo-block-templates/front-page.html +++ b/lib/demo-block-templates/front-page.html @@ -1,3 +1,4 @@ +
diff --git a/lib/edit-site-page.php b/lib/edit-site-page.php index a85d0bb8ec39a8..6851a5e869e449 100644 --- a/lib/edit-site-page.php +++ b/lib/edit-site-page.php @@ -28,7 +28,12 @@ class="edit-site" * @param string $hook Page. */ function gutenberg_edit_site_init( $hook ) { - global $_wp_current_template_id; + global + $_wp_current_template_id, + $_wp_current_template_name, + $_wp_current_template_content, + $_wp_current_template_hierarchy, + $_wp_current_template_part_ids; if ( 'gutenberg_page_gutenberg-edit-site' !== $hook ) { return; } @@ -74,11 +79,53 @@ function gutenberg_edit_site_init( $hook ) { $settings['fontSizes'] = $font_sizes; } - // Get root template by trigerring `./template-loader.php`'s logic. + // Get all templates by triggering `./template-loader.php`'s logic. + $template_getters = array( + 'get_embed_template', + 'get_404_template', + 'get_search_template', + 'get_home_template', + 'get_privacy_policy_template', + 'get_post_type_archive_template', + 'get_taxonomy_template', + 'get_attachment_template', + 'get_single_template', + 'get_page_template', + 'get_singular_template', + 'get_category_template', + 'get_tag_template', + 'get_author_template', + 'get_date_template', + 'get_archive_template', + ); + $template_ids = array(); + $template_part_ids = array(); + foreach ( $template_getters as $template_getter ) { + call_user_func( $template_getter ); + apply_filters( 'template_include', null ); + if ( isset( $_wp_current_template_id ) ) { + $template_ids[ $_wp_current_template_name ] = $_wp_current_template_id; + } + if ( isset( $_wp_current_template_part_ids ) ) { + $template_part_ids = $template_part_ids + $_wp_current_template_part_ids; + } + $_wp_current_template_id = null; + $_wp_current_template_name = null; + $_wp_current_template_content = null; + $_wp_current_template_hierarchy = null; + $_wp_current_template_part_ids = null; + } get_front_page_template(); get_index_template(); apply_filters( 'template_include', null ); - $settings['templateId'] = $_wp_current_template_id; + $template_ids[ $_wp_current_template_name ] = $_wp_current_template_id; + if ( isset( $_wp_current_template_part_ids ) ) { + $template_part_ids = $template_part_ids + $_wp_current_template_part_ids; + } + $settings['templateId'] = $_wp_current_template_id; + $settings['templateType'] = 'wp_template'; + $settings['templateIds'] = array_values( $template_ids ); + $settings['templatePartIds'] = array_values( $template_part_ids ); // This is so other parts of the code can hook their own settings. // Example: Global Styles. diff --git a/lib/template-loader.php b/lib/template-loader.php index 97dec72d7b1b8b..174a093b6f6a80 100644 --- a/lib/template-loader.php +++ b/lib/template-loader.php @@ -81,24 +81,44 @@ function gutenberg_override_query_template( $template, $type, array $templates = * @param array $block The root block to start traversing from. */ function create_auto_draft_for_template_part_block( $block ) { - if ( 'core/template-part' === $block['blockName'] && ! isset( $block['attrs']['id'] ) ) { - $template_part_file_path = - get_stylesheet_directory() . '/block-template-parts/' . $block['attrs']['slug'] . '.html'; - if ( ! file_exists( $template_part_file_path ) ) { - return; + global $_wp_current_template_part_ids; + + if ( 'core/template-part' === $block['blockName'] ) { + if ( ! isset( $block['attrs']['postId'] ) ) { + $template_part_file_path = + get_stylesheet_directory() . '/block-template-parts/' . $block['attrs']['slug'] . '.html'; + if ( ! file_exists( $template_part_file_path ) ) { + if ( gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing-demo' ) ) { + $template_part_file_path = + dirname( __FILE__ ) . '/demo-block-template-parts/' . $block['attrs']['slug'] . '.html'; + if ( ! file_exists( $template_part_file_path ) ) { + return; + } + } else { + return; + } + } + $template_part_id = wp_insert_post( + array( + 'post_content' => file_get_contents( $template_part_file_path ), + 'post_title' => $block['attrs']['slug'], + 'post_status' => 'auto-draft', + 'post_type' => 'wp_template_part', + 'post_name' => $block['attrs']['slug'], + 'meta_input' => array( + 'theme' => $block['attrs']['theme'], + ), + ) + ); + } else { + $template_part_id = $block['attrs']['postId']; + } + + if ( isset( $_wp_current_template_part_ids ) ) { + $_wp_current_template_part_ids[ $block['attrs']['slug'] ] = $template_part_id; + } else { + $_wp_current_template_part_ids = array( $block['attrs']['slug'] => $template_part_id ); } - wp_insert_post( - array( - 'post_content' => file_get_contents( $template_part_file_path ), - 'post_title' => ucfirst( $block['attrs']['slug'] ), - 'post_status' => 'auto-draft', - 'post_type' => 'wp_template_part', - 'post_name' => $block['attrs']['slug'], - 'meta_input' => array( - 'theme' => $block['attrs']['theme'], - ), - ) - ); } foreach ( $block['innerBlocks'] as $inner_block ) { @@ -114,7 +134,7 @@ function create_auto_draft_for_template_part_block( $block ) { * @return string Path to the canvas file to include. */ function gutenberg_find_template( $template_file ) { - global $_wp_current_template_id, $_wp_current_template_content, $_wp_current_template_hierarchy; + global $_wp_current_template_id, $_wp_current_template_name, $_wp_current_template_content, $_wp_current_template_hierarchy; // Bail if no relevant template hierarchy was determined, or if the template file // was overridden another way. @@ -173,7 +193,7 @@ function gutenberg_find_template( $template_file ) { $post_name = basename( $higher_priority_block_template_path, '.html' ); $current_template_post = array( 'post_content' => file_get_contents( $higher_priority_block_template_path ), - 'post_title' => ucfirst( $post_name ), + 'post_title' => $post_name, 'post_status' => 'auto-draft', 'post_type' => 'wp_template', 'post_name' => $post_name, @@ -192,6 +212,14 @@ function gutenberg_find_template( $template_file ) { } } + if ( isset( $_GET['_wp-find-template'] ) ) { + if ( $current_template_post ) { + wp_send_json_success( $current_template_post ); + } else { + wp_send_json_error( array( 'message' => __( 'No matching template found.', 'gutenberg' ) ) ); + } + } + if ( $current_template_post ) { if ( is_admin() ) { foreach ( parse_blocks( $current_template_post->post_content ) as $block ) { @@ -199,6 +227,7 @@ function gutenberg_find_template( $template_file ) { } } $_wp_current_template_id = $current_template_post->ID; + $_wp_current_template_name = $current_template_post->post_name; $_wp_current_template_content = empty( $current_template_post->post_content ) ? __( 'Empty template.', 'gutenberg' ) : $current_template_post->post_content; } diff --git a/lib/templates.php b/lib/templates.php index a1a519a7a10ae6..95382cdc16fc56 100644 --- a/lib/templates.php +++ b/lib/templates.php @@ -85,6 +85,26 @@ function gutenberg_grant_template_caps( array $allcaps ) { } add_filter( 'user_has_cap', 'gutenberg_grant_template_caps' ); +/** + * Filters `wp_template` posts slug resolution to bypass deduplication logic as + * template slugs should be unique. + * + * @param string $slug The resolved slug (post_name). + * @param int $post_ID Post ID. + * @param string $post_status No uniqueness checks are made if the post is still draft or pending. + * @param string $post_type Post type. + * @param int $post_parent Post parent ID. + * @param int $original_slug The desired slug (post_name). + * @return string The original, desired slug. + */ +function gutenberg_filter_wp_template_wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug ) { + if ( 'wp_template' === $post_type ) { + return $original_slug; + } + return $slug; +} +add_filter( 'wp_unique_post_slug', 'gutenberg_filter_wp_template_wp_unique_post_slug', 10, 6 ); + /** * Fixes the label of the 'wp_template' admin menu entry. */ diff --git a/package-lock.json b/package-lock.json index ea91c2ebcf32f7..085004621a2e5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10417,7 +10417,9 @@ "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", "@wordpress/media-utils": "file:packages/media-utils", - "@wordpress/notices": "file:packages/notices" + "@wordpress/notices": "file:packages/notices", + "file-saver": "^2.0.2", + "jszip": "^3.2.2" } }, "@wordpress/edit-widgets": { @@ -15491,8 +15493,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "corejs-upgrade-webpack-plugin": { "version": "2.2.0", @@ -19036,6 +19037,11 @@ } } }, + "file-saver": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz", + "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==" + }, "file-system-cache": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/file-system-cache/-/file-system-cache-1.0.5.tgz", @@ -21208,6 +21214,11 @@ "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==", "dev": true }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "immer": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", @@ -22170,8 +22181,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -25729,6 +25739,17 @@ "object.assign": "^4.1.0" } }, + "jszip": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz", + "integrity": "sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -25855,6 +25876,14 @@ "type-check": "~0.3.2" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "line-height": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/line-height/-/line-height-0.3.1.tgz", @@ -30318,8 +30347,7 @@ "pako": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz", - "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==", - "dev": true + "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==" }, "parallel-transform": { "version": "1.1.0", @@ -31580,8 +31608,7 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { "version": "2.0.3", @@ -33910,7 +33937,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -35499,6 +35525,11 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -36846,7 +36877,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -39158,8 +39188,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util-promisify": { "version": "2.1.0", diff --git a/packages/block-editor/src/components/url-popover/link-viewer.js b/packages/block-editor/src/components/url-popover/link-viewer.js index f5cbd7459355f8..ba957fac9de9f2 100644 --- a/packages/block-editor/src/components/url-popover/link-viewer.js +++ b/packages/block-editor/src/components/url-popover/link-viewer.js @@ -7,9 +7,12 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { ExternalLink, Button } from '@wordpress/components'; -import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; import { pencil } from '@wordpress/icons'; +import { createSlotFill, ExternalLink, Button } from '@wordpress/components'; +import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; +import { useMemo } from '@wordpress/element'; + +const { Slot, Fill } = createSlotFill( 'BlockEditorURLPopoverLinkViewer' ); function LinkViewerUrl( { url, urlLabel, className } ) { const linkClassName = classnames( @@ -28,7 +31,7 @@ function LinkViewerUrl( { url, urlLabel, className } ) { ); } -export default function LinkViewer( { +function LinkViewer( { className, linkClassName, onEditLinkClick, @@ -56,6 +59,11 @@ export default function LinkViewer( { onClick={ onEditLinkClick } /> ) } + ( { url } ), [ url ] ) } />
); } + +LinkViewer.Fill = Fill; + +export default LinkViewer; diff --git a/packages/components/src/dropdown-menu/index.js b/packages/components/src/dropdown-menu/index.js index 1d5f334a585e16..7d7c16f1d7a2a0 100644 --- a/packages/components/src/dropdown-menu/index.js +++ b/packages/components/src/dropdown-menu/index.js @@ -9,6 +9,7 @@ import { flatMap, isEmpty, isFunction } from 'lodash'; */ import { DOWN } from '@wordpress/keycodes'; import deprecated from '@wordpress/deprecated'; +import { Fragment } from '@wordpress/element'; /** * Internal dependencies @@ -105,6 +106,23 @@ function DropdownMenu( { toggleProps ); + let buttonChildren; + if ( + mergedToggleProps.children || + ! icon || + hasArrowIndicator + ) { + buttonChildren = [ + + { mergedToggleProps.children } + , + + { ( ! icon || hasArrowIndicator ) && ( + + ) } + , + ]; + } return ( ); } } diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 8d25b17abbaa3c..3304f513a6db92 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -32,7 +32,9 @@ "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/media-utils": "file:../media-utils", - "@wordpress/notices": "file:../notices" + "@wordpress/notices": "file:../notices", + "file-saver": "^2.0.2", + "jszip": "^3.2.2" }, "publishConfig": { "access": "public" diff --git a/packages/edit-site/src/components/add-template/index.js b/packages/edit-site/src/components/add-template/index.js new file mode 100644 index 00000000000000..9cdf5352001231 --- /dev/null +++ b/packages/edit-site/src/components/add-template/index.js @@ -0,0 +1,91 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useState, useCallback } from '@wordpress/element'; +import { cleanForSlug } from '@wordpress/editor'; +import { __ } from '@wordpress/i18n'; +import { Modal, TextControl, Button } from '@wordpress/components'; + +export default function AddTemplate( { + ids, + onAddTemplateId, + onRequestClose, + isOpen, +} ) { + const slugs = useSelect( + ( select ) => { + const { getEntityRecord } = select( 'core' ); + return ids.reduce( ( acc, id ) => { + const template = getEntityRecord( + 'postType', + 'wp_template', + id + ); + acc[ template ? template.slug : 'loading' ] = true; + return acc; + }, {} ); + }, + [ ids ] + ); + const { saveEntityRecord } = useDispatch( 'core' ); + + const [ slug, _setSlug ] = useState(); + const [ help, setHelp ] = useState(); + const setSlug = useCallback( + ( nextSlug ) => { + _setSlug( nextSlug ); + const cleanSlug = cleanForSlug( nextSlug ); + setHelp( + slugs[ cleanSlug ] + ? __( 'Template already exists, edit it instead.' ) + : cleanSlug + ); + }, + [ slugs ] + ); + const add = useCallback( async () => { + _setSlug( '' ); + const cleanSlug = cleanForSlug( slug ); + + try { + const template = await saveEntityRecord( + 'postType', + 'wp_template', + { + title: cleanSlug, + status: 'publish', + slug: cleanSlug, + } + ); + onAddTemplateId( template.id ); + onRequestClose(); + } catch ( err ) { + setHelp( __( 'Error adding template.' ) ); + } + }, [ slug, onRequestClose ] ); + return ( + ! slugs.loading && + isOpen && ( + + + + + ) + ); +} diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index ef886a68f53b59..8438927f338cbc 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -4,11 +4,11 @@ import { useSelect } from '@wordpress/data'; import { useMemo, useCallback } from '@wordpress/element'; import { uploadMedia } from '@wordpress/media-utils'; -import { useEntityProp } from '@wordpress/core-data'; -import { parse, serialize } from '@wordpress/blocks'; +import { useEntityBlockEditor } from '@wordpress/core-data'; import { BlockEditorProvider, BlockEditorKeyboardShortcuts, + URLPopover, BlockInspector, WritingFlow, ObserveTyping, @@ -19,9 +19,12 @@ import { /** * Internal dependencies */ +import { useEditorContext } from '../editor'; +import NavigateToLink from '../navigate-to-link'; import Sidebar from '../sidebar'; -export default function BlockEditor( { settings: _settings } ) { +export default function BlockEditor() { + const { settings: _settings, setSettings } = useEditorContext(); const canUserCreateMedia = useSelect( ( select ) => { const _canUserCreateMedia = select( 'core' ).canUser( 'create', @@ -44,38 +47,49 @@ export default function BlockEditor( { settings: _settings } ) { }, }; }, [ canUserCreateMedia, _settings ] ); - const [ content, _setContent ] = useEntityProp( + const [ blocks, onInput, onChange ] = useEntityBlockEditor( 'postType', - 'wp_template', - 'content' + settings.templateType ); - const initialBlocks = useMemo( () => { - if ( typeof content !== 'function' ) { - const parsedContent = parse( content ); - return parsedContent.length ? parsedContent : undefined; - } - }, [] ); - const [ blocks = initialBlocks, setBlocks ] = useEntityProp( - 'postType', - 'wp_template', - 'blocks' + const setActiveTemplateId = useCallback( + ( newTemplateId ) => + setSettings( ( prevSettings ) => ( { + ...prevSettings, + templateId: newTemplateId, + templateType: 'wp_template', + } ) ), + [] ); - const setContent = useCallback( ( nextBlocks ) => { - setBlocks( nextBlocks ); - _setContent( serialize( nextBlocks ) ); - }, [] ); return ( + + { useCallback( + ( fillProps ) => ( + + ), + [ + settings.templateIds, + settings.templateId, + setActiveTemplateId, + ] + ) } + -
+
select( 'core' ).getEntityRecord( 'postType', - 'wp_template', + settings.templateType, settings.templateId ), - [] + [ settings.templateType, settings.templateId ] ); + const context = useMemo( () => ( { settings, setSettings } ), [ + settings, + setSettings, + ] ); return template ? ( - -
- - - + + +
+ + + + ) : null; } - export default navigateRegions( Editor ); diff --git a/packages/edit-site/src/components/header/index.js b/packages/edit-site/src/components/header/index.js index 196b80834eadd6..661ab38fb38aed 100644 --- a/packages/edit-site/src/components/header/index.js +++ b/packages/edit-site/src/components/header/index.js @@ -1,14 +1,46 @@ /** * WordPress dependencies */ +import { useCallback } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { BlockNavigationDropdown, ToolSelector } from '@wordpress/block-editor'; /** * Internal dependencies */ +import { useEditorContext } from '../editor'; +import TemplateSwitcher from '../template-switcher'; import SaveButton from '../save-button'; export default function Header() { + const { settings, setSettings } = useEditorContext(); + const setActiveTemplateId = useCallback( + ( newTemplateId ) => + setSettings( ( prevSettings ) => ( { + ...prevSettings, + templateId: newTemplateId, + templateType: 'wp_template', + } ) ), + [] + ); + const setActiveTemplatePartId = useCallback( + ( newTemplatePartId ) => + setSettings( ( prevSettings ) => ( { + ...prevSettings, + templateId: newTemplatePartId, + templateType: 'wp_template_part', + } ) ), + [] + ); + const addTemplateId = useCallback( + ( newTemplateId ) => + setSettings( ( prevSettings ) => ( { + ...prevSettings, + templateId: newTemplateId, + templateIds: [ ...prevSettings.templateIds, newTemplateId ], + } ) ), + [] + ); return (
-

- { __( 'Site Editor' ) } { __( '(beta)' ) } -

+
+ + + +
diff --git a/packages/edit-site/src/components/header/style.scss b/packages/edit-site/src/components/header/style.scss index 84fa28b4cd5918..194515370a889f 100644 --- a/packages/edit-site/src/components/header/style.scss +++ b/packages/edit-site/src/components/header/style.scss @@ -24,14 +24,35 @@ top: $admin-bar-height; } } - @include editor-left(".edit-site-header"); -.edit-site-header__title { - font-size: 16px; - padding: 0 20px; +.edit-site-header__toolbar, +.edit-site-header__actions { + display: flex; + + & > * { + margin-left: 5px; + } } -.edit-site-header__actions { - padding: 0 20px; +.edit-site-header__actions-more-menu { + margin-left: -4px; + + // The padding and margin of the more menu is intentionally non-standard. + .components-icon-button { + padding: 8px 2px; + width: auto; + } + + @include break-small() { + margin-left: 4px; + + .components-icon-button { + padding: 8px 4px; + } + } + + .components-button svg { + transform: rotate(90deg); + } } diff --git a/packages/edit-site/src/components/navigate-to-link/index.js b/packages/edit-site/src/components/navigate-to-link/index.js new file mode 100644 index 00000000000000..2ac427803969d0 --- /dev/null +++ b/packages/edit-site/src/components/navigate-to-link/index.js @@ -0,0 +1,64 @@ +/** + * WordPress dependencies + */ +import { useState, useEffect, useMemo } from '@wordpress/element'; +import { select } from '@wordpress/data'; +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Browser dependencies + */ +const { fetch } = window; + +export default function NavigateToLink( { + url, + templateIds, + activeId, + onActiveIdChange, +} ) { + const [ templateId, setTemplateId ] = useState(); + useEffect( () => { + const effect = async () => { + try { + const { success, data } = await fetch( + `${ url }?_wp-find-template` + ).then( ( res ) => res.json() ); + if ( success ) { + let newTemplateId = data.ID; + if ( newTemplateId === null ) { + const { getEntityRecord } = select( 'core' ); + newTemplateId = templateIds + .map( ( id ) => + getEntityRecord( 'postType', 'wp_template', id ) + ) + .find( + ( template ) => template.slug === data.post_name + ).id; + } + setTemplateId( newTemplateId ); + } else { + throw new Error(); + } + } catch ( err ) { + setTemplateId( null ); + } + }; + effect(); + }, [ url, templateIds ] ); + const onClick = useMemo( () => { + if ( ! templateId || templateId === activeId ) { + return null; + } + return () => onActiveIdChange( templateId ); + }, [ templateId, activeId, onActiveIdChange ] ); + return ( + onClick && ( + diff --git a/packages/edit-site/src/components/template-switcher/index.js b/packages/edit-site/src/components/template-switcher/index.js new file mode 100644 index 00000000000000..ea8947360c6710 --- /dev/null +++ b/packages/edit-site/src/components/template-switcher/index.js @@ -0,0 +1,142 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { useState, useCallback } from '@wordpress/element'; +import { + Tooltip, + Icon, + DropdownMenu, + MenuGroup, + MenuItemsChoice, + MenuItem, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import AddTemplate from '../add-template'; + +function TemplateLabel( { template } ) { + return ( +
+ { template.slug }{ ' ' } + { template.status !== 'auto-draft' && ( + +
+ +
+
+ ) } +
+ ); +} + +export default function TemplateSwitcher( { + ids, + templatePartIds, + activeId, + isTemplatePart, + onActiveIdChange, + onActiveTemplatePartIdChange, + onAddTemplateId, +} ) { + const { templates, templateParts } = useSelect( + ( select ) => { + const { getEntityRecord } = select( 'core' ); + return { + templates: ids.map( ( id ) => { + const template = getEntityRecord( + 'postType', + 'wp_template', + id + ); + return { + label: template ? ( + + ) : ( + __( 'loading…' ) + ), + value: id, + slug: template ? template.slug : __( 'loading…' ), + }; + } ), + templateParts: templatePartIds.map( ( id ) => { + const template = getEntityRecord( + 'postType', + 'wp_template_part', + id + ); + return { + label: template ? ( + + ) : ( + __( 'loading…' ) + ), + value: id, + slug: template ? template.slug : __( 'loading…' ), + }; + } ), + }; + }, + [ ids, templatePartIds ] + ); + const [ isAddTemplateOpen, setIsAddTemplateOpen ] = useState( false ); + return ( + <> + choice.value === activeId ).slug, + } } + > + { ( { onClose } ) => ( + <> + + + { + onClose(); + setIsAddTemplateOpen( true ); + } } + > + { __( 'New' ) } + + + + + + + ) } + + setIsAddTemplateOpen( false ), + [] + ) } + isOpen={ isAddTemplateOpen } + /> + + ); +} diff --git a/packages/edit-site/src/components/template-switcher/style.scss b/packages/edit-site/src/components/template-switcher/style.scss new file mode 100644 index 00000000000000..f0d135db7fd52b --- /dev/null +++ b/packages/edit-site/src/components/template-switcher/style.scss @@ -0,0 +1,15 @@ +.edit-site-template-switcher__label { + position: relative; + width: 100%; +} + +.edit-site-template-switcher__label-customized-icon-container { + position: absolute; + right: 5px; + top: 0; + width: 10px; +} + +.edit-site-template-switcher__label-customized-icon-icon { + width: 100%; +} diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index 4b1041c3d05f5d..d0214003ad311b 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -2,6 +2,7 @@ @import "./components/header/style.scss"; @import "./components/notices/style.scss"; @import "./components/sidebar/style.scss"; +@import "./components/template-switcher/style.scss"; // In order to use mix-blend-mode, this element needs to have an explicitly set background-color. // We scope it to .wp-toolbar to be wp-admin only, to prevent bleed into other implementations.