diff --git a/lib/compat/wordpress-6.4/block-patterns.php b/lib/compat/wordpress-6.4/block-patterns.php index bbb910ff400d0..922dea910b47a 100644 --- a/lib/compat/wordpress-6.4/block-patterns.php +++ b/lib/compat/wordpress-6.4/block-patterns.php @@ -16,21 +16,20 @@ */ function gutenberg_register_taxonomy_patterns() { $args = array( - 'public' => true, - 'publicly_queryable' => false, - 'hierarchical' => false, - 'labels' => array( + 'public' => true, + 'publicly_queryable' => false, + 'hierarchical' => false, + 'labels' => array( 'name' => _x( 'Pattern Categories', 'taxonomy general name' ), 'singular_name' => _x( 'Pattern Category', 'taxonomy singular name' ), ), - 'query_var' => false, - 'rewrite' => false, - 'show_ui' => true, - '_builtin' => true, - 'show_in_nav_menus' => false, - 'show_in_rest' => true, - 'show_admin_column' => true, - 'rest_controller_class' => 'Gutenberg_REST_Pattern_Categories_Controller', + 'query_var' => false, + 'rewrite' => false, + 'show_ui' => true, + '_builtin' => true, + 'show_in_nav_menus' => false, + 'show_in_rest' => true, + 'show_admin_column' => true, ); register_taxonomy( 'wp_pattern_category', array( 'wp_block' ), $args ); } diff --git a/lib/compat/wordpress-6.4/blocks.php b/lib/compat/wordpress-6.4/blocks.php index 073302dbab65f..74fa9253e45d5 100644 --- a/lib/compat/wordpress-6.4/blocks.php +++ b/lib/compat/wordpress-6.4/blocks.php @@ -21,27 +21,3 @@ function gutenberg_add_custom_capabilities_to_wp_block( $args ) { return $args; } add_filter( 'register_wp_block_post_type_args', 'gutenberg_add_custom_capabilities_to_wp_block', 10, 1 ); - -/** - * Updates the wp_block REST enpoint in order to modify the wp_pattern_category action - * links that are returned because as although the taxonomy is flat Author level users - * are only allowed to assign categories. - * - * Note: This should be removed when the minimum required WP version is >= 6.4. - * - * @see https://github.com/WordPress/gutenberg/pull/55379 - * - * @param array $args Register post type args. - * @param string $post_type The post type string. - * - * @return array Register post type args. - */ -function gutenberg_update_patterns_block_rest_controller_class( $args, $post_type ) { - if ( 'wp_block' === $post_type ) { - $args['rest_controller_class'] = 'Gutenberg_REST_Blocks_Controller_6_4'; - } - - return $args; -} - -add_filter( 'register_post_type_args', 'gutenberg_update_patterns_block_rest_controller_class', 11, 2 ); diff --git a/lib/compat/wordpress-6.4/class-gutenberg-rest-blocks-controller-6-4.php b/lib/compat/wordpress-6.4/class-gutenberg-rest-blocks-controller-6-4.php deleted file mode 100644 index bc91492e26979..0000000000000 --- a/lib/compat/wordpress-6.4/class-gutenberg-rest-blocks-controller-6-4.php +++ /dev/null @@ -1,75 +0,0 @@ -post_type ); - - if ( 'attachment' !== $this->post_type && current_user_can( $post_type->cap->publish_posts ) ) { - $rels[] = 'https://api.w.org/action-publish'; - } - - if ( current_user_can( 'unfiltered_html' ) ) { - $rels[] = 'https://api.w.org/action-unfiltered-html'; - } - - if ( 'post' === $post_type->name ) { - if ( current_user_can( $post_type->cap->edit_others_posts ) && current_user_can( $post_type->cap->publish_posts ) ) { - $rels[] = 'https://api.w.org/action-sticky'; - } - } - - if ( post_type_supports( $post_type->name, 'author' ) ) { - if ( current_user_can( $post_type->cap->edit_others_posts ) ) { - $rels[] = 'https://api.w.org/action-assign-author'; - } - } - - $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); - - foreach ( $taxonomies as $tax ) { - $tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name; - - if ( current_user_can( $tax->cap->edit_terms ) ) { - $rels[] = 'https://api.w.org/action-create-' . $tax_base; - } - - if ( current_user_can( $tax->cap->assign_terms ) ) { - $rels[] = 'https://api.w.org/action-assign-' . $tax_base; - } - } - - return $rels; - } -} diff --git a/lib/compat/wordpress-6.4/class-gutenberg-rest-pattern-categories-controller.php b/lib/compat/wordpress-6.4/class-gutenberg-rest-pattern-categories-controller.php deleted file mode 100644 index e249d67e8acaa..0000000000000 --- a/lib/compat/wordpress-6.4/class-gutenberg-rest-pattern-categories-controller.php +++ /dev/null @@ -1,45 +0,0 @@ -check_is_taxonomy_allowed( $this->taxonomy ) ) { - return false; - } - - $taxonomy_obj = get_taxonomy( $this->taxonomy ); - - // Patterns categories are a flat hierarchy (like tags), but work more like post categories in terms of permissions. - if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) { - return new WP_Error( - 'rest_cannot_create', - __( 'Sorry, you are not allowed to create terms in this taxonomy.' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - return true; - } -} diff --git a/lib/load.php b/lib/load.php index 381248e0f44bf..2b178af5fb9bf 100644 --- a/lib/load.php +++ b/lib/load.php @@ -53,8 +53,6 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.4 compat. require_once __DIR__ . '/compat/wordpress-6.4/class-gutenberg-rest-global-styles-revisions-controller-6-4.php'; require_once __DIR__ . '/compat/wordpress-6.4/class-gutenberg-rest-block-patterns-controller.php'; - require_once __DIR__ . '/compat/wordpress-6.4/class-gutenberg-rest-blocks-controller-6-4.php'; - require_once __DIR__ . '/compat/wordpress-6.4/class-gutenberg-rest-pattern-categories-controller.php'; require_once __DIR__ . '/compat/wordpress-6.4/rest-api.php'; require_once __DIR__ . '/compat/wordpress-6.4/theme-previews.php'; diff --git a/package-lock.json b/package-lock.json index ed33a0a19a48e..7603e03b936bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46332,6 +46332,14 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/postcss-prefixwrap": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.41.0.tgz", + "integrity": "sha512-gmwwAEE+ci3/ZKjUZppTETINlh1QwihY8gCstInuS7ohk353KYItU4d64hvnUvO2GUy29hBGPHz4Ce+qJRi90A==", + "peerDependencies": { + "postcss": "*" + } + }, "node_modules/postcss-reduce-initial": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz", @@ -46441,6 +46449,22 @@ "postcss": "^8.2.15" } }, + "node_modules/postcss-urlrebase": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-urlrebase/-/postcss-urlrebase-1.3.0.tgz", + "integrity": "sha512-LOFN43n1IewKriXiypMNNinXeptttSyGGRLPbBMdQzuTvvCEo5mz/gG06y/HqrkN7p3ayHQf2R2bTBv639FOaQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.3.0" + } + }, + "node_modules/postcss-urlrebase/node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, "node_modules/postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", @@ -52850,11 +52874,6 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true }, - "node_modules/traverse": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", - "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" - }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -56066,13 +56085,14 @@ "diff": "^4.0.2", "dom-scroll-into-view": "^1.2.1", "fast-deep-equal": "^3.1.3", - "inherits": "^2.0.3", "memize": "^2.1.0", + "postcss": "^8.4.21", + "postcss-prefixwrap": "^1.41.0", + "postcss-urlrebase": "^1.0.0", "react-autosize-textarea": "^7.1.0", "react-easy-crop": "^4.5.1", "rememo": "^4.0.2", - "remove-accents": "^0.5.0", - "traverse": "^0.6.6" + "remove-accents": "^0.5.0" }, "engines": { "node": ">=12" @@ -56082,6 +56102,41 @@ "react-dom": "^18.0.0" } }, + "packages/block-editor/node_modules/postcss": { + "version": "8.4.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", + "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "packages/block-editor/node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, "packages/block-library": { "name": "@wordpress/block-library", "version": "8.21.0", @@ -69479,13 +69534,31 @@ "diff": "^4.0.2", "dom-scroll-into-view": "^1.2.1", "fast-deep-equal": "^3.1.3", - "inherits": "^2.0.3", "memize": "^2.1.0", + "postcss": "^8.4.21", + "postcss-prefixwrap": "^1.41.0", + "postcss-urlrebase": "^1.0.0", "react-autosize-textarea": "^7.1.0", "react-easy-crop": "^4.5.1", "rememo": "^4.0.2", - "remove-accents": "^0.5.0", - "traverse": "^0.6.6" + "remove-accents": "^0.5.0" + }, + "dependencies": { + "postcss": { + "version": "8.4.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", + "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + } } }, "@wordpress/block-library": { @@ -95672,6 +95745,11 @@ } } }, + "postcss-prefixwrap": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.41.0.tgz", + "integrity": "sha512-gmwwAEE+ci3/ZKjUZppTETINlh1QwihY8gCstInuS7ohk353KYItU4d64hvnUvO2GUy29hBGPHz4Ce+qJRi90A==" + }, "postcss-reduce-initial": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz", @@ -95748,6 +95826,21 @@ "postcss-selector-parser": "^6.0.5" } }, + "postcss-urlrebase": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-urlrebase/-/postcss-urlrebase-1.3.0.tgz", + "integrity": "sha512-LOFN43n1IewKriXiypMNNinXeptttSyGGRLPbBMdQzuTvvCEo5mz/gG06y/HqrkN7p3ayHQf2R2bTBv639FOaQ==", + "requires": { + "postcss-value-parser": "^4.2.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + } + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", @@ -100640,11 +100733,6 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true }, - "traverse": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", - "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" - }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss index b621c34551da5..b988c0499f1fb 100644 --- a/packages/base-styles/_mixins.scss +++ b/packages/base-styles/_mixins.scss @@ -64,7 +64,7 @@ */ @mixin block-toolbar-button-style__focus() { - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color), inset 0 0 0 4px $white; + box-shadow: inset 0 0 0 $border-width var(--wp-components-color-background, $white), 0 0 0 var(--wp-admin-border-width-focus) var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)); // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 02671d5dca0e3..9c7a72f089714 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -789,13 +789,23 @@ Applies a series of CSS rule transforms to wrap selectors inside a given class a _Parameters_ -- _styles_ `Object|Array`: CSS rules. -- _wrapperClassName_ `string`: Wrapper Class Name. +- _styles_ `EditorStyle[]`: CSS rules. +- _wrapperSelector_ `string`: Wrapper selector. _Returns_ - `Array`: converted rules. +_Type Definition_ + +- _EditorStyle_ `Object` + +_Properties_ + +- _css_ `string`: the CSS block(s), as a single string. +- _baseURL_ `?string`: the base URL to be used as the reference when rewritting urls. +- _ignoredSelectors_ `?string[]`: the selectors not to wrap. + ### Typewriter Ensures that the text selection keeps the same vertical distance from the viewport during keyboard events within this component. The vertical distance can vary. It is the last clicked or scrolled to position. diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 225d9c987638a..5abf843b85f51 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -71,13 +71,14 @@ "diff": "^4.0.2", "dom-scroll-into-view": "^1.2.1", "fast-deep-equal": "^3.1.3", - "inherits": "^2.0.3", "memize": "^2.1.0", + "postcss": "^8.4.21", + "postcss-prefixwrap": "^1.41.0", + "postcss-urlrebase": "^1.0.0", "react-autosize-textarea": "^7.1.0", "react-easy-crop": "^4.5.1", "rememo": "^4.0.2", - "remove-accents": "^0.5.0", - "traverse": "^0.6.6" + "remove-accents": "^0.5.0" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/block-editor/src/components/block-styles/style.scss b/packages/block-editor/src/components/block-styles/style.scss index 9e80e93b0cd64..ab2a4b0c9ac98 100644 --- a/packages/block-editor/src/components/block-styles/style.scss +++ b/packages/block-editor/src/components/block-styles/style.scss @@ -58,7 +58,7 @@ &:focus, &.is-active:focus { - box-shadow: inset 0 0 0 $border-width var(--wp-components-color-background, #fff), 0 0 0 var(--wp-admin-border-width-focus) var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)); + @include block-toolbar-button-style__focus(); } } diff --git a/packages/block-editor/src/utils/test/__snapshots__/transform-styles.js.snap b/packages/block-editor/src/utils/test/__snapshots__/transform-styles.js.snap new file mode 100644 index 0000000000000..28c4202f414a9 --- /dev/null +++ b/packages/block-editor/src/utils/test/__snapshots__/transform-styles.js.snap @@ -0,0 +1,103 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`transformStyles URL rewrite should not replace absolute paths 1`] = ` +[ + "h1 { background: url(/images/test.png); }", +] +`; + +exports[`transformStyles URL rewrite should not replace remote paths 1`] = ` +[ + "h1 { background: url(http://wp.org/images/test.png); }", +] +`; + +exports[`transformStyles URL rewrite should replace complex relative paths 1`] = ` +[ + "h1 { background: url(http://wp-site.local/themes/gut/images/test.png); }", +] +`; + +exports[`transformStyles URL rewrite should rewrite relative paths 1`] = ` +[ + "h1 { background: url(http://wp-site.local/themes/gut/css/images/test.png); }", +] +`; + +exports[`transformStyles selector wrap should ignore font-face selectors 1`] = ` +[ + " + @font-face { + font-family: myFirstFont; + src: url(sansation_light.woff); + }", +] +`; + +exports[`transformStyles selector wrap should ignore keyframes 1`] = ` +[ + " + @keyframes edit-post__fade-in-animation { + from { + opacity: 0; + } + }", +] +`; + +exports[`transformStyles selector wrap should ignore selectors 1`] = ` +[ + ".my-namespace h1, body { color: red; }", +] +`; + +exports[`transformStyles selector wrap should not double wrap selectors 1`] = ` +[ + " .my-namespace h1, .my-namespace .red { color: red; }", +] +`; + +exports[`transformStyles selector wrap should replace :root selectors 1`] = ` +[ + " + .my-namespace { + --my-color: #ff0000; + }", +] +`; + +exports[`transformStyles selector wrap should replace root tags 1`] = ` +[ + ".my-namespace, .my-namespace h1 { color: red; }", +] +`; + +exports[`transformStyles selector wrap should wrap multiple selectors 1`] = ` +[ + ".my-namespace h1, .my-namespace h2 { color: red; }", +] +`; + +exports[`transformStyles selector wrap should wrap regular selectors 1`] = ` +[ + ".my-namespace h1 { color: red; }", +] +`; + +exports[`transformStyles selector wrap should wrap selectors inside container queries 1`] = ` +[ + " + @container (width > 400px) { + .my-namespace h1 { color: red; } + }", +] +`; + +exports[`transformStyles should not break with data urls 1`] = ` +[ + ".wp-block-group { + background-image: url("data:image/svg+xml,%3Csvg%3E.b%7Bclip-path:url(test);%7D%3C/svg%3E"); + color: red !important; + }", +] +`; diff --git a/packages/block-editor/src/utils/test/transform-styles.js b/packages/block-editor/src/utils/test/transform-styles.js new file mode 100644 index 0000000000000..f162a0b2f6048 --- /dev/null +++ b/packages/block-editor/src/utils/test/transform-styles.js @@ -0,0 +1,217 @@ +/** + * Internal dependencies + */ +import transformStyles from '../transform-styles'; + +describe( 'transformStyles', () => { + describe( 'selector wrap', () => { + it( 'should wrap regular selectors', () => { + const input = `h1 { color: red; }`; + const output = transformStyles( + [ + { + css: input, + }, + ], + '.my-namespace' + ); + + expect( output ).toMatchSnapshot(); + } ); + + it( 'should wrap multiple selectors', () => { + const input = `h1, h2 { color: red; }`; + const output = transformStyles( + [ + { + css: input, + }, + ], + '.my-namespace' + ); + + expect( output ).toMatchSnapshot(); + } ); + + it( 'should ignore selectors', () => { + const input = `h1, body { color: red; }`; + const output = transformStyles( + [ + { + css: input, + ignoredSelectors: [ 'body' ], + }, + ], + '.my-namespace' + ); + + expect( output ).toMatchSnapshot(); + } ); + + it( 'should replace root tags', () => { + const input = `body, h1 { color: red; }`; + const output = transformStyles( + [ + { + css: input, + }, + ], + '.my-namespace' + ); + + expect( output ).toMatchSnapshot(); + } ); + + it( 'should ignore keyframes', () => { + const input = ` + @keyframes edit-post__fade-in-animation { + from { + opacity: 0; + } + }`; + const output = transformStyles( + [ + { + css: input, + }, + ], + '.my-namespace' + ); + + expect( output ).toMatchSnapshot(); + } ); + + it( 'should wrap selectors inside container queries', () => { + const input = ` + @container (width > 400px) { + h1 { color: red; } + }`; + const output = transformStyles( + [ + { + css: input, + }, + ], + '.my-namespace' + ); + + expect( output ).toMatchSnapshot(); + } ); + + it( 'should ignore font-face selectors', () => { + const input = ` + @font-face { + font-family: myFirstFont; + src: url(sansation_light.woff); + }`; + const output = transformStyles( + [ + { + css: input, + }, + ], + '.my-namespace' + ); + + expect( output ).toMatchSnapshot(); + } ); + + it( 'should replace :root selectors', () => { + const input = ` + :root { + --my-color: #ff0000; + }`; + const output = transformStyles( + [ + { + css: input, + }, + ], + '.my-namespace' + ); + + expect( output ).toMatchSnapshot(); + } ); + + it( 'should not double wrap selectors', () => { + const input = ` .my-namespace h1, .red { color: red; }`; + + const output = transformStyles( + [ + { + css: input, + }, + ], + '.my-namespace' + ); + + expect( output ).toMatchSnapshot(); + } ); + } ); + + it( 'should not break with data urls', () => { + const input = `.wp-block-group { + background-image: url("data:image/svg+xml,%3Csvg%3E.b%7Bclip-path:url(test);%7D%3C/svg%3E"); + color: red !important; + }`; + + const output = transformStyles( [ + { + css: input, + baseURL: 'http://wp-site.local/themes/gut/css/', + }, + ] ); + + expect( output ).toMatchSnapshot(); + } ); + + describe( 'URL rewrite', () => { + it( 'should rewrite relative paths', () => { + const input = `h1 { background: url(images/test.png); }`; + const output = transformStyles( [ + { + css: input, + baseURL: 'http://wp-site.local/themes/gut/css/', + }, + ] ); + + expect( output ).toMatchSnapshot(); + } ); + + it( 'should replace complex relative paths', () => { + const input = `h1 { background: url(../images/test.png); }`; + const output = transformStyles( [ + { + css: input, + baseURL: 'http://wp-site.local/themes/gut/css/', + }, + ] ); + + expect( output ).toMatchSnapshot(); + } ); + + it( 'should not replace absolute paths', () => { + const input = `h1 { background: url(/images/test.png); }`; + const output = transformStyles( [ + { + css: input, + baseURL: 'http://wp-site.local/themes/gut/css/', + }, + ] ); + + expect( output ).toMatchSnapshot(); + } ); + + it( 'should not replace remote paths', () => { + const input = `h1 { background: url(http://wp.org/images/test.png); }`; + const output = transformStyles( [ + { + css: input, + baseURL: 'http://wp-site.local/themes/gut/css/', + }, + ] ); + + expect( output ).toMatchSnapshot(); + } ); + } ); +} ); diff --git a/packages/block-editor/src/utils/transform-styles/ast/index.js b/packages/block-editor/src/utils/transform-styles/ast/index.js deleted file mode 100644 index b4dc1de499f47..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/ast/index.js +++ /dev/null @@ -1,5 +0,0 @@ -// Adapted from https://github.com/reworkcss/css -// because we needed to remove source map support. - -export { default as parse } from './parse'; -export { default as stringify } from './stringify'; diff --git a/packages/block-editor/src/utils/transform-styles/ast/parse.js b/packages/block-editor/src/utils/transform-styles/ast/parse.js deleted file mode 100644 index 8f7d227d61442..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/ast/parse.js +++ /dev/null @@ -1,732 +0,0 @@ -/* eslint-disable @wordpress/no-unused-vars-before-return */ - -// Adapted from https://github.com/reworkcss/css -// because we needed to remove source map support. - -// http://www.w3.org/TR/CSS21/grammar.htm -// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027 -const commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g; - -export default function ( css, options ) { - options = options || {}; - - /** - * Positional. - */ - - let lineno = 1; - let column = 1; - - /** - * Update lineno and column based on `str`. - */ - - function updatePosition( str ) { - const lines = str.match( /\n/g ); - if ( lines ) { - lineno += lines.length; - } - const i = str.lastIndexOf( '\n' ); - // eslint-disable-next-line no-bitwise - column = ~i ? str.length - i : column + str.length; - } - - /** - * Mark position and patch `node.position`. - */ - - function position() { - const start = { line: lineno, column }; - return function ( node ) { - node.position = new Position( start ); - whitespace(); - return node; - }; - } - - /** - * Store position information for a node - */ - - function Position( start ) { - this.start = start; - this.end = { line: lineno, column }; - this.source = options.source; - } - - /** - * Non-enumerable source string - */ - - Position.prototype.content = css; - - /** - * Error `msg`. - */ - - const errorsList = []; - - function error( msg ) { - const err = new Error( - options.source + ':' + lineno + ':' + column + ': ' + msg - ); - err.reason = msg; - err.filename = options.source; - err.line = lineno; - err.column = column; - err.source = css; - - if ( options.silent ) { - errorsList.push( err ); - } else { - throw err; - } - } - - /** - * Parse stylesheet. - */ - - function stylesheet() { - const rulesList = rules(); - - return { - type: 'stylesheet', - stylesheet: { - source: options.source, - rules: rulesList, - parsingErrors: errorsList, - }, - }; - } - - /** - * Opening brace. - */ - - function open() { - return match( /^{\s*/ ); - } - - /** - * Closing brace. - */ - - function close() { - return match( /^}/ ); - } - - /** - * Parse ruleset. - */ - - function rules() { - let node; - const accumulator = []; - whitespace(); - comments( accumulator ); - while ( - css.length && - css.charAt( 0 ) !== '}' && - ( node = atrule() || rule() ) - ) { - if ( node !== false ) { - accumulator.push( node ); - comments( accumulator ); - } - } - return accumulator; - } - - /** - * Match `re` and return captures. - */ - - function match( re ) { - const m = re.exec( css ); - if ( ! m ) { - return; - } - const str = m[ 0 ]; - updatePosition( str ); - css = css.slice( str.length ); - return m; - } - - /** - * Parse whitespace. - */ - - function whitespace() { - match( /^\s*/ ); - } - - /** - * Parse comments; - */ - - function comments( accumulator ) { - let c; - accumulator = accumulator || []; - // eslint-disable-next-line no-cond-assign - while ( ( c = comment() ) ) { - if ( c !== false ) { - accumulator.push( c ); - } - } - return accumulator; - } - - /** - * Parse comment. - */ - - function comment() { - const pos = position(); - if ( '/' !== css.charAt( 0 ) || '*' !== css.charAt( 1 ) ) { - return; - } - - let i = 2; - while ( - '' !== css.charAt( i ) && - ( '*' !== css.charAt( i ) || '/' !== css.charAt( i + 1 ) ) - ) { - ++i; - } - i += 2; - - if ( '' === css.charAt( i - 1 ) ) { - return error( 'End of comment missing' ); - } - - const str = css.slice( 2, i - 2 ); - column += 2; - updatePosition( str ); - css = css.slice( i ); - column += 2; - - return pos( { - type: 'comment', - comment: str, - } ); - } - - /** - * Parse selector. - */ - - function selector() { - const m = match( /^([^{]+)/ ); - if ( ! m ) { - return; - } - // FIXME: Remove all comments from selectors http://ostermiller.org/findcomment.html - return trim( m[ 0 ] ) - .replace( /\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '' ) - .replace( /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function ( matched ) { - return matched.replace( /,/g, '\u200C' ); - } ) - .split( /\s*(?![^(]*\)),\s*/ ) - .map( function ( s ) { - return s.replace( /\u200C/g, ',' ); - } ); - } - - /** - * Parse declaration. - */ - - function declaration() { - const pos = position(); - - // prop. - let prop = match( /^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/ ); - if ( ! prop ) { - return; - } - prop = trim( prop[ 0 ] ); - - // : - if ( ! match( /^:\s*/ ) ) { - return error( "property missing ':'" ); - } - - // val. - const val = match( - /^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/ - ); - - const ret = pos( { - type: 'declaration', - property: prop.replace( commentre, '' ), - value: val ? trim( val[ 0 ] ).replace( commentre, '' ) : '', - } ); - - // ; - match( /^[;\s]*/ ); - - return ret; - } - - /** - * Parse declarations. - */ - - function declarations() { - const decls = []; - - if ( ! open() ) { - return error( "missing '{'" ); - } - comments( decls ); - - // declarations. - let decl; - // eslint-disable-next-line no-cond-assign - while ( ( decl = declaration() ) ) { - if ( decl !== false ) { - decls.push( decl ); - comments( decls ); - } - } - - if ( ! close() ) { - return error( "missing '}'" ); - } - return decls; - } - - /** - * Parse keyframe. - */ - - function keyframe() { - let m; - const vals = []; - const pos = position(); - - // eslint-disable-next-line no-cond-assign - while ( ( m = match( /^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/ ) ) ) { - vals.push( m[ 1 ] ); - match( /^,\s*/ ); - } - - if ( ! vals.length ) { - return; - } - - return pos( { - type: 'keyframe', - values: vals, - declarations: declarations(), - } ); - } - - /** - * Parse keyframes. - */ - - function atkeyframes() { - const pos = position(); - let m = match( /^@([-\w]+)?keyframes\s*/ ); - - if ( ! m ) { - return; - } - const vendor = m[ 1 ]; - - // identifier - m = match( /^([-\w]+)\s*/ ); - if ( ! m ) { - return error( '@keyframes missing name' ); - } - const name = m[ 1 ]; - - if ( ! open() ) { - return error( "@keyframes missing '{'" ); - } - - let frame; - let frames = comments(); - // eslint-disable-next-line no-cond-assign - while ( ( frame = keyframe() ) ) { - frames.push( frame ); - frames = frames.concat( comments() ); - } - - if ( ! close() ) { - return error( "@keyframes missing '}'" ); - } - - return pos( { - type: 'keyframes', - name, - vendor, - keyframes: frames, - } ); - } - - /** - * Parse supports. - */ - - function atsupports() { - const pos = position(); - const m = match( /^@supports *([^{]+)/ ); - - if ( ! m ) { - return; - } - const supports = trim( m[ 1 ] ); - - if ( ! open() ) { - return error( "@supports missing '{'" ); - } - - const style = comments().concat( rules() ); - - if ( ! close() ) { - return error( "@supports missing '}'" ); - } - - return pos( { - type: 'supports', - supports, - rules: style, - } ); - } - - /** - * Parse host. - */ - - function athost() { - const pos = position(); - const m = match( /^@host\s*/ ); - - if ( ! m ) { - return; - } - - if ( ! open() ) { - return error( "@host missing '{'" ); - } - - const style = comments().concat( rules() ); - - if ( ! close() ) { - return error( "@host missing '}'" ); - } - - return pos( { - type: 'host', - rules: style, - } ); - } - - /** - * Parse media. - */ - - function atmedia() { - const pos = position(); - const m = match( /^@media *([^{]+)/ ); - - if ( ! m ) { - return; - } - const media = trim( m[ 1 ] ); - - if ( ! open() ) { - return error( "@media missing '{'" ); - } - - const style = comments().concat( rules() ); - - if ( ! close() ) { - return error( "@media missing '}'" ); - } - - return pos( { - type: 'media', - media, - rules: style, - } ); - } - - /** - * Parse container. - */ - - function atcontainer() { - const pos = position(); - const m = match( /^@container *([^{]+)/ ); - - if ( ! m ) { - return; - } - const container = trim( m[ 1 ] ); - - if ( ! open() ) { - return error( "@container missing '{'" ); - } - - const style = comments().concat( rules() ); - - if ( ! close() ) { - return error( "@container missing '}'" ); - } - - return pos( { - type: 'container', - container, - rules: style, - } ); - } - - /** - * Parse custom-media. - */ - - function atcustommedia() { - const pos = position(); - const m = match( /^@custom-media\s+(--[^\s]+)\s*([^{;]+);/ ); - if ( ! m ) { - return; - } - - return pos( { - type: 'custom-media', - name: trim( m[ 1 ] ), - media: trim( m[ 2 ] ), - } ); - } - - /** - * Parse paged media. - */ - - function atpage() { - const pos = position(); - const m = match( /^@page */ ); - if ( ! m ) { - return; - } - - const sel = selector() || []; - - if ( ! open() ) { - return error( "@page missing '{'" ); - } - let decls = comments(); - - // declarations. - let decl; - // eslint-disable-next-line no-cond-assign - while ( ( decl = declaration() ) ) { - decls.push( decl ); - decls = decls.concat( comments() ); - } - - if ( ! close() ) { - return error( "@page missing '}'" ); - } - - return pos( { - type: 'page', - selectors: sel, - declarations: decls, - } ); - } - - /** - * Parse document. - */ - - function atdocument() { - const pos = position(); - const m = match( /^@([-\w]+)?document *([^{]+)/ ); - if ( ! m ) { - return; - } - - const vendor = trim( m[ 1 ] ); - const doc = trim( m[ 2 ] ); - - if ( ! open() ) { - return error( "@document missing '{'" ); - } - - const style = comments().concat( rules() ); - - if ( ! close() ) { - return error( "@document missing '}'" ); - } - - return pos( { - type: 'document', - document: doc, - vendor, - rules: style, - } ); - } - - /** - * Parse font-face. - */ - - function atfontface() { - const pos = position(); - const m = match( /^@font-face\s*/ ); - if ( ! m ) { - return; - } - - if ( ! open() ) { - return error( "@font-face missing '{'" ); - } - let decls = comments(); - - // declarations. - let decl; - // eslint-disable-next-line no-cond-assign - while ( ( decl = declaration() ) ) { - decls.push( decl ); - decls = decls.concat( comments() ); - } - - if ( ! close() ) { - return error( "@font-face missing '}'" ); - } - - return pos( { - type: 'font-face', - declarations: decls, - } ); - } - - /** - * Parse import - */ - - const atimport = _compileAtrule( 'import' ); - - /** - * Parse charset - */ - - const atcharset = _compileAtrule( 'charset' ); - - /** - * Parse namespace - */ - - const atnamespace = _compileAtrule( 'namespace' ); - - /** - * Parse non-block at-rules - */ - - function _compileAtrule( name ) { - const re = new RegExp( '^@' + name + '\\s*([^;]+);' ); - return function () { - const pos = position(); - const m = match( re ); - if ( ! m ) { - return; - } - const ret = { type: name }; - ret[ name ] = m[ 1 ].trim(); - return pos( ret ); - }; - } - - /** - * Parse at rule. - */ - - function atrule() { - if ( css[ 0 ] !== '@' ) { - return; - } - - return ( - atkeyframes() || - atmedia() || - atcontainer() || - atcustommedia() || - atsupports() || - atimport() || - atcharset() || - atnamespace() || - atdocument() || - atpage() || - athost() || - atfontface() - ); - } - - /** - * Parse rule. - */ - - function rule() { - const pos = position(); - const sel = selector(); - - if ( ! sel ) { - return error( 'selector missing' ); - } - comments(); - - return pos( { - type: 'rule', - selectors: sel, - declarations: declarations(), - } ); - } - - return addParent( stylesheet() ); -} - -/** - * Trim `str`. - */ - -function trim( str ) { - return str ? str.replace( /^\s+|\s+$/g, '' ) : ''; -} - -/** - * Adds non-enumerable parent node reference to each node. - */ - -function addParent( obj, parent ) { - const isNode = obj && typeof obj.type === 'string'; - const childParent = isNode ? obj : parent; - - for ( const k in obj ) { - const value = obj[ k ]; - if ( Array.isArray( value ) ) { - value.forEach( function ( v ) { - addParent( v, childParent ); - } ); - } else if ( value && typeof value === 'object' ) { - addParent( value, childParent ); - } - } - - if ( isNode ) { - Object.defineProperty( obj, 'parent', { - configurable: true, - writable: true, - enumerable: false, - value: parent || null, - } ); - } - - return obj; -} - -/* eslint-enable @wordpress/no-unused-vars-before-return */ diff --git a/packages/block-editor/src/utils/transform-styles/ast/stringify/compiler.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/compiler.js deleted file mode 100644 index d2500b730424f..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/ast/stringify/compiler.js +++ /dev/null @@ -1,50 +0,0 @@ -// Adapted from https://github.com/reworkcss/css -// because we needed to remove source map support. - -/** - * Expose `Compiler`. - */ - -export default Compiler; - -/** - * Initialize a compiler. - */ - -function Compiler( opts ) { - this.options = opts || {}; -} - -/** - * Emit `str` - */ - -Compiler.prototype.emit = function ( str ) { - return str; -}; - -/** - * Visit `node`. - */ - -Compiler.prototype.visit = function ( node ) { - return this[ node.type ]( node ); -}; - -/** - * Map visit over array of `nodes`, optionally using a `delim` - */ - -Compiler.prototype.mapVisit = function ( nodes, delim ) { - let buf = ''; - delim = delim || ''; - - for ( let i = 0, length = nodes.length; i < length; i++ ) { - buf += this.visit( nodes[ i ] ); - if ( delim && i < length - 1 ) { - buf += this.emit( delim ); - } - } - - return buf; -}; diff --git a/packages/block-editor/src/utils/transform-styles/ast/stringify/compress.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/compress.js deleted file mode 100644 index 6a2a3af3769be..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/ast/stringify/compress.js +++ /dev/null @@ -1,238 +0,0 @@ -// Adapted from https://github.com/reworkcss/css -// because we needed to remove source map support. - -/** - * External dependencies - */ -import inherits from 'inherits'; - -/** - * Internal dependencies - */ -import Base from './compiler'; - -/** - * Expose compiler. - */ - -export default Compiler; - -/** - * Initialize a new `Compiler`. - */ - -function Compiler( options ) { - Base.call( this, options ); -} - -/** - * Inherit from `Base.prototype`. - */ - -inherits( Compiler, Base ); - -/** - * Compile `node`. - */ - -Compiler.prototype.compile = function ( node ) { - return node.stylesheet.rules.map( this.visit, this ).join( '' ); -}; - -/** - * Visit comment node. - */ - -Compiler.prototype.comment = function ( node ) { - return this.emit( '', node.position ); -}; - -/** - * Visit import node. - */ - -Compiler.prototype.import = function ( node ) { - return this.emit( '@import ' + node.import + ';', node.position ); -}; - -/** - * Visit media node. - */ - -Compiler.prototype.media = function ( node ) { - return ( - this.emit( '@media ' + node.media, node.position ) + - this.emit( '{' ) + - this.mapVisit( node.rules ) + - this.emit( '}' ) - ); -}; - -/** - * Visit container node. - */ - -Compiler.prototype.container = function ( node ) { - return ( - this.emit( '@container ' + node.container, node.position ) + - this.emit( '{' ) + - this.mapVisit( node.rules ) + - this.emit( '}' ) - ); -}; - -/** - * Visit document node. - */ - -Compiler.prototype.document = function ( node ) { - const doc = '@' + ( node.vendor || '' ) + 'document ' + node.document; - - return ( - this.emit( doc, node.position ) + - this.emit( '{' ) + - this.mapVisit( node.rules ) + - this.emit( '}' ) - ); -}; - -/** - * Visit charset node. - */ - -Compiler.prototype.charset = function ( node ) { - return this.emit( '@charset ' + node.charset + ';', node.position ); -}; - -/** - * Visit namespace node. - */ - -Compiler.prototype.namespace = function ( node ) { - return this.emit( '@namespace ' + node.namespace + ';', node.position ); -}; - -/** - * Visit supports node. - */ - -Compiler.prototype.supports = function ( node ) { - return ( - this.emit( '@supports ' + node.supports, node.position ) + - this.emit( '{' ) + - this.mapVisit( node.rules ) + - this.emit( '}' ) - ); -}; - -/** - * Visit keyframes node. - */ - -Compiler.prototype.keyframes = function ( node ) { - return ( - this.emit( - '@' + ( node.vendor || '' ) + 'keyframes ' + node.name, - node.position - ) + - this.emit( '{' ) + - this.mapVisit( node.keyframes ) + - this.emit( '}' ) - ); -}; - -/** - * Visit keyframe node. - */ - -Compiler.prototype.keyframe = function ( node ) { - const decls = node.declarations; - - return ( - this.emit( node.values.join( ',' ), node.position ) + - this.emit( '{' ) + - this.mapVisit( decls ) + - this.emit( '}' ) - ); -}; - -/** - * Visit page node. - */ - -Compiler.prototype.page = function ( node ) { - const sel = node.selectors.length ? node.selectors.join( ', ' ) : ''; - - return ( - this.emit( '@page ' + sel, node.position ) + - this.emit( '{' ) + - this.mapVisit( node.declarations ) + - this.emit( '}' ) - ); -}; - -/** - * Visit font-face node. - */ - -Compiler.prototype[ 'font-face' ] = function ( node ) { - return ( - this.emit( '@font-face', node.position ) + - this.emit( '{' ) + - this.mapVisit( node.declarations ) + - this.emit( '}' ) - ); -}; - -/** - * Visit host node. - */ - -Compiler.prototype.host = function ( node ) { - return ( - this.emit( '@host', node.position ) + - this.emit( '{' ) + - this.mapVisit( node.rules ) + - this.emit( '}' ) - ); -}; - -/** - * Visit custom-media node. - */ - -Compiler.prototype[ 'custom-media' ] = function ( node ) { - return this.emit( - '@custom-media ' + node.name + ' ' + node.media + ';', - node.position - ); -}; - -/** - * Visit rule node. - */ - -Compiler.prototype.rule = function ( node ) { - const decls = node.declarations; - if ( ! decls.length ) { - return ''; - } - - return ( - this.emit( node.selectors.join( ',' ), node.position ) + - this.emit( '{' ) + - this.mapVisit( decls ) + - this.emit( '}' ) - ); -}; - -/** - * Visit declaration node. - */ - -Compiler.prototype.declaration = function ( node ) { - return ( - this.emit( node.property + ':' + node.value, node.position ) + - this.emit( ';' ) - ); -}; diff --git a/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js deleted file mode 100644 index 760ca4044631e..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js +++ /dev/null @@ -1,286 +0,0 @@ -/* eslint-disable @wordpress/no-unused-vars-before-return */ - -// Adapted from https://github.com/reworkcss/css -// because we needed to remove source map support. - -/** - * External dependencies - */ -import inherits from 'inherits'; - -/** - * Internal dependencies - */ -import Base from './compiler'; - -/** - * Expose compiler. - */ - -export default Compiler; - -/** - * Initialize a new `Compiler`. - */ - -function Compiler( options ) { - options = options || {}; - Base.call( this, options ); - this.indentation = options.indent; -} - -/** - * Inherit from `Base.prototype`. - */ - -inherits( Compiler, Base ); - -/** - * Compile `node`. - */ - -Compiler.prototype.compile = function ( node ) { - return this.stylesheet( node ); -}; - -/** - * Visit stylesheet node. - */ - -Compiler.prototype.stylesheet = function ( node ) { - return this.mapVisit( node.stylesheet.rules, '\n\n' ); -}; - -/** - * Visit comment node. - */ - -Compiler.prototype.comment = function ( node ) { - return this.emit( - this.indent() + '/*' + node.comment + '*/', - node.position - ); -}; - -/** - * Visit import node. - */ - -Compiler.prototype.import = function ( node ) { - return this.emit( '@import ' + node.import + ';', node.position ); -}; - -/** - * Visit media node. - */ - -Compiler.prototype.media = function ( node ) { - return ( - this.emit( '@media ' + node.media, node.position ) + - this.emit( ' {\n' + this.indent( 1 ) ) + - this.mapVisit( node.rules, '\n\n' ) + - this.emit( this.indent( -1 ) + '\n}' ) - ); -}; - -/** - * Visit container node. - */ - -Compiler.prototype.container = function ( node ) { - return ( - this.emit( '@container ' + node.container, node.position ) + - this.emit( ' {\n' + this.indent( 1 ) ) + - this.mapVisit( node.rules, '\n\n' ) + - this.emit( this.indent( -1 ) + '\n}' ) - ); -}; - -/** - * Visit document node. - */ - -Compiler.prototype.document = function ( node ) { - const doc = '@' + ( node.vendor || '' ) + 'document ' + node.document; - - return ( - this.emit( doc, node.position ) + - this.emit( ' ' + ' {\n' + this.indent( 1 ) ) + - this.mapVisit( node.rules, '\n\n' ) + - this.emit( this.indent( -1 ) + '\n}' ) - ); -}; - -/** - * Visit charset node. - */ - -Compiler.prototype.charset = function ( node ) { - return this.emit( '@charset ' + node.charset + ';', node.position ); -}; - -/** - * Visit namespace node. - */ - -Compiler.prototype.namespace = function ( node ) { - return this.emit( '@namespace ' + node.namespace + ';', node.position ); -}; - -/** - * Visit supports node. - */ - -Compiler.prototype.supports = function ( node ) { - return ( - this.emit( '@supports ' + node.supports, node.position ) + - this.emit( ' {\n' + this.indent( 1 ) ) + - this.mapVisit( node.rules, '\n\n' ) + - this.emit( this.indent( -1 ) + '\n}' ) - ); -}; - -/** - * Visit keyframes node. - */ - -Compiler.prototype.keyframes = function ( node ) { - return ( - this.emit( - '@' + ( node.vendor || '' ) + 'keyframes ' + node.name, - node.position - ) + - this.emit( ' {\n' + this.indent( 1 ) ) + - this.mapVisit( node.keyframes, '\n' ) + - this.emit( this.indent( -1 ) + '}' ) - ); -}; - -/** - * Visit keyframe node. - */ - -Compiler.prototype.keyframe = function ( node ) { - const decls = node.declarations; - - return ( - this.emit( this.indent() ) + - this.emit( node.values.join( ', ' ), node.position ) + - this.emit( ' {\n' + this.indent( 1 ) ) + - this.mapVisit( decls, '\n' ) + - this.emit( this.indent( -1 ) + '\n' + this.indent() + '}\n' ) - ); -}; - -/** - * Visit page node. - */ - -Compiler.prototype.page = function ( node ) { - const sel = node.selectors.length ? node.selectors.join( ', ' ) + ' ' : ''; - - return ( - this.emit( '@page ' + sel, node.position ) + - this.emit( '{\n' ) + - this.emit( this.indent( 1 ) ) + - this.mapVisit( node.declarations, '\n' ) + - this.emit( this.indent( -1 ) ) + - this.emit( '\n}' ) - ); -}; - -/** - * Visit font-face node. - */ - -Compiler.prototype[ 'font-face' ] = function ( node ) { - return ( - this.emit( '@font-face ', node.position ) + - this.emit( '{\n' ) + - this.emit( this.indent( 1 ) ) + - this.mapVisit( node.declarations, '\n' ) + - this.emit( this.indent( -1 ) ) + - this.emit( '\n}' ) - ); -}; - -/** - * Visit host node. - */ - -Compiler.prototype.host = function ( node ) { - return ( - this.emit( '@host', node.position ) + - this.emit( ' {\n' + this.indent( 1 ) ) + - this.mapVisit( node.rules, '\n\n' ) + - this.emit( this.indent( -1 ) + '\n}' ) - ); -}; - -/** - * Visit custom-media node. - */ - -Compiler.prototype[ 'custom-media' ] = function ( node ) { - return this.emit( - '@custom-media ' + node.name + ' ' + node.media + ';', - node.position - ); -}; - -/** - * Visit rule node. - */ - -Compiler.prototype.rule = function ( node ) { - const indent = this.indent(); - const decls = node.declarations; - if ( ! decls.length ) { - return ''; - } - - return ( - this.emit( - node.selectors - .map( function ( s ) { - return indent + s; - } ) - .join( ',\n' ), - node.position - ) + - this.emit( ' {\n' ) + - this.emit( this.indent( 1 ) ) + - this.mapVisit( decls, '\n' ) + - this.emit( this.indent( -1 ) ) + - this.emit( '\n' + this.indent() + '}' ) - ); -}; - -/** - * Visit declaration node. - */ - -Compiler.prototype.declaration = function ( node ) { - return ( - this.emit( this.indent() ) + - this.emit( node.property + ': ' + node.value, node.position ) + - this.emit( ';' ) - ); -}; - -/** - * Increase, decrease or return current indentation. - */ - -Compiler.prototype.indent = function ( level ) { - this.level = this.level || 1; - - if ( null !== level ) { - this.level += level; - return ''; - } - - return Array( this.level ).join( this.indentation || ' ' ); -}; - -/* eslint-enable @wordpress/no-unused-vars-before-return */ diff --git a/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js deleted file mode 100644 index 2f332cdb52bec..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js +++ /dev/null @@ -1,32 +0,0 @@ -// Adapted from https://github.com/reworkcss/css -// because we needed to remove source map support. - -/** - * Internal dependencies - */ -import Compressed from './compress'; -import Identity from './identity'; - -/** - * Stringfy the given AST `node`. - * - * Options: - * - * - `compress` space-optimized output - * - `sourcemap` return an object with `.code` and `.map` - * - * @param {Object} node - * @param {Object} [options] - * @return {string} - */ - -export default function ( node, options ) { - options = options || {}; - - const compiler = options.compress - ? new Compressed( options ) - : new Identity( options ); - - const code = compiler.compile( node ); - return code; -} diff --git a/packages/block-editor/src/utils/transform-styles/index.js b/packages/block-editor/src/utils/transform-styles/index.js index c43e816d401e9..8f5e1702307a4 100644 --- a/packages/block-editor/src/utils/transform-styles/index.js +++ b/packages/block-editor/src/utils/transform-styles/index.js @@ -1,36 +1,36 @@ /** - * WordPress dependencies + * External dependencies */ -import { compose } from '@wordpress/compose'; - -/** - * Internal dependencies - */ -import traverse from './traverse'; -import urlRewrite from './transforms/url-rewrite'; -import wrap from './transforms/wrap'; +import postcss from 'postcss'; +import wrap from 'postcss-prefixwrap'; +import rebaseUrl from 'postcss-urlrebase'; /** * Applies a series of CSS rule transforms to wrap selectors inside a given class and/or rewrite URLs depending on the parameters passed. * - * @param {Object|Array} styles CSS rules. - * @param {string} wrapperClassName Wrapper Class Name. + * @typedef {Object} EditorStyle + * @property {string} css the CSS block(s), as a single string. + * @property {?string} baseURL the base URL to be used as the reference when rewritting urls. + * @property {?string[]} ignoredSelectors the selectors not to wrap. + * + * @param {EditorStyle[]} styles CSS rules. + * @param {string} wrapperSelector Wrapper selector. * @return {Array} converted rules. */ -const transformStyles = ( styles, wrapperClassName = '' ) => { - return Object.values( styles ?? [] ).map( ( { css, baseURL } ) => { - const transforms = []; - if ( wrapperClassName ) { - transforms.push( wrap( wrapperClassName ) ); - } - if ( baseURL ) { - transforms.push( urlRewrite( baseURL ) ); - } - if ( transforms.length ) { - return traverse( css, compose( transforms ) ); - } - - return css; +const transformStyles = ( styles, wrapperSelector = '' ) => { + return styles.map( ( { css, ignoredSelectors = [], baseURL } ) => { + return postcss( + [ + wrapperSelector && + wrap( wrapperSelector, { + ignoredSelectors: [ + ...ignoredSelectors, + wrapperSelector, + ], + } ), + baseURL && rebaseUrl( { rootUrl: baseURL } ), + ].filter( Boolean ) + ).process( css, {} ).css; // use sync PostCSS API } ); }; diff --git a/packages/block-editor/src/utils/transform-styles/test/__snapshots__/traverse.js.snap b/packages/block-editor/src/utils/transform-styles/test/__snapshots__/traverse.js.snap deleted file mode 100644 index 1ff3cab7d6336..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/test/__snapshots__/traverse.js.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CSS traverse Should traverse the CSS 1`] = ` -"namespace h1 { -color: red; -}" -`; diff --git a/packages/block-editor/src/utils/transform-styles/test/traverse.js b/packages/block-editor/src/utils/transform-styles/test/traverse.js deleted file mode 100644 index bb1be2635fe53..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/test/traverse.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Internal dependencies - */ -import traverse from '../traverse'; - -describe( 'CSS traverse', () => { - it( 'Should traverse the CSS', () => { - const input = `h1 { color: red; }`; - const output = traverse( input, ( node ) => { - if ( node.type === 'rule' ) { - return { - ...node, - selectors: node.selectors.map( - ( selector ) => 'namespace ' + selector - ), - }; - } - - return node; - } ); - - expect( output ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/url-rewrite.js.snap b/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/url-rewrite.js.snap deleted file mode 100644 index 48aaf43221e7d..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/url-rewrite.js.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`URL rewrite should not replace absolute paths 1`] = ` -"h1 { -background: url(/images/test.png); -}" -`; - -exports[`URL rewrite should not replace remote paths 1`] = ` -"h1 { -background: url(http://wp.org/images/test.png); -}" -`; - -exports[`URL rewrite should replace complex relative paths 1`] = ` -"h1 { -background: url(http://wp-site.local/themes/gut/images/test.png); -}" -`; - -exports[`URL rewrite should replace relative paths 1`] = ` -"h1 { -background: url(http://wp-site.local/themes/gut/css/images/test.png); -}" -`; diff --git a/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/wrap.js.snap b/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/wrap.js.snap deleted file mode 100644 index b9815cdc700b3..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/wrap.js.snap +++ /dev/null @@ -1,64 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CSS selector wrap should ignore font-face selectors 1`] = ` -"@font-face { -font-family: myFirstFont; -src: url(sansation_light.woff); -}" -`; - -exports[`CSS selector wrap should ignore keyframes 1`] = ` -"@keyframes edit-post__fade-in-animation { -from { -opacity: 0; -} -}" -`; - -exports[`CSS selector wrap should ignore selectors 1`] = ` -".my-namespace h1, -body { -color: red; -}" -`; - -exports[`CSS selector wrap should not double wrap selectors 1`] = ` -".my-namespace h1, -.my-namespace .red { -color: red; -}" -`; - -exports[`CSS selector wrap should replace :root selectors 1`] = ` -".my-namespace { ---my-color: #ff0000; -}" -`; - -exports[`CSS selector wrap should replace root tags 1`] = ` -".my-namespace, -.my-namespace h1 { -color: red; -}" -`; - -exports[`CSS selector wrap should wrap multiple selectors 1`] = ` -".my-namespace h1, -.my-namespace h2 { -color: red; -}" -`; - -exports[`CSS selector wrap should wrap regular selectors 1`] = ` -".my-namespace h1 { -color: red; -}" -`; - -exports[`CSS selector wrap should wrap selectors inside container queries 1`] = ` -"@container (width > 400px) { -.my-namespace h1 { -color: red; -} -}" -`; diff --git a/packages/block-editor/src/utils/transform-styles/transforms/test/url-rewrite.js b/packages/block-editor/src/utils/transform-styles/transforms/test/url-rewrite.js deleted file mode 100644 index abbbf0754187e..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/transforms/test/url-rewrite.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Internal dependencies - */ -import traverse from '../../traverse'; -import rewrite from '../url-rewrite'; - -describe( 'URL rewrite', () => { - it( 'should replace relative paths', () => { - const callback = rewrite( 'http://wp-site.local/themes/gut/css/' ); - const input = `h1 { background: url(images/test.png); }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); - - it( 'should replace complex relative paths', () => { - const callback = rewrite( 'http://wp-site.local/themes/gut/css/' ); - const input = `h1 { background: url(../images/test.png); }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); - - it( 'should not replace absolute paths', () => { - const callback = rewrite( 'http://wp-site.local/themes/gut/css/' ); - const input = `h1 { background: url(/images/test.png); }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); - - it( 'should not replace remote paths', () => { - const callback = rewrite( 'http://wp-site.local/themes/gut/css/' ); - const input = `h1 { background: url(http://wp.org/images/test.png); }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-editor/src/utils/transform-styles/transforms/test/wrap.js b/packages/block-editor/src/utils/transform-styles/transforms/test/wrap.js deleted file mode 100644 index a1f4f141d21c9..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/transforms/test/wrap.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Internal dependencies - */ -import traverse from '../../traverse'; -import wrap from '../wrap'; - -describe( 'CSS selector wrap', () => { - it( 'should wrap regular selectors', () => { - const callback = wrap( '.my-namespace' ); - const input = `h1 { color: red; }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); - - it( 'should wrap multiple selectors', () => { - const callback = wrap( '.my-namespace' ); - const input = `h1, h2 { color: red; }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); - - it( 'should ignore selectors', () => { - const callback = wrap( '.my-namespace', [ 'body' ] ); - const input = `h1, body { color: red; }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); - - it( 'should replace root tags', () => { - const callback = wrap( '.my-namespace' ); - const input = `body, h1 { color: red; }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); - - it( 'should ignore keyframes', () => { - const callback = wrap( '.my-namespace' ); - const input = ` - @keyframes edit-post__fade-in-animation { - from { - opacity: 0; - } - }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); - - it( 'should wrap selectors inside container queries', () => { - const callback = wrap( '.my-namespace' ); - const input = ` - @container (width > 400px) { - h1 { color: red; } - }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); - - it( 'should ignore font-face selectors', () => { - const callback = wrap( '.my-namespace' ); - const input = ` - @font-face { - font-family: myFirstFont; - src: url(sansation_light.woff); - }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); - - it( 'should replace :root selectors', () => { - const callback = wrap( '.my-namespace' ); - const input = ` - :root { - --my-color: #ff0000; - }`; - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); - - it( 'should not double wrap selectors', () => { - const callback = wrap( '.my-namespace' ); - const input = ` .my-namespace h1, .red { color: red; }`; - - const output = traverse( input, callback ); - - expect( output ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js b/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js deleted file mode 100644 index e3461cb1088d7..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Return `true` if the given path is http/https. - * - * @param {string} filePath path - * - * @return {boolean} is remote path. - */ -function isRemotePath( filePath ) { - return /^(?:https?:)?\/\//.test( filePath ); -} - -/** - * Return `true` if the given filePath is an absolute url. - * - * @param {string} filePath path - * - * @return {boolean} is absolute path. - */ -function isAbsolutePath( filePath ) { - return /^\/(?!\/)/.test( filePath ); -} - -/** - * Whether or not the url should be inluded. - * - * @param {Object} meta url meta info - * - * @return {boolean} is valid. - */ -function isValidURL( meta ) { - // Ignore hashes or data uris. - if ( - meta.value.indexOf( 'data:' ) === 0 || - meta.value.indexOf( '#' ) === 0 - ) { - return false; - } - - if ( isAbsolutePath( meta.value ) ) { - return false; - } - - // Do not handle the http/https urls if `includeRemote` is false. - if ( isRemotePath( meta.value ) ) { - return false; - } - - return true; -} - -/** - * Get the absolute path of the url, relative to the basePath - * - * @param {string} str the url - * @param {string} baseURL base URL - * - * @return {string} the full path to the file - */ -function getResourcePath( str, baseURL ) { - return new URL( str, baseURL ).toString(); -} - -/** - * Process the single `url()` pattern - * - * @param {string} baseURL the base URL for relative URLs. - * - * @return {Promise} the Promise. - */ -function processURL( baseURL ) { - return ( meta ) => ( { - ...meta, - newUrl: - 'url(' + - meta.before + - meta.quote + - getResourcePath( meta.value, baseURL ) + - meta.quote + - meta.after + - ')', - } ); -} - -/** - * Get all `url()`s, and return the meta info - * - * @param {string} value decl.value. - * - * @return {Array} the urls. - */ -function getURLs( value ) { - const reg = /url\((\s*)(['"]?)(.+?)\2(\s*)\)/g; - let match; - const URLs = []; - - while ( ( match = reg.exec( value ) ) !== null ) { - const meta = { - source: match[ 0 ], - before: match[ 1 ], - quote: match[ 2 ], - value: match[ 3 ], - after: match[ 4 ], - }; - if ( isValidURL( meta ) ) { - URLs.push( meta ); - } - } - return URLs; -} - -/** - * Replace the raw value's `url()` segment to the new value - * - * @param {string} raw the raw value. - * @param {Array} URLs the URLs to replace. - * - * @return {string} the new value. - */ -function replaceURLs( raw, URLs ) { - URLs.forEach( ( item ) => { - raw = raw.replace( item.source, item.newUrl ); - } ); - - return raw; -} - -const rewrite = ( rootURL ) => ( node ) => { - if ( node.type === 'declaration' ) { - const updatedURLs = getURLs( node.value ).map( processURL( rootURL ) ); - return { - ...node, - value: replaceURLs( node.value, updatedURLs ), - }; - } - - return node; -}; - -export default rewrite; diff --git a/packages/block-editor/src/utils/transform-styles/transforms/wrap.js b/packages/block-editor/src/utils/transform-styles/transforms/wrap.js deleted file mode 100644 index 74b940f80352b..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/transforms/wrap.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @constant string IS_ROOT_TAG Regex to check if the selector is a root tag selector. - */ -const IS_ROOT_TAG = /^(body|html|:root).*$/; - -/** - * Creates a callback to modify selectors so they only apply within a certain - * namespace. - * - * @param {string} namespace Namespace to prefix selectors with. - * @param {string[]} ignore Selectors to not prefix. - * - * @return {(node: Object) => Object} Callback to wrap selectors. - */ -const wrap = - ( namespace, ignore = [] ) => - ( node ) => { - /** - * Updates selector if necessary. - * - * @param {string} selector Selector to modify. - * - * @return {string} Updated selector. - */ - const updateSelector = ( selector ) => { - if ( ignore.includes( selector.trim() ) ) { - return selector; - } - - // Skip the update when a selector already has a namespace + space (" "). - if ( selector.trim().startsWith( `${ namespace } ` ) ) { - return selector; - } - - // Anything other than a root tag is always prefixed. - { - if ( ! selector.match( IS_ROOT_TAG ) ) { - return namespace + ' ' + selector; - } - } - - // HTML and Body elements cannot be contained within our container so lets extract their styles. - return selector.replace( /^(body|html|:root)/, namespace ); - }; - - if ( node.type === 'rule' ) { - return { - ...node, - selectors: node.selectors.map( updateSelector ), - }; - } - - return node; - }; - -export default wrap; diff --git a/packages/block-editor/src/utils/transform-styles/traverse.js b/packages/block-editor/src/utils/transform-styles/traverse.js deleted file mode 100644 index 28ad59b4ea799..0000000000000 --- a/packages/block-editor/src/utils/transform-styles/traverse.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * External dependencies - */ -import traverse from 'traverse'; - -/** - * Internal dependencies - */ -import { parse, stringify } from './ast'; - -function traverseCSS( css, callback ) { - try { - const parsed = parse( css ); - - const updated = traverse.map( parsed, function ( node ) { - if ( ! node ) { - return node; - } - const updatedNode = callback( node ); - return this.update( updatedNode ); - } ); - - return stringify( updated ); - } catch ( err ) { - // eslint-disable-next-line no-console - console.warn( 'Error while traversing the CSS: ' + err ); - - return null; - } -} - -export default traverseCSS; diff --git a/packages/block-library/src/block/edit-title.native.js b/packages/block-library/src/block/edit-title.native.js index 0a574f2f0cfa8..d0c7d981202d9 100644 --- a/packages/block-library/src/block/edit-title.native.js +++ b/packages/block-library/src/block/edit-title.native.js @@ -6,7 +6,7 @@ import { Text, View } from 'react-native'; /** * WordPress dependencies */ -import { Icon } from '@wordpress/components'; +import { Icon, useGlobalStyles } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withPreferredColorScheme } from '@wordpress/compose'; import { help, lock } from '@wordpress/icons'; @@ -17,18 +17,21 @@ import { help, lock } from '@wordpress/icons'; import styles from './editor.scss'; function EditTitle( { getStylesFromColorScheme, title } ) { - const lockIconStyle = getStylesFromColorScheme( - styles.lockIcon, - styles.lockIconDark - ); - const titleStyle = getStylesFromColorScheme( - styles.title, - styles.titleDark - ); - const infoIconStyle = getStylesFromColorScheme( - styles.infoIcon, - styles.infoIconDark - ); + const globalStyles = useGlobalStyles(); + const baseColors = globalStyles?.baseColors?.color; + + const lockIconStyle = [ + getStylesFromColorScheme( styles.lockIcon, styles.lockIconDark ), + baseColors && { color: baseColors.text }, + ]; + const titleStyle = [ + getStylesFromColorScheme( styles.title, styles.titleDark ), + baseColors && { color: baseColors.text }, + ]; + const infoIconStyle = [ + getStylesFromColorScheme( styles.infoIcon, styles.infoIconDark ), + baseColors && { color: baseColors.text }, + ]; const separatorStyle = getStylesFromColorScheme( styles.separator, styles.separatorDark diff --git a/packages/block-library/src/html/preview.js b/packages/block-library/src/html/preview.js index 4d3fe5f41915a..d515c0119aa8d 100644 --- a/packages/block-library/src/html/preview.js +++ b/packages/block-library/src/html/preview.js @@ -22,12 +22,17 @@ const DEFAULT_STYLES = ` `; export default function HTMLEditPreview( { content, isSelected } ) { - const settingStyles = useSelect( ( select ) => { - return select( blockEditorStore ).getSettings()?.styles; - }, [] ); + const settingStyles = useSelect( + ( select ) => select( blockEditorStore ).getSettings().styles + ); const styles = useMemo( - () => [ DEFAULT_STYLES, ...transformStyles( settingStyles ) ], + () => [ + DEFAULT_STYLES, + ...transformStyles( + settingStyles.filter( ( style ) => style.css ) + ), + ], [ settingStyles ] ); diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index f29202fe63d19..c465677a986e0 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -235,6 +235,7 @@ function block_core_image_render_lightbox( $block_content, $block ) { $button = $img[0] . ''; @@ -319,12 +320,13 @@ function block_core_image_render_lightbox( $block_content, $block ) { data-wp-on--touchmove="actions.core.image.handleTouchMove" data-wp-on--touchend="actions.core.image.handleTouchEnd" data-wp-on--click="actions.core.image.hideLightbox" + tabindex="-1" > - + HTML; diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js index 30d1259637e3d..331c0e79c731f 100644 --- a/packages/block-library/src/image/view.js +++ b/packages/block-library/src/image/view.js @@ -135,7 +135,7 @@ store( false ); }, - hideLightbox: async ( { context, event } ) => { + hideLightbox: async ( { context } ) => { context.core.image.hideAnimationEnabled = true; if ( context.core.image.lightboxEnabled ) { // We want to wait until the close animation is completed @@ -149,19 +149,15 @@ store( 'scroll', scrollCallback ); + // If we don't delay before changing the focus, + // the focus ring will appear on Firefox before + // the image has finished animating, which looks broken. + context.core.image.lightboxTriggerRef.focus( { + preventScroll: true, + } ); }, 450 ); context.core.image.lightboxEnabled = false; - - // We want to avoid drawing attention to the button - // after the lightbox closes for mouse and touch users. - // Note that the `event.pointerType` property returns - // as an empty string if a keyboard fired the event. - if ( event.pointerType === '' ) { - context.core.image.lastFocusedElement.focus( { - preventScroll: true, - } ); - } } }, handleKeydown: ( { context, actions, event } ) => { @@ -266,6 +262,10 @@ store( image: { initOriginImage: ( { context, ref } ) => { context.core.image.imageRef = ref; + context.core.image.lightboxTriggerRef = + ref.parentElement.querySelector( + '.lightbox-trigger' + ); if ( ref.complete ) { context.core.image.imageLoaded = true; context.core.image.imageCurrentSrc = ref.currentSrc; @@ -282,14 +282,8 @@ store( focusableElements.length - 1 ]; - // We want to avoid drawing unnecessary attention to the close - // button for mouse and touch users. Note that even if opening - // the lightbox via keyboard, the event fired is of type - // `pointerEvent`, so we need to rely on the `event.pointerType` - // property, which returns an empty string for keyboard events. - if ( context.core.image.pointerType === '' ) { - ref.querySelector( '.close-button' ).focus(); - } + // Move focus to the dialog when opening it. + ref.focus(); } }, setButtonStyles: ( { context, ref } ) => { diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 5fc7cd14f35c0..07e9cd98cb5ec 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -651,7 +651,6 @@ export const getUserPatternCategories = { per_page: -1, _fields: 'id,name,description,slug', - context: 'view', } ); diff --git a/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache b/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache index 2322881ab0d71..52c9c4966646f 100644 --- a/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache +++ b/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache @@ -30,6 +30,10 @@ * @package {{namespace}} */ +if ( ! defined( 'ABSPATH' ) ) { + exit; // Exit if accessed directly. +} + /** * Registers the block using the metadata loaded from the `block.json` file. * Behind the scenes, it registers also all assets so they can be enqueued diff --git a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache index 2322881ab0d71..52c9c4966646f 100644 --- a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache +++ b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache @@ -30,6 +30,10 @@ * @package {{namespace}} */ +if ( ! defined( 'ABSPATH' ) ) { + exit; // Exit if accessed directly. +} + /** * Registers the block using the metadata loaded from the `block.json` file. * Behind the scenes, it registers also all assets so they can be enqueued diff --git a/packages/create-block/lib/templates/es5/$slug.php.mustache b/packages/create-block/lib/templates/es5/$slug.php.mustache index a04553af1e7d8..0f64471e434a7 100644 --- a/packages/create-block/lib/templates/es5/$slug.php.mustache +++ b/packages/create-block/lib/templates/es5/$slug.php.mustache @@ -30,6 +30,10 @@ * @package {{namespace}} */ +if ( ! defined( 'ABSPATH' ) ) { + exit; // Exit if accessed directly. +} + /** * Registers all block assets so that they can be enqueued through the block editor * in the corresponding context. diff --git a/packages/create-block/lib/templates/plugin/$slug.php.mustache b/packages/create-block/lib/templates/plugin/$slug.php.mustache index 2ed0354314cc4..90f293f1472f4 100644 --- a/packages/create-block/lib/templates/plugin/$slug.php.mustache +++ b/packages/create-block/lib/templates/plugin/$slug.php.mustache @@ -30,6 +30,10 @@ * @package {{namespace}} */ +if ( ! defined( 'ABSPATH' ) ) { + exit; // Exit if accessed directly. +} + /** * Registers the block using the metadata loaded from the `block.json` file. * Behind the scenes, it registers also all assets so they can be enqueued diff --git a/packages/edit-site/src/components/dataviews/sidebar-content.js b/packages/edit-site/src/components/dataviews/sidebar-content.js index c10565b38de37..5015e73bc4a63 100644 --- a/packages/edit-site/src/components/dataviews/sidebar-content.js +++ b/packages/edit-site/src/components/dataviews/sidebar-content.js @@ -1,3 +1,4 @@ export default function DataViewsSidebarContent() { - return

Add views ui here

; + // TODO: add views UI. + return null; } diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index 99de8c83c6fbe..39b562806c109 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -62,7 +62,6 @@ export { export { default as PostTaxonomies } from './post-taxonomies'; export { FlatTermSelector as PostTaxonomiesFlatTermSelector } from './post-taxonomies/flat-term-selector'; export { HierarchicalTermSelector as PostTaxonomiesHierarchicalTermSelector } from './post-taxonomies/hierarchical-term-selector'; -export { PatternCategoriesSelector as PostPatternCategoriesSelector } from './post-taxonomies/pattern-categories-selector'; export { default as PostTaxonomiesCheck } from './post-taxonomies/check'; export { default as PostTextEditor } from './post-text-editor'; export { default as PostTitle } from './post-title'; diff --git a/packages/editor/src/components/post-taxonomies/pattern-categories-selector.js b/packages/editor/src/components/post-taxonomies/pattern-categories-selector.js deleted file mode 100644 index ac6a60aa00934..0000000000000 --- a/packages/editor/src/components/post-taxonomies/pattern-categories-selector.js +++ /dev/null @@ -1,119 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; -import { addFilter } from '@wordpress/hooks'; -import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; - -/** - * Internal dependencies - */ -import { unlock } from '../../lock-unlock'; -import { store as editorStore } from '../../store'; - -const { CategorySelector } = unlock( patternsPrivateApis ); - -const EMPTY_ARRAY = []; - -const DEFAULT_QUERY = { - per_page: -1, - orderby: 'name', - order: 'asc', - _fields: 'id,name,parent', - context: 'view', -}; - -/* - * Pattern categories are a flat taxonomy but do not allow Author users and below to create - * new categories, so this selector overrides the default flat taxonomy selector for - * wp_block post types and users without 'create' capability for wp_pattern_category. - */ -export function PatternCategoriesSelector( { slug } ) { - const { hasAssignAction, terms, availableTerms, taxonomy, loading } = - useSelect( - ( select ) => { - const { getCurrentPost, getEditedPostAttribute } = - select( editorStore ); - const { getTaxonomy, getEntityRecords, isResolving } = - select( coreStore ); - const _taxonomy = getTaxonomy( slug ); - const post = getCurrentPost(); - - return { - hasAssignAction: _taxonomy - ? post._links?.[ - 'wp:action-assign-' + _taxonomy.rest_base - ] ?? false - : false, - terms: _taxonomy - ? getEditedPostAttribute( _taxonomy.rest_base ) - : EMPTY_ARRAY, - loading: isResolving( 'getEntityRecords', [ - 'taxonomy', - slug, - DEFAULT_QUERY, - ] ), - availableTerms: - getEntityRecords( 'taxonomy', slug, DEFAULT_QUERY ) || - EMPTY_ARRAY, - taxonomy: _taxonomy, - }; - }, - [ slug ] - ); - - const { editPost } = useDispatch( editorStore ); - - if ( ! hasAssignAction || loading || availableTerms.length === 0 ) { - return null; - } - - const onUpdateTerms = ( termIds ) => { - editPost( { [ taxonomy.rest_base ]: termIds } ); - }; - - const onChange = ( term ) => { - const hasTerm = terms.includes( term.id ); - const newTerms = hasTerm - ? terms.filter( ( id ) => id !== term.id ) - : [ ...terms, term.id ]; - onUpdateTerms( newTerms ); - }; - - const isCategorySelected = ( term ) => terms.includes( term.id ); - - const categoryOptions = availableTerms.map( ( term ) => ( { - ...term, - label: term.name, - } ) ); - - return ( - - ); -} - -export default function patternCategorySelector( OriginalComponent ) { - return function ( props ) { - const canAddCategories = useSelect( ( select ) => { - const { canUser } = select( coreStore ); - return canUser( 'create', 'wp_pattern_category' ); - } ); - if ( props.slug === 'wp_pattern_category' && ! canAddCategories ) { - return ; - } - - return ; - }; -} - -addFilter( - 'editor.PostTaxonomyType', - 'core/pattern-category-selector', - patternCategorySelector -); diff --git a/packages/patterns/src/components/category-editor.js b/packages/patterns/src/components/category-editor.js deleted file mode 100644 index a394013af333b..0000000000000 --- a/packages/patterns/src/components/category-editor.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { useMemo, useState } from '@wordpress/element'; -import { FormTokenField } from '@wordpress/components'; -import { useDebounce } from '@wordpress/compose'; -import { decodeEntities } from '@wordpress/html-entities'; - -/** - * Internal dependencies - */ -import CategorySelector from './category-selector'; - -const unescapeString = ( arg ) => decodeEntities( arg ); - -export const CATEGORY_SLUG = 'wp_pattern_category'; - -export default function CategoryEditor( { - categoryTerms, - onChange, - categoryMap, - canAddCategories, -} ) { - const categoryOptions = Array.from( categoryMap.values() ); - const [ search, setSearch ] = useState( '' ); - const debouncedSearch = useDebounce( setSearch, 500 ); - - const suggestions = useMemo( () => { - return Array.from( categoryMap.values() ) - .map( ( category ) => unescapeString( category.label ) ) - .filter( ( category ) => { - if ( search !== '' ) { - return category - .toLowerCase() - .includes( search.toLowerCase() ); - } - return true; - } ) - .sort( ( a, b ) => a.localeCompare( b ) ); - }, [ search, categoryMap ] ); - - function handleChange( termNames ) { - const uniqueTerms = termNames.reduce( ( terms, newTerm ) => { - if ( - ! terms.some( - ( term ) => term.toLowerCase() === newTerm.toLowerCase() - ) - ) { - terms.push( newTerm ); - } - return terms; - }, [] ); - - onChange( uniqueTerms ); - } - const isCategorySelected = ( selectedCategory ) => - categoryTerms.includes( selectedCategory.label ); - - const onCategorySelectChange = ( selectedCategory ) => { - if ( categoryTerms.includes( selectedCategory.label ) ) { - onChange( - categoryTerms.filter( - ( categoryTerm ) => categoryTerm !== selectedCategory.label - ) - ); - } else { - onChange( [ ...categoryTerms, selectedCategory.label ] ); - } - }; - - return ( - <> - { canAddCategories && ( - - ) } - { ! canAddCategories && categoryOptions.length > 0 && ( - - ) } - - ); -} diff --git a/packages/patterns/src/components/category-selector.js b/packages/patterns/src/components/category-selector.js index 84dae97e232f8..7f00350e278ec 100644 --- a/packages/patterns/src/components/category-selector.js +++ b/packages/patterns/src/components/category-selector.js @@ -2,48 +2,65 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { CheckboxControl, BaseControl } from '@wordpress/components'; +import { useMemo, useState } from '@wordpress/element'; +import { FormTokenField } from '@wordpress/components'; +import { useDebounce } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; +const unescapeString = ( arg ) => { + return decodeEntities( arg ); +}; + +export const CATEGORY_SLUG = 'wp_pattern_category'; + export default function CategorySelector( { + categoryTerms, onChange, - isCategorySelected, - categoryOptions, - showLabel = true, + categoryMap, } ) { - const renderTerms = ( renderedTerms ) => { - return renderedTerms.map( ( category ) => { - return ( -
- onChange( category ) } - label={ decodeEntities( category.label ) } - /> -
- ); - } ); - }; + const [ search, setSearch ] = useState( '' ); + const debouncedSearch = useDebounce( setSearch, 500 ); + + const suggestions = useMemo( () => { + return Array.from( categoryMap.values() ) + .map( ( category ) => unescapeString( category.label ) ) + .filter( ( category ) => { + if ( search !== '' ) { + return category + .toLowerCase() + .includes( search.toLowerCase() ); + } + return true; + } ) + .sort( ( a, b ) => a.localeCompare( b ) ); + }, [ search, categoryMap ] ); + + function handleChange( termNames ) { + const uniqueTerms = termNames.reduce( ( terms, newTerm ) => { + if ( + ! terms.some( + ( term ) => term.toLowerCase() === newTerm.toLowerCase() + ) + ) { + terms.push( newTerm ); + } + return terms; + }, [] ); + + onChange( uniqueTerms ); + } return ( - - { showLabel && ( - - { __( 'Categories' ) } - - ) } -
- { renderTerms( categoryOptions ) } -
-
+ ); } diff --git a/packages/patterns/src/components/create-pattern-modal.js b/packages/patterns/src/components/create-pattern-modal.js index 67953ecdee668..22d20fd037265 100644 --- a/packages/patterns/src/components/create-pattern-modal.js +++ b/packages/patterns/src/components/create-pattern-modal.js @@ -24,7 +24,7 @@ import { PATTERN_DEFAULT_CATEGORY, PATTERN_SYNC_TYPES } from '../constants'; * Internal dependencies */ import { store as patternsStore } from '../store'; -import CategoryEditor, { CATEGORY_SLUG } from './category-editor'; +import CategorySelector, { CATEGORY_SLUG } from './category-selector'; import { unlock } from '../lock-unlock'; export default function CreatePatternModal( { @@ -48,46 +48,41 @@ export default function CreatePatternModal( { const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore ); const { createErrorNotice } = useDispatch( noticesStore ); - const { corePatternCategories, userPatternCategories, canAddCategories } = - useSelect( ( select ) => { - const { - getUserPatternCategories, - getBlockPatternCategories, - canUser, - } = select( coreStore ); + const { corePatternCategories, userPatternCategories } = useSelect( + ( select ) => { + const { getUserPatternCategories, getBlockPatternCategories } = + select( coreStore ); return { corePatternCategories: getBlockPatternCategories(), userPatternCategories: getUserPatternCategories(), - canAddCategories: canUser( 'create', 'wp_pattern_category' ), }; - } ); + } + ); const categoryMap = useMemo( () => { // Merge the user and core pattern categories and remove any duplicates. const uniqueCategories = new Map(); - [ - ...userPatternCategories, - ...( canAddCategories ? corePatternCategories : [] ), - ].forEach( ( category ) => { - if ( - ! uniqueCategories.has( category.label ) && - // There are two core categories with `Post` label so explicitly remove the one with - // the `query` slug to avoid any confusion. - category.name !== 'query' - ) { - // We need to store the name separately as this is used as the slug in the - // taxonomy and may vary from the label. - uniqueCategories.set( category.label, { - label: category.label, - value: category.label, - name: category.name, - id: category.id, - } ); + [ ...userPatternCategories, ...corePatternCategories ].forEach( + ( category ) => { + if ( + ! uniqueCategories.has( category.label ) && + // There are two core categories with `Post` label so explicitly remove the one with + // the `query` slug to avoid any confusion. + category.name !== 'query' + ) { + // We need to store the name separately as this is used as the slug in the + // taxonomy and may vary from the label. + uniqueCategories.set( category.label, { + label: category.label, + value: category.label, + name: category.name, + } ); + } } - } ); + ); return uniqueCategories; - }, [ userPatternCategories, corePatternCategories, canAddCategories ] ); + }, [ userPatternCategories, corePatternCategories ] ); async function onCreate( patternTitle, sync ) { if ( ! title || isSaving ) { @@ -96,18 +91,11 @@ export default function CreatePatternModal( { try { setIsSaving( true ); - let categories; - if ( canAddCategories ) { - categories = await Promise.all( - categoryTerms.map( ( termName ) => - findOrCreateTerm( termName ) - ) - ); - } else { - categories = categoryTerms.map( - ( term ) => categoryMap.get( term ).id - ); - } + const categories = await Promise.all( + categoryTerms.map( ( termName ) => + findOrCreateTerm( termName ) + ) + ); const newPattern = await createPattern( patternTitle, @@ -185,11 +173,10 @@ export default function CreatePatternModal( { placeholder={ __( 'My pattern' ) } className="patterns-create-modal__name-input" /> - ## Unreleased +- [*] Synced Patterns: Fix visibility of heading section when used with block based themes in dark mode [#55399] - [*] Classic block: Add option to convert to blocks [#55461] ## 1.106.0 diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 8e80d806b17e7..89ae3f1fff05c 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -491,7 +491,7 @@ Should there be any situation where you want to provide your own Jest config, yo - there is a file called `jest-unit.config.js`, `jest-unit.config.json`, `jest.config.js`, or `jest.config.json` in the top-level directory of your package (at the same level than your `package.json`). - a `jest` object can be provided in the `package.json` file with the test configuration. -### `test-plyawright` +### `test-playwright` Launches the Playwright End-To-End (E2E) test runner. Similar to Puppeteer, it provides a high-level API to control a headless browser. diff --git a/test/performance/fixtures/perf-utils.ts b/test/performance/fixtures/perf-utils.ts index a61af86684e6b..dcd9579364e10 100644 --- a/test/performance/fixtures/perf-utils.ts +++ b/test/performance/fixtures/perf-utils.ts @@ -33,26 +33,19 @@ export class PerfUtils { * @return Locator for the editor canvas element. */ async getCanvas() { - return await Promise.any( [ - ( async () => { - const legacyCanvasLocator = this.page.locator( - '.wp-block-post-content' - ); - await legacyCanvasLocator.waitFor( { - timeout: 120_000, - } ); - return legacyCanvasLocator; - } )(), - ( async () => { - const iframedCanvasLocator = this.page.frameLocator( - '[name=editor-canvas]' - ); - await iframedCanvasLocator - .locator( 'body' ) - .waitFor( { timeout: 120_000 } ); - return iframedCanvasLocator; - } )(), - ] ); + const canvasLocator = this.page.locator( + '.wp-block-post-content, iframe[name=editor-canvas]' + ); + + const isFramed = await canvasLocator.evaluate( + ( node ) => node.tagName === 'IFRAME' + ); + + if ( isFramed ) { + return canvasLocator.frameLocator( ':scope' ); + } + + return canvasLocator; } /** @@ -61,9 +54,7 @@ export class PerfUtils { * @return URL of the saved draft. */ async saveDraft() { - await this.page - .getByRole( 'button', { name: 'Save draft' } ) - .click( { timeout: 60_000 } ); + await this.page.getByRole( 'button', { name: 'Save draft' } ).click(); await expect( this.page.getByRole( 'button', { name: 'Saved' } ) ).toBeDisabled(); @@ -75,6 +66,8 @@ export class PerfUtils { * Disables the editor autosave function. */ async disableAutosave() { + await this.page.waitForFunction( () => window?.wp?.data ); + await this.page.evaluate( () => { return window.wp.data .dispatch( 'core/editor' ) @@ -83,12 +76,6 @@ export class PerfUtils { localAutosaveInterval: 100000000000, } ); } ); - - const { autosaveInterval } = await this.page.evaluate( () => { - return window.wp.data.select( 'core/editor' ).getEditorSettings(); - } ); - - expect( autosaveInterval ).toBe( 100000000000 ); } /** @@ -139,6 +126,10 @@ export class PerfUtils { throw new Error( `File not found: ${ filepath }` ); } + await this.page.waitForFunction( + () => window?.wp?.blocks && window?.wp?.data + ); + return await this.page.evaluate( ( html: string ) => { const { parse } = window.wp.blocks; const { dispatch } = window.wp.data; @@ -159,6 +150,10 @@ export class PerfUtils { * Generates and loads a 1000 empty paragraphs into the editor canvas. */ async load1000Paragraphs() { + await this.page.waitForFunction( + () => window?.wp?.blocks && window?.wp?.data + ); + await this.page.evaluate( () => { const { createBlock } = window.wp.blocks; const { dispatch } = window.wp.data; diff --git a/test/performance/playwright.config.ts b/test/performance/playwright.config.ts index a8208342ac2d8..ed221b1dc7bfb 100644 --- a/test/performance/playwright.config.ts +++ b/test/performance/playwright.config.ts @@ -27,6 +27,7 @@ const config = defineConfig( { ), use: { ...baseConfig.use, + actionTimeout: 120_000, // 2 minutes. video: 'off', }, } ); diff --git a/test/performance/specs/post-editor.spec.js b/test/performance/specs/post-editor.spec.js index 7f59033046527..da20e3c3e667b 100644 --- a/test/performance/specs/post-editor.spec.js +++ b/test/performance/specs/post-editor.spec.js @@ -70,9 +70,7 @@ test.describe( 'Post Editor Performance', () => { const canvas = await perfUtils.getCanvas(); // Wait for the first block. - await canvas.locator( '.wp-block' ).first().waitFor( { - timeout: 120_000, - } ); + await canvas.locator( '.wp-block' ).first().waitFor(); // Get the durations. const loadingDurations = await metrics.getLoadingDurations(); diff --git a/test/performance/specs/site-editor.spec.js b/test/performance/specs/site-editor.spec.js index f2f211dd52e6e..28a1cbb0ecde2 100644 --- a/test/performance/specs/site-editor.spec.js +++ b/test/performance/specs/site-editor.spec.js @@ -86,9 +86,7 @@ test.describe( 'Site Editor Performance', () => { const canvas = await perfUtils.getCanvas(); // Wait for the first block. - await canvas.locator( '.wp-block' ).first().waitFor( { - timeout: 120_000, - } ); + await canvas.locator( '.wp-block' ).first().waitFor(); // Get the durations. const loadingDurations = await metrics.getLoadingDurations(); @@ -142,7 +140,7 @@ test.describe( 'Site Editor Performance', () => { // Spinner was used instead of the progress bar in an earlier version of the site editor. '.edit-site-canvas-loader, .edit-site-canvas-spinner' ) - .waitFor( { state: 'hidden', timeout: 120_000 } ); + .waitFor( { state: 'hidden' } ); const canvas = await perfUtils.getCanvas();