diff --git a/lib/compat/wordpress-6.3/theme-previews.php b/lib/compat/wordpress-6.3/theme-previews.php new file mode 100644 index 0000000000000..5671274925765 --- /dev/null +++ b/lib/compat/wordpress-6.3/theme-previews.php @@ -0,0 +1,129 @@ +errors() ) ) { + return sanitize_text_field( $preview_stylesheet ); + } + + return $current_stylesheet; +} + +/** + * Filters the blog option to return the parent theme directory for the previewed theme. + * + * @param string $current_stylesheet The current theme directory. + * @return string The previewed theme directory. + */ +function gutenberg_theme_preview_template( $current_stylesheet = null ) { + $preview_stylesheet = ! empty( $_GET['theme_preview'] ) ? $_GET['theme_preview'] : null; + $wp_theme = wp_get_theme( $preview_stylesheet ); + if ( ! is_wp_error( $wp_theme->errors() ) ) { + return sanitize_text_field( $wp_theme->get_template() ); + } + + return $current_stylesheet; +} + +/** + * Adds a middleware to the REST API to set the theme for the preview. + */ +function gutenberg_attach_theme_preview_middleware() { + wp_add_inline_script( + 'wp-api-fetch', + sprintf( + 'wp.apiFetch.use( wp.apiFetch.createThemePreviewMiddleware( %s ) );', + wp_json_encode( sanitize_text_field( $_GET['theme_preview'] ) ) + ), + 'after' + ); +} + +/** + * Temporary function to add a live preview button to block themes. + * Remove when https://core.trac.wordpress.org/ticket/58190 lands. + */ +function add_live_preview_button() { + global $pagenow; + if ( 'themes.php' === $pagenow ) { + ?> + + + + __( 'Enable Block Theme Previews', 'gutenberg' ), + 'id' => 'gutenberg-theme-previews', + ) + ); + register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/lib/load.php b/lib/load.php index 8cceb21906293..a322e8af03a9d 100644 --- a/lib/load.php +++ b/lib/load.php @@ -48,6 +48,7 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/compat/wordpress-6.3/class-gutenberg-rest-templates-controller-6-3.php'; require_once __DIR__ . '/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php'; require_once __DIR__ . '/compat/wordpress-6.3/rest-api.php'; + require_once __DIR__ . '/compat/wordpress-6.3/theme-previews.php'; // Experimental. if ( ! class_exists( 'WP_Rest_Customizer_Nonces' ) ) { diff --git a/packages/api-fetch/src/index.js b/packages/api-fetch/src/index.js index a0cda678f8d3e..408f2af0901f1 100644 --- a/packages/api-fetch/src/index.js +++ b/packages/api-fetch/src/index.js @@ -14,6 +14,7 @@ import namespaceEndpointMiddleware from './middlewares/namespace-endpoint'; import httpV1Middleware from './middlewares/http-v1'; import userLocaleMiddleware from './middlewares/user-locale'; import mediaUploadMiddleware from './middlewares/media-upload'; +import createThemePreviewMiddleware from './middlewares/theme-preview'; import { parseResponseAndNormalizeError, parseAndThrowError, @@ -193,5 +194,6 @@ apiFetch.createPreloadingMiddleware = createPreloadingMiddleware; apiFetch.createRootURLMiddleware = createRootURLMiddleware; apiFetch.fetchAllMiddleware = fetchAllMiddleware; apiFetch.mediaUploadMiddleware = mediaUploadMiddleware; +apiFetch.createThemePreviewMiddleware = createThemePreviewMiddleware; export default apiFetch; diff --git a/packages/api-fetch/src/middlewares/theme-preview.js b/packages/api-fetch/src/middlewares/theme-preview.js new file mode 100644 index 0000000000000..2f41293230eae --- /dev/null +++ b/packages/api-fetch/src/middlewares/theme-preview.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { addQueryArgs, hasQueryArg } from '@wordpress/url'; + +/** + * This appends a `theme_preview` parameter to the REST API request URL if + * the admin URL contains a `theme` GET parameter. + * + * @param {Record} themePath + * @return {import('../types').APIFetchMiddleware} Preloading middleware. + */ +const createThemePreviewMiddleware = ( themePath ) => ( options, next ) => { + if ( + typeof options.url === 'string' && + ! hasQueryArg( options.url, 'theme_preview' ) + ) { + options.url = addQueryArgs( options.url, { + theme_preview: themePath, + } ); + } + + if ( + typeof options.path === 'string' && + ! hasQueryArg( options.path, 'theme_preview' ) + ) { + options.path = addQueryArgs( options.path, { + theme_preview: themePath, + } ); + } + + return next( options ); +}; + +export default createThemePreviewMiddleware; diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 434554bbda7e8..62b62082e4b92 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -171,7 +171,12 @@ export default function Editor() { } + notices={ + ( isEditMode || + window?.__experimentalEnableThemePreviews ) && ( + + ) + } content={ <> diff --git a/packages/edit-site/src/components/routes/link.js b/packages/edit-site/src/components/routes/link.js index a77f83944dd9d..71313176a1c60 100644 --- a/packages/edit-site/src/components/routes/link.js +++ b/packages/edit-site/src/components/routes/link.js @@ -8,6 +8,10 @@ import { privateApis as routerPrivateApis } from '@wordpress/router'; * Internal dependencies */ import { unlock } from '../../private-apis'; +import { + isPreviewingTheme, + currentlyPreviewingTheme, +} from '../../utils/is-previewing-theme'; const { useHistory } = unlock( routerPrivateApis ); @@ -29,6 +33,14 @@ export function useLink( params = {}, state, shouldReplace = false ) { window.location.href, ...Object.keys( currentArgs ) ); + + if ( isPreviewingTheme() ) { + params = { + ...params, + theme_preview: currentlyPreviewingTheme(), + }; + } + const newUrl = addQueryArgs( currentUrlWithoutArgs, params ); return { diff --git a/packages/edit-site/src/components/save-button/index.js b/packages/edit-site/src/components/save-button/index.js index 0e4bbf6296abc..4e4c6dfd35f65 100644 --- a/packages/edit-site/src/components/save-button/index.js +++ b/packages/edit-site/src/components/save-button/index.js @@ -11,6 +11,7 @@ import { displayShortcut } from '@wordpress/keycodes'; * Internal dependencies */ import { store as editSiteStore } from '../../store'; +import { isPreviewingTheme } from '../../utils/is-previewing-theme'; export default function SaveButton( { className = 'edit-site-save-button__button', @@ -33,9 +34,17 @@ export default function SaveButton( { }, [] ); const { setIsSaveViewOpened } = useDispatch( editSiteStore ); - const disabled = ! isDirty || isSaving; + const activateSaveEnabled = isPreviewingTheme() || isDirty; + const disabled = isSaving || ! activateSaveEnabled; - const label = __( 'Save' ); + let label; + if ( isPreviewingTheme() && isDirty ) { + label = __( 'Activate & Save' ); + } else if ( isPreviewingTheme() ) { + label = __( 'Activate' ); + } else { + label = __( 'Save' ); + } return (