diff --git a/package-lock.json b/package-lock.json index ed33a0a19a48eb..7603e03b936bff 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 b621c34551da53..b988c0499f1fb8 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 02671d5dca0e3c..9c7a72f0897143 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 225d9c987638af..5abf843b85f51a 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 9e80e93b0cd641..ab2a4b0c9ac982 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 00000000000000..28c4202f414a9e --- /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 00000000000000..f162a0b2f6048c --- /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 b4dc1de499f474..00000000000000 --- 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 8f7d227d61442d..00000000000000 --- 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 d2500b730424f7..00000000000000 --- 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 6a2a3af3769be0..00000000000000 --- 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 760ca4044631ee..00000000000000 --- 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 2f332cdb52bec9..00000000000000 --- 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 c43e816d401e9c..8f5e1702307a45 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 1ff3cab7d63365..00000000000000 --- 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 bb1be2635fe535..00000000000000 --- 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 48aaf43221e7d5..00000000000000 --- 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 b9815cdc700b38..00000000000000 --- 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 abbbf0754187e3..00000000000000 --- 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 a1f4f141d21c9b..00000000000000 --- 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 e3461cb1088d78..00000000000000 --- 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 74b940f80352b9..00000000000000 --- 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 28ad59b4ea7996..00000000000000 --- 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 0a574f2f0cfa83..d0c7d981202d99 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/freeform/test/__snapshots__/index.native.js.snap b/packages/block-library/src/freeform/test/__snapshots__/index.native.js.snap new file mode 100644 index 00000000000000..aad87487b93bec --- /dev/null +++ b/packages/block-library/src/freeform/test/__snapshots__/index.native.js.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Classic block converts content into blocks 1`] = ` +" +
I'm classic!
+" +`; diff --git a/packages/block-library/src/freeform/test/index.native.js b/packages/block-library/src/freeform/test/index.native.js new file mode 100644 index 00000000000000..08ba635d4cd777 --- /dev/null +++ b/packages/block-library/src/freeform/test/index.native.js @@ -0,0 +1,57 @@ +/** + * External dependencies + */ +import { + fireEvent, + getBlock, + getEditorHtml, + initializeEditor, + screen, + setupCoreBlocks, + within, +} from 'test/helpers'; + +const CLASSIC_BLOCK_HTML = `I'm classic!
`; +const DEFAULT_EDITOR_CAPABILITIES = { + unsupportedBlockEditor: true, + canEnableUnsupportedBlockEditor: true, +}; + +setupCoreBlocks(); + +describe( 'Classic block', () => { + it( 'displays option to edit using web editor', async () => { + await initializeEditor( { + initialHtml: CLASSIC_BLOCK_HTML, + capabilities: DEFAULT_EDITOR_CAPABILITIES, + } ); + + const block = getBlock( screen, 'Classic' ); + fireEvent.press( block ); + + // Tap the block to open the unsupported block details + fireEvent.press( within( block ).getByText( 'Unsupported' ) ); + + const actionButton = screen.getByText( 'Edit using web editor' ); + expect( actionButton ).toBeVisible(); + } ); + + it( 'converts content into blocks', async () => { + await initializeEditor( { + initialHtml: CLASSIC_BLOCK_HTML, + capabilities: DEFAULT_EDITOR_CAPABILITIES, + } ); + + const block = getBlock( screen, 'Classic' ); + fireEvent.press( block ); + + // Tap the block to open the unsupported block details + fireEvent.press( within( block ).getByText( 'Unsupported' ) ); + + const actionButton = screen.getByText( 'Convert to blocks' ); + expect( actionButton ).toBeVisible(); + + fireEvent.press( actionButton ); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/block-library/src/html/preview.js b/packages/block-library/src/html/preview.js index 4d3fe5f41915a6..d515c0119aa8db 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 f29202fe63d195..c465677a986e05 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" >Add views ui here
; + // TODO: add views UI. + return null; } diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js index 61de80838f6372..08ff1a95d6e41a 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js @@ -244,7 +244,7 @@ function FontCollection( { id } ) { ! fonts.length && (