mirror of
https://github.com/WordPress/create-block-theme.git
synced 2025-08-17 14:51:20 +08:00
Merge branch 'trunk' into add/handle-google-font-credits
This commit is contained in:
commit
2b4348f11e
33 changed files with 5545 additions and 3082 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -25,6 +25,7 @@ vendor/
|
|||
|
||||
# ignore system files
|
||||
.DS_Store
|
||||
.vscode
|
||||
|
||||
# plugin build folder
|
||||
build/
|
||||
|
|
|
@ -29,6 +29,28 @@ class Create_Block_Theme_Admin {
|
|||
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' ) );
|
||||
add_action( 'rest_api_init', array( $this, 'register_theme_export' ) );
|
||||
}
|
||||
|
||||
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',
|
||||
);
|
||||
}
|
||||
|
||||
function create_admin_menu() {
|
||||
|
@ -55,14 +77,14 @@ class Create_Block_Theme_Admin {
|
|||
* Export activated child theme
|
||||
*/
|
||||
function export_child_theme( $theme ) {
|
||||
$theme['slug'] = wp_get_theme()->get( 'TextDomain' );
|
||||
$theme['slug'] = Theme_Utils::get_theme_slug( $theme['name'] );
|
||||
|
||||
// Create ZIP file in the temporary directory.
|
||||
$filename = tempnam( get_temp_dir(), $theme['slug'] );
|
||||
$zip = Theme_Zip::create_zip( $filename );
|
||||
|
||||
$zip = Theme_Zip::copy_theme_to_zip( $zip, null, null );
|
||||
$zip = Theme_Zip::add_templates_to_zip( $zip, 'current', null );
|
||||
$zip = Theme_Zip::add_templates_to_zip( $zip, 'current', $theme['slug'] );
|
||||
$zip = Theme_Zip::add_theme_json_to_zip( $zip, 'current' );
|
||||
|
||||
$zip->close();
|
||||
|
@ -79,6 +101,8 @@ class Create_Block_Theme_Admin {
|
|||
* Create a sibling theme of the activated theme
|
||||
*/
|
||||
function create_sibling_theme( $theme, $screenshot ) {
|
||||
$theme_slug = Theme_Utils::get_theme_slug( $theme['name'] );
|
||||
|
||||
// Sanitize inputs.
|
||||
$theme['name'] = sanitize_text_field( $theme['name'] );
|
||||
$theme['description'] = sanitize_text_field( $theme['description'] );
|
||||
|
@ -86,8 +110,9 @@ class Create_Block_Theme_Admin {
|
|||
$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['slug'] = Theme_Utils::get_theme_slug( $theme['name'] );
|
||||
$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'] );
|
||||
|
@ -136,6 +161,8 @@ class Create_Block_Theme_Admin {
|
|||
* Clone the activated theme to create a new theme
|
||||
*/
|
||||
function clone_theme( $theme, $screenshot ) {
|
||||
$theme_slug = Theme_Utils::get_theme_slug( $theme['name'] );
|
||||
|
||||
// Sanitize inputs.
|
||||
$theme['name'] = sanitize_text_field( $theme['name'] );
|
||||
$theme['description'] = sanitize_text_field( $theme['description'] );
|
||||
|
@ -143,9 +170,15 @@ class Create_Block_Theme_Admin {
|
|||
$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['slug'] = Theme_Utils::get_theme_slug( $theme['name'] );
|
||||
$theme['template'] = wp_get_theme()->get( 'Template' );
|
||||
$theme['slug'] = $theme_slug;
|
||||
$theme['template'] = '';
|
||||
$theme['original_theme'] = wp_get_theme()->get( 'Name' );
|
||||
$theme['text_domain'] = $theme_slug;
|
||||
|
||||
// Use previous theme's tags if custom tags are empty.
|
||||
if ( empty( $theme['tags_custom'] ) ) {
|
||||
$theme['tags_custom'] = implode( ', ', wp_get_theme()->get( 'Tags' ) );
|
||||
}
|
||||
|
||||
// Create ZIP file in the temporary directory.
|
||||
$filename = tempnam( get_temp_dir(), $theme['slug'] );
|
||||
|
@ -187,13 +220,38 @@ class Create_Block_Theme_Admin {
|
|||
header( 'Content-Disposition: attachment; filename=' . $theme['slug'] . '.zip' );
|
||||
header( 'Content-Length: ' . filesize( $filename ) );
|
||||
flush();
|
||||
echo readfile( $filename );
|
||||
die();
|
||||
readfile( $filename );
|
||||
unlink( $filename );
|
||||
exit;
|
||||
}
|
||||
|
||||
function rest_export_theme( $request ) {
|
||||
$theme = $request->get_params();
|
||||
$this->clone_theme( $theme, null );
|
||||
}
|
||||
|
||||
public function register_theme_export() {
|
||||
register_rest_route(
|
||||
'create-block-theme/v1',
|
||||
'/export',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'rest_export_theme' ),
|
||||
'permission_callback' => function () {
|
||||
return current_user_can( 'edit_theme_options' );
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a child theme of the activated theme
|
||||
*/
|
||||
function create_child_theme( $theme, $screenshot ) {
|
||||
|
||||
$parent_theme_slug = Theme_Utils::get_theme_slug( wp_get_theme()->get( 'Name' ) );
|
||||
$child_theme_slug = Theme_Utils::get_theme_slug( $theme['name'] );
|
||||
|
||||
// Sanitize inputs.
|
||||
$theme['name'] = sanitize_text_field( $theme['name'] );
|
||||
$theme['description'] = sanitize_text_field( $theme['description'] );
|
||||
|
@ -201,14 +259,16 @@ class Create_Block_Theme_Admin {
|
|||
$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['slug'] = Theme_Utils::get_theme_slug( $theme['name'] );
|
||||
$theme['template'] = wp_get_theme()->get( 'TextDomain' );
|
||||
|
||||
$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 = Theme_Zip::create_zip( $filename );
|
||||
|
||||
$zip = Theme_Zip::add_templates_to_zip( $zip, 'user', null );
|
||||
$zip = Theme_Zip::add_templates_to_zip( $zip, 'user', $theme['slug'] );
|
||||
$zip = Theme_Zip::add_theme_json_to_zip( $zip, 'user' );
|
||||
|
||||
// Add readme.txt.
|
||||
|
@ -266,6 +326,8 @@ class Create_Block_Theme_Admin {
|
|||
}
|
||||
|
||||
function create_blank_theme( $theme, $screenshot ) {
|
||||
$theme_slug = Theme_Utils::get_theme_slug( $theme['name'] );
|
||||
|
||||
// Sanitize inputs.
|
||||
$theme['name'] = sanitize_text_field( $theme['name'] );
|
||||
$theme['description'] = sanitize_text_field( $theme['description'] );
|
||||
|
@ -274,7 +336,8 @@ class Create_Block_Theme_Admin {
|
|||
$theme['author_uri'] = sanitize_text_field( $theme['author_uri'] );
|
||||
$theme['tags_custom'] = sanitize_text_field( $theme['tags_custom'] );
|
||||
$theme['template'] = '';
|
||||
$theme['slug'] = Theme_Utils::get_theme_slug( $theme['name'] );
|
||||
$theme['slug'] = $theme_slug;
|
||||
$theme['text_domain'] = $theme_slug;
|
||||
|
||||
// Create theme directory.
|
||||
$source = plugin_dir_path( __DIR__ ) . 'assets/boilerplate';
|
32
admin/class-react-app.php
Normal file
32
admin/class-react-app.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
class React_App {
|
||||
public static function bootstrap() {
|
||||
// Load the required WordPress packages.
|
||||
// Automatically load imported dependencies and assets version.
|
||||
$asset_file = include plugin_dir_path( __DIR__ ) . 'build/index.asset.php';
|
||||
|
||||
// Enqueue CSS dependencies of the scripts included in the build.
|
||||
foreach ( $asset_file['dependencies'] as $style ) {
|
||||
wp_enqueue_style( $style );
|
||||
}
|
||||
|
||||
// Enqueue CSS of the app
|
||||
wp_enqueue_style( 'create-block-theme-app', plugins_url( 'build/index.css', __DIR__ ), array(), $asset_file['version'] );
|
||||
|
||||
// Load our app.js.
|
||||
array_push( $asset_file['dependencies'], 'wp-i18n' );
|
||||
wp_enqueue_script( 'create-block-theme-app', plugins_url( 'build/index.js', __DIR__ ), $asset_file['dependencies'], $asset_file['version'] );
|
||||
|
||||
// Set google fonts json file url.
|
||||
wp_localize_script(
|
||||
'create-block-theme-app',
|
||||
'createBlockTheme',
|
||||
array(
|
||||
'googleFontsDataUrl' => plugins_url( 'assets/google-fonts/fallback-fonts-list.json', __DIR__ ),
|
||||
'adminUrl' => admin_url(),
|
||||
'themeUrl' => get_stylesheet_directory_uri(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ class Form_Messages {
|
|||
printf(
|
||||
// translators: %1$s: Theme name
|
||||
esc_html__( 'Blank theme created, head over to Appearance > Themes to activate %1$s', 'create-block-theme' ),
|
||||
esc_html( $theme_name )
|
||||
esc_html( stripslashes( $theme_name ) )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
|
|
|
@ -188,4 +188,5 @@ class Theme_Blocks {
|
|||
$markup = html_entity_decode( $markup, ENT_QUOTES | ENT_XML1, 'UTF-8' );
|
||||
return $markup;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -53,8 +53,7 @@ class Theme_Form {
|
|||
</label>
|
||||
<br />
|
||||
<?php _e( '[Create a new theme cloning the activated child theme. The parent theme will be the same as the parent of the currently activated theme. The resulting theme will have all of the assets of the activated theme, none of the assets provided by the parent theme, as well as user changes.]', 'create-block-theme' ); ?>
|
||||
<p><b><?php _e( 'NOTE: Sibling themes created from this theme will have the original namespacing. This should be changed manually once the theme has been created.', 'create-block-theme' ); ?></b></p>
|
||||
<br />
|
||||
<br /><br />
|
||||
<?php else : ?>
|
||||
<label>
|
||||
<input value="child" type="radio" name="theme[type]" class="regular-text code" onchange="toggleForm( this );"/>
|
||||
|
@ -132,7 +131,7 @@ class Theme_Form {
|
|||
<p><em><?php _e( 'Items indicated with (*) are required.', 'create-block-theme' ); ?></em></p>
|
||||
<label>
|
||||
<?php _e( 'Theme Name (*):', 'create-block-theme' ); ?><br />
|
||||
<input placeholder="<?php _e( 'Theme Name', 'create-block-theme' ); ?>" type="text" name="theme[name]" class="large-text" />
|
||||
<input placeholder="<?php _e( 'Theme Name', 'create-block-theme' ); ?>" type="text" name="theme[name]" class="large-text" id="theme-name" autocomplete="off" />
|
||||
</label>
|
||||
<br /><br />
|
||||
<label>
|
||||
|
|
|
@ -137,15 +137,15 @@ class Theme_Media {
|
|||
|
||||
public static function add_media_to_local( $media ) {
|
||||
foreach ( $media as $url ) {
|
||||
$download_file = file_get_contents( $url );
|
||||
$media_path = get_stylesheet_directory() . DIRECTORY_SEPARATOR . self::get_media_folder_path_from_url( $url );
|
||||
if ( ! is_dir( $media_path ) ) {
|
||||
wp_mkdir_p( $media_path );
|
||||
$download_file = download_url( $url );
|
||||
// TODO: implement a warning if the file is missing
|
||||
if ( ! is_wp_error( $download_file ) ) {
|
||||
$media_path = get_stylesheet_directory() . DIRECTORY_SEPARATOR . self::get_media_folder_path_from_url( $url );
|
||||
if ( ! is_dir( $media_path ) ) {
|
||||
wp_mkdir_p( $media_path );
|
||||
}
|
||||
rename( $download_file, $media_path . basename( $url ) );
|
||||
}
|
||||
file_put_contents(
|
||||
$media_path . basename( $url ),
|
||||
$download_file
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
class Theme_Patterns {
|
||||
public static function pattern_from_template( $template ) {
|
||||
$theme_slug = wp_get_theme()->get( 'TextDomain' );
|
||||
public static function pattern_from_template( $template, $new_slug = null ) {
|
||||
$theme_slug = $new_slug ? $new_slug : wp_get_theme()->get( 'TextDomain' );
|
||||
$pattern_slug = $theme_slug . '/' . $template->slug;
|
||||
$pattern_content = (
|
||||
'<?php
|
||||
|
@ -61,4 +61,10 @@ class Theme_Patterns {
|
|||
return "<?php echo esc_attr_e( '" . $text . "', '" . wp_get_theme()->get( 'Name' ) . "' ); ?>";
|
||||
}
|
||||
}
|
||||
|
||||
public static function create_pattern_link( $attributes ) {
|
||||
$block_attributes = array_filter( $attributes );
|
||||
$attributes_json = json_encode( $block_attributes, JSON_UNESCAPED_SLASHES );
|
||||
return '<!-- wp:pattern ' . $attributes_json . ' /-->';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ class Theme_Readme {
|
|||
$author = $theme['author'];
|
||||
$author_uri = $theme['author_uri'];
|
||||
$copy_year = gmdate( 'Y' );
|
||||
$wp_version = get_bloginfo( 'version' );
|
||||
$original_theme = $theme['original_theme'] ?? '';
|
||||
$original_theme_credits = $original_theme ? self::original_theme_credits( $name ) : '';
|
||||
|
||||
|
@ -24,7 +25,7 @@ class Theme_Readme {
|
|||
return "=== {$name} ===
|
||||
Contributors: {$author}
|
||||
Requires at least: 5.8
|
||||
Tested up to: 5.9
|
||||
Tested up to: {$wp_version}
|
||||
Requires PHP: 5.7
|
||||
License: GPLv2 or later
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
|
|
@ -8,29 +8,39 @@ class Theme_Styles {
|
|||
*/
|
||||
public static function build_child_style_css( $theme ) {
|
||||
$slug = $theme['slug'];
|
||||
$name = $theme['name'];
|
||||
$description = $theme['description'];
|
||||
$name = stripslashes( $theme['name'] );
|
||||
$description = stripslashes( $theme['description'] );
|
||||
$uri = $theme['uri'];
|
||||
$author = $theme['author'];
|
||||
$author = stripslashes( $theme['author'] );
|
||||
$author_uri = $theme['author_uri'];
|
||||
$wp_version = get_bloginfo( 'version' );
|
||||
$template = $theme['template'];
|
||||
$text_domain = $theme['text_domain'];
|
||||
$tags = Theme_Tags::theme_tags_list( $theme );
|
||||
return "/*
|
||||
|
||||
$style_css = "/*
|
||||
Theme Name: {$name}
|
||||
Theme URI: {$uri}
|
||||
Author: {$author}
|
||||
Author URI: {$author_uri}
|
||||
Description: {$description}
|
||||
Requires at least: 5.8
|
||||
Tested up to: 5.9
|
||||
Tested up to: {$wp_version}
|
||||
Requires PHP: 5.7
|
||||
Version: 0.0.1
|
||||
License: GNU General Public License v2 or later
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
Template: {$template}
|
||||
Text Domain: {$slug}
|
||||
";
|
||||
|
||||
if ( ! empty( $template ) ) {
|
||||
$style_css .= "Template: {$template}\n";
|
||||
}
|
||||
|
||||
$style_css .= "Text Domain: {$text_domain}
|
||||
Tags: {$tags}
|
||||
*/";
|
||||
|
||||
return $style_css;
|
||||
}
|
||||
|
||||
public static function clear_user_styles_customizations() {
|
||||
|
|
|
@ -14,17 +14,15 @@ class Theme_Tags {
|
|||
* @since 1.5.2
|
||||
*/
|
||||
public static function theme_tags_list( $theme ) {
|
||||
$checkbox_tags_merged = array_merge( $theme['tags-subject'] ?? array(), $theme['tags-layout'] ?? array(), $theme['tags-features'] ?? array() );
|
||||
$checkbox_tags = $checkbox_tags_merged ? ', ' . implode( ', ', $checkbox_tags_merged ) : '';
|
||||
$custom_tags = $theme['tags_custom'] ? ', ' . $theme['tags_custom'] : '';
|
||||
$tags = $checkbox_tags . $custom_tags;
|
||||
$checkbox_tags_merged = array_merge(
|
||||
$theme['tags-subject'] ?? array(),
|
||||
$theme['tags-layout'] ?? array(),
|
||||
$theme['tags-features'] ?? array(),
|
||||
);
|
||||
$custom_tags = array_map( 'trim', explode( ',', $theme['tags_custom'] ?? '' ) );
|
||||
$tags = array_unique( array_merge( $checkbox_tags_merged, $custom_tags ) );
|
||||
|
||||
// Remove comma and space from start of tags list
|
||||
if ( ', ' === substr( $tags, 0, 2 ) ) {
|
||||
$tags = substr( $tags, 2 );
|
||||
}
|
||||
|
||||
return $tags;
|
||||
return implode( ', ', $tags );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,9 +8,7 @@ class Theme_Templates {
|
|||
/*
|
||||
* Build a collection of templates and template-parts that should be exported (and modified) based on the given export_type and new slug
|
||||
*/
|
||||
public static function get_theme_templates( $export_type, $new_slug ) {
|
||||
|
||||
$old_slug = wp_get_theme()->get( 'TextDomain' );
|
||||
public static function get_theme_templates( $export_type ) {
|
||||
$templates = get_block_templates();
|
||||
$template_parts = get_block_templates( array(), 'wp_template_part' );
|
||||
$exported_templates = array();
|
||||
|
@ -25,9 +23,7 @@ class Theme_Templates {
|
|||
$template = self::filter_theme_template(
|
||||
$template,
|
||||
$export_type,
|
||||
$templates_path,
|
||||
$old_slug,
|
||||
$new_slug
|
||||
$templates_path
|
||||
);
|
||||
if ( $template ) {
|
||||
$exported_templates[] = $template;
|
||||
|
@ -38,9 +34,7 @@ class Theme_Templates {
|
|||
$template = self::filter_theme_template(
|
||||
$template,
|
||||
$export_type,
|
||||
$parts_path,
|
||||
$old_slug,
|
||||
$new_slug
|
||||
$parts_path
|
||||
);
|
||||
if ( $template ) {
|
||||
$exported_parts[] = $template;
|
||||
|
@ -59,7 +53,7 @@ class Theme_Templates {
|
|||
* Templates not filtered out are modified based on the slug information provided and cleaned up
|
||||
* to have the expected exported value.
|
||||
*/
|
||||
static function filter_theme_template( $template, $export_type, $path, $old_slug, $new_slug ) {
|
||||
static function filter_theme_template( $template, $export_type, $path ) {
|
||||
if ( 'theme' === $template->source && 'user' === $export_type ) {
|
||||
return false;
|
||||
}
|
||||
|
@ -75,10 +69,14 @@ class Theme_Templates {
|
|||
// This replaces that with dashes again. We should consider decoding the entire string but that is proving difficult.
|
||||
$template->content = str_replace( '\u002d', '-', $template->content );
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
public static function replace_template_namespace( $template, $new_slug ) {
|
||||
$old_slug = wp_get_theme()->get( 'TextDomain' );
|
||||
if ( $new_slug ) {
|
||||
$template->content = str_replace( $old_slug, $new_slug, $template->content );
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
|
@ -103,7 +101,7 @@ class Theme_Templates {
|
|||
|
||||
public static function add_templates_to_local( $export_type ) {
|
||||
|
||||
$theme_templates = self::get_theme_templates( $export_type, null );
|
||||
$theme_templates = self::get_theme_templates( $export_type );
|
||||
$template_folders = get_block_theme_folders();
|
||||
|
||||
// If there is no templates folder, create it.
|
||||
|
@ -122,8 +120,11 @@ class Theme_Templates {
|
|||
}
|
||||
|
||||
// If there are external images, add it as a pattern
|
||||
$pattern = Theme_Patterns::pattern_from_template( $template_data );
|
||||
$template_data->content = '<!-- wp:pattern {"slug":"' . $pattern['slug'] . '"} /-->';
|
||||
$pattern = Theme_Patterns::pattern_from_template( $template_data );
|
||||
$pattern_link_attributes = array(
|
||||
'slug' => $pattern['slug'],
|
||||
);
|
||||
$template_data->content = Theme_Patterns::create_pattern_link( $pattern_link_attributes );
|
||||
|
||||
// Write the pattern
|
||||
file_put_contents(
|
||||
|
@ -159,8 +160,11 @@ class Theme_Templates {
|
|||
}
|
||||
|
||||
// If there are external images, add it as a pattern
|
||||
$pattern = Theme_Patterns::pattern_from_template( $template_data );
|
||||
$template_data->content = '<!-- wp:pattern {"slug":"' . $pattern['slug'] . '"} /-->';
|
||||
$pattern = Theme_Patterns::pattern_from_template( $template_data );
|
||||
$pattern_link_attributes = array(
|
||||
'slug' => $pattern['slug'],
|
||||
);
|
||||
$template_data->content = Theme_Patterns::create_pattern_link( $pattern_link_attributes );
|
||||
|
||||
// Write the pattern
|
||||
file_put_contents(
|
||||
|
|
|
@ -89,7 +89,7 @@ class Theme_Zip {
|
|||
* all = all templates no matter what
|
||||
*/
|
||||
public static function add_templates_to_zip( $zip, $export_type, $new_slug ) {
|
||||
$theme_templates = Theme_Templates::get_theme_templates( $export_type, $new_slug );
|
||||
$theme_templates = Theme_Templates::get_theme_templates( $export_type );
|
||||
|
||||
if ( $theme_templates->templates ) {
|
||||
$zip->addEmptyDir( 'templates' );
|
||||
|
@ -101,11 +101,15 @@ class Theme_Zip {
|
|||
|
||||
foreach ( $theme_templates->templates as $template ) {
|
||||
$template_data = Theme_Blocks::make_template_images_local( $template );
|
||||
$template_data = Theme_Templates::replace_template_namespace( $template_data, $new_slug );
|
||||
|
||||
// If there are images in the template, add it as a pattern
|
||||
if ( count( $template_data->media ) > 0 ) {
|
||||
$pattern = Theme_Patterns::pattern_from_template( $template_data );
|
||||
$template_data->content = '<!-- wp:pattern {"slug":"' . $pattern['slug'] . '"} /-->';
|
||||
$pattern = Theme_Patterns::pattern_from_template( $template_data, $new_slug );
|
||||
$pattern_link_attributes = array(
|
||||
'slug' => $pattern['slug'],
|
||||
);
|
||||
$template_data->content = Theme_Patterns::create_pattern_link( $pattern_link_attributes );
|
||||
|
||||
// Add pattern to zip
|
||||
$zip->addFromString(
|
||||
|
@ -127,11 +131,15 @@ class Theme_Zip {
|
|||
|
||||
foreach ( $theme_templates->parts as $template_part ) {
|
||||
$template_data = Theme_Blocks::make_template_images_local( $template_part );
|
||||
$template_data = Theme_Templates::replace_template_namespace( $template_data, $new_slug );
|
||||
|
||||
// If there are images in the template, add it as a pattern
|
||||
if ( count( $template_data->media ) > 0 ) {
|
||||
$pattern = Theme_Patterns::pattern_from_template( $template_data );
|
||||
$template_data->content = '<!-- wp:pattern {"slug":"' . $pattern['slug'] . '"} /-->';
|
||||
$pattern = Theme_Patterns::pattern_from_template( $template_data, $new_slug );
|
||||
$pattern_link_attributes = array(
|
||||
'slug' => $pattern['slug'],
|
||||
);
|
||||
$template_data->content = Theme_Patterns::create_pattern_link( $pattern_link_attributes );
|
||||
|
||||
// Add pattern to zip
|
||||
$zip->addFromString(
|
||||
|
@ -154,11 +162,21 @@ class Theme_Zip {
|
|||
}
|
||||
|
||||
static function add_media_to_zip( $zip, $media ) {
|
||||
if ( ! function_exists( 'download_url' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
}
|
||||
|
||||
$media = array_unique( $media );
|
||||
foreach ( $media as $url ) {
|
||||
$folder_path = Theme_Media::get_media_folder_path_from_url( $url );
|
||||
$download_file = file_get_contents( $url );
|
||||
$zip->addFromString( $folder_path . basename( $url ), $download_file );
|
||||
$download_file = download_url( $url );
|
||||
// If there was an error downloading the file, skip it.
|
||||
// TODO: Implement a warning if the file is missing
|
||||
if ( ! is_wp_error( $download_file ) ) {
|
||||
$content_array = file( $download_file );
|
||||
$file_as_string = implode( '', $content_array );
|
||||
$zip->addFromString( $folder_path . basename( $url ), $file_as_string );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
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 ) {
|
||||
|
@ -9,11 +11,13 @@ function toggleForm( element ) {
|
|||
case 'export':
|
||||
case 'save':
|
||||
// Forms should stay hidden
|
||||
resetThemeName();
|
||||
break;
|
||||
|
||||
case 'child':
|
||||
case 'clone':
|
||||
case 'blank':
|
||||
case 'sibling':
|
||||
// Show New Theme form
|
||||
document
|
||||
.getElementById( 'new_theme_metadata_form' )
|
||||
|
@ -93,11 +97,121 @@ function limitCheckboxSelection( checkboxesSelector, max = 0 ) {
|
|||
|
||||
// Store active theme tags when page is loaded
|
||||
let activeThemeTags = [];
|
||||
window.onload = () => {
|
||||
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' );
|
||||
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 = 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 ) {
|
||||
|
@ -127,3 +241,9 @@ function resetThemeTags( themeType ) {
|
|||
} );
|
||||
}
|
||||
}
|
||||
|
||||
function resetThemeName() {
|
||||
const themeNameInput = document.getElementById( 'theme-name' );
|
||||
themeNameInput.value = '';
|
||||
themeNameInput.setCustomValidity( '' );
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<?php
|
||||
|
||||
require_once( dirname( __DIR__ ) . '/class-react-app.php' );
|
||||
|
||||
class Fonts_Page {
|
||||
public static function manage_fonts_admin_page() {
|
||||
self::load_fonts_react_app();
|
||||
React_App::bootstrap();
|
||||
|
||||
$theme_name = wp_get_theme()->get( 'Name' );
|
||||
|
||||
|
@ -25,37 +27,8 @@ class Fonts_Page {
|
|||
|
||||
?>
|
||||
<p name="theme-fonts-json" id="theme-fonts-json" class="hidden"><?php echo $fonts_json_string; ?></p>
|
||||
<div id="fonts-app"></div>
|
||||
<div id="create-block-theme-app"></div>
|
||||
<input type="hidden" name="nonce" id="nonce" value="<?php echo wp_create_nonce( 'create_block_theme' ); ?>" />
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function load_fonts_react_app() {
|
||||
// Load the required WordPress packages.
|
||||
// Automatically load imported dependencies and assets version.
|
||||
$asset_file = include plugin_dir_path( dirname( __DIR__ ) ) . 'build/index.asset.php';
|
||||
|
||||
// Enqueue CSS dependencies of the scripts included in the build.
|
||||
foreach ( $asset_file['dependencies'] as $style ) {
|
||||
wp_enqueue_style( $style );
|
||||
}
|
||||
|
||||
// Enqueue CSS of the app
|
||||
wp_enqueue_style( 'fonts-app', plugins_url( 'build/index.css', dirname( __DIR__ ) ), array(), $asset_file['version'] );
|
||||
|
||||
// Load our app.js.
|
||||
array_push( $asset_file['dependencies'], 'wp-i18n' );
|
||||
wp_enqueue_script( 'create-block-theme-app', plugins_url( 'build/index.js', dirname( __DIR__ ) ), $asset_file['dependencies'], $asset_file['version'] );
|
||||
|
||||
// Set google fonts json file url.
|
||||
wp_localize_script(
|
||||
'create-block-theme-app',
|
||||
'createBlockTheme',
|
||||
array(
|
||||
'googleFontsDataUrl' => plugins_url( 'assets/google-fonts/fallback-fonts-list.json', dirname( __DIR__ ) ),
|
||||
'adminUrl' => admin_url(),
|
||||
'themeUrl' => get_stylesheet_directory_uri(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
|
||||
require_once( __DIR__ . '/fonts-page.php' );
|
||||
require_once( dirname( __DIR__ ) . '/class-react-app.php' );
|
||||
|
||||
class Google_Fonts {
|
||||
public static function google_fonts_admin_page() {
|
||||
Fonts_Page::load_fonts_react_app();
|
||||
React_App::bootstrap();
|
||||
?>
|
||||
<input id="nonce" type="hidden" value="<?php echo wp_create_nonce( 'create_block_theme' ); ?>" />
|
||||
<div id="fonts-app"></div>
|
||||
<div id="create-block-theme-app"></div>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
require_once( __DIR__ . '/fonts-page.php' );
|
||||
require_once( dirname( __DIR__ ) . '/class-react-app.php' );
|
||||
|
||||
class Local_Fonts {
|
||||
public static function local_fonts_admin_page() {
|
||||
|
@ -8,11 +8,11 @@ class Local_Fonts {
|
|||
wp_enqueue_script( 'inflate', plugin_dir_url( dirname( __FILE__ ) ) . 'js/lib/inflate.js', array(), '', false );
|
||||
wp_enqueue_script( 'unbrotli', plugin_dir_url( dirname( __FILE__ ) ) . 'js/lib/unbrotli.js', array(), '', false );
|
||||
|
||||
Fonts_Page::load_fonts_react_app();
|
||||
React_App::bootstrap();
|
||||
|
||||
?>
|
||||
<input id="nonce" type="hidden" value="<?php echo wp_create_nonce( 'create_block_theme' ); ?>" />
|
||||
<div id="fonts-app"></div>
|
||||
<div id="create-block-theme-app"></div>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
|
75
admin/wp-org-theme-directory.php
Normal file
75
admin/wp-org-theme-directory.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
class 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( 'WP_Theme_Directory', 'get_theme_names' ),
|
||||
'permission_callback' => function () {
|
||||
return current_user_can( 'edit_theme_options' );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public static function get_theme_names() {
|
||||
$html = wp_remote_get( self::THEME_NAMES_ENDPOINT );
|
||||
|
||||
// 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,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,40 +1,34 @@
|
|||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/theme.json",
|
||||
"settings": {
|
||||
"appearanceTools": true,
|
||||
"layout": {
|
||||
"contentSize": "620px",
|
||||
"wideSize": "1000px"
|
||||
},
|
||||
"spacing": {
|
||||
"units": [
|
||||
"%",
|
||||
"px",
|
||||
"em",
|
||||
"rem",
|
||||
"vh",
|
||||
"vw"
|
||||
]
|
||||
},
|
||||
"typography": {
|
||||
"fontFamilies": [
|
||||
{
|
||||
"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif",
|
||||
"name": "System Font",
|
||||
"slug": "system-font"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"templateParts": [
|
||||
{
|
||||
"area": "header",
|
||||
"name": "header"
|
||||
},
|
||||
{
|
||||
"area": "footer",
|
||||
"name": "footer"
|
||||
}
|
||||
],
|
||||
"version": 2
|
||||
}
|
||||
"$schema": "https://schemas.wp.org/trunk/theme.json",
|
||||
"settings": {
|
||||
"appearanceTools": true,
|
||||
"layout": {
|
||||
"contentSize": "620px",
|
||||
"wideSize": "1000px"
|
||||
},
|
||||
"spacing": {
|
||||
"units": [ "%", "px", "em", "rem", "vh", "vw" ]
|
||||
},
|
||||
"typography": {
|
||||
"fontFamilies": [
|
||||
{
|
||||
"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif",
|
||||
"name": "System Font",
|
||||
"slug": "system-font"
|
||||
}
|
||||
]
|
||||
},
|
||||
"useRootPaddingAwareAlignments": true
|
||||
},
|
||||
"templateParts": [
|
||||
{
|
||||
"area": "header",
|
||||
"name": "header"
|
||||
},
|
||||
{
|
||||
"area": "footer",
|
||||
"name": "footer"
|
||||
}
|
||||
],
|
||||
"version": 2
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,7 +5,7 @@
|
|||
* Plugin Name: Create Block Theme
|
||||
* Plugin URI: https://wordpress.org/plugins/create-block-theme
|
||||
* Description: Generates a block theme
|
||||
* Version: 1.7.1
|
||||
* Version: 1.8.2
|
||||
* Author: WordPress.org
|
||||
* Author URI: https://wordpress.org/
|
||||
* License: GNU General Public License v2 or later
|
||||
|
|
|
@ -40,8 +40,9 @@ class Create_Block_Theme {
|
|||
/**
|
||||
* The class responsible for defining all actions that occur in the admin area.
|
||||
*/
|
||||
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-create-block-theme-admin.php';
|
||||
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-create-theme.php';
|
||||
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-manage-fonts.php';
|
||||
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/wp-org-theme-directory.php';
|
||||
|
||||
$this->loader = new Create_Block_Theme_Loader();
|
||||
|
||||
|
@ -58,6 +59,7 @@ class Create_Block_Theme {
|
|||
|
||||
$plugin_admin = new Create_Block_Theme_Admin();
|
||||
$manage_fonts_admin = new Manage_Fonts_Admin();
|
||||
$wp_theme_directory = new WP_Theme_Directory();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "create-block-theme",
|
||||
"version": "1.7.1",
|
||||
"version": "1.8.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "create-block-theme",
|
||||
"version": "1.7.1",
|
||||
"version": "1.8.2",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"dependencies": {
|
||||
"@wordpress/icons": "^9.17.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "create-block-theme",
|
||||
"version": "1.7.1",
|
||||
"version": "1.8.2",
|
||||
"private": true,
|
||||
"description": "Create a block-based theme",
|
||||
"author": "The WordPress Contributors",
|
||||
|
@ -38,7 +38,7 @@
|
|||
"simple-git": "^3.14.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "wp-scripts build",
|
||||
"build": "wp-scripts build src/index.js src/plugin-sidebar.js src/wp-org-theme-directory.js",
|
||||
"format": "wp-scripts format",
|
||||
"lint:css": "wp-scripts lint-style",
|
||||
"lint:css:fix": "npm run lint:css -- --fix",
|
||||
|
@ -47,7 +47,7 @@
|
|||
"lint:php": "composer run-script lint",
|
||||
"lint:php:fix": "composer run-script format",
|
||||
"packages-update": "wp-scripts packages-update",
|
||||
"start": "wp-scripts start",
|
||||
"start": "wp-scripts start src/index.js src/plugin-sidebar.js src/wp-org-theme-directory.js",
|
||||
"update-version": "node update-version-and-changelog.js",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
</rule>
|
||||
|
||||
<!-- Directories and third party library exclusions. -->
|
||||
<exclude-pattern>/build/*</exclude-pattern>
|
||||
<exclude-pattern>/vendor/*</exclude-pattern>
|
||||
<exclude-pattern>/node_modules/*</exclude-pattern>
|
||||
|
||||
|
|
39
readme.txt
39
readme.txt
|
@ -3,8 +3,8 @@ Contributors: wordpressdotorg, mikachan, onemaggie, pbking, scruffian, mmaattiia
|
|||
Donate link: https://automattic.com/
|
||||
Tags: themes, theme, block-theme
|
||||
Requires at least: 6.0
|
||||
Tested up to: 6.1
|
||||
Stable tag: 1.7.1
|
||||
Tested up to: 6.2
|
||||
Stable tag: 1.8.2
|
||||
Requires PHP: 7.0
|
||||
License: GPLv2 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
@ -36,6 +36,41 @@ Still in the WordPress dashboard, navigate to "Appearance" -> "Create Block Them
|
|||
|
||||
== Changelog ==
|
||||
|
||||
= 1.8.2 =
|
||||
* Merge branch 'trunk' into update/tested-up-wp-version-62
|
||||
* Updating Tested up to: 6.2 WordPress version
|
||||
* fix tag duplication in exported theme
|
||||
* Fixing error checking
|
||||
* Update Google Fonts JSON data from API
|
||||
* Refactor react app code for general purpose
|
||||
* add build directory to php exclude list
|
||||
* Do not call replace_template_namespace when overwrting theme
|
||||
* Merge branch 'trunk' into fix/314
|
||||
* Fix error when switching to template edit mode in the post editor
|
||||
* Add useRootPaddingAwareAlignments to blank theme
|
||||
* Update Google Fonts JSON data from API
|
||||
* Merge branch 'trunk' into fix/314
|
||||
* Avoid adding Template info to style.css if it's empty
|
||||
* Fix delete font family/face when name is different from family
|
||||
* Add theme name validation
|
||||
* Fix export theme from Site Editor
|
||||
* Strip escaping characters before printing stylesheet
|
||||
* Linting unlinted file
|
||||
|
||||
= 1.8.1 =
|
||||
* Add current WordPress version to style.css and readme.txt
|
||||
* Add labels around Google font family checkbox controls
|
||||
* Fix theme slug, textdomain, and template for cloned, child and sibling themes.
|
||||
* Replace theme slug in templates after getting media urls from them
|
||||
|
||||
= 1.8.0 =
|
||||
* Export style variations just with the changes made by the user
|
||||
* fix issue where package-lock is not updated on version bump
|
||||
* Adding default value to an to avoid error when calling export_theme_data()
|
||||
* Fixing image downloading not working in some cases
|
||||
* Update Google Fonts JSON data from API
|
||||
* Add Export (Clone) to site editor
|
||||
|
||||
= 1.7.1 =
|
||||
* Update screenshots
|
||||
* Fix manage fonts UI and backend when no settings are defined in theme.json
|
||||
|
|
|
@ -32,21 +32,31 @@ function FontVariant( { font, variant, isSelected, handleToggle } ) {
|
|||
} );
|
||||
}, [ font, variant ] );
|
||||
|
||||
const formattedFontFamily = font.family.toLowerCase().replace( ' ', '-' );
|
||||
const fontId = `${ formattedFontFamily }-${ variant }`;
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td className="">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="google-font-variant"
|
||||
id={ fontId }
|
||||
value={ variant }
|
||||
checked={ isSelected }
|
||||
onClick={ handleToggle }
|
||||
/>
|
||||
</td>
|
||||
<td className="">{ weight }</td>
|
||||
<td className="">{ style }</td>
|
||||
<td className="">
|
||||
<label htmlFor={ fontId }>{ weight }</label>
|
||||
</td>
|
||||
<td className="">
|
||||
<label htmlFor={ fontId }>{ style }</label>
|
||||
</td>
|
||||
<td className="demo-cell">
|
||||
<Demo style={ previewStyles } />
|
||||
<label htmlFor={ fontId }>
|
||||
<Demo style={ previewStyles } />
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
|
|
@ -182,6 +182,13 @@ function GoogleFonts() {
|
|||
getFontCredits( googleFontsData.items[ value ] );
|
||||
};
|
||||
|
||||
let selectedFontFamilyId = '';
|
||||
if ( selectedFont ) {
|
||||
selectedFontFamilyId = selectedFont.family
|
||||
.toLowerCase()
|
||||
.replace( ' ', '-' );
|
||||
}
|
||||
|
||||
return (
|
||||
<FontsPageLayout>
|
||||
<main>
|
||||
|
@ -261,6 +268,7 @@ function GoogleFonts() {
|
|||
<td className="">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={ `select-all-${ selectedFontFamilyId }` }
|
||||
onClick={ () =>
|
||||
handleToggleAllVariants(
|
||||
selectedFont.family
|
||||
|
@ -276,22 +284,34 @@ function GoogleFonts() {
|
|||
/>
|
||||
</td>
|
||||
<td className="">
|
||||
{ __(
|
||||
'Weight',
|
||||
'create-block-theme'
|
||||
) }
|
||||
<label
|
||||
htmlFor={ `select-all-${ selectedFontFamilyId }` }
|
||||
>
|
||||
{ __(
|
||||
'Weight',
|
||||
'create-block-theme'
|
||||
) }
|
||||
</label>
|
||||
</td>
|
||||
<td className="">
|
||||
{ __(
|
||||
'Style',
|
||||
'create-block-theme'
|
||||
) }
|
||||
<label
|
||||
htmlFor={ `select-all-${ selectedFontFamilyId }` }
|
||||
>
|
||||
{ __(
|
||||
'Style',
|
||||
'create-block-theme'
|
||||
) }
|
||||
</label>
|
||||
</td>
|
||||
<td className="">
|
||||
{ __(
|
||||
'Preview',
|
||||
'create-block-theme'
|
||||
) }
|
||||
<label
|
||||
htmlFor={ `select-all-${ selectedFontFamilyId }` }
|
||||
>
|
||||
{ __(
|
||||
'Preview',
|
||||
'create-block-theme'
|
||||
) }
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -34,7 +34,7 @@ function App() {
|
|||
window.addEventListener(
|
||||
'load',
|
||||
function () {
|
||||
render( <App />, document.querySelector( '#fonts-app' ) );
|
||||
render( <App />, document.querySelector( '#create-block-theme-app' ) );
|
||||
},
|
||||
false
|
||||
);
|
||||
|
|
|
@ -84,7 +84,10 @@ function ManageFonts() {
|
|||
|
||||
function deleteFontFamily( fontFamily ) {
|
||||
const updatedFonts = newThemeFonts.map( ( family ) => {
|
||||
if ( fontFamily === family.fontFamily ) {
|
||||
if (
|
||||
fontFamily === family.fontFamily ||
|
||||
fontFamily === family.name
|
||||
) {
|
||||
return {
|
||||
...family,
|
||||
shouldBeRemoved: true,
|
||||
|
@ -110,7 +113,8 @@ function ManageFonts() {
|
|||
if (
|
||||
weight === face.fontWeight &&
|
||||
style === face.fontStyle &&
|
||||
fontFamily === family.fontFamily
|
||||
( fontFamily === family.fontFamily ||
|
||||
fontFamily === family.name )
|
||||
) {
|
||||
return {
|
||||
...face,
|
||||
|
|
183
src/plugin-sidebar.js
Normal file
183
src/plugin-sidebar.js
Normal file
|
@ -0,0 +1,183 @@
|
|||
import { registerPlugin } from '@wordpress/plugins';
|
||||
import { PluginSidebar, PluginSidebarMoreMenuItem } from '@wordpress/edit-site';
|
||||
import { tool } from '@wordpress/icons';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
Button,
|
||||
// eslint-disable-next-line
|
||||
__experimentalVStack as VStack,
|
||||
// eslint-disable-next-line
|
||||
__experimentalSpacer as Spacer,
|
||||
// eslint-disable-next-line
|
||||
__experimentalText as Text,
|
||||
// eslint-disable-next-line
|
||||
__experimentalHeading as Heading,
|
||||
PanelBody,
|
||||
TextControl,
|
||||
} from '@wordpress/components';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import { downloadFile } from './utils';
|
||||
|
||||
const ExportTheme = () => {
|
||||
const { createErrorNotice } = useDispatch( noticesStore );
|
||||
const [ theme, setTheme ] = useState( {
|
||||
name: '',
|
||||
description: '',
|
||||
uri: '',
|
||||
author: '',
|
||||
author_uri: '',
|
||||
tags_custom: '',
|
||||
} );
|
||||
|
||||
useSelect( ( select ) => {
|
||||
const themeData = select( 'core' ).getCurrentTheme();
|
||||
setTheme( {
|
||||
name: themeData.name.raw,
|
||||
description: themeData.description.raw,
|
||||
author: themeData.author.raw,
|
||||
author_uri: themeData.author_uri.raw,
|
||||
theme_uri: themeData.theme_uri.raw,
|
||||
} );
|
||||
}, [] );
|
||||
|
||||
const handleSubmit = () => {
|
||||
const fetchOptions = {
|
||||
path: '/create-block-theme/v1/export',
|
||||
method: 'POST',
|
||||
data: theme,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
parse: false,
|
||||
};
|
||||
|
||||
async function exportTheme() {
|
||||
try {
|
||||
const response = await apiFetch( fetchOptions );
|
||||
downloadFile( response );
|
||||
} catch ( error ) {
|
||||
const errorMessage =
|
||||
error.message && error.code !== 'unknown_error'
|
||||
? error.message
|
||||
: __(
|
||||
'An error occurred while attempting to export the theme.'
|
||||
);
|
||||
createErrorNotice( errorMessage, { type: 'snackbar' } );
|
||||
}
|
||||
}
|
||||
|
||||
exportTheme();
|
||||
};
|
||||
|
||||
return (
|
||||
<PanelBody>
|
||||
<Heading>{ __( 'Export', 'create-block-theme' ) }</Heading>
|
||||
<VStack>
|
||||
<Text variant="muted">
|
||||
{ __(
|
||||
'Export your theme with updated templates and styles.',
|
||||
'create-block-theme'
|
||||
) }
|
||||
</Text>
|
||||
<Spacer />
|
||||
<TextControl
|
||||
label={ __( 'Theme name', 'create-block-theme' ) }
|
||||
value={ theme.name }
|
||||
onChange={ ( value ) =>
|
||||
setTheme( { ...theme, name: value } )
|
||||
}
|
||||
placeholder={ __( 'Theme name', 'create-block-theme' ) }
|
||||
/>
|
||||
<TextControl
|
||||
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={ __( 'Theme URI', 'create-block-theme' ) }
|
||||
value={ theme.uri }
|
||||
onChange={ ( value ) =>
|
||||
setTheme( { ...theme, uri: value } )
|
||||
}
|
||||
placeholder={ __(
|
||||
'https://github.com/wordpress/twentytwentythree/',
|
||||
'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'
|
||||
) }
|
||||
/>
|
||||
<TextControl
|
||||
label={ __( 'Author URI', 'create-block-theme' ) }
|
||||
value={ theme.author_uri }
|
||||
onChange={ ( value ) =>
|
||||
setTheme( { ...theme, author_uri: value } )
|
||||
}
|
||||
placeholder={ __(
|
||||
'https://wordpress.org/',
|
||||
'create-block-theme'
|
||||
) }
|
||||
/>
|
||||
</VStack>
|
||||
<Spacer />
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={ ! theme.name }
|
||||
onClick={ handleSubmit }
|
||||
>
|
||||
{ __( 'Export', 'create-block-theme' ) }
|
||||
</Button>
|
||||
<Spacer />
|
||||
{ ! theme.name && (
|
||||
<Text variant="muted">
|
||||
{ __(
|
||||
'Theme name is required for export.',
|
||||
'create-block-theme'
|
||||
) }
|
||||
</Text>
|
||||
) }
|
||||
</PanelBody>
|
||||
);
|
||||
};
|
||||
|
||||
const CreateBlockThemePlugin = () => {
|
||||
return (
|
||||
<>
|
||||
<PluginSidebarMoreMenuItem
|
||||
target="create-block-theme-sidebar"
|
||||
icon={ tool }
|
||||
>
|
||||
{ __( 'Create Block Theme' ) }
|
||||
</PluginSidebarMoreMenuItem>
|
||||
<PluginSidebar
|
||||
name="create-block-theme-sidebar"
|
||||
icon={ tool }
|
||||
title={ __( 'Create Block Theme' ) }
|
||||
>
|
||||
<ExportTheme />
|
||||
</PluginSidebar>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
registerPlugin( 'cbt-plugin-sidebar', {
|
||||
render: CreateBlockThemePlugin,
|
||||
} );
|
27
src/utils.js
27
src/utils.js
|
@ -39,3 +39,30 @@ export function localFileAsThemeAssetUrl( url ) {
|
|||
}
|
||||
return url.replace( 'file:./', createBlockTheme.themeUrl + '/' );
|
||||
}
|
||||
|
||||
export async function downloadFile( response ) {
|
||||
const blob = await response.blob();
|
||||
const filename = response.headers
|
||||
.get( 'Content-Disposition' )
|
||||
.split( 'filename=' )[ 1 ];
|
||||
|
||||
// Check if the browser supports navigator.msSaveBlob or navigator.saveBlob
|
||||
if ( navigator.msSaveBlob || navigator.saveBlob ) {
|
||||
const saveBlob = navigator.msSaveBlob || navigator.saveBlob;
|
||||
saveBlob.call( navigator, blob, filename );
|
||||
} else {
|
||||
// Fall back to creating an object URL and triggering a download using an anchor element
|
||||
const url = URL.createObjectURL( blob );
|
||||
|
||||
const a = document.createElement( 'a' );
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild( a );
|
||||
a.click();
|
||||
document.body.removeChild( a );
|
||||
|
||||
setTimeout( () => {
|
||||
URL.revokeObjectURL( url );
|
||||
}, 100 );
|
||||
}
|
||||
}
|
||||
|
|
11
src/wp-org-theme-directory.js
Normal file
11
src/wp-org-theme-directory.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
async function loadUnavailableThemeNames() {
|
||||
const requestOptions = {
|
||||
path: '/create-block-theme/v1/wp-org-theme-names',
|
||||
};
|
||||
const request = await apiFetch( requestOptions );
|
||||
wpOrgThemeDirectory.themeSlugs = request.names;
|
||||
}
|
||||
|
||||
window.addEventListener( 'load', loadUnavailableThemeNames );
|
Loading…
Add table
Add a link
Reference in a new issue