diff --git a/.eslintrc.json b/.eslintrc.json index 0b8e5d2e..d1bbe284 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,8 +1,7 @@ { "extends": "plugin:@wordpress/eslint-plugin/recommended", - "globals": { - "FileReader": true, - "FontFace": true + "env": { + "browser": true }, "rules": { "@wordpress/dependency-group": "error", @@ -12,8 +11,11 @@ "allowedTextDomain": "create-block-theme" } ], - "react/jsx-boolean-value": "error" + "react/jsx-boolean-value": "error", + "unicorn/no-abusive-eslint-disable": "error" }, + "ignorePatterns": [ "src/lib" ], + "plugins": [ "unicorn" ], "overrides": [ { "files": [ "**/test/**/*.js" ], diff --git a/admin/class-create-theme.php b/admin/class-create-theme.php deleted file mode 100644 index 50fde92d..00000000 --- a/admin/class-create-theme.php +++ /dev/null @@ -1,496 +0,0 @@ -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['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['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['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['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' ) ); - } - } - } -} diff --git a/admin/css/form.css b/admin/css/form.css deleted file mode 100644 index e4904377..00000000 --- a/admin/css/form.css +++ /dev/null @@ -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; - } -} diff --git a/admin/index.php b/admin/index.php deleted file mode 100644 index 8142269b..00000000 --- a/admin/index.php +++ /dev/null @@ -1 +0,0 @@ - { - 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( '' ); -} diff --git a/admin/wp-org-theme-directory.php b/admin/wp-org-theme-directory.php deleted file mode 100644 index bcb77f1a..00000000 --- a/admin/wp-org-theme-directory.php +++ /dev/null @@ -1,77 +0,0 @@ - '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 = '/
  • (.*?)<\/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, - ) - ); - } - -} diff --git a/assets/faq_fonts.webp b/assets/faq_fonts.webp new file mode 100644 index 00000000..176c97a2 Binary files /dev/null and b/assets/faq_fonts.webp differ diff --git a/assets/faq_icon.webp b/assets/faq_icon.webp new file mode 100644 index 00000000..8edad83c Binary files /dev/null and b/assets/faq_icon.webp differ diff --git a/assets/faq_save.webp b/assets/faq_save.webp new file mode 100644 index 00000000..b2e87d30 Binary files /dev/null and b/assets/faq_save.webp differ diff --git a/assets/header_logo.webp b/assets/header_logo.webp new file mode 100644 index 00000000..d0776cdc Binary files /dev/null and b/assets/header_logo.webp differ diff --git a/includes/class-create-block-theme-admin-landing.php b/includes/class-create-block-theme-admin-landing.php new file mode 100644 index 00000000..8592f3d3 --- /dev/null +++ b/includes/class-create-block-theme-admin-landing.php @@ -0,0 +1,52 @@ + 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 '
    '; + } +} diff --git a/includes/class-create-block-theme-api.php b/includes/class-create-block-theme-api.php index b49623a1..02729f49 100644 --- a/includes/class-create-block-theme-api.php +++ b/includes/class-create-block-theme-api.php @@ -1,5 +1,21 @@ 'GET', + 'callback' => array( $this, 'rest_get_font_families' ), + 'permission_callback' => function () { + return current_user_can( 'edit_theme_options' ); + }, + ), + ); } function rest_get_theme_data( $request ) { @@ -187,6 +214,8 @@ function rest_clone_theme( $request ) { $response = CBT_Theme_Create::clone_current_theme( $this->sanitize_theme_data( $request->get_params() ) ); + wp_cache_flush(); + if ( is_wp_error( $response ) ) { return $response; } @@ -208,6 +237,8 @@ function rest_create_child_theme( $request ) { $response = CBT_Theme_Create::create_child_theme( $theme, $screenshot ); + wp_cache_flush(); + if ( is_wp_error( $response ) ) { return $response; } @@ -224,6 +255,8 @@ function rest_create_variation( $request ) { $response = CBT_Theme_JSON::add_theme_json_variation_to_local( 'variation', $this->sanitize_theme_data( $request->get_params() ) ); + wp_cache_flush(); + if ( is_wp_error( $response ) ) { return $response; } @@ -244,6 +277,8 @@ function rest_create_blank_theme( $request ) { $response = CBT_Theme_Create::create_blank_theme( $theme, $screenshot ); + wp_cache_flush(); + if ( is_wp_error( $response ) ) { return $response; } @@ -304,6 +339,8 @@ function rest_export_cloned_theme( $request ) { $zip->close(); + wp_cache_flush(); + header( 'Content-Type: application/zip' ); header( 'Content-Disposition: attachment; filename=' . $theme['slug'] . '.zip' ); header( 'Content-Length: ' . filesize( $filename ) ); @@ -357,6 +394,8 @@ function rest_export_child_cloned_theme( $request ) { $zip->close(); + wp_cache_flush(); + header( 'Content-Type: application/zip' ); header( 'Content-Disposition: attachment; filename=' . $theme['slug'] . '.zip' ); header( 'Content-Length: ' . filesize( $filename ) ); @@ -374,7 +413,7 @@ function rest_export_theme( $request ) { __( 'Unable to create a zip file. ZipArchive not available.', 'create-block-theme' ), ); } - + wp_cache_flush(); $theme_slug = wp_get_theme()->get( 'TextDomain' ); // Create ZIP file in the temporary directory. @@ -384,6 +423,7 @@ function rest_export_theme( $request ) { $zip = CBT_Theme_Zip::copy_theme_to_zip( $zip, null, null ); if ( is_child_theme() ) { + wp_cache_flush(); $zip = CBT_Theme_Zip::add_templates_to_zip( $zip, 'current', $theme_slug ); $theme_json = CBT_Theme_JSON_Resolver::export_theme_data( 'current' ); } else { @@ -397,6 +437,8 @@ function rest_export_theme( $request ) { $zip->close(); + wp_cache_flush(); + header( 'Content-Type: application/zip' ); header( 'Content-Disposition: attachment; filename=' . $theme_slug . '.zip' ); header( 'Content-Length: ' . filesize( $filename ) ); @@ -432,6 +474,8 @@ function rest_update_theme( $request ) { return $response; } + wp_cache_flush(); + return new WP_REST_Response( array( 'status' => 'SUCCESS', @@ -473,6 +517,8 @@ function rest_save_theme( $request ) { CBT_Theme_Styles::clear_user_styles_customizations(); } + wp_cache_flush(); + return new WP_REST_Response( array( 'status' => 'SUCCESS', @@ -481,6 +527,24 @@ function rest_save_theme( $request ) { ); } + /** + * Get a list of all the font families used in the theme. + * + * It includes the font families from the theme.json data (theme.json file + global styles) and the theme style variations. + * The font families with font faces containing src urls relative to the theme folder are converted to absolute urls. + */ + function rest_get_font_families( $request ) { + $font_families = CBT_Theme_Fonts::get_all_fonts(); + + return new WP_REST_Response( + array( + 'status' => 'SUCCESS', + 'message' => __( 'Font Families retrieved.', 'create-block-theme' ), + 'data' => $font_families, + ) + ); + } + private function sanitize_theme_data( $theme ) { $sanitized_theme['name'] = sanitize_text_field( $theme['name'] ); $sanitized_theme['description'] = sanitize_text_field( $theme['description'] ?? '' ); @@ -492,6 +556,7 @@ private function sanitize_theme_data( $theme ) { $sanitized_theme['version'] = sanitize_text_field( $theme['version'] ?? '' ); $sanitized_theme['screenshot'] = sanitize_text_field( $theme['screenshot'] ?? '' ); $sanitized_theme['recommended_plugins'] = sanitize_textarea_field( $theme['recommended_plugins'] ?? '' ); + $sanitized_theme['font_credits'] = sanitize_textarea_field( $theme['font_credits'] ?? '' ); $sanitized_theme['template'] = ''; $sanitized_theme['slug'] = sanitize_title( $theme['name'] ); $sanitized_theme['text_domain'] = $sanitized_theme['slug']; diff --git a/includes/class-create-block-theme-editor-tools.php b/includes/class-create-block-theme-editor-tools.php new file mode 100644 index 00000000..6f8efef2 --- /dev/null +++ b/includes/class-create-block-theme-editor-tools.php @@ -0,0 +1,38 @@ +loader = new CBT_Plugin_Loader(); @@ -57,10 +56,9 @@ private function load_dependencies() { * @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(); } /** diff --git a/admin/create-theme/cbt-zip-archive.php b/includes/create-theme/cbt-zip-archive.php similarity index 100% rename from admin/create-theme/cbt-zip-archive.php rename to includes/create-theme/cbt-zip-archive.php diff --git a/admin/create-theme/form-messages.php b/includes/create-theme/form-messages.php similarity index 100% rename from admin/create-theme/form-messages.php rename to includes/create-theme/form-messages.php diff --git a/admin/resolver_additions.php b/includes/create-theme/resolver_additions.php similarity index 100% rename from admin/resolver_additions.php rename to includes/create-theme/resolver_additions.php diff --git a/admin/create-theme/theme-create.php b/includes/create-theme/theme-create.php similarity index 100% rename from admin/create-theme/theme-create.php rename to includes/create-theme/theme-create.php diff --git a/admin/create-theme/theme-fonts.php b/includes/create-theme/theme-fonts.php similarity index 70% rename from admin/create-theme/theme-fonts.php rename to includes/create-theme/theme-fonts.php index adf30120..e2539c1d 100644 --- a/admin/create-theme/theme-fonts.php +++ b/includes/create-theme/theme-fonts.php @@ -3,6 +3,75 @@ class CBT_Theme_Fonts { + + /** + * Make the font face theme src urls absolute. + * + * It replaces the 'file:./' prefix with the theme directory uri. + * + * Example: 'file:./assets/fonts/my-font.ttf' -> 'http://example.com/wp-content/themes/my-theme/assets/fonts/my-font.ttf' + * Example: [ 'https://example.com/assets/fonts/my-font.ttf' ] -> [ 'https://example.com/assets/fonts/my-font.ttf' ] + * + * @param array|string $src + * @return array|string + */ + private static function make_theme_font_src_absolute( $src ) { + $make_absolute = function ( $url ) { + if ( str_starts_with( $url, 'file:./' ) ) { + return str_replace( 'file:./', get_stylesheet_directory_uri() . '/', $url ); + } + return $url; + }; + + if ( is_array( $src ) ) { + foreach ( $src as &$url ) { + $url = $make_absolute( $url ); + } + } else { + $src = $make_absolute( $src ); + } + + return $src; + } + + /** + * Get all fonts from the theme.json data + all the style variations. + * + * @return array + */ + public static function get_all_fonts() { + $font_families = array(); + $theme = CBT_Theme_JSON_Resolver::get_merged_data(); + $settings = $theme->get_settings(); + + if ( isset( $settings['typography']['fontFamilies']['theme'] ) ) { + $font_families = array_merge( $font_families, $settings['typography']['fontFamilies']['theme'] ); + } + + if ( isset( $settings['typography']['fontFamilies']['custom'] ) ) { + $font_families = array_merge( $font_families, $settings['typography']['fontFamilies']['custom'] ); + } + + $variations = CBT_Theme_JSON_Resolver::get_style_variations(); + + foreach ( $variations as $variation ) { + if ( isset( $variation['settings']['typography']['fontFamilies']['theme'] ) ) { + $font_families = array_merge( $font_families, $variation['settings']['typography']['fontFamilies']['theme'] ); + } + } + + // Iterates through the font families and makes the urls absolute to use in the frontend code. + foreach ( $font_families as &$font_family ) { + if ( isset( $font_family['fontFace'] ) ) { + foreach ( $font_family['fontFace'] as &$font_face ) { + $font_face['src'] = CBT_Theme_Fonts::make_theme_font_src_absolute( $font_face['src'] ); + } + } + } + + return $font_families; + } + /** * Copy any ACTIVATED fonts from USER configuration to THEME configuration including any font face assets. * Remove any DEACTIVATED fronts from the THEME configuration. diff --git a/admin/create-theme/theme-form.php b/includes/create-theme/theme-form.php similarity index 100% rename from admin/create-theme/theme-form.php rename to includes/create-theme/theme-form.php diff --git a/admin/create-theme/theme-json.php b/includes/create-theme/theme-json.php similarity index 100% rename from admin/create-theme/theme-json.php rename to includes/create-theme/theme-json.php diff --git a/admin/create-theme/theme-locale.php b/includes/create-theme/theme-locale.php similarity index 100% rename from admin/create-theme/theme-locale.php rename to includes/create-theme/theme-locale.php diff --git a/admin/create-theme/theme-media.php b/includes/create-theme/theme-media.php similarity index 100% rename from admin/create-theme/theme-media.php rename to includes/create-theme/theme-media.php diff --git a/admin/create-theme/theme-patterns.php b/includes/create-theme/theme-patterns.php similarity index 100% rename from admin/create-theme/theme-patterns.php rename to includes/create-theme/theme-patterns.php diff --git a/admin/create-theme/theme-readme.php b/includes/create-theme/theme-readme.php similarity index 96% rename from admin/create-theme/theme-readme.php rename to includes/create-theme/theme-readme.php index d904c8b7..ae303bde 100644 --- a/admin/create-theme/theme-readme.php +++ b/includes/create-theme/theme-readme.php @@ -55,6 +55,7 @@ public static function create( $theme ) { $license_uri = $theme['license_uri'] ?? 'http://www.gnu.org/licenses/gpl-2.0.html'; $image_credits = $theme['image_credits'] ?? ''; $recommended_plugins = $theme['recommended_plugins'] ?? ''; + $font_credits = $theme['font_credits'] ?? ''; $is_child_theme = $theme['is_child_theme'] ?? false; // Generates the copyright section text. @@ -87,6 +88,9 @@ public static function create( $theme ) { // Adds the recommended plugins section $readme_content = self::add_or_update_section( 'Recommended Plugins', $recommended_plugins, $readme_content ); + // Adds the font credits section + $readme_content = self::add_or_update_section( 'Fonts', $font_credits, $readme_content ); + // Adds the Copyright section $readme_content = self::add_or_update_section( 'Copyright', $copyright_section_content, $readme_content ); @@ -193,6 +197,7 @@ private static function get_copyright_text( $theme ) { * @type string $author The theme author. * @type string $image_credits The image credits. * @type string $recommended_plugins The recommended plugins. + * @type string $font_credits The font credits. * } * @param string $readme_content readme.txt content. * @return string @@ -204,6 +209,7 @@ public static function update( $theme, $readme_content = '' ) { $wp_version = $theme['wp_version'] ?? CBT_Theme_Utils::get_current_wordpress_version(); $image_credits = $theme['image_credits'] ?? ''; $recommended_plugins = $theme['recommended_plugins'] ?? ''; + $font_credits = $theme['font_credits'] ?? ''; // Update description. $readme_content = self::add_or_update_section( 'Description', $description, $readme_content ); @@ -217,6 +223,9 @@ public static function update( $theme, $readme_content = '' ) { // Update recommended plugins section. $readme_content = self::add_or_update_section( 'Recommended Plugins', $recommended_plugins, $readme_content ); + // Update font credits section. + $readme_content = self::add_or_update_section( 'Fonts', $font_credits, $readme_content ); + // Update image credits section. $readme_content = self::add_or_update_section( 'Images', $image_credits, $readme_content ); diff --git a/admin/create-theme/theme-styles.php b/includes/create-theme/theme-styles.php similarity index 100% rename from admin/create-theme/theme-styles.php rename to includes/create-theme/theme-styles.php diff --git a/admin/create-theme/theme-tags.php b/includes/create-theme/theme-tags.php similarity index 100% rename from admin/create-theme/theme-tags.php rename to includes/create-theme/theme-tags.php diff --git a/admin/create-theme/theme-templates.php b/includes/create-theme/theme-templates.php similarity index 100% rename from admin/create-theme/theme-templates.php rename to includes/create-theme/theme-templates.php diff --git a/admin/create-theme/theme-utils.php b/includes/create-theme/theme-utils.php similarity index 100% rename from admin/create-theme/theme-utils.php rename to includes/create-theme/theme-utils.php diff --git a/admin/create-theme/theme-zip.php b/includes/create-theme/theme-zip.php similarity index 100% rename from admin/create-theme/theme-zip.php rename to includes/create-theme/theme-zip.php diff --git a/package-lock.json b/package-lock.json index df11be26..daa8759e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@wordpress/scripts": "^26.16.0", "@wordpress/stylelint-config": "^21.16.0", "babel-plugin-inline-json-import": "^0.3.2", + "eslint-plugin-unicorn": "^53.0.0", "husky": "^8.0.3", "lint-staged": "^13.2.2", "prettier": "npm:wp-prettier@3.0.3", @@ -7166,6 +7167,27 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, + "node_modules/clean-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", + "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clean-regexp/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/clean-webpack-plugin": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz", @@ -9767,6 +9789,203 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/eslint-plugin-unicorn": { + "version": "53.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-53.0.0.tgz", + "integrity": "sha512-kuTcNo9IwwUCfyHGwQFOK/HjJAYzbODHN3wP0PgqbW+jbXqpNWxNVpVhj2tO9SixBwuAdmal8rVcWKBxwFnGuw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.5", + "@eslint-community/eslint-utils": "^4.4.0", + "@eslint/eslintrc": "^3.0.2", + "ci-info": "^4.0.0", + "clean-regexp": "^1.0.0", + "core-js-compat": "^3.37.0", + "esquery": "^1.5.0", + "indent-string": "^4.0.0", + "is-builtin-module": "^3.2.1", + "jsesc": "^3.0.2", + "pluralize": "^8.0.0", + "read-pkg-up": "^7.0.1", + "regexp-tree": "^0.1.27", + "regjsparser": "^0.10.0", + "semver": "^7.6.1", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=18.18" + }, + "funding": { + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" + }, + "peerDependencies": { + "eslint": ">=8.56.0" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint-plugin-unicorn/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/ci-info": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", + "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/espree": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", + "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/regjsparser": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", + "integrity": "sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -17464,6 +17683,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -18744,6 +18972,15 @@ "@babel/runtime": "^7.8.4" } }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", diff --git a/package.json b/package.json index fa6701af..b956e989 100644 --- a/package.json +++ b/package.json @@ -36,13 +36,14 @@ "@wordpress/scripts": "^26.16.0", "@wordpress/stylelint-config": "^21.16.0", "babel-plugin-inline-json-import": "^0.3.2", + "eslint-plugin-unicorn": "^53.0.0", "husky": "^8.0.3", "lint-staged": "^13.2.2", "prettier": "npm:wp-prettier@3.0.3", "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", @@ -56,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", diff --git a/src/admin-landing-page.js b/src/admin-landing-page.js new file mode 100644 index 00000000..03d870f2 --- /dev/null +++ b/src/admin-landing-page.js @@ -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 ; +} + +window.addEventListener( + 'load', + function () { + const domNode = document.getElementById( 'create-block-theme-app' ); + const root = createRoot( domNode ); + root.render( ); + }, + false +); diff --git a/src/admin-landing-page.scss b/src/admin-landing-page.scss new file mode 100644 index 00000000..0407f6f9 --- /dev/null +++ b/src/admin-landing-page.scss @@ -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; + } + } + } + } +} diff --git a/src/editor-sidebar/create-panel.js b/src/editor-sidebar/create-panel.js index ab061313..b80aa1fb 100644 --- a/src/editor-sidebar/create-panel.js +++ b/src/editor-sidebar/create-panel.js @@ -4,19 +4,14 @@ 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 + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalVStack as VStack, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalSpacer as Spacer, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalText as Text, - // eslint-disable-next-line - __experimentalHeading as Heading, - // eslint-disable-next-line - __experimentalNavigatorToParentButton as NavigatorToParentButton, PanelBody, Button, TextControl, @@ -28,10 +23,23 @@ 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 ); + const subfolder = useSelect( ( select ) => { + const stylesheet = select( 'core' ).getCurrentTheme().stylesheet; + if ( stylesheet.lastIndexOf( '/' ) > 1 ) { + return stylesheet.substring( 0, stylesheet.lastIndexOf( '/' ) ); + } + return ''; + }, [] ); + const [ theme, setTheme ] = useState( { name: '', description: '', @@ -39,23 +47,9 @@ export const CreateThemePanel = ( { createType } ) => { author: '', author_uri: '', tags_custom: '', - subfolder: '', + subfolder, } ); - useSelect( ( select ) => { - const themeData = select( 'core' ).getCurrentTheme(); - setTheme( { - ...theme, - subfolder: - themeData.stylesheet.lastIndexOf( '/' ) > 1 - ? themeData.stylesheet.substring( - 0, - themeData.stylesheet.lastIndexOf( '/' ) - ) - : '', - } ); - }, [] ); - const cloneTheme = () => { if ( createType === 'createClone' ) { handleCloneClick(); @@ -65,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 - alert( + // eslint-disable-next-line no-alert + window.alert( __( 'Theme created successfully. The editor will now reload.', 'create-block-theme' @@ -95,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 - alert( + // eslint-disable-next-line no-alert + window.alert( __( 'Theme cloned successfully. The editor will now reload.', 'create-block-theme' @@ -125,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 - alert( + // eslint-disable-next-line no-alert + window.alert( __( 'Child theme created successfully. The editor will now reload.', 'create-block-theme' diff --git a/src/editor-sidebar/create-variation-panel.js b/src/editor-sidebar/create-variation-panel.js index 71d878df..6a26481a 100644 --- a/src/editor-sidebar/create-variation-panel.js +++ b/src/editor-sidebar/create-variation-panel.js @@ -6,14 +6,10 @@ import { useState } from '@wordpress/element'; import { useDispatch } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; import { - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalVStack as VStack, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalText as Text, - // eslint-disable-next-line - __experimentalHeading as Heading, - // eslint-disable-next-line - __experimentalNavigatorToParentButton as NavigatorToParentButton, PanelBody, Button, TextControl, @@ -36,8 +32,8 @@ export const CreateVariationPanel = () => { const handleCreateVariationClick = () => { postCreateThemeVariation( theme.name ) .then( () => { - // eslint-disable-next-line - alert( + // eslint-disable-next-line no-alert + window.alert( __( 'Theme variation created successfully. The editor will now reload.', 'create-block-theme' diff --git a/src/editor-sidebar/metadata-editor-modal.js b/src/editor-sidebar/metadata-editor-modal.js index f1b566c4..b1d55af0 100644 --- a/src/editor-sidebar/metadata-editor-modal.js +++ b/src/editor-sidebar/metadata-editor-modal.js @@ -6,13 +6,13 @@ import { useState } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; import { - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalHStack as HStack, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalVStack as VStack, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalSpacer as Spacer, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalText as Text, BaseControl, FormTokenField, @@ -50,6 +50,7 @@ export const ThemeMetadataEditorModal = ( { onRequestClose } ) => { tags_custom: '', recommended_plugins: '', font_credits: '', + subfolder: '', } ); const { createErrorNotice } = useDispatch( noticesStore ); @@ -82,8 +83,8 @@ export const ThemeMetadataEditorModal = ( { onRequestClose } ) => { const handleUpdateClick = () => { postUpdateThemeMetadata( theme ) .then( () => { - // eslint-disable-next-line - alert( + // eslint-disable-next-line no-alert + window.alert( __( 'Theme updated successfully. The editor will now reload.', 'create-block-theme' @@ -107,7 +108,7 @@ export const ThemeMetadataEditorModal = ( { onRequestClose } ) => { const credits = await getFontsCreditsText(); setTheme( { ...theme, font_credits: credits } ); } catch ( error ) { - // eslint-disable-next-line + // eslint-disable-next-line no-alert alert( sprintf( /* translators: %1: error code, %2: error message */ diff --git a/src/editor-sidebar/save-panel.js b/src/editor-sidebar/save-panel.js index 73e77f80..116d04a9 100644 --- a/src/editor-sidebar/save-panel.js +++ b/src/editor-sidebar/save-panel.js @@ -6,12 +6,8 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; import apiFetch from '@wordpress/api-fetch'; import { - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalVStack as VStack, - // eslint-disable-next-line - __experimentalHeading as Heading, - // eslint-disable-next-line - __experimentalNavigatorToParentButton as NavigatorToParentButton, PanelBody, Button, CheckboxControl, @@ -65,8 +61,8 @@ export const SaveThemePanel = () => { }, } ) .then( () => { - // eslint-disable-next-line - alert( + // eslint-disable-next-line no-alert + window.alert( __( 'Theme saved successfully. The editor will now reload.', 'create-block-theme' diff --git a/src/editor-sidebar/screen-header.js b/src/editor-sidebar/screen-header.js index 55aa5440..4ac21a16 100644 --- a/src/editor-sidebar/screen-header.js +++ b/src/editor-sidebar/screen-header.js @@ -2,15 +2,13 @@ * WordPress dependencies */ import { - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalHStack as HStack, - // eslint-disable-next-line - __experimentalVStack as VStack, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalSpacer as Spacer, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalHeading as Heading, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalNavigatorToParentButton as NavigatorToParentButton, } from '@wordpress/components'; import { isRTL, __ } from '@wordpress/i18n'; diff --git a/src/landing-page/create-modal.js b/src/landing-page/create-modal.js new file mode 100644 index 00000000..0688430d --- /dev/null +++ b/src/landing-page/create-modal.js @@ -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 ( + +

    { errorMessage }

    +
    + ); + } + + return ( + + + + { __( + "Let's get started creating a new Block Theme.", + 'create-block-theme' + ) } + + + setTheme( { ...theme, name: value } ) + } + /> + + + { __( + '(Tip: You can edit all of this and more in the Editor later.)', + 'create-block-theme' + ) } + + + setTheme( { ...theme, description: value } ) + } + placeholder={ __( + 'A short description of the theme', + 'create-block-theme' + ) } + /> + + setTheme( { ...theme, author: value } ) + } + placeholder={ __( + 'the WordPress team', + 'create-block-theme' + ) } + /> + + + + + + ); +}; diff --git a/src/landing-page/landing-page.js b/src/landing-page/landing-page.js new file mode 100644 index 00000000..e954e5a9 --- /dev/null +++ b/src/landing-page/landing-page.js @@ -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/download-file'; +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 ( +
    + { createModalType && ( + setCreateModalType( false ) } + /> + ) } + +

    + { +

    + + + +

    + { __( + 'What would you like to do?', + 'create-block-theme' + ) } +

    +

    + { createInterpolateElement( + __( + 'You can do everything from within the Editor 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 + + ), + } + ) } +

    + +

    + { __( + 'Export a zip file ready to be imported into another WordPress environment.', + 'create-block-theme' + ) } +

    + +

    + { __( + 'Start from scratch! Create a blank theme to get started with your own design ideas.', + 'create-block-theme' + ) } +

    + +

    + { __( + 'Use the currently activated theme as a starting point.', + 'create-block-theme' + ) } +

    + +

    + { __( + 'Make a theme that uses the currently activated theme as a parent.', + 'create-block-theme' + ) } +

    + + +

    { __( 'About the Plugin', 'create-block-theme' ) }

    +

    + { __( + "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' + ) } +

    +

    + { __( 'Do you need some help?', 'create-block-theme' ) } +

    +

    + { createInterpolateElement( + __( + 'Have a question? Ask for some help in the forums.', + 'create-block-theme' + ), + { + ExternalLink: ( + + ), + } + ) } +

    +

    + { createInterpolateElement( + __( + 'Found a bug? Report it on GitHub.', + 'create-block-theme' + ), + { + ExternalLink: ( + + ), + } + ) } +

    +

    + { createInterpolateElement( + __( + 'Want to contribute? Check out the project on GitHub.', + 'create-block-theme' + ), + { + ExternalLink: ( + + ), + } + ) } +

    +
    +

    { __( 'FAQ', 'create-block-theme' ) }

    +
    + + { __( + 'How do I access the features of Create Block Theme from within the editor?', + 'create-block-theme' + ) } + +

    + { __( + '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' + ) } +

    + { +
    +
    + + { __( + 'How do I save the customizations I made with the Editor to the Theme?', + 'create-block-theme' + ) } + +

    + { __( + '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' + ) } +

    + { +
    +
    + + { __( + 'How do I install and remove fonts?', + 'create-block-theme' + ) } + +

    + { __( + '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' + ) } +

    + { +
    +
    +
    + +
    + ); +} diff --git a/src/plugin-sidebar.js b/src/plugin-sidebar.js index e8026b3a..2a08ca59 100644 --- a/src/plugin-sidebar.js +++ b/src/plugin-sidebar.js @@ -8,24 +8,20 @@ import { __, _x } from '@wordpress/i18n'; import { useDispatch } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; import { - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalVStack as VStack, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalSpacer as Spacer, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalNavigatorProvider as NavigatorProvider, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalNavigatorScreen as NavigatorScreen, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalNavigatorButton as NavigatorButton, - // eslint-disable-next-line - __experimentalNavigatorToParentButton as NavigatorToParentButton, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalHStack as HStack, - // eslint-disable-next-line + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalText as Text, - // eslint-disable-next-line - __experimentalHeading as Heading, Button, Icon, FlexItem, diff --git a/src/resolvers.js b/src/resolvers.js index 66648f48..e7a81f57 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -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 ) { diff --git a/src/wp-org-theme-directory.js b/src/wp-org-theme-directory.js deleted file mode 100644 index 8d350c6f..00000000 --- a/src/wp-org-theme-directory.js +++ /dev/null @@ -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 ); diff --git a/tests/CbtThemeReadme/create.php b/tests/CbtThemeReadme/create.php index f9d4b0e5..a266f80f 100644 --- a/tests/CbtThemeReadme/create.php +++ b/tests/CbtThemeReadme/create.php @@ -55,6 +55,12 @@ public function test_create( $data ) { 'The expected reference to the parent theme is missing.' ); } + + // Assertion specific to font credits. + if ( isset( $data['font_credits'] ) ) { + $expected_font_credits = '== Fonts ==' . $data['font_credits']; + $this->assertStringContainsString( $expected_font_credits, $readme_without_newlines, 'The expected font credits are missing.' ); + } } public function data_test_create() { @@ -73,6 +79,7 @@ public function data_test_create() { 'license_uri' => 'https://www.gnu.org/licenses/gpl-2.0.html', 'image_credits' => 'The images were taken from https://example.org and have a CC0 license.', 'recommended_plugins' => 'The theme is best used with the following plugins: Plugin 1, Plugin 2, Plugin 3.', + 'font_credits' => 'Font credit example text', ), ), 'complete data for a child theme' => array( @@ -90,6 +97,7 @@ public function data_test_create() { 'image_credits' => 'The images were taken from https://example.org and have a CC0 license.', 'recommended_plugins' => 'The theme is best used with the following plugins: Plugin 1, Plugin 2, Plugin 3.', 'is_child_theme' => true, + 'font_credits' => 'Font credit example text', ), ), 'complete data for a cloned theme' => array( @@ -107,6 +115,23 @@ public function data_test_create() { 'image_credits' => 'The images were taken from https://example.org and have a CC0 license.', 'recommended_plugins' => 'The theme is best used with the following plugins: Plugin 1, Plugin 2, Plugin 3.', 'is_cloned_theme' => true, + 'font_credits' => 'Font credit example text', + ), + ), + 'missing font credits' => array( + 'data' => array( + 'name' => 'My Theme', + 'description' => 'New theme description', + 'uri' => 'https://example.com', + 'author' => 'New theme author', + 'author_uri' => 'https://example.com/author', + 'copyright_year' => '2077', + 'wp_version' => '12.12', + 'required_php_version' => '10.0', + 'license' => 'GPLv2 or later', + 'license_uri' => 'https://www.gnu.org/licenses/gpl-2.0.html', + 'image_credits' => 'The images were taken from https://example.org and have a CC0 license.', + 'recommended_plugins' => 'The theme is best used with the following plugins: Plugin 1, Plugin 2, Plugin 3.', ), ), // TODO: Add more test cases. diff --git a/tests/CbtThemeReadme/update.php b/tests/CbtThemeReadme/update.php index 2af65782..158736fd 100644 --- a/tests/CbtThemeReadme/update.php +++ b/tests/CbtThemeReadme/update.php @@ -30,11 +30,27 @@ public function test_update( $data ) { $this->assertStringContainsString( $expected_wp_version, $readme_without_newlines, 'The expected WP version is missing.' ); $this->assertStringContainsString( $expected_image_credits, $readme_without_newlines, 'The expected image credits are missing.' ); $this->assertStringContainsString( $expected_recommended_plugins, $readme_without_newlines, 'The expected recommended plugins are missing.' ); + + // Assertion specific to font credits. + if ( isset( $data['font_credits'] ) ) { + $expected_font_credits = '== Fonts ==' . $data['font_credits']; + $this->assertStringContainsString( $expected_font_credits, $readme_without_newlines, 'The expected font credits are missing.' ); + } } public function data_test_update() { return array( - 'complete data' => array( + 'complete data' => array( + 'data' => array( + 'description' => 'New theme description', + 'author' => 'New theme author', + 'wp_version' => '12.12', + 'image_credits' => 'New image credits', + 'recommended_plugins' => 'New recommended plugins', + 'font_credits' => 'Example font credits text', + ), + ), + 'missing font credits' => array( 'data' => array( 'description' => 'New theme description', 'author' => 'New theme author', diff --git a/tests/test-theme-fonts.php b/tests/test-theme-fonts.php index dee50090..e1a3aeff 100644 --- a/tests/test-theme-fonts.php +++ b/tests/test-theme-fonts.php @@ -101,12 +101,121 @@ public function test_remove_deactivated_fonts_from_theme() { $this->uninstall_theme( $test_theme_slug ); } + public function test_get_all_fonts_just_theme() { + + wp_set_current_user( self::$admin_id ); + + $test_theme_slug = $this->create_blank_theme(); + + $theme_json = CBT_Theme_JSON_Resolver::get_theme_file_contents(); + $theme_json['settings']['typography']['fontFamilies'] = array( + array( + 'slug' => 'open-sans', + 'name' => 'Open Sans', + 'fontFamily' => 'Open Sans', + 'fontFace' => array( + array( + 'fontFamily' => 'Open Sans', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'file:./assets/fonts/open-sans-normal-400.ttf', + ), + ), + ), + array( + 'slug' => 'closed-sans', + 'name' => 'Closed Sans', + 'fontFamily' => 'Closed Sans', + 'fontFace' => array( + array( + 'fontFamily' => 'Closed Sans', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://example.com/closed-sans-normal-400.ttf', + ), + ), + ), + ); + CBT_Theme_JSON_Resolver::write_theme_file_contents( $theme_json ); + + $fonts = CBT_Theme_Fonts::get_all_fonts(); + + $this->assertCount( 2, $fonts ); + $this->assertEquals( 'open-sans', $fonts[0]['slug'] ); + $this->assertEquals( 'closed-sans', $fonts[1]['slug'] ); + $this->assertStringNotContainsString( 'file:.', $fonts[0]['fontFace'][0]['src'] ); + $this->assertStringNotContainsString( 'file:.', $fonts[1]['fontFace'][0]['src'] ); + + $this->uninstall_theme( $test_theme_slug ); + } + + public function test_get_all_fonts_from_theme_and_variation() { + + wp_set_current_user( self::$admin_id ); + + $test_theme_slug = $this->create_blank_theme(); + + $theme_json = CBT_Theme_JSON_Resolver::get_theme_file_contents(); + $theme_json['settings']['typography']['fontFamilies'] = array( + array( + 'slug' => 'open-sans', + 'name' => 'Open Sans', + 'fontFamily' => 'Open Sans', + 'fontFace' => array( + array( + 'fontFamily' => 'Open Sans', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'file:./assets/fonts/open-sans-normal-400.ttf', + ), + ), + ), + ); + CBT_Theme_JSON_Resolver::write_theme_file_contents( $theme_json ); + + $variation_json = array( + 'version' => '2', + 'title' => 'Variation', + ); + $variation_json['settings']['typography']['fontFamilies'] = array( + array( + 'slug' => 'closed-sans', + 'name' => 'Closed Sans', + 'fontFamily' => 'Closed Sans', + 'fontFace' => array( + array( + 'fontFamily' => 'Closed Sans', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://example.com/closed-sans-normal-400.ttf', + ), + ), + ), + ); + + // Save the variation + $variation_path = get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'styles' . DIRECTORY_SEPARATOR; + $variation_slug = 'variation'; + wp_mkdir_p( $variation_path ); + file_put_contents( + $variation_path . $variation_slug . '.json', + wp_json_encode( $variation_json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) + ); + + $fonts = CBT_Theme_Fonts::get_all_fonts(); + + $this->assertCount( 2, $fonts ); + $this->assertEquals( 'open-sans', $fonts[0]['slug'] ); + $this->assertEquals( 'closed-sans', $fonts[1]['slug'] ); + $this->assertStringNotContainsString( 'file:.', $fonts[0]['fontFace'][0]['src'] ); + $this->assertStringNotContainsString( 'file:.', $fonts[1]['fontFace'][0]['src'] ); + + $this->uninstall_theme( $test_theme_slug ); + + } + private function save_theme() { CBT_Theme_Fonts::persist_font_settings(); - // CBT_Theme_Templates::add_templates_to_local( 'all' ); - // CBT_Theme_JSON::add_theme_json_to_local( 'all' ); - // CBT_Theme_Styles::clear_user_styles_customizations(); - // CBT_Theme_Templates::clear_user_templates_customizations(); } private function create_blank_theme() {