Replace/admin interface (#637)

Co-authored-by: Vicente Canales <vicente.canales@automattic.com>
Co-authored-by: Vicente Canales <1157901+vcanales@users.noreply.github.com>
Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
This commit is contained in:
Jason Crist 2024-05-28 07:53:06 -04:00 committed by GitHub
parent 244cb1c5c3
commit 2b17395ef2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 755 additions and 1030 deletions

View file

@ -1,6 +1,6 @@
{
"extends": "plugin:@wordpress/eslint-plugin/recommended",
"env":{
"env": {
"browser": true
},
"rules": {
@ -14,12 +14,8 @@
"react/jsx-boolean-value": "error",
"unicorn/no-abusive-eslint-disable": "error"
},
"ignorePatterns": [
"src/lib"
],
"plugins":[
"unicorn"
],
"ignorePatterns": [ "src/lib" ],
"plugins": [ "unicorn" ],
"overrides": [
{
"files": [ "**/test/**/*.js" ],

View file

@ -1,500 +0,0 @@
<?php

require_once __DIR__ . '/resolver_additions.php';
require_once __DIR__ . '/create-theme/theme-locale.php';
require_once __DIR__ . '/create-theme/theme-tags.php';
require_once __DIR__ . '/create-theme/theme-zip.php';
require_once __DIR__ . '/create-theme/theme-media.php';
require_once __DIR__ . '/create-theme/theme-patterns.php';
require_once __DIR__ . '/create-theme/theme-templates.php';
require_once __DIR__ . '/create-theme/theme-styles.php';
require_once __DIR__ . '/create-theme/theme-json.php';
require_once __DIR__ . '/create-theme/theme-utils.php';
require_once __DIR__ . '/create-theme/theme-readme.php';
require_once __DIR__ . '/create-theme/theme-form.php';
require_once __DIR__ . '/create-theme/form-messages.php';
require_once __DIR__ . '/create-theme/theme-fonts.php';
require_once __DIR__ . '/create-theme/theme-create.php';
/**
* The admin-specific functionality of the plugin.
*
* @package Create_Block_Theme
* @subpackage Create_Block_Theme/admin
* @author WordPress.org
*/
class CBT_WP_Admin {
public $theme;

/**
* Initialize the class and set its properties.
*/
public function __construct() {
add_action( 'admin_menu', array( $this, 'create_admin_menu' ) );
add_action( 'admin_init', array( $this, 'blockbase_save_theme' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'create_block_theme_enqueue' ) );

$this->theme = wp_get_theme();
}

function create_block_theme_enqueue() {
global $pagenow;

if ( 'site-editor.php' !== $pagenow ) {
return;
}

$asset_file = include plugin_dir_path( dirname( __FILE__ ) ) . 'build/plugin-sidebar.asset.php';

wp_register_script(
'create-block-theme-slot-fill',
plugins_url( 'build/plugin-sidebar.js', dirname( __FILE__ ) ),
$asset_file['dependencies'],
$asset_file['version']
);
wp_enqueue_script(
'create-block-theme-slot-fill',
);

// Enable localization in the plugin sidebar.
wp_set_script_translations( 'create-block-theme-slot-fill', 'create-block-theme' );
}

function create_admin_menu() {
if ( ! wp_is_block_theme() ) {
return;
}
$page_title = _x( 'Create Block Theme', 'UI String', 'create-block-theme' );
$menu_title = _x( 'Create Block Theme', 'UI String', 'create-block-theme' );
add_theme_page( $page_title, $menu_title, 'edit_theme_options', 'create-block-theme', array( 'CBT_Theme_Form', 'create_admin_form_page' ) );

add_action( 'admin_enqueue_scripts', array( 'CBT_Theme_Form', 'form_script' ) );
}

function save_theme_locally( $export_type ) {
CBT_Theme_Templates::add_templates_to_local( $export_type );
CBT_Theme_JSON::add_theme_json_to_local( $export_type );
}

function save_variation( $export_type, $theme ) {
CBT_Theme_JSON::add_theme_json_variation_to_local( 'variation', $theme );
}

public function download_file( $file, $filename ) {
// Set headers.
header( 'Content-Type: application/zip' );
header( 'Content-Disposition: attachment; filename=' . $filename );
header( 'Content-Length: ' . filesize( $file ) );

// Send file.
readfile( $file );

// Delete file.
unlink( $file );
}

/**
* Export activated child theme
*/
function export_child_theme( $theme ) {
if ( $theme['name'] ) {
// Used when CREATING a child theme
$theme['slug'] = CBT_Theme_Utils::get_theme_slug( $theme['name'] );
} else {
// Used with EXPORTING a child theme
$theme['slug'] = wp_get_theme()->get( 'TextDomain' );
}

// Create ZIP file in the temporary directory.
$filename = tempnam( get_temp_dir(), $theme['slug'] );
$zip = CBT_Theme_Zip::create_zip( $filename );

$zip = CBT_Theme_Zip::copy_theme_to_zip( $zip, null, null );
$zip = CBT_Theme_Zip::add_templates_to_zip( $zip, 'current', $theme['slug'] );
$zip = CBT_Theme_Zip::add_theme_json_to_zip( $zip, CBT_Theme_JSON_Resolver::export_theme_data( 'current' ) );

$zip->close();

// Download the ZIP file.
$this->download_file( $filename, $theme['slug'] . '.zip' );

return $filename;
}

/**
* Create a sibling theme of the activated theme
*/
function create_sibling_theme( $theme, $screenshot ) {
$theme_slug = CBT_Theme_Utils::get_theme_slug( $theme['name'] );

// Sanitize inputs.
$theme['name'] = sanitize_text_field( $theme['name'] );
$theme['description'] = sanitize_text_field( $theme['description'] );
$theme['uri'] = sanitize_text_field( $theme['uri'] );
$theme['author'] = sanitize_text_field( $theme['author'] );
$theme['author_uri'] = sanitize_text_field( $theme['author_uri'] );
$theme['tags_custom'] = sanitize_text_field( $theme['tags_custom'] );
$theme['image_credits'] = sanitize_textarea_field( $theme['image_credits'] );
$theme['recommended_plugins'] = sanitize_textarea_field( $theme['recommended_plugins'] );
$theme['font_credits'] = sanitize_textarea_field( $theme['font_credits'] );
$theme['slug'] = $theme_slug;
$theme['template'] = wp_get_theme()->get( 'Template' );
$theme['text_domain'] = $theme_slug;

// Create ZIP file in the temporary directory.
$filename = tempnam( get_temp_dir(), $theme['slug'] );
$zip = CBT_Theme_Zip::create_zip( $filename );

$zip = CBT_Theme_Zip::copy_theme_to_zip( $zip, $theme['slug'], $theme['name'] );
$zip = CBT_Theme_Zip::add_templates_to_zip( $zip, 'current', $theme['slug'] );
$zip = CBT_Theme_Zip::add_theme_json_to_zip( $zip, CBT_Theme_JSON_Resolver::export_theme_data( 'current' ) );

// Add readme.txt.
$zip->addFromStringToTheme(
'readme.txt',
CBT_Theme_Readme::create( $theme )
);

// Augment style.css
$css_contents = file_get_contents( get_stylesheet_directory() . '/style.css' );
// Remove metadata from style.css file
$css_contents = trim( substr( $css_contents, strpos( $css_contents, '*/' ) + 2 ) );
// Add new metadata
$css_contents = CBT_Theme_Styles::build_style_css( $theme ) . $css_contents;
$zip->addFromStringToTheme(
'style.css',
$css_contents
);

// Add / replace screenshot.
if ( CBT_Theme_Utils::is_valid_screenshot( $screenshot ) ) {
$zip->addFileToTheme(
$screenshot['tmp_name'],
'screenshot.png'
);
}

$zip->close();

header( 'Content-Type: application/zip' );
header( 'Content-Disposition: attachment; filename=' . $theme['slug'] . '.zip' );
header( 'Content-Length: ' . filesize( $filename ) );
flush();
echo readfile( $filename );
die();
}

/**
* Clone the activated theme to create a new theme
*/
function clone_theme( $theme, $screenshot ) {
$theme_slug = CBT_Theme_Utils::get_theme_slug( $theme['name'] );

// Sanitize inputs.
$theme['name'] = sanitize_text_field( $theme['name'] );
$theme['description'] = sanitize_text_field( $theme['description'] );
$theme['uri'] = sanitize_text_field( $theme['uri'] );
$theme['author'] = sanitize_text_field( $theme['author'] );
$theme['author_uri'] = sanitize_text_field( $theme['author_uri'] );
$theme['tags_custom'] = sanitize_text_field( $theme['tags_custom'] );
$theme['image_credits'] = sanitize_textarea_field( $theme['image_credits'] );
$theme['recommended_plugins'] = sanitize_textarea_field( $theme['recommended_plugins'] );
$theme['font_credits'] = sanitize_textarea_field( $theme['font_credits'] );
$theme['slug'] = $theme_slug;
$theme['template'] = '';
$theme['text_domain'] = $theme_slug;

// Use previous theme's tags if custom tags are empty.
if ( empty( $theme['tags_custom'] ) ) {
$theme['tags_custom'] = implode( ', ', $this->theme->get( 'Tags' ) );
}

// Create ZIP file in the temporary directory.
$filename = tempnam( get_temp_dir(), $theme['slug'] );
$zip = CBT_Theme_Zip::create_zip( $filename );

$zip = CBT_Theme_Zip::copy_theme_to_zip( $zip, $theme['slug'], $theme['name'] );

$zip = CBT_Theme_Zip::add_templates_to_zip( $zip, 'all', $theme['slug'] );
$zip = CBT_Theme_Zip::add_theme_json_to_zip( $zip, CBT_Theme_JSON_Resolver::export_theme_data( 'all' ) );

// Add readme.txt.
$zip->addFromStringToTheme(
'readme.txt',
CBT_Theme_Readme::create( $theme )
);

// Augment style.css
$css_contents = file_get_contents( get_stylesheet_directory() . '/style.css' );
// Remove metadata from style.css file
$css_contents = trim( substr( $css_contents, strpos( $css_contents, '*/' ) + 2 ) );
// Add new metadata
$css_contents = CBT_Theme_Styles::build_style_css( $theme ) . $css_contents;
$zip->addFromStringToTheme(
'style.css',
$css_contents
);

// Add / replace screenshot.
if ( CBT_Theme_Utils::is_valid_screenshot( $screenshot ) ) {
$zip->addFileToTheme(
$screenshot['tmp_name'],
'screenshot.png'
);
}

$zip->close();

header( 'Content-Type: application/zip' );
header( 'Content-Disposition: attachment; filename=' . $theme['slug'] . '.zip' );
header( 'Content-Length: ' . filesize( $filename ) );
flush();
readfile( $filename );
unlink( $filename );
exit;
}

/**
* Create a child theme of the activated theme
*/
function create_child_theme( $theme, $screenshot ) {
$parent_theme_slug = CBT_Theme_Utils::get_theme_slug( $this->theme->get( 'Name' ) );
$child_theme_slug = CBT_Theme_Utils::get_theme_slug( $theme['name'] );

// Sanitize inputs.
$theme['name'] = sanitize_text_field( $theme['name'] );
$theme['description'] = sanitize_text_field( $theme['description'] );
$theme['uri'] = sanitize_text_field( $theme['uri'] );
$theme['author'] = sanitize_text_field( $theme['author'] );
$theme['author_uri'] = sanitize_text_field( $theme['author_uri'] );
$theme['tags_custom'] = sanitize_text_field( $theme['tags_custom'] );
$theme['image_credits'] = sanitize_textarea_field( $theme['image_credits'] );
$theme['recommended_plugins'] = sanitize_textarea_field( $theme['recommended_plugins'] );
$theme['font_credits'] = sanitize_textarea_field( $theme['font_credits'] );
$theme['is_child_theme'] = true;
$theme['text_domain'] = $child_theme_slug;
$theme['template'] = $parent_theme_slug;
$theme['slug'] = $child_theme_slug;

// Create ZIP file in the temporary directory.
$filename = tempnam( get_temp_dir(), $theme['slug'] );
$zip = CBT_Theme_Zip::create_zip( $filename );

$zip = CBT_Theme_Zip::add_templates_to_zip( $zip, 'user', $theme['slug'] );
$zip = CBT_Theme_Zip::add_theme_json_to_zip( $zip, CBT_Theme_JSON_Resolver::export_theme_data( 'user' ) );

// Add readme.txt.
$zip->addFromStringToTheme(
'readme.txt',
CBT_Theme_Readme::create( $theme )
);

// Add style.css.
$zip->addFromStringToTheme(
'style.css',
CBT_Theme_Styles::build_style_css( $theme )
);

// Add / replace screenshot.
if ( CBT_Theme_Utils::is_valid_screenshot( $screenshot ) ) {
$zip->addFileToTheme(
$screenshot['tmp_name'],
'screenshot.png'
);
}

$zip->close();

header( 'Content-Type: application/zip' );
header( 'Content-Disposition: attachment; filename=' . $theme['slug'] . '.zip' );
header( 'Content-Length: ' . filesize( $filename ) );
flush();
echo readfile( $filename );
die();
}

/**
* Export activated parent theme
*/
function export_theme( $theme ) {
$theme['slug'] = $this->theme->get( 'TextDomain' );

// Create ZIP file in the temporary directory.
$filename = tempnam( get_temp_dir(), $theme['slug'] );
$zip = CBT_Theme_Zip::create_zip( $filename );

$zip = CBT_Theme_Zip::copy_theme_to_zip( $zip, null, null );
$zip = CBT_Theme_Zip::add_templates_to_zip( $zip, 'all', null );
$zip = CBT_Theme_Zip::add_theme_json_to_zip( $zip, CBT_Theme_JSON_Resolver::export_theme_data( 'all' ) );

$zip->close();

header( 'Content-Type: application/zip' );
header( 'Content-Disposition: attachment; filename=' . $theme['slug'] . '.zip' );
header( 'Content-Length: ' . filesize( $filename ) );
flush();
echo readfile( $filename );
die();
}

function create_blank_theme( $theme, $screenshot ) {
$theme_slug = CBT_Theme_Utils::get_theme_slug( $theme['name'] );

// Sanitize inputs.
$theme['name'] = sanitize_text_field( $theme['name'] );
$theme['description'] = sanitize_text_field( $theme['description'] );
$theme['uri'] = sanitize_text_field( $theme['uri'] );
$theme['author'] = sanitize_text_field( $theme['author'] );
$theme['author_uri'] = sanitize_text_field( $theme['author_uri'] );
$theme['tags_custom'] = sanitize_text_field( $theme['tags_custom'] );
$theme['image_credits'] = sanitize_textarea_field( $theme['image_credits'] );
$theme['recommended_plugins'] = sanitize_textarea_field( $theme['recommended_plugins'] );
$theme['font_credits'] = sanitize_textarea_field( $theme['font_credits'] );
$theme['template'] = '';
$theme['slug'] = $theme_slug;
$theme['text_domain'] = $theme_slug;

// Create theme directory.
$source = plugin_dir_path( __DIR__ ) . 'assets/boilerplate';
$blank_theme_path = get_theme_root() . DIRECTORY_SEPARATOR . $theme['slug'];
if ( ! file_exists( $blank_theme_path ) ) {
wp_mkdir_p( $blank_theme_path );
// Add readme.txt.
file_put_contents(
$blank_theme_path . DIRECTORY_SEPARATOR . 'readme.txt',
CBT_Theme_Readme::create( $theme )
);

// Add new metadata.
$css_contents = CBT_Theme_Styles::build_style_css( $theme );

// Add style.css.
file_put_contents(
$blank_theme_path . DIRECTORY_SEPARATOR . 'style.css',
$css_contents
);

$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator( $source, \RecursiveDirectoryIterator::SKIP_DOTS ),
\RecursiveIteratorIterator::SELF_FIRST
);

foreach (
$iterator as $item
) {
if ( $item->isDir() ) {
wp_mkdir_p( $blank_theme_path . DIRECTORY_SEPARATOR . $iterator->getSubPathname() );
} else {
copy( $item, $blank_theme_path . DIRECTORY_SEPARATOR . $iterator->getSubPathname() );
}
}

// Overwrite default screenshot if one is provided.
if ( CBT_Theme_Utils::is_valid_screenshot( $screenshot ) ) {
file_put_contents(
$blank_theme_path . DIRECTORY_SEPARATOR . 'screenshot.png',
file_get_contents( $screenshot['tmp_name'] )
);
}

if ( ! defined( 'IS_GUTENBERG_PLUGIN' ) ) {
global $wp_version;
$theme_json_version = 'wp/' . substr( $wp_version, 0, 3 );
$schema = '"$schema": "https://schemas.wp.org/' . $theme_json_version . '/theme.json"';
$theme_json_path = $blank_theme_path . DIRECTORY_SEPARATOR . 'theme.json';
$theme_json_string = file_get_contents( $theme_json_path );
$theme_json_string = str_replace( '"$schema": "https://schemas.wp.org/trunk/theme.json"', $schema, $theme_json_string );
file_put_contents( $theme_json_path, $theme_json_string );
}
}
}

function blockbase_save_theme() {
if ( ! empty( $_GET['page'] ) && 'create-block-theme' === $_GET['page'] && ! empty( $_POST['theme'] ) ) {

// Check user capabilities.
if ( ! current_user_can( 'edit_theme_options' ) ) {
return add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_error_theme_name' ) );
}

// Check nonce
if ( ! wp_verify_nonce( $_POST['nonce'], 'create_block_theme' ) ) {
return add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_error_theme_name' ) );
}

if ( 'save' === $_POST['theme']['type'] ) {
// Avoid running if WordPress dosn't have permission to overwrite the theme folder
if ( ! wp_is_writable( get_stylesheet_directory() ) ) {
return add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_error_theme_file_permissions' ) );
}

if ( is_child_theme() ) {
$this->save_theme_locally( 'current' );
} else {
$this->save_theme_locally( 'all' );
}
CBT_Theme_Styles::clear_user_styles_customizations();
CBT_Theme_Templates::clear_user_templates_customizations();

add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_save_success' ) );
} elseif ( 'variation' === $_POST['theme']['type'] ) {
if ( '' === $_POST['theme']['variation'] ) {
return add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_error_variation_name' ) );
}

// Avoid running if WordPress dosn't have permission to write the theme folder
if ( ! wp_is_writable( get_stylesheet_directory() ) ) {
return add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_error_theme_file_permissions' ) );
}

if ( is_child_theme() ) {
$this->save_variation( 'current', $_POST['theme'] );
} else {
$this->save_variation( 'all', $_POST['theme'] );
}
CBT_Theme_Styles::clear_user_styles_customizations();

add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_variation_success' ) );
} elseif ( 'blank' === $_POST['theme']['type'] ) {
// Avoid running if WordPress dosn't have permission to write the themes folder
if ( ! wp_is_writable( get_theme_root() ) ) {
return add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_error_themes_file_permissions' ) );
}

if ( '' === $_POST['theme']['name'] ) {
return add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_error_theme_name' ) );
}
$this->create_blank_theme( $_POST['theme'], $_FILES['screenshot'] );

add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_blank_success' ) );
} elseif ( ! class_exists( 'ZipArchive' ) ) {
// Avoid running if ZipArchive is not enabled.
add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_error_unsupported_zip_archive' ) );
} elseif ( is_child_theme() ) {
if ( 'sibling' === $_POST['theme']['type'] ) {
if ( '' === $_POST['theme']['name'] ) {
return add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_error_theme_name' ) );
}
$this->create_sibling_theme( $_POST['theme'], $_FILES['screenshot'] );
} else {
$this->export_child_theme( $_POST['theme'] );
}
add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_export_success' ) );
} else {
if ( 'child' === $_POST['theme']['type'] ) {
if ( '' === $_POST['theme']['name'] ) {
return add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_error_theme_name' ) );
}
$this->create_child_theme( $_POST['theme'], $_FILES['screenshot'] );
} elseif ( 'clone' === $_POST['theme']['type'] ) {
if ( '' === $_POST['theme']['name'] ) {
return add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_error_theme_name' ) );
}
$this->clone_theme( $_POST['theme'], $_FILES['screenshot'] );
} else {
$this->export_theme( $_POST['theme'] );
}
add_action( 'admin_notices', array( 'CBT_Form_Messages', 'admin_notice_export_success' ) );
}
}
}
}

View file

@ -1,93 +0,0 @@
.appearance_page_create-block-theme h2 {
margin-bottom: 0;
}

.appearance_page_create-block-theme p.description {
margin-bottom: 1rem;
}

.appearance_page_create-block-theme .submit {
clear: both;
}

#col-left label,
.theme-form label,
.theme-form legend {
font-weight: 600;
}

.theme-tag-form-control label {
font-weight: 400;
}

.theme-form label {
display: block;
margin-bottom: 0.25rem;
}

/* Theme Tag Checkboxes */
.theme-tags {
display: grid;
margin-top: 0.5rem;
}

.theme-tags fieldset {
margin-bottom: 1rem;
}

.theme-tags legend {
margin-bottom: 1rem;
}

.theme-tags label {
display: inline-block;
margin-left: 0.25rem;
margin-bottom: 0.25rem;
}

#features-tags {
display: grid;
}

.theme-tag-form-control {
margin-bottom: 0.5rem;
}

@media screen and (min-width: 600px) {
.theme-tags {
grid-template-columns: 1fr 1fr;
}

/* Feature tags have items than other categores, so display the list in 2 columns on wider screens. */
#features-tags {
grid-template-columns: 1fr 1fr;
grid-column: 1 / 3;
margin-top: 1rem;
}
}

@media screen and (min-width: 783px) {
.theme-tags fieldset {
margin-bottom: 0.5rem;
}

.theme-tags legend {
margin-bottom: 0.5rem;
}

/* Checkboxes shrink to standard size. */
.theme-tag-form-control {
margin-bottom: 0;
}
}

@media screen and (min-width: 1440px) {
.theme-tags {
grid-template-columns: 1fr 1fr 1fr 1fr;
}

#features-tags {
grid-column: 3 / 5;
margin-top: 0;
}
}

View file

@ -1 +0,0 @@
<?php // Silence is golden

View file

@ -1,274 +0,0 @@
const { __ } = wp.i18n;

// Toggles the visibility of the forms based on the selected theme type
// eslint-disable-next-line no-unused-vars
function toggleForm( element ) {
if ( ! element?.value ) return;
const themeType = element.value;

toggleInputsOnBlankTheme();
hideAllForms();

switch ( themeType ) {
case 'export':
case 'save':
// Forms should stay hidden
resetThemeName();
break;

case 'child':
case 'clone':
case 'sibling':
// Show New Theme form
document
.getElementById( 'new_theme_metadata_form' )
.toggleAttribute( 'hidden', false );

resetThemeTags( element.value );
validateThemeTags( 'subject' );
break;

case 'blank':
// Show New Theme form
// and hide image credits input
document
.getElementById( 'new_theme_metadata_form' )
.toggleAttribute( 'hidden', false );

toggleInputsOnBlankTheme( true );
resetThemeTags( element.value );
validateThemeTags( 'subject' );
break;

case 'variation':
// Show Variation form
document
.getElementById( 'new_variation_metadata_form' )
.toggleAttribute( 'hidden', false );
break;

default:
break;
}
}

function toggleInputsOnBlankTheme( isHidden = false ) {
const inputsHiddenOnBlankTheme = document.getElementsByClassName(
'hide-on-blank-theme'
);

for ( let i = 0; i < inputsHiddenOnBlankTheme.length; i++ ) {
inputsHiddenOnBlankTheme[ i ].toggleAttribute( 'hidden', isHidden );
}
}

function hideAllForms() {
const allForms = document.querySelectorAll( '.theme-form' );
allForms.forEach( ( form ) => {
form.toggleAttribute( 'hidden', true );
} );
}

// Handle theme tag validation
function validateThemeTags( tagCategory ) {
if ( ! tagCategory ) return;
let checkboxes;

if ( 'subject' === tagCategory ) {
checkboxes = 'input[name="theme[tags-subject][]"]';
}

// Maximum number of checkboxes that can be selected
const max = 3;

// Run validation on form load
limitCheckboxSelection( checkboxes, max );

const allCheckboxes = document.querySelectorAll( checkboxes );

// Run validation on each checkbox change
if ( allCheckboxes.length > max ) {
for ( let i = 0; i < allCheckboxes.length; i++ ) {
allCheckboxes[ i ].addEventListener( 'change', function () {
limitCheckboxSelection( checkboxes, max );
} );
}
}
}

// Takes a checkbox selector and limits the number of checkboxes that can be selected
function limitCheckboxSelection( checkboxesSelector, max = 0 ) {
if ( ! checkboxesSelector ) return;

const checked = document.querySelectorAll(
`${ checkboxesSelector }:checked`
);
const unchecked = document.querySelectorAll(
`${ checkboxesSelector }:not(:checked)`
);

if ( checked.length >= max ) {
for ( let i = 0; i < unchecked.length; i++ ) {
unchecked[ i ].setAttribute( 'disabled', true );
}
} else {
for ( let i = 0; i < unchecked.length; i++ ) {
unchecked[ i ].removeAttribute( 'disabled' );
}
}
}

// Store active theme tags when page is loaded
let activeThemeTags = [];
function onWindowLoad() {
activeThemeTags = document.querySelectorAll(
'.theme-tags input[type="checkbox"]:checked'
);
}

window.addEventListener( 'load', onWindowLoad );
window.addEventListener( 'load', prepareThemeNameValidation );

function prepareThemeNameValidation() {
const themeNameInput = document.getElementById( 'theme-name' );
if ( themeNameInput ) {
themeNameInput.addEventListener( 'input', validateThemeNameInput );
}
}

function slugify( text ) {
// Removes spaces
return text.toLowerCase().replace( / /g, '' );
}

function slugifyUnderscores( text ) {
// Replaces spaces with underscores
return text.toLowerCase().replace( / /g, '_' );
}

function slugifyDashes( text ) {
// Replaces spaces with dashes
return text.toLowerCase().replace( / /g, '-' );
}

function slugifyNoDashes( text ) {
// Removes spaces, dashes, and underscores
return text.toLowerCase().replace( / /g, '' ).replace( /[-_]/g, '' );
}

const ERROR_NAME_NOT_AVAILABLE = __(
'Theme name is not available in the WordPress.org theme directory',
'create-block-theme'
);
const ERROR_NAME_CONTAINS_THEME = __(
'Theme name cannot contain the word "theme"',
'create-block-theme'
);
const ERROR_NAME_CONTAINS_WORDPRESS = __(
'Theme name cannot contain the word "WordPress"',
'create-block-theme'
);

function isThemeNameValid( themeName ) {
// Check the validity of the theme name following the WordPress.org theme directory rules
// https://meta.svn.wordpress.org/sites/trunk/wordpress.org/public_html/wp-content/plugins/theme-directory/class-wporg-themes-upload.php

/* eslint-disable @wordpress/no-unused-vars-before-return */
const lowerCaseName = themeName.toLowerCase();
const slug = slugify( themeName );
const slugDashes = slugifyUnderscores( themeName );
const slugUnderscores = slugifyDashes( themeName );
const slugNoDashes = slugifyNoDashes( themeName );

const validityStatus = {
isValid: true,
errorMessage: '',
};

// Check if the theme contains the word theme
if ( lowerCaseName.includes( 'theme' ) ) {
validityStatus.isValid = false;
validityStatus.errorMessage = ERROR_NAME_CONTAINS_THEME;
return validityStatus;
}

// Check if the theme name contains WordPress
if ( slugNoDashes.includes( 'wordpress' ) ) {
validityStatus.isValid = false;
validityStatus.errorMessage = ERROR_NAME_CONTAINS_WORDPRESS;
return validityStatus;
}

// Check if the theme name is available
const isNameAvailable = () => {
// default to empty array if the unavailable theme names are not loaded yet from the API
const notAvailableSlugs = window.wpOrgThemeDirectory.themeSlugs || [];

// Compare the theme name to the list of unavailable theme names using several different slug formats
return ! notAvailableSlugs.some(
( s ) =>
s === slug ||
s === slugDashes ||
s === slugUnderscores ||
slugifyNoDashes( s ) === slugNoDashes
);
};

if ( ! isNameAvailable() ) {
validityStatus.isValid = false;
validityStatus.errorMessage = ERROR_NAME_NOT_AVAILABLE;
return validityStatus;
}

return validityStatus;
}

function validateThemeNameInput() {
const themeName = this?.value;
if ( ! themeName ) return true;

// Check if theme name is available
const validityStatus = isThemeNameValid( themeName );

if ( ! validityStatus.isValid ) {
this.setCustomValidity( validityStatus.errorMessage );
this.reportValidity();
} else {
this.setCustomValidity( '' );
}
}

// Resets all theme tag states (checked, disabled) to default values
function resetThemeTags( themeType ) {
// Clear all checkboxes
const allCheckboxes = document.querySelectorAll(
'.theme-tags input[type="checkbox"]'
);
allCheckboxes.forEach( ( checkbox ) => {
checkbox.checked = false;
checkbox.removeAttribute( 'disabled' );
} );

// Recheck default tags
const defaultTags = document.querySelectorAll(
'.theme-tags input[type="checkbox"].default-tag'
);
defaultTags.forEach( ( checkbox ) => {
checkbox.checked = true;
} );

if ( 'blank' !== themeType ) {
// Recheck active theme tags
if ( ! activeThemeTags ) return;

activeThemeTags.forEach( ( checkbox ) => {
checkbox.checked = true;
} );
}
}

function resetThemeName() {
const themeNameInput = document.getElementById( 'theme-name' );
themeNameInput.value = '';
themeNameInput.setCustomValidity( '' );
}

View file

@ -1,77 +0,0 @@
<?php

class CBT_WP_Theme_Directory {

const THEME_NAMES_ENDPOINT = 'https://themes.svn.wordpress.org/';

/**
* Initialize the class and set its properties.
*/
public function __construct() {
add_action( 'rest_api_init', array( $this, 'register_theme_names_endpoint' ) );
add_action( 'admin_init', array( $this, 'assets_enqueue' ) );
}

public static function register_theme_names_endpoint() {
register_rest_route(
'create-block-theme/v1',
'/wp-org-theme-names',
array(
'methods' => 'GET',
'callback' => array( 'CBT_WP_Theme_Directory', 'get_theme_names' ),
'permission_callback' => function () {
return current_user_can( 'edit_theme_options' );
},
)
);

}

public static function get_theme_names() {
$html = wp_safe_remote_get( self::THEME_NAMES_ENDPOINT );

if ( is_wp_error( $html ) ) {
return $html;
}

// parse the html response extracting all the a inside li elements
$pattern = '/<li><a href=".*?">(.*?)<\/a><\/li>/';
preg_match_all( $pattern, $html['body'], $matches );

// Revemo the / from the end of the theme name
$cleaned_names = array_map(
function ( $name ) {
return str_replace( '/', '', $name );
},
$matches[1]
);

$names = array( 'names' => $cleaned_names );
return rest_ensure_response( $names );
}

function assets_enqueue() {
$asset_file = include( plugin_dir_path( dirname( __FILE__ ) ) . 'build/wp-org-theme-directory.asset.php' );

wp_register_script(
'wp-org-theme-directory',
plugins_url( 'build/wp-org-theme-directory.js', dirname( __FILE__ ) ),
$asset_file['dependencies'],
$asset_file['version']
);

wp_enqueue_script(
'wp-org-theme-directory',
);

// Initialize and empty array of theme names to be shared between different client side scripts
wp_localize_script(
'wp-org-theme-directory',
'wpOrgThemeDirectory',
array(
'themeSlugs' => null,
)
);
}

}

BIN
assets/faq_fonts.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
assets/faq_icon.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

BIN
assets/faq_save.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
assets/header_logo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,52 @@
<?php

/**
* The wp-admin landing page for the Create Block Theme plugin.
* @since 2.2.0
* @package Create_Block_Theme
* @subpackage Create_Block_Theme/includes
* @author WordPress.org
*/
class CBT_Admin_Landing {

public function __construct() {
add_action( 'admin_menu', array( $this, 'create_admin_menu' ) );
}

function create_admin_menu() {
if ( ! wp_is_block_theme() ) {
return;
}

$landing_page_slug = 'create-block-theme-landing';
$landing_page_title = _x( 'Create Block Theme', 'UI String', 'create-block-theme' );
$landing_page_menu_title = $landing_page_title;
add_theme_page( $landing_page_title, $landing_page_menu_title, 'edit_theme_options', $landing_page_slug, array( 'CBT_Admin_Landing', 'admin_menu_page' ) );

}

public static function admin_menu_page() {

$asset_file = include plugin_dir_path( __DIR__ ) . 'build/admin-landing-page.asset.php';

// Enqueue CSS of the app
wp_enqueue_style( 'create-block-theme-app', plugins_url( 'build/admin-landing-page.css', __DIR__ ), array( 'wp-components' ), $asset_file['version'] );

// Load our app.js.
wp_enqueue_script( 'create-block-theme-app', plugins_url( 'build/admin-landing-page.js', __DIR__ ), $asset_file['dependencies'], $asset_file['version'] );

wp_localize_script(
'create-block-theme-app',
'cbt_landingpage_variables',
array(
'assets_url' => plugins_url( 'create-block-theme/assets/' ),
'editor_url' => admin_url( 'site-editor.php?canvas=edit' ),
)
);

// Enable localization in the app.
wp_set_script_translations( 'create-block-theme-app', 'create-block-theme' );

echo '<div id="create-block-theme-app"></div>';
}
}

View file

@ -1,5 +1,21 @@
<?php

require_once __DIR__ . '/create-theme/resolver_additions.php';
require_once __DIR__ . '/create-theme/theme-locale.php';
require_once __DIR__ . '/create-theme/theme-tags.php';
require_once __DIR__ . '/create-theme/theme-zip.php';
require_once __DIR__ . '/create-theme/theme-media.php';
require_once __DIR__ . '/create-theme/theme-patterns.php';
require_once __DIR__ . '/create-theme/theme-templates.php';
require_once __DIR__ . '/create-theme/theme-styles.php';
require_once __DIR__ . '/create-theme/theme-json.php';
require_once __DIR__ . '/create-theme/theme-utils.php';
require_once __DIR__ . '/create-theme/theme-readme.php';
require_once __DIR__ . '/create-theme/theme-form.php';
require_once __DIR__ . '/create-theme/form-messages.php';
require_once __DIR__ . '/create-theme/theme-fonts.php';
require_once __DIR__ . '/create-theme/theme-create.php';

/**
* The api functionality of the plugin leveraged by the site editor UI.
*

View file

@ -0,0 +1,38 @@
<?php

/**
* The Editor integration for the Create Block Theme plugin.
* @since 2.2.0
* @package Create_Block_Theme
* @subpackage Create_Block_Theme/includes
* @author WordPress.org
*/
class CBT_Editor_Tools {

public function __construct() {
add_action( 'enqueue_block_editor_assets', array( $this, 'create_block_theme_sidebar_enqueue' ) );
}

function create_block_theme_sidebar_enqueue() {
global $pagenow;

if ( 'site-editor.php' !== $pagenow ) {
return;
}

$asset_file = include plugin_dir_path( dirname( __FILE__ ) ) . 'build/plugin-sidebar.asset.php';

wp_register_script(
'create-block-theme-slot-fill',
plugins_url( 'build/plugin-sidebar.js', dirname( __FILE__ ) ),
$asset_file['dependencies'],
$asset_file['version']
);
wp_enqueue_script(
'create-block-theme-slot-fill',
);

// Enable localization in the plugin sidebar.
wp_set_script_translations( 'create-block-theme-slot-fill', 'create-block-theme' );
}
}

View file

@ -40,10 +40,9 @@ class CBT_Plugin {
/**
* The class responsible for defining all actions that occur in the admin area.
*/
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-create-theme.php';
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/wp-org-theme-directory.php';

require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-create-block-theme-api.php';
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-create-block-theme-editor-tools.php';
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-create-block-theme-admin-landing.php';

$this->loader = new CBT_Plugin_Loader();

@ -57,10 +56,9 @@ class CBT_Plugin {
* @access private
*/
private function define_admin_hooks() {

$plugin_admin = new CBT_WP_Admin();
$wp_theme_directory = new CBT_WP_Theme_Directory();
$plugin_api = new CBT_Theme_API();
$plugin_api = new CBT_Theme_API();
$editor_tools = new CBT_Editor_Tools();
$admin_landing = new CBT_Admin_Landing();
}

/**

View file

@ -43,7 +43,7 @@
"simple-git": "^3.18.0"
},
"scripts": {
"build": "wp-scripts build src/plugin-sidebar.js src/wp-org-theme-directory.js",
"build": "wp-scripts build src/admin-landing-page.js src/plugin-sidebar.js",
"format": "wp-scripts format",
"lint:css": "wp-scripts lint-style",
"lint:css:fix": "npm run lint:css -- --fix",
@ -57,7 +57,7 @@
"test:php:watch": "wp-env run cli --env-cwd='wp-content/plugins/create-block-theme' composer run-script test:watch",
"test:php:setup": "wp-env start",
"packages-update": "wp-scripts packages-update",
"start": "wp-scripts start src/plugin-sidebar.js src/wp-org-theme-directory.js",
"start": "wp-scripts start src/admin-landing-page.js src/plugin-sidebar.js",
"composer": "wp-env run cli --env-cwd=wp-content/plugins/create-block-theme composer",
"update-version": "node update-version-and-changelog.js",
"prepare": "husky install",

24
src/admin-landing-page.js Normal file
View file

@ -0,0 +1,24 @@
/**
* WordPress dependencies
*/
import { createRoot } from '@wordpress/element';

/**
* Internal dependencies
*/
import './admin-landing-page.scss';
import LandingPage from './landing-page/landing-page';

function App() {
return <LandingPage />;
}

window.addEventListener(
'load',
function () {
const domNode = document.getElementById( 'create-block-theme-app' );
const root = createRoot( domNode );
root.render( <App /> );
},
false
);

View file

@ -0,0 +1,80 @@
@import "../node_modules/@wordpress/base-styles/mixins";
@include wordpress-admin-schemes();


.create-block-theme {
&__landing-page {
background-color: #fff;
margin-left: -20px;
a,
button {
color: #3858e9;
}
&__header {
width: 100%;
background-color: #2d59f2;
margin: 0;
}
&__body {
font-weight: 200;
padding: 40px 0;
p {
margin-top: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 0.3em;
margin-bottom: 0.3em;
}
h2 {
font-size: 2em;
}
h3 {
font-size: 1em;
}
@media screen and (max-width: 775px) {
flex-direction: column;
h2 {
font-size: 1.5em;
}
}
&__left-column {
flex: 1;
margin: 0 60px;
button {
font-size: 1.75em;
@media screen and (max-width: 775px) {
font-size: 1.25em;
}
}
}
&__right-column {
max-width: 330px;
margin: 0 60px;
@media screen and (max-width: 775px) {
max-width: 100%;
}
p {
margin-bottom: 0;
}
}

&__faq {
img {
max-width: 100%;
}
p {
padding: 10px;
font-style: italic;
}
details {
padding-bottom: 20px;
}
}
}
}
}

View file

@ -4,7 +4,6 @@
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import apiFetch from '@wordpress/api-fetch';
import { store as noticesStore } from '@wordpress/notices';
import {
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
@ -24,6 +23,11 @@ import { addCard, copy } from '@wordpress/icons';
* Internal dependencies
*/
import ScreenHeader from './screen-header';
import {
createBlankTheme,
createClonedTheme,
createChildTheme,
} from '../resolvers';

export const CreateThemePanel = ( { createType } ) => {
const { createErrorNotice } = useDispatch( noticesStore );
@ -55,17 +59,10 @@ export const CreateThemePanel = ( { createType } ) => {
};

const handleCreateBlankClick = () => {
apiFetch( {
path: '/create-block-theme/v1/create-blank',
method: 'POST',
data: theme,
headers: {
'Content-Type': 'application/json',
},
} )
createBlankTheme( theme )
.then( () => {
// eslint-disable-next-line no-alert
alert(
window.alert(
__(
'Theme created successfully. The editor will now reload.',
'create-block-theme'
@ -85,17 +82,10 @@ export const CreateThemePanel = ( { createType } ) => {
};

const handleCloneClick = () => {
apiFetch( {
path: '/create-block-theme/v1/clone',
method: 'POST',
data: theme,
headers: {
'Content-Type': 'application/json',
},
} )
createClonedTheme( theme )
.then( () => {
// eslint-disable-next-line no-alert
alert(
window.alert(
__(
'Theme cloned successfully. The editor will now reload.',
'create-block-theme'
@ -115,17 +105,10 @@ export const CreateThemePanel = ( { createType } ) => {
};

const handleCreateChildClick = () => {
apiFetch( {
path: '/create-block-theme/v1/create-child',
method: 'POST',
data: theme,
headers: {
'Content-Type': 'application/json',
},
} )
createChildTheme( theme )
.then( () => {
// eslint-disable-next-line no-alert
alert(
window.alert(
__(
'Child theme created successfully. The editor will now reload.',
'create-block-theme'

View file

@ -33,7 +33,7 @@ export const CreateVariationPanel = () => {
postCreateThemeVariation( theme.name )
.then( () => {
// eslint-disable-next-line no-alert
alert(
window.alert(
__(
'Theme variation created successfully. The editor will now reload.',
'create-block-theme'

View file

@ -80,7 +80,7 @@ export const ThemeMetadataEditorModal = ( { onRequestClose } ) => {
postUpdateThemeMetadata( theme )
.then( () => {
// eslint-disable-next-line no-alert
alert(
window.alert(
__(
'Theme updated successfully. The editor will now reload.',
'create-block-theme'

View file

@ -62,7 +62,7 @@ export const SaveThemePanel = () => {
} )
.then( () => {
// eslint-disable-next-line no-alert
alert(
window.alert(
__(
'Theme saved successfully. The editor will now reload.',
'create-block-theme'

View file

@ -0,0 +1,165 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import {
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalHStack as HStack,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalVStack as VStack,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalText as Text,
Modal,
Button,
TextControl,
TextareaControl,
} from '@wordpress/components';

/**
* Internal dependencies
*/
import {
createBlankTheme,
createClonedTheme,
createChildTheme,
} from '../resolvers';

export const CreateThemeModal = ( { onRequestClose, creationType } ) => {
const [ errorMessage, setErrorMessage ] = useState( null );

const [ theme, setTheme ] = useState( {
name: '',
description: '',
author: '',
} );

const renderCreateButtonText = ( type ) => {
switch ( type ) {
case 'blank':
return __(
'Create and Activate Blank Theme',
'create-block-theme'
);
case 'clone':
return __( 'Clone Block Theme', 'create-block-theme' );
case 'child':
return __( 'Create Child Theme', 'create-block-theme' );
}
};

const createBlockTheme = async () => {
let constructionFunction = null;
switch ( creationType ) {
case 'blank':
constructionFunction = createBlankTheme;
break;
case 'clone':
constructionFunction = createClonedTheme;
break;
case 'child':
constructionFunction = createChildTheme;
break;
}

if ( ! constructionFunction ) {
return;
}
constructionFunction( theme )
.then( () => {
// eslint-disable-next-line no-alert
window.alert(
__(
'Theme created successfully. The editor will now load.',
'create-block-theme'
)
);
window.location = '/wp-admin/site-editor.php?canvas=edit';
} )
.catch( ( error ) => {
setErrorMessage(
error.message ||
__(
'An error occurred while attempting to create the theme.',
'create-block-theme'
)
);
} );
};

if ( errorMessage ) {
return (
<Modal
title={ __( 'Create Block Theme', 'create-block-theme' ) }
onRequestClose={ onRequestClose }
>
<p>{ errorMessage }</p>
</Modal>
);
}

return (
<Modal
title={ __( 'Create Block Theme', 'create-block-theme' ) }
onRequestClose={ onRequestClose }
>
<VStack spacing="5">
<Text>
{ __(
"Let's get started creating a new Block Theme.",
'create-block-theme'
) }
</Text>
<TextControl
label={ __(
'Theme name (required)',
'create-block-theme'
) }
value={ theme.name }
required
onChange={ ( value ) =>
setTheme( { ...theme, name: value } )
}
/>

<Text variant="muted">
{ __(
'(Tip: You can edit all of this and more in the Editor later.)',
'create-block-theme'
) }
</Text>
<TextareaControl
label={ __( 'Theme description', 'create-block-theme' ) }
value={ theme.description }
onChange={ ( value ) =>
setTheme( { ...theme, description: value } )
}
placeholder={ __(
'A short description of the theme',
'create-block-theme'
) }
/>
<TextControl
label={ __( 'Author', 'create-block-theme' ) }
value={ theme.author }
onChange={ ( value ) =>
setTheme( { ...theme, author: value } )
}
placeholder={ __(
'the WordPress team',
'create-block-theme'
) }
/>
<HStack>
<Button
variant="primary"
disabled={ ! theme.name }
onClick={ () => createBlockTheme() }
>
{ renderCreateButtonText( creationType ) }
</Button>
</HStack>
</VStack>
</Modal>
);
};

View file

@ -0,0 +1,290 @@
/**
* WordPress dependencies
*/
import { sprintf, __ } from '@wordpress/i18n';
import { useState, createInterpolateElement } from '@wordpress/element';
import { store as coreStore } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import {
Button,
ExternalLink,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalVStack as VStack,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalHStack as HStack,
} from '@wordpress/components';

/**
* Internal dependencies
*/
import { downloadExportedTheme } from '../resolvers';
import { downloadFile } from '../utils';
import { CreateThemeModal } from './create-modal';

export default function LandingPage() {
const [ createModalType, setCreateModalType ] = useState( false );

const themeName = useSelect( ( select ) =>
select( coreStore ).getCurrentTheme()
)?.name?.raw;

const handleExportClick = async () => {
const response = await downloadExportedTheme();
downloadFile( response );
};

return (
<div className="create-block-theme__landing-page">
{ createModalType && (
<CreateThemeModal
creationType={ createModalType }
onRequestClose={ () => setCreateModalType( false ) }
/>
) }

<h1 className="create-block-theme__landing-page__header">
<img
src={
window.cbt_landingpage_variables.assets_url +
'header_logo.webp'
}
alt={ __( 'Create Block Theme', 'create-block-theme' ) }
/>
</h1>

<HStack
alignment="topLeft"
className="create-block-theme__landing-page__body"
>
<VStack
alignment="left"
className="create-block-theme__landing-page__body__left-column"
>
<h2>
{ __(
'What would you like to do?',
'create-block-theme'
) }
</h2>
<p>
{ createInterpolateElement(
__(
'You can do everything from within the <a>Editor</a> but here are a few things you can do to get started.',
'create-block-theme'
),
{
a: (
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a
href={
window.cbt_landingpage_variables
.editor_url
}
/>
),
}
) }
</p>
<Button
variant="link"
onClick={ () => handleExportClick() }
>
{ sprintf(
// translators: %s: theme name.
__(
'Export "%s" as a Zip File',
'create-block-theme'
),
themeName
) }
</Button>
<p>
{ __(
'Export a zip file ready to be imported into another WordPress environment.',
'create-block-theme'
) }
</p>
<Button
variant="link"
onClick={ () => setCreateModalType( 'blank' ) }
>
{ __(
'Create a new Blank Theme',
'create-block-theme'
) }
</Button>
<p>
{ __(
'Start from scratch! Create a blank theme to get started with your own design ideas.',
'create-block-theme'
) }
</p>
<Button
variant="link"
onClick={ () => setCreateModalType( 'clone' ) }
>
{ sprintf(
// translators: %s: theme name.
__(
'Create a Clone of "%s"',
'create-block-theme'
),
themeName
) }
</Button>
<p>
{ __(
'Use the currently activated theme as a starting point.',
'create-block-theme'
) }
</p>
<Button
variant="link"
onClick={ () => setCreateModalType( 'child' ) }
>
{ sprintf(
// translators: %s: theme name.
__(
'Create a Child of "%s"',
'create-block-theme'
),
themeName
) }
</Button>
<p>
{ __(
'Make a theme that uses the currently activated theme as a parent.',
'create-block-theme'
) }
</p>
</VStack>
<VStack className="create-block-theme__landing-page__body__right-column">
<h3>{ __( 'About the Plugin', 'create-block-theme' ) }</h3>
<p>
{ __(
"Create Block Theme is a tool to help you make Block Themes using the WordPress Editor. It does this by adding tools to the Editor to help you create and manage your theme. Themes created with Create Block Theme don't require Create Block Theme to be installed on the site where the theme is used.",
'create-block-theme'
) }
</p>
<h3>
{ __( 'Do you need some help?', 'create-block-theme' ) }
</h3>
<p>
{ createInterpolateElement(
__(
'Have a question? Ask for some help in the <ExternalLink>forums</ExternalLink>.',
'create-block-theme'
),
{
ExternalLink: (
<ExternalLink href="https://wordpress.org/support/plugin/create-block-theme/" />
),
}
) }
</p>
<p>
{ createInterpolateElement(
__(
'Found a bug? Report it on <ExternalLink>GitHub</ExternalLink>.',
'create-block-theme'
),
{
ExternalLink: (
<ExternalLink href="https://github.com/WordPress/create-block-theme/issues/new" />
),
}
) }
</p>
<p>
{ createInterpolateElement(
__(
'Want to contribute? Check out the <ExternalLink>project on GitHub</ExternalLink>.',
'create-block-theme'
),
{
ExternalLink: (
<ExternalLink href="https://github.com/WordPress/create-block-theme" />
),
}
) }
</p>
<div className="create-block-theme__landing-page__body__faq">
<h3>{ __( 'FAQ', 'create-block-theme' ) }</h3>
<details>
<summary>
{ __(
'How do I access the features of Create Block Theme from within the editor?',
'create-block-theme'
) }
</summary>
<p>
{ __(
'There is a new panel accessible from the WordPress Editor which you can open by clicking on a new icon to the right of the “Save” button, at the top of the Editor.',
'create-block-theme'
) }
</p>
<img
src={
window.cbt_landingpage_variables
.assets_url + 'faq_icon.webp'
}
alt={ __(
'A screenshot of the Create Block Theme icon in the editor',
'create-block-theme'
) }
/>
</details>
<details>
<summary>
{ __(
'How do I save the customizations I made with the Editor to the Theme?',
'create-block-theme'
) }
</summary>
<p>
{ __(
'In the Create Block Theme Panel click "Save Changes to Theme". You will be presented with a number of options of which things you want to be saved to your theme. Make your choices and then click "Save Changes".',
'create-block-theme'
) }
</p>
<img
src={
window.cbt_landingpage_variables
.assets_url + 'faq_save.webp'
}
alt={ __(
'A screenshot of the Create Block Theme save changes panel',
'create-block-theme'
) }
/>
</details>
<details>
<summary>
{ __(
'How do I install and remove fonts?',
'create-block-theme'
) }
</summary>
<p>
{ __(
'First Install and activate a font from any source using the WordPress Font Library. Then, using the Create Block Theme Panel select “Save Changes To Theme” and select “Save Fonts” before saving the theme. All of the active fonts will be activated in the theme and deactivated in the system (and may be safely deleted from the system). Any fonts that are installed in the theme that have been deactivated with the WordPress Font Library will be removed from the theme.',
'create-block-theme'
) }
</p>
<img
src={
window.cbt_landingpage_variables
.assets_url + 'faq_fonts.webp'
}
alt={ __(
'A screenshot of the WordPress Font Library modal window',
'create-block-theme'
) }
/>
</details>
</div>
</VStack>
</HStack>
</div>
);
}

View file

@ -4,17 +4,13 @@
import apiFetch from '@wordpress/api-fetch';

export async function fetchThemeJson() {
const fetchOptions = {
return apiFetch( {
path: '/create-block-theme/v1/get-theme-data',
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
};

try {
const response = await apiFetch( fetchOptions );

} ).then( ( response ) => {
if ( ! response?.data || 'SUCCESS' !== response?.status ) {
throw new Error(
`Failed to fetch theme data: ${
@ -22,24 +18,78 @@ export async function fetchThemeJson() {
}`
);
}

return JSON.stringify( response?.data, null, 2 );
} catch ( e ) {
// @todo: handle error
}
} );
}

export async function createBlankTheme( theme ) {
return apiFetch( {
path: '/create-block-theme/v1/create-blank',
method: 'POST',
data: theme,
headers: {
'Content-Type': 'application/json',
},
} ).then( ( response ) => {
if ( 'SUCCESS' !== response?.status ) {
throw new Error(
`Failed to create blank theme: ${
response?.message || response?.status
}`
);
}
return response;
} );
}

export async function createClonedTheme( theme ) {
return apiFetch( {
path: '/create-block-theme/v1/clone',
method: 'POST',
data: theme,
headers: {
'Content-Type': 'application/json',
},
} ).then( ( response ) => {
if ( 'SUCCESS' !== response?.status ) {
throw new Error(
`Failed to clone theme: ${
response?.message || response?.status
}`
);
}
return response;
} );
}

export async function createChildTheme( theme ) {
return apiFetch( {
path: '/create-block-theme/v1/create-child',
method: 'POST',
data: theme,
headers: {
'Content-Type': 'application/json',
},
} ).then( ( response ) => {
if ( 'SUCCESS' !== response?.status ) {
throw new Error(
`Failed to create child theme: ${
response?.message || response?.status
}`
);
}
return response;
} );
}

export async function fetchReadmeData() {
const fetchOptions = {
return apiFetch( {
path: '/create-block-theme/v1/get-readme-data',
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
};

try {
const response = await apiFetch( fetchOptions );
} ).then( ( response ) => {
if ( ! response?.data || 'SUCCESS' !== response?.status ) {
throw new Error(
`Failed to fetch readme data: ${
@ -48,9 +98,7 @@ export async function fetchReadmeData() {
);
}
return response?.data;
} catch ( e ) {
// @todo: handle error
}
} );
}

export async function postCreateThemeVariation( name ) {

View file

@ -1,20 +0,0 @@
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';

async function loadUnavailableThemeNames() {
const requestOptions = {
path: '/create-block-theme/v1/wp-org-theme-names',
};

try {
const request = await apiFetch( requestOptions );
window.wpOrgThemeDirectory.themeSlugs = request.names;
} catch ( error ) {
// eslint-disable-next-line no-console
console.error( error );
}
}

window.addEventListener( 'load', loadUnavailableThemeNames );